summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes39
-rw-r--r--.gitignore85
-rw-r--r--.hgignore36
-rw-r--r--CREDITS.md38
-rw-r--r--LICENSE619
-rw-r--r--LICENSE.md596
-rw-r--r--README88
-rw-r--r--README.md139
-rw-r--r--docs/access_api.rst18
-rw-r--r--docs/conf.py10
-rw-r--r--docs/docs.conf4
-rwxr-xr-xdocs/extend_pyload.rst7
-rw-r--r--docs/index.rst2
-rw-r--r--docs/module_overview.rst22
-rw-r--r--docs/plugin_licensing.rst18
-rw-r--r--docs/resources/banner.pngbin0 -> 18927 bytes
-rw-r--r--docs/resources/icon.icobin0 -> 5430 bytes
-rw-r--r--docs/resources/logo.pngbin0 -> 39315 bytes
-rw-r--r--docs/resources/logo.svg317
-rw-r--r--docs/write_addons.rst158
-rw-r--r--docs/write_hooks.rst162
-rw-r--r--docs/write_plugins.rst22
-rw-r--r--docs/write_scripts.rst25
-rw-r--r--icons/abort.pngbin2569 -> 0 bytes
-rw-r--r--icons/add_small.pngbin3356 -> 0 bytes
-rw-r--r--icons/clipboard.pngbin1344 -> 0 bytes
-rw-r--r--icons/close.pngbin2569 -> 0 bytes
-rw-r--r--icons/edit_small.pngbin570 -> 0 bytes
-rw-r--r--icons/logo-gui.pngbin8093 -> 0 bytes
-rw-r--r--icons/logo.pngbin8172 -> 0 bytes
-rw-r--r--icons/pull_small.pngbin614 -> 0 bytes
-rw-r--r--icons/push_small.pngbin618 -> 0 bytes
-rw-r--r--icons/pyload-gui.icobin51122 -> 0 bytes
-rw-r--r--icons/pyload.icobin7206 -> 0 bytes
-rw-r--r--icons/pyload2.icobin9662 -> 0 bytes
-rw-r--r--icons/refresh1_small.pngbin3474 -> 0 bytes
-rw-r--r--icons/refresh_small.pngbin821 -> 0 bytes
-rw-r--r--icons/remove_small.pngbin287 -> 0 bytes
-rw-r--r--icons/toolbar_add.pngbin932 -> 0 bytes
-rw-r--r--icons/toolbar_pause.pngbin943 -> 0 bytes
-rw-r--r--icons/toolbar_remove.pngbin496 -> 0 bytes
-rw-r--r--icons/toolbar_start.pngbin1504 -> 0 bytes
-rw-r--r--icons/toolbar_stop.pngbin523 -> 0 bytes
-rw-r--r--locale/README.md55
-rw-r--r--locale/af/LC_MESSAGES/django.po684
-rw-r--r--locale/af/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/af/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/af/LC_MESSAGES/setup.po459
-rw-r--r--locale/ar/LC_MESSAGES/django.po684
-rw-r--r--locale/ar/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ar/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ar/LC_MESSAGES/setup.po459
-rw-r--r--locale/bn/LC_MESSAGES/django.po684
-rw-r--r--locale/bn/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/bn/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/bn/LC_MESSAGES/setup.po459
-rw-r--r--locale/ca/LC_MESSAGES/django.po684
-rw-r--r--locale/ca/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ca/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ca/LC_MESSAGES/setup.po459
-rw-r--r--locale/cli.pot182
-rw-r--r--locale/core.pot799
-rw-r--r--locale/crowdin.yaml15
-rw-r--r--locale/cs/LC_MESSAGES/django.mobin8921 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/django.po684
-rw-r--r--locale/cs/LC_MESSAGES/pyLoad.mobin17425 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadCli.mobin4985 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/cs/LC_MESSAGES/pyLoadGui.mobin5810 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/setup.mobin11394 -> 0 bytes
-rw-r--r--locale/cs/LC_MESSAGES/setup.po459
-rw-r--r--locale/da/LC_MESSAGES/django.po684
-rw-r--r--locale/da/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/da/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/da/LC_MESSAGES/setup.po459
-rw-r--r--locale/de/LC_MESSAGES/django.mobin9080 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/django.po684
-rw-r--r--locale/de/LC_MESSAGES/pyLoad.mobin18222 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/de/LC_MESSAGES/pyLoadCli.mobin5266 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/de/LC_MESSAGES/pyLoadGui.mobin6034 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/setup.mobin11467 -> 0 bytes
-rw-r--r--locale/de/LC_MESSAGES/setup.po459
-rw-r--r--locale/django.pot693
-rw-r--r--locale/el/LC_MESSAGES/django.po684
-rw-r--r--locale/el/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/el/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/el/LC_MESSAGES/setup.po459
-rwxr-xr-xlocale/en/LC_MESSAGES/django.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoad.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoadCli.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/pyLoadGui.mobin434 -> 0 bytes
-rwxr-xr-xlocale/en/LC_MESSAGES/setup.mobin434 -> 0 bytes
-rw-r--r--locale/eo/LC_MESSAGES/django.po684
-rw-r--r--locale/eo/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/eo/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/eo/LC_MESSAGES/setup.po459
-rw-r--r--locale/es/LC_MESSAGES/django.mobin9001 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/django.po684
-rw-r--r--locale/es/LC_MESSAGES/pyLoad.mobin17919 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/es/LC_MESSAGES/pyLoadCli.mobin5139 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/es/LC_MESSAGES/pyLoadGui.mobin5973 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/setup.mobin11585 -> 0 bytes
-rw-r--r--locale/es/LC_MESSAGES/setup.po459
-rw-r--r--locale/fa/LC_MESSAGES/django.po684
-rw-r--r--locale/fa/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/fa/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/fa/LC_MESSAGES/setup.po459
-rw-r--r--locale/fi/LC_MESSAGES/django.po684
-rw-r--r--locale/fi/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/fi/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/fi/LC_MESSAGES/setup.po459
-rw-r--r--locale/fr/LC_MESSAGES/django.mobin8407 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/django.po684
-rw-r--r--locale/fr/LC_MESSAGES/pyLoad.mobin17270 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadCli.mobin5363 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/fr/LC_MESSAGES/pyLoadGui.mobin5977 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/setup.mobin11849 -> 0 bytes
-rw-r--r--locale/fr/LC_MESSAGES/setup.po459
-rw-r--r--locale/ga/LC_MESSAGES/django.po684
-rw-r--r--locale/ga/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ga/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ga/LC_MESSAGES/setup.po459
-rw-r--r--locale/gl/LC_MESSAGES/django.po684
-rw-r--r--locale/gl/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/gl/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/gl/LC_MESSAGES/setup.po459
-rw-r--r--locale/gui.pot511
-rw-r--r--locale/he/LC_MESSAGES/django.po684
-rw-r--r--locale/he/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/he/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/he/LC_MESSAGES/setup.po459
-rw-r--r--locale/hi/LC_MESSAGES/django.po684
-rw-r--r--locale/hi/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/hi/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/hi/LC_MESSAGES/setup.po459
-rw-r--r--locale/hr/LC_MESSAGES/django.po684
-rw-r--r--locale/hr/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/hr/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/hr/LC_MESSAGES/setup.po459
-rw-r--r--locale/hu/LC_MESSAGES/django.po684
-rw-r--r--locale/hu/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/hu/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/hu/LC_MESSAGES/setup.po459
-rw-r--r--locale/id/LC_MESSAGES/django.po684
-rw-r--r--locale/id/LC_MESSAGES/pyLoad.po859
-rw-r--r--locale/id/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/id/LC_MESSAGES/setup.po459
-rw-r--r--locale/it/LC_MESSAGES/django.mobin8770 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/django.po684
-rw-r--r--locale/it/LC_MESSAGES/pyLoad.mobin16689 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/it/LC_MESSAGES/pyLoadCli.mobin5113 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/it/LC_MESSAGES/pyLoadGui.mobin5830 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/setup.mobin11235 -> 0 bytes
-rw-r--r--locale/it/LC_MESSAGES/setup.po459
-rw-r--r--locale/ja/LC_MESSAGES/django.po684
-rw-r--r--locale/ja/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ja/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ja/LC_MESSAGES/setup.po459
-rw-r--r--locale/ko/LC_MESSAGES/django.po684
-rw-r--r--locale/ko/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ko/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ko/LC_MESSAGES/setup.po459
-rw-r--r--locale/ms/LC_MESSAGES/django.po684
-rw-r--r--locale/ms/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ms/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ms/LC_MESSAGES/setup.po459
-rw-r--r--locale/nl/LC_MESSAGES/django.mobin7957 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/django.po684
-rw-r--r--locale/nl/LC_MESSAGES/pyLoad.mobin16191 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadCli.mobin5102 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/nl/LC_MESSAGES/pyLoadGui.mobin5884 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/setup.mobin10634 -> 0 bytes
-rw-r--r--locale/nl/LC_MESSAGES/setup.po459
-rw-r--r--locale/no/LC_MESSAGES/django.po684
-rw-r--r--locale/no/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/no/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/no/LC_MESSAGES/setup.po459
-rw-r--r--locale/pa/LC_MESSAGES/django.po684
-rw-r--r--locale/pa/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/pa/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/pa/LC_MESSAGES/setup.po459
-rw-r--r--locale/pl/LC_MESSAGES/django.mobin7971 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/django.po684
-rw-r--r--locale/pl/LC_MESSAGES/pyLoad.mobin16488 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadCli.mobin5119 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/pl/LC_MESSAGES/pyLoadGui.mobin5881 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/setup.mobin11215 -> 0 bytes
-rw-r--r--locale/pl/LC_MESSAGES/setup.po459
-rw-r--r--locale/pt/LC_MESSAGES/django.po684
-rw-r--r--locale/pt/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/pt/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/pt/LC_MESSAGES/setup.po459
-rw-r--r--locale/pt_BR/LC_MESSAGES/django.mobin7957 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoad.mobin10427 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoadCli.mobin5121 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/pyLoadGui.mobin5825 -> 0 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/setup.mobin8329 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/django.po684
-rw-r--r--locale/ro/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadCli.mobin375 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ro/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/ro/LC_MESSAGES/setup.po459
-rw-r--r--locale/ru/LC_MESSAGES/django.mobin9739 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/django.po684
-rw-r--r--locale/ru/LC_MESSAGES/pyLoad.mobin15299 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadCli.mobin4299 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/ru/LC_MESSAGES/pyLoadGui.mobin7275 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/setup.mobin11323 -> 0 bytes
-rw-r--r--locale/ru/LC_MESSAGES/setup.po459
-rw-r--r--locale/setup.pot272
-rw-r--r--locale/si/LC_MESSAGES/django.po684
-rw-r--r--locale/si/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/si/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/si/LC_MESSAGES/setup.po459
-rw-r--r--locale/sq/LC_MESSAGES/django.po684
-rw-r--r--locale/sq/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/sq/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/sq/LC_MESSAGES/setup.po459
-rw-r--r--locale/sr/LC_MESSAGES/django.mobin9128 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/django.po684
-rw-r--r--locale/sr/LC_MESSAGES/pyLoad.mobin19506 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadCli.mobin6004 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/sr/LC_MESSAGES/pyLoadGui.mobin6756 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/setup.mobin13146 -> 0 bytes
-rw-r--r--locale/sr/LC_MESSAGES/setup.po459
-rw-r--r--locale/sv/LC_MESSAGES/django.mobin5358 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/django.po684
-rw-r--r--locale/sv/LC_MESSAGES/pyLoad.mobin6860 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadCli.mobin3366 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/sv/LC_MESSAGES/pyLoadGui.mobin5238 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/setup.mobin10953 -> 0 bytes
-rw-r--r--locale/sv/LC_MESSAGES/setup.po459
-rw-r--r--locale/te/LC_MESSAGES/django.po684
-rw-r--r--locale/te/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/te/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/te/LC_MESSAGES/setup.po459
-rw-r--r--locale/tr/LC_MESSAGES/django.po684
-rw-r--r--locale/tr/LC_MESSAGES/pyLoad.mobin356 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadCli.mobin1298 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/tr/LC_MESSAGES/pyLoadGui.mobin375 -> 0 bytes
-rw-r--r--locale/tr/LC_MESSAGES/setup.po459
-rw-r--r--locale/uk/LC_MESSAGES/django.po684
-rw-r--r--locale/uk/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/uk/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/uk/LC_MESSAGES/setup.po459
-rw-r--r--locale/vi/LC_MESSAGES/django.po684
-rw-r--r--locale/vi/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/vi/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/vi/LC_MESSAGES/setup.po459
-rw-r--r--locale/zh/LC_MESSAGES/django.po684
-rw-r--r--locale/zh/LC_MESSAGES/pyLoad.po865
-rw-r--r--locale/zh/LC_MESSAGES/pyLoadCli.po295
-rw-r--r--locale/zh/LC_MESSAGES/setup.po459
-rw-r--r--module/Api.py1033
-rw-r--r--module/CaptchaManager.py158
-rw-r--r--module/ConfigParser.py392
-rw-r--r--module/HookManager.py315
-rw-r--r--module/InitHomeDir.py83
-rw-r--r--module/PluginThread.py677
-rw-r--r--module/PullEvents.py120
-rw-r--r--module/PyFile.py285
-rw-r--r--module/PyPackage.py80
-rw-r--r--module/Scheduler.py141
-rw-r--r--module/ThreadManager.py318
-rw-r--r--module/cli/AddPackage.py66
-rw-r--r--module/cli/Handler.py48
-rw-r--r--module/cli/ManageFiles.py204
-rw-r--r--module/cli/__init__.py2
-rw-r--r--module/cli/printer.py26
-rw-r--r--module/common/APIExerciser.py157
-rw-r--r--module/common/ImportDebugger.py19
-rw-r--r--module/common/JsEngine.py162
-rw-r--r--module/common/__init__.py2
-rw-r--r--module/common/json_layer.py13
-rw-r--r--module/common/packagetools.py155
-rw-r--r--module/common/pylgettext.py61
-rw-r--r--module/config/default.conf65
-rw-r--r--module/config/gui_default.xml13
-rw-r--r--module/database/DatabaseBackend.py352
-rw-r--r--module/database/FileDatabase.py944
-rw-r--r--module/database/StorageDatabase.py49
-rw-r--r--module/database/UserDatabase.py108
-rw-r--r--module/database/__init__.py6
-rw-r--r--module/debug.py95
-rw-r--r--module/forwarder.py73
-rw-r--r--module/gui/AccountEdit.py104
-rw-r--r--module/gui/Accounts.py213
-rw-r--r--module/gui/CNLServer.py226
-rw-r--r--module/gui/CaptchaDock.py94
-rw-r--r--module/gui/Collector.py407
-rw-r--r--module/gui/ConnectionManager.py302
-rw-r--r--module/gui/CoreConfigParser.py165
-rw-r--r--module/gui/LinkDock.py56
-rw-r--r--module/gui/MainWindow.py697
-rw-r--r--module/gui/Overview.py197
-rw-r--r--module/gui/PackageDock.py90
-rw-r--r--module/gui/Queue.py390
-rw-r--r--module/gui/SettingsWidget.py202
-rw-r--r--module/gui/XMLParser.py71
-rw-r--r--module/gui/__init__.py1
-rw-r--r--module/gui/connector.py165
-rw-r--r--module/lib/BeautifulSoup.py2012
-rw-r--r--module/lib/Unzip.py50
-rw-r--r--module/lib/beaker/__init__.py1
-rw-r--r--module/lib/beaker/cache.py459
-rw-r--r--module/lib/beaker/container.py633
-rw-r--r--module/lib/beaker/converters.py26
-rw-r--r--module/lib/beaker/crypto/__init__.py40
-rw-r--r--module/lib/beaker/crypto/jcecrypto.py30
-rw-r--r--module/lib/beaker/crypto/pbkdf2.py342
-rw-r--r--module/lib/beaker/crypto/pycrypto.py31
-rw-r--r--module/lib/beaker/crypto/util.py30
-rw-r--r--module/lib/beaker/exceptions.py24
-rw-r--r--module/lib/beaker/ext/database.py165
-rw-r--r--module/lib/beaker/ext/google.py120
-rw-r--r--module/lib/beaker/ext/memcached.py82
-rw-r--r--module/lib/beaker/ext/sqla.py133
-rw-r--r--module/lib/beaker/middleware.py165
-rw-r--r--module/lib/beaker/session.py618
-rw-r--r--module/lib/beaker/synchronization.py381
-rw-r--r--module/lib/beaker/util.py302
-rw-r--r--module/lib/bottle.py2922
-rw-r--r--module/lib/feedparser.py3885
-rw-r--r--module/lib/jinja2/__init__.py73
-rw-r--r--module/lib/jinja2/_markupsafe/__init__.py225
-rw-r--r--module/lib/jinja2/_markupsafe/_bundle.py49
-rw-r--r--module/lib/jinja2/_markupsafe/_native.py45
-rw-r--r--module/lib/jinja2/_markupsafe/tests.py80
-rw-r--r--module/lib/jinja2/_stringdefs.py130
-rw-r--r--module/lib/jinja2/bccache.py280
-rw-r--r--module/lib/jinja2/compiler.py1640
-rw-r--r--module/lib/jinja2/debug.py308
-rw-r--r--module/lib/jinja2/defaults.py40
-rw-r--r--module/lib/jinja2/environment.py1118
-rw-r--r--module/lib/jinja2/exceptions.py143
-rw-r--r--module/lib/jinja2/ext.py610
-rw-r--r--module/lib/jinja2/filters.py719
-rw-r--r--module/lib/jinja2/lexer.py681
-rw-r--r--module/lib/jinja2/loaders.py449
-rw-r--r--module/lib/jinja2/meta.py102
-rw-r--r--module/lib/jinja2/nodes.py901
-rw-r--r--module/lib/jinja2/parser.py896
-rw-r--r--module/lib/jinja2/runtime.py544
-rw-r--r--module/lib/jinja2/sandbox.py271
-rw-r--r--module/lib/jinja2/tests.py146
-rw-r--r--module/lib/jinja2/utils.py601
-rw-r--r--module/lib/simplejson/__init__.py466
-rw-r--r--module/lib/simplejson/decoder.py421
-rw-r--r--module/lib/simplejson/encoder.py534
-rw-r--r--module/lib/simplejson/scanner.py77
-rw-r--r--module/lib/simplejson/tool.py39
-rw-r--r--module/lib/thrift/TSCons.py33
-rw-r--r--module/lib/thrift/TSerialization.py34
-rw-r--r--module/lib/thrift/Thrift.py154
-rw-r--r--module/lib/thrift/protocol/TBase.py72
-rw-r--r--module/lib/thrift/protocol/TBinaryProtocol.py259
-rw-r--r--module/lib/thrift/protocol/TCompactProtocol.py395
-rw-r--r--module/lib/thrift/protocol/TProtocol.py404
-rw-r--r--module/lib/thrift/protocol/__init__.py20
-rw-r--r--module/lib/thrift/server/THttpServer.py82
-rw-r--r--module/lib/thrift/server/TNonblockingServer.py310
-rw-r--r--module/lib/thrift/server/TProcessPoolServer.py125
-rw-r--r--module/lib/thrift/server/TServer.py274
-rw-r--r--module/lib/thrift/transport/THttpClient.py126
-rw-r--r--module/lib/thrift/transport/TSocket.py163
-rw-r--r--module/lib/thrift/transport/TTransport.py331
-rw-r--r--module/lib/thrift/transport/TTwisted.py219
-rw-r--r--module/lib/thrift/transport/TZlibTransport.py261
-rw-r--r--module/lib/thrift/transport/__init__.py20
-rw-r--r--module/lib/wsgiserver/LICENSE.txt25
-rw-r--r--module/lib/wsgiserver/__init__.py1794
-rw-r--r--module/network/Browser.py146
-rw-r--r--module/network/Bucket.py61
-rw-r--r--module/network/CookieJar.py50
-rw-r--r--module/network/HTTPChunk.py293
-rw-r--r--module/network/HTTPDownload.py340
-rw-r--r--module/network/HTTPRequest.py306
-rw-r--r--module/network/RequestFactory.py126
-rw-r--r--module/network/XDCCRequest.py162
-rw-r--r--module/plugins/Account.py281
-rw-r--r--module/plugins/AccountManager.py167
-rw-r--r--module/plugins/Container.py61
-rw-r--r--module/plugins/Crypter.py58
-rw-r--r--module/plugins/Hook.py149
-rw-r--r--module/plugins/Hoster.py20
-rw-r--r--module/plugins/Plugin.py604
-rw-r--r--module/plugins/PluginManager.py364
-rw-r--r--module/plugins/ReCaptcha.py24
-rw-r--r--module/plugins/accounts/AlldebridCom.py58
-rw-r--r--module/plugins/accounts/BayfilesCom.py36
-rw-r--r--module/plugins/accounts/BitshareCom.py31
-rw-r--r--module/plugins/accounts/CramitIn.py15
-rw-r--r--module/plugins/accounts/CyberlockerCh.py35
-rw-r--r--module/plugins/accounts/CzshareCom.py41
-rw-r--r--module/plugins/accounts/DebridItaliaCom.py36
-rw-r--r--module/plugins/accounts/DepositfilesCom.py32
-rw-r--r--module/plugins/accounts/EasybytezCom.py61
-rw-r--r--module/plugins/accounts/EgoFilesCom.py44
-rw-r--r--module/plugins/accounts/EuroshareEu.py41
-rw-r--r--module/plugins/accounts/FastixRu.py36
-rw-r--r--module/plugins/accounts/FastshareCz.py41
-rw-r--r--module/plugins/accounts/File4safeCom.py18
-rw-r--r--module/plugins/accounts/FilecloudIo.py57
-rw-r--r--module/plugins/accounts/FilefactoryCom.py46
-rw-r--r--module/plugins/accounts/FilejungleCom.py47
-rw-r--r--module/plugins/accounts/FilerNet.py49
-rw-r--r--module/plugins/accounts/FilerioCom.py15
-rw-r--r--module/plugins/accounts/FilesMailRu.py27
-rw-r--r--module/plugins/accounts/FileserveCom.py43
-rw-r--r--module/plugins/accounts/FourSharedCom.py29
-rw-r--r--module/plugins/accounts/FreakshareCom.py39
-rw-r--r--module/plugins/accounts/FreeWayMe.py52
-rw-r--r--module/plugins/accounts/FshareVn.py59
-rw-r--r--module/plugins/accounts/Ftp.py16
-rw-r--r--module/plugins/accounts/HellshareCz.py74
-rw-r--r--module/plugins/accounts/HotfileCom.py74
-rw-r--r--module/plugins/accounts/Http.py16
-rw-r--r--module/plugins/accounts/LetitbitNet.py33
-rw-r--r--module/plugins/accounts/LinksnappyCom.py49
-rw-r--r--module/plugins/accounts/MegaDebridEu.py37
-rw-r--r--module/plugins/accounts/MegasharesCom.py46
-rw-r--r--module/plugins/accounts/MovReelCom.py21
-rw-r--r--module/plugins/accounts/MultishareCz.py44
-rw-r--r--module/plugins/accounts/MyfastfileCom.py32
-rwxr-xr-xmodule/plugins/accounts/NetloadIn.py38
-rw-r--r--module/plugins/accounts/OboomCom.py53
-rw-r--r--module/plugins/accounts/OneFichierCom.py48
-rw-r--r--module/plugins/accounts/OverLoadMe.py35
-rw-r--r--module/plugins/accounts/PremiumTo.py28
-rw-r--r--module/plugins/accounts/PremiumizeMe.py46
-rw-r--r--module/plugins/accounts/QuickshareCz.py39
-rw-r--r--module/plugins/accounts/RPNetBiz.py49
-rw-r--r--module/plugins/accounts/RapidgatorNet.py56
-rw-r--r--module/plugins/accounts/RapidshareCom.py54
-rw-r--r--module/plugins/accounts/RarefileNet.py15
-rw-r--r--module/plugins/accounts/RealdebridCom.py35
-rw-r--r--module/plugins/accounts/RehostTo.py37
-rw-r--r--module/plugins/accounts/RyushareCom.py23
-rw-r--r--module/plugins/accounts/ShareRapidCom.py52
-rw-r--r--module/plugins/accounts/ShareonlineBiz.py42
-rw-r--r--module/plugins/accounts/SimplyPremiumCom.py45
-rw-r--r--module/plugins/accounts/SimplydebridCom.py33
-rw-r--r--module/plugins/accounts/StahnuTo.py34
-rw-r--r--module/plugins/accounts/TurbobitNet.py41
-rw-r--r--module/plugins/accounts/UlozTo.py45
-rw-r--r--module/plugins/accounts/UnrestrictLi.py43
-rw-r--r--module/plugins/accounts/UploadedTo.py53
-rw-r--r--module/plugins/accounts/UploadheroCom.py40
-rw-r--r--module/plugins/accounts/UploadingCom.py40
-rw-r--r--module/plugins/accounts/UptoboxCom.py17
-rw-r--r--module/plugins/accounts/YibaishiwuCom.py38
-rw-r--r--module/plugins/accounts/ZeveraCom.py54
-rw-r--r--module/plugins/captcha/GigasizeCom.py23
-rw-r--r--module/plugins/captcha/LinksaveIn.py149
-rw-r--r--module/plugins/captcha/NetloadIn.py28
-rw-r--r--module/plugins/captcha/ShareonlineBiz.py38
-rw-r--r--module/plugins/captcha/captcha.py303
-rw-r--r--module/plugins/container/CCF.py43
-rw-r--r--module/plugins/container/LinkList.py73
-rw-r--r--module/plugins/container/RSDF.py51
-rw-r--r--module/plugins/crypter/BitshareComFolder.py18
-rw-r--r--module/plugins/crypter/C1neonCom.py15
-rw-r--r--module/plugins/crypter/ChipDe.py27
-rw-r--r--module/plugins/crypter/CrockoComFolder.py17
-rw-r--r--module/plugins/crypter/CryptItCom.py15
-rw-r--r--module/plugins/crypter/CzshareComFolder.py31
-rw-r--r--module/plugins/crypter/DDLMusicOrg.py48
-rw-r--r--module/plugins/crypter/DailymotionBatch.py98
-rw-r--r--module/plugins/crypter/DataHuFolder.py43
-rw-r--r--module/plugins/crypter/DdlstorageComFolder.py18
-rw-r--r--module/plugins/crypter/DepositfilesComFolder.py17
-rw-r--r--module/plugins/crypter/Dereferer.py24
-rw-r--r--module/plugins/crypter/DlProtectCom.py62
-rw-r--r--module/plugins/crypter/DontKnowMe.py26
-rw-r--r--module/plugins/crypter/DuckCryptInfo.py59
-rw-r--r--module/plugins/crypter/DuploadOrgFolder.py17
-rw-r--r--module/plugins/crypter/EasybytezComFolder.py20
-rw-r--r--module/plugins/crypter/EmbeduploadCom.py55
-rw-r--r--module/plugins/crypter/FilebeerInfoFolder.py15
-rw-r--r--module/plugins/crypter/FilecloudIoFolder.py18
-rw-r--r--module/plugins/crypter/FilefactoryComFolder.py25
-rw-r--r--module/plugins/crypter/FilerNetFolder.py22
-rw-r--r--module/plugins/crypter/FileserveComFolder.py37
-rw-r--r--module/plugins/crypter/FilestubeCom.py18
-rw-r--r--module/plugins/crypter/FiletramCom.py18
-rw-r--r--module/plugins/crypter/FiredriveComFolder.py28
-rw-r--r--module/plugins/crypter/FourChanOrg.py25
-rw-r--r--module/plugins/crypter/FreakhareComFolder.py35
-rw-r--r--module/plugins/crypter/FreetexthostCom.py25
-rw-r--r--module/plugins/crypter/FshareVnFolder.py17
-rw-r--r--module/plugins/crypter/GooGl.py29
-rw-r--r--module/plugins/crypter/HoerbuchIn.py57
-rw-r--r--module/plugins/crypter/HotfileFolderCom.py30
-rw-r--r--module/plugins/crypter/ILoadTo.py15
-rw-r--r--module/plugins/crypter/ImgurComAlbum.py24
-rw-r--r--module/plugins/crypter/LetitbitNetFolder.py32
-rw-r--r--module/plugins/crypter/LinkSaveIn.py225
-rw-r--r--module/plugins/crypter/LinkdecrypterCom.py91
-rw-r--r--module/plugins/crypter/LixIn.py59
-rw-r--r--module/plugins/crypter/LofCc.py15
-rw-r--r--module/plugins/crypter/MBLinkInfo.py15
-rw-r--r--module/plugins/crypter/MediafireComFolder.py56
-rw-r--r--module/plugins/crypter/Movie2kTo.py15
-rw-r--r--module/plugins/crypter/MultiUpOrg.py35
-rw-r--r--module/plugins/crypter/MultiloadCz.py42
-rw-r--r--module/plugins/crypter/MultiuploadCom.py64
-rw-r--r--module/plugins/crypter/NCryptIn.py303
-rw-r--r--module/plugins/crypter/NetfolderIn.py73
-rw-r--r--module/plugins/crypter/NosvideoCom.py18
-rw-r--r--module/plugins/crypter/OneKhDe.py38
-rwxr-xr-xmodule/plugins/crypter/OronComFolder.py15
-rw-r--r--module/plugins/crypter/PastebinCom.py18
-rw-r--r--module/plugins/crypter/QuickshareCzFolder.py31
-rw-r--r--module/plugins/crypter/RSLayerCom.py15
-rw-r--r--module/plugins/crypter/RelinkUs.py263
-rw-r--r--module/plugins/crypter/SafelinkingNet.py82
-rw-r--r--module/plugins/crypter/SecuredIn.py15
-rw-r--r--module/plugins/crypter/SerienjunkiesOrg.py324
-rw-r--r--module/plugins/crypter/ShareLinksBiz.py269
-rw-r--r--module/plugins/crypter/ShareRapidComFolder.py17
-rw-r--r--module/plugins/crypter/SpeedLoadOrgFolder.py15
-rw-r--r--module/plugins/crypter/StealthTo.py15
-rw-r--r--module/plugins/crypter/TnyCz.py24
-rw-r--r--module/plugins/crypter/TrailerzoneInfo.py15
-rw-r--r--module/plugins/crypter/TurbobitNetFolder.py39
-rw-r--r--module/plugins/crypter/TusfilesNetFolder.py40
-rw-r--r--module/plugins/crypter/UlozToFolder.py45
-rw-r--r--module/plugins/crypter/UploadableChFolder.py21
-rw-r--r--module/plugins/crypter/UploadedToFolder.py38
-rw-r--r--module/plugins/crypter/WiiReloadedOrg.py15
-rw-r--r--module/plugins/crypter/XupPl.py23
-rw-r--r--module/plugins/crypter/YoutubeBatch.py138
-rw-r--r--module/plugins/hooks/AlldebridCom.py28
-rw-r--r--module/plugins/hooks/BypassCaptcha.py127
-rwxr-xr-xmodule/plugins/hooks/Captcha9kw.py156
-rw-r--r--module/plugins/hooks/CaptchaBrotherhood.py157
-rw-r--r--module/plugins/hooks/Checksum.py175
-rw-r--r--module/plugins/hooks/ClickAndLoad.py76
-rw-r--r--module/plugins/hooks/DeathByCaptcha.py202
-rw-r--r--module/plugins/hooks/DebridItaliaCom.py29
-rw-r--r--module/plugins/hooks/DeleteFinished.py69
-rw-r--r--module/plugins/hooks/DownloadScheduler.py75
-rw-r--r--module/plugins/hooks/EasybytezCom.py37
-rw-r--r--module/plugins/hooks/Ev0InFetcher.py81
-rw-r--r--module/plugins/hooks/ExpertDecoders.py94
-rw-r--r--module/plugins/hooks/ExternalScripts.py104
-rw-r--r--module/plugins/hooks/ExtractArchive.py320
-rw-r--r--module/plugins/hooks/FastixRu.py28
-rw-r--r--module/plugins/hooks/FreeWayMe.py26
-rw-r--r--module/plugins/hooks/HotFolder.py65
-rw-r--r--module/plugins/hooks/IRCInterface.py404
-rw-r--r--module/plugins/hooks/ImageTyperz.py143
-rw-r--r--module/plugins/hooks/LinkdecrypterCom.py55
-rw-r--r--module/plugins/hooks/LinksnappyCom.py28
-rw-r--r--module/plugins/hooks/MegaDebridEu.py31
-rw-r--r--module/plugins/hooks/MergeFiles.py76
-rw-r--r--module/plugins/hooks/MultiHome.py75
-rw-r--r--module/plugins/hooks/MultishareCz.py27
-rw-r--r--module/plugins/hooks/MyfastfileCom.py26
-rw-r--r--module/plugins/hooks/OverLoadMe.py31
-rw-r--r--module/plugins/hooks/PremiumTo.py32
-rw-r--r--module/plugins/hooks/PremiumizeMe.py54
-rw-r--r--module/plugins/hooks/RPNetBiz.py52
-rw-r--r--module/plugins/hooks/RealdebridCom.py28
-rw-r--r--module/plugins/hooks/RehostTo.py40
-rw-r--r--module/plugins/hooks/RestartFailed.py42
-rw-r--r--module/plugins/hooks/SimplyPremiumCom.py30
-rw-r--r--module/plugins/hooks/SimplydebridCom.py23
-rw-r--r--module/plugins/hooks/UnSkipOnFail.py85
-rw-r--r--module/plugins/hooks/UnrestrictLi.py31
-rw-r--r--module/plugins/hooks/UpdateManager.py281
-rw-r--r--module/plugins/hooks/WindowsPhoneToastNotify.py59
-rw-r--r--module/plugins/hooks/XFileSharingPro.py78
-rw-r--r--module/plugins/hooks/XMPPInterface.py233
-rw-r--r--module/plugins/hooks/ZeveraCom.py23
-rw-r--r--module/plugins/hoster/AlldebridCom.py87
-rw-r--r--module/plugins/hoster/BasePlugin.py116
-rw-r--r--module/plugins/hoster/BayfilesCom.py84
-rw-r--r--module/plugins/hoster/BezvadataCz.py87
-rw-r--r--module/plugins/hoster/BillionuploadsCom.py23
-rw-r--r--module/plugins/hoster/BitshareCom.py151
-rw-r--r--module/plugins/hoster/BoltsharingCom.py18
-rw-r--r--module/plugins/hoster/CatShareNet.py44
-rw-r--r--module/plugins/hoster/CloudzerNet.py18
-rw-r--r--module/plugins/hoster/CramitIn.py27
-rw-r--r--module/plugins/hoster/CrockoCom.py75
-rw-r--r--module/plugins/hoster/CyberlockerCh.py18
-rw-r--r--module/plugins/hoster/CzshareCom.py148
-rw-r--r--module/plugins/hoster/DailymotionCom.py111
-rw-r--r--module/plugins/hoster/DataHu.py41
-rw-r--r--module/plugins/hoster/DataportCz.py56
-rw-r--r--module/plugins/hoster/DateiTo.py83
-rw-r--r--module/plugins/hoster/DdlstorageCom.py18
-rw-r--r--module/plugins/hoster/DebridItaliaCom.py49
-rw-r--r--module/plugins/hoster/DepositfilesCom.py129
-rw-r--r--module/plugins/hoster/DlFreeFr.py205
-rw-r--r--module/plugins/hoster/DuploadOrg.py22
-rw-r--r--module/plugins/hoster/EasybytezCom.py31
-rw-r--r--module/plugins/hoster/EdiskCz.py54
-rw-r--r--module/plugins/hoster/EgoFilesCom.py89
-rw-r--r--module/plugins/hoster/EpicShareNet.py26
-rw-r--r--module/plugins/hoster/EuroshareEu.py64
-rw-r--r--module/plugins/hoster/ExtabitCom.py77
-rw-r--r--module/plugins/hoster/FastixRu.py71
-rw-r--r--module/plugins/hoster/FastshareCz.py88
-rw-r--r--module/plugins/hoster/File4safeCom.py40
-rw-r--r--module/plugins/hoster/FileApeCom.py18
-rw-r--r--module/plugins/hoster/FileParadoxIn.py25
-rw-r--r--module/plugins/hoster/FileStoreTo.py34
-rw-r--r--module/plugins/hoster/FilebeerInfo.py18
-rw-r--r--module/plugins/hoster/FilecloudIo.py115
-rw-r--r--module/plugins/hoster/FilefactoryCom.py106
-rw-r--r--module/plugins/hoster/FilejungleCom.py28
-rw-r--r--module/plugins/hoster/FileomCom.py39
-rw-r--r--module/plugins/hoster/FilepostCom.py129
-rw-r--r--module/plugins/hoster/FilerNet.py109
-rw-r--r--module/plugins/hoster/FilerioCom.py27
-rw-r--r--module/plugins/hoster/FilesMailRu.py101
-rw-r--r--module/plugins/hoster/FileserveCom.py209
-rw-r--r--module/plugins/hoster/FileshareInUa.py83
-rw-r--r--module/plugins/hoster/FilezyNet.py42
-rw-r--r--module/plugins/hoster/FiredriveCom.py51
-rw-r--r--module/plugins/hoster/FlyFilesNet.py46
-rw-r--r--module/plugins/hoster/FourSharedCom.py59
-rw-r--r--module/plugins/hoster/FreakshareCom.py173
-rw-r--r--module/plugins/hoster/FreeWayMe.py35
-rw-r--r--module/plugins/hoster/FreevideoCz.py18
-rw-r--r--module/plugins/hoster/FshareVn.py120
-rw-r--r--module/plugins/hoster/Ftp.py74
-rw-r--r--module/plugins/hoster/GamefrontCom.py84
-rw-r--r--module/plugins/hoster/GigapetaCom.py64
-rw-r--r--module/plugins/hoster/GooIm.py36
-rw-r--r--module/plugins/hoster/HellshareCz.py47
-rw-r--r--module/plugins/hoster/HellspyCz.py18
-rw-r--r--module/plugins/hoster/HotfileCom.py18
-rw-r--r--module/plugins/hoster/HugefilesNet.py25
-rw-r--r--module/plugins/hoster/HundredEightyUploadCom.py26
-rw-r--r--module/plugins/hoster/IFileWs.py23
-rw-r--r--module/plugins/hoster/IcyFilesCom.py18
-rw-r--r--module/plugins/hoster/IfileIt.py62
-rw-r--r--module/plugins/hoster/IfolderRu.py75
-rw-r--r--module/plugins/hoster/JumbofilesCom.py36
-rw-r--r--module/plugins/hoster/Keep2shareCC.py110
-rw-r--r--module/plugins/hoster/LemUploadsCom.py26
-rw-r--r--module/plugins/hoster/LetitbitNet.py160
-rw-r--r--module/plugins/hoster/LinksnappyCom.py72
-rw-r--r--module/plugins/hoster/LoadTo.py69
-rw-r--r--module/plugins/hoster/LomafileCom.py61
-rw-r--r--module/plugins/hoster/LuckyShareNet.py75
-rw-r--r--module/plugins/hoster/MediafireCom.py125
-rw-r--r--module/plugins/hoster/MegaDebridEu.py89
-rw-r--r--module/plugins/hoster/MegaFilesSe.py23
-rw-r--r--module/plugins/hoster/MegaNz.py132
-rw-r--r--module/plugins/hoster/MegacrypterCom.py53
-rw-r--r--module/plugins/hoster/MegareleaseOrg.py22
-rw-r--r--module/plugins/hoster/MegasharesCom.py105
-rw-r--r--module/plugins/hoster/MovReelCom.py24
-rw-r--r--module/plugins/hoster/MultishareCz.py72
-rw-r--r--module/plugins/hoster/MyfastfileCom.py42
-rw-r--r--module/plugins/hoster/MyvideoDe.py45
-rw-r--r--module/plugins/hoster/NarodRu.py60
-rw-r--r--module/plugins/hoster/NetloadIn.py258
-rw-r--r--module/plugins/hoster/NosuploadCom.py42
-rw-r--r--module/plugins/hoster/NovafileCom.py33
-rw-r--r--module/plugins/hoster/NowDownloadEu.py60
-rw-r--r--module/plugins/hoster/OboomCom.py132
-rw-r--r--module/plugins/hoster/OneFichierCom.py90
-rw-r--r--module/plugins/hoster/OverLoadMe.py82
-rw-r--r--module/plugins/hoster/PandaPlanet.py28
-rw-r--r--module/plugins/hoster/PornhostCom.py76
-rw-r--r--module/plugins/hoster/PornhubCom.py85
-rw-r--r--module/plugins/hoster/PotloadCom.py22
-rw-r--r--module/plugins/hoster/PremiumTo.py72
-rw-r--r--module/plugins/hoster/PremiumizeMe.py55
-rw-r--r--module/plugins/hoster/PromptfileCom.py45
-rw-r--r--module/plugins/hoster/QuickshareCz.py92
-rw-r--r--module/plugins/hoster/RPNetBiz.py80
-rw-r--r--module/plugins/hoster/RapidgatorNet.py191
-rw-r--r--module/plugins/hoster/RapidshareCom.py223
-rw-r--r--module/plugins/hoster/RarefileNet.py39
-rw-r--r--module/plugins/hoster/RealdebridCom.py91
-rw-r--r--module/plugins/hoster/RedtubeCom.py58
-rw-r--r--module/plugins/hoster/RehostTo.py41
-rw-r--r--module/plugins/hoster/RemixshareCom.py59
-rw-r--r--module/plugins/hoster/RgHostNet.py32
-rw-r--r--module/plugins/hoster/RyushareCom.py85
-rw-r--r--module/plugins/hoster/SecureUploadEu.py23
-rw-r--r--module/plugins/hoster/SendmywayCom.py23
-rw-r--r--module/plugins/hoster/SendspaceCom.py60
-rw-r--r--module/plugins/hoster/Share4webCom.py21
-rw-r--r--module/plugins/hoster/Share76Com.py18
-rw-r--r--module/plugins/hoster/ShareFilesCo.py18
-rw-r--r--module/plugins/hoster/ShareRapidCom.py66
-rw-r--r--module/plugins/hoster/SharebeesCom.py18
-rw-r--r--module/plugins/hoster/ShareonlineBiz.py199
-rw-r--r--module/plugins/hoster/ShareplaceCom.py84
-rw-r--r--module/plugins/hoster/ShragleCom.py18
-rw-r--r--module/plugins/hoster/SimplyPremiumCom.py81
-rw-r--r--module/plugins/hoster/SimplydebridCom.py62
-rw-r--r--module/plugins/hoster/SockshareCom.py88
-rw-r--r--module/plugins/hoster/SoundcloudCom.py57
-rw-r--r--module/plugins/hoster/SpeedLoadOrg.py18
-rw-r--r--module/plugins/hoster/SpeedfileCz.py18
-rw-r--r--module/plugins/hoster/SpeedyshareCom.py43
-rw-r--r--module/plugins/hoster/StreamCz.py70
-rw-r--r--module/plugins/hoster/StreamcloudEu.py124
-rw-r--r--module/plugins/hoster/TurbobitNet.py167
-rw-r--r--module/plugins/hoster/TurbouploadCom.py18
-rw-r--r--module/plugins/hoster/TusfilesNet.py31
-rw-r--r--module/plugins/hoster/TwoSharedCom.py39
-rw-r--r--module/plugins/hoster/UlozTo.py158
-rw-r--r--module/plugins/hoster/UloziskoSk.py70
-rw-r--r--module/plugins/hoster/UnibytesCom.py71
-rw-r--r--module/plugins/hoster/UnrestrictLi.py89
-rw-r--r--module/plugins/hoster/UploadStationCom.py18
-rw-r--r--module/plugins/hoster/UploadedTo.py240
-rw-r--r--module/plugins/hoster/UploadheroCom.py77
-rw-r--r--module/plugins/hoster/UploadingCom.py99
-rw-r--r--module/plugins/hoster/UpstoreNet.py75
-rw-r--r--module/plugins/hoster/UptoboxCom.py69
-rw-r--r--module/plugins/hoster/VeehdCom.py79
-rw-r--r--module/plugins/hoster/VeohCom.py51
-rw-r--r--module/plugins/hoster/VidPlayNet.py27
-rw-r--r--module/plugins/hoster/VimeoCom.py72
-rw-r--r--module/plugins/hoster/Vipleech4uCom.py18
-rw-r--r--module/plugins/hoster/WarserverCz.py18
-rw-r--r--module/plugins/hoster/WebshareCz.py60
-rw-r--r--module/plugins/hoster/WrzucTo.py51
-rw-r--r--module/plugins/hoster/WuploadCom.py18
-rw-r--r--module/plugins/hoster/X7To.py18
-rw-r--r--module/plugins/hoster/XFileSharingPro.py324
-rw-r--r--module/plugins/hoster/XHamsterCom.py123
-rw-r--r--module/plugins/hoster/XVideosCom.py28
-rw-r--r--module/plugins/hoster/Xdcc.py205
-rw-r--r--module/plugins/hoster/YibaishiwuCom.py54
-rw-r--r--module/plugins/hoster/YoupornCom.py56
-rw-r--r--module/plugins/hoster/YourfilesTo.py81
-rw-r--r--module/plugins/hoster/YoutubeCom.py180
-rw-r--r--module/plugins/hoster/ZDF.py56
-rw-r--r--module/plugins/hoster/ZeveraCom.py108
-rw-r--r--module/plugins/hoster/ZippyshareCom.py74
-rw-r--r--module/plugins/internal/DeadCrypter.py19
-rw-r--r--module/plugins/internal/DeadHoster.py27
-rw-r--r--module/plugins/internal/MultiHoster.py191
-rw-r--r--module/plugins/internal/SimpleCrypter.py118
-rw-r--r--module/plugins/internal/SimpleHoster.py292
-rw-r--r--module/plugins/internal/UnRar.py212
-rw-r--r--module/plugins/internal/UnZip.py38
-rw-r--r--module/plugins/internal/XFSPAccount.py69
-rw-r--r--module/remote/ClickAndLoadBackend.py170
-rw-r--r--module/remote/RemoteManager.py91
-rw-r--r--module/remote/SocketBackend.py25
-rw-r--r--module/remote/ThriftBackend.py56
-rw-r--r--module/remote/__init__.py2
-rw-r--r--module/remote/socketbackend/__init__.py2
-rw-r--r--module/remote/socketbackend/create_ttypes.py91
-rw-r--r--module/remote/socketbackend/ttypes.py383
-rw-r--r--module/remote/thriftbackend/Protocol.py30
-rw-r--r--module/remote/thriftbackend/Socket.py129
-rw-r--r--module/remote/thriftbackend/ThriftClient.py109
-rw-r--r--module/remote/thriftbackend/ThriftTest.py92
-rw-r--r--module/remote/thriftbackend/Transport.py37
-rw-r--r--module/remote/thriftbackend/pyload.thrift337
-rwxr-xr-xmodule/remote/thriftbackend/thriftgen/pyload/Pyload-remote571
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/Pyload.py5534
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/__init__.py1
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/constants.py11
-rw-r--r--module/remote/thriftbackend/thriftgen/pyload/ttypes.py835
-rw-r--r--module/setup.py514
-rw-r--r--module/unescape.py3
-rw-r--r--module/utils.py201
-rw-r--r--module/web/ServerThread.py109
-rw-r--r--module/web/api_app.py102
-rw-r--r--module/web/cnl_app.py164
-rw-r--r--module/web/filters.py62
-rw-r--r--module/web/json_app.py312
-rw-r--r--module/web/media/default/css/MooDialog.css92
-rw-r--r--module/web/media/default/css/default.css908
-rw-r--r--module/web/media/default/css/log.css72
-rw-r--r--module/web/media/img/favicon.icobin7206 -> 0 bytes
-rw-r--r--module/web/media/js/MooDialog_static.js401
-rw-r--r--module/web/media/js/MooDropMenu_static.js89
-rw-r--r--module/web/media/js/admin.coffee58
-rw-r--r--module/web/media/js/admin.js3
-rw-r--r--module/web/media/js/base.coffee173
-rw-r--r--module/web/media/js/base.js3
-rw-r--r--module/web/media/js/mootools-core-1.4.1.js476
-rw-r--r--module/web/media/js/mootools-more-1.4.0.1.js216
-rw-r--r--module/web/media/js/package_ui.js377
-rw-r--r--module/web/media/js/purr_static.js308
-rw-r--r--module/web/media/js/settings.coffee107
-rw-r--r--module/web/media/js/settings.js3
-rw-r--r--module/web/media/js/tinytab_static.js50
-rw-r--r--module/web/middlewares.py133
-rw-r--r--module/web/pyload_app.py533
-rw-r--r--module/web/servers/lighttpd_default.conf153
-rw-r--r--module/web/templates/500.html10
-rw-r--r--module/web/templates/default/admin.html98
-rw-r--r--module/web/templates/default/base.html180
-rw-r--r--module/web/templates/default/captcha.html42
-rw-r--r--module/web/templates/default/downloads.html29
-rw-r--r--module/web/templates/default/filemanager.html80
-rw-r--r--module/web/templates/default/filemanager_ui.js291
-rw-r--r--module/web/templates/default/folder.html15
-rw-r--r--module/web/templates/default/home.html266
-rw-r--r--module/web/templates/default/info.html81
-rw-r--r--module/web/templates/default/login.html36
-rw-r--r--module/web/templates/default/logout.html9
-rw-r--r--module/web/templates/default/logs.html41
-rw-r--r--module/web/templates/default/pathchooser.html76
-rw-r--r--module/web/templates/default/queue.html104
-rw-r--r--module/web/templates/default/settings.html204
-rw-r--r--module/web/templates/default/setup.html13
-rw-r--r--module/web/templates/default/window.html46
-rw-r--r--module/web/utils.py137
-rw-r--r--module/web/webinterface.py157
-rw-r--r--pavement.py132
-rwxr-xr-xpyLoadCli.py590
-rwxr-xr-xpyLoadCore.py668
-rwxr-xr-xpyLoadGui.py765
-rw-r--r--pyload-cli.py7
-rw-r--r--pyload.py7
-rw-r--r--pyload/Core.py651
-rw-r--r--pyload/InitHomeDir.py79
-rw-r--r--pyload/__init__.py20
-rw-r--r--pyload/api/__init__.py1030
-rw-r--r--pyload/cli/AddPackage.py65
-rw-r--r--pyload/cli/Cli.py585
-rw-r--r--pyload/cli/Handler.py47
-rw-r--r--pyload/cli/ManageFiles.py203
-rw-r--r--pyload/cli/__init__.py4
-rw-r--r--pyload/config/Parser.py373
-rw-r--r--pyload/config/Setup.py538
-rw-r--r--pyload/config/default.conf64
-rw-r--r--pyload/database/DatabaseBackend.py305
-rw-r--r--pyload/database/FileDatabase.py891
-rw-r--r--pyload/database/StorageDatabase.py49
-rw-r--r--pyload/database/UserDatabase.py108
-rw-r--r--pyload/database/__init__.py8
-rw-r--r--pyload/datatypes/PyFile.py284
-rw-r--r--pyload/datatypes/PyPackage.py79
-rw-r--r--pyload/datatypes/__init__.py (renamed from module/__init__.py)0
-rw-r--r--pyload/lib/BeautifulSoup.py2017
-rw-r--r--pyload/lib/Getch.py (renamed from module/lib/Getch.py)0
-rw-r--r--pyload/lib/MultipartPostHandler.py (renamed from module/lib/MultipartPostHandler.py)0
-rw-r--r--pyload/lib/SafeEval.py (renamed from module/lib/SafeEval.py)0
-rw-r--r--pyload/lib/__init__.py (renamed from module/lib/__init__.py)0
-rw-r--r--pyload/lib/beaker/__init__.py1
-rw-r--r--pyload/lib/beaker/cache.py589
-rw-r--r--pyload/lib/beaker/container.py750
-rw-r--r--pyload/lib/beaker/converters.py29
-rw-r--r--pyload/lib/beaker/crypto/__init__.py44
-rw-r--r--pyload/lib/beaker/crypto/jcecrypto.py32
-rw-r--r--pyload/lib/beaker/crypto/nsscrypto.py45
-rw-r--r--pyload/lib/beaker/crypto/pbkdf2.py347
-rw-r--r--pyload/lib/beaker/crypto/pycrypto.py34
-rw-r--r--pyload/lib/beaker/crypto/util.py30
-rw-r--r--pyload/lib/beaker/exceptions.py29
-rw-r--r--pyload/lib/beaker/ext/__init__.py (renamed from module/lib/beaker/ext/__init__.py)0
-rw-r--r--pyload/lib/beaker/ext/database.py174
-rw-r--r--pyload/lib/beaker/ext/google.py121
-rw-r--r--pyload/lib/beaker/ext/memcached.py203
-rw-r--r--pyload/lib/beaker/ext/sqla.py136
-rw-r--r--pyload/lib/beaker/middleware.py168
-rw-r--r--pyload/lib/beaker/session.py726
-rw-r--r--pyload/lib/beaker/synchronization.py386
-rw-r--r--pyload/lib/beaker/util.py462
-rw-r--r--pyload/lib/bottle.py3732
-rw-r--r--pyload/lib/colorama/__init__.py7
-rw-r--r--pyload/lib/colorama/ansi.py50
-rw-r--r--pyload/lib/colorama/ansitowin32.py191
-rw-r--r--pyload/lib/colorama/initialise.py66
-rw-r--r--pyload/lib/colorama/win32.py136
-rw-r--r--pyload/lib/colorama/winterm.py120
-rw-r--r--pyload/lib/feedparser.py4013
-rw-r--r--pyload/lib/jinja2/__init__.py69
-rw-r--r--pyload/lib/jinja2/_compat.py150
-rw-r--r--pyload/lib/jinja2/_stringdefs.py132
-rw-r--r--pyload/lib/jinja2/bccache.py344
-rw-r--r--pyload/lib/jinja2/compiler.py1640
-rw-r--r--pyload/lib/jinja2/constants.py (renamed from module/lib/jinja2/constants.py)0
-rw-r--r--pyload/lib/jinja2/debug.py337
-rw-r--r--pyload/lib/jinja2/defaults.py43
-rw-r--r--pyload/lib/jinja2/environment.py1191
-rw-r--r--pyload/lib/jinja2/exceptions.py146
-rw-r--r--pyload/lib/jinja2/ext.py636
-rw-r--r--pyload/lib/jinja2/filters.py987
-rw-r--r--pyload/lib/jinja2/lexer.py733
-rw-r--r--pyload/lib/jinja2/loaders.py471
-rw-r--r--pyload/lib/jinja2/meta.py103
-rw-r--r--pyload/lib/jinja2/nodes.py914
-rw-r--r--pyload/lib/jinja2/optimizer.py (renamed from module/lib/jinja2/optimizer.py)0
-rw-r--r--pyload/lib/jinja2/parser.py895
-rw-r--r--pyload/lib/jinja2/runtime.py581
-rw-r--r--pyload/lib/jinja2/sandbox.py368
-rw-r--r--pyload/lib/jinja2/tests.py149
-rw-r--r--pyload/lib/jinja2/testsuite/__init__.py156
-rw-r--r--pyload/lib/jinja2/testsuite/api.py261
-rw-r--r--pyload/lib/jinja2/testsuite/bytecode_cache.py37
-rw-r--r--pyload/lib/jinja2/testsuite/core_tags.py305
-rw-r--r--pyload/lib/jinja2/testsuite/debug.py58
-rw-r--r--pyload/lib/jinja2/testsuite/doctests.py29
-rw-r--r--pyload/lib/jinja2/testsuite/ext.py459
-rw-r--r--pyload/lib/jinja2/testsuite/filters.py515
-rw-r--r--pyload/lib/jinja2/testsuite/imports.py141
-rw-r--r--pyload/lib/jinja2/testsuite/inheritance.py250
-rw-r--r--pyload/lib/jinja2/testsuite/lexnparse.py593
-rw-r--r--pyload/lib/jinja2/testsuite/loader.py226
-rw-r--r--pyload/lib/jinja2/testsuite/regression.py279
-rw-r--r--pyload/lib/jinja2/testsuite/res/__init__.py (renamed from module/plugins/__init__.py)0
-rw-r--r--pyload/lib/jinja2/testsuite/res/templates/broken.html3
-rw-r--r--pyload/lib/jinja2/testsuite/res/templates/foo/test.html1
-rw-r--r--pyload/lib/jinja2/testsuite/res/templates/syntaxerror.html4
-rw-r--r--pyload/lib/jinja2/testsuite/res/templates/test.html1
-rw-r--r--pyload/lib/jinja2/testsuite/security.py166
-rw-r--r--pyload/lib/jinja2/testsuite/tests.py93
-rw-r--r--pyload/lib/jinja2/testsuite/utils.py82
-rw-r--r--pyload/lib/jinja2/utils.py520
-rw-r--r--pyload/lib/jinja2/visitor.py (renamed from module/lib/jinja2/visitor.py)0
-rw-r--r--pyload/lib/markupsafe/__init__.py298
-rw-r--r--pyload/lib/markupsafe/_compat.py26
-rw-r--r--pyload/lib/markupsafe/_constants.py (renamed from module/lib/jinja2/_markupsafe/_constants.py)0
-rw-r--r--pyload/lib/markupsafe/_native.py46
-rw-r--r--pyload/lib/markupsafe/_speedups.c239
-rw-r--r--pyload/lib/markupsafe/tests.py179
-rw-r--r--pyload/lib/rename_process.py (renamed from module/lib/rename_process.py)0
-rw-r--r--pyload/lib/simplejson/__init__.py560
-rw-r--r--pyload/lib/simplejson/_speedups.c3339
-rw-r--r--pyload/lib/simplejson/compat.py46
-rw-r--r--pyload/lib/simplejson/decoder.py400
-rw-r--r--pyload/lib/simplejson/encoder.py648
-rw-r--r--pyload/lib/simplejson/ordered_dict.py (renamed from module/lib/simplejson/ordered_dict.py)0
-rw-r--r--pyload/lib/simplejson/scanner.py133
-rw-r--r--pyload/lib/simplejson/tests/__init__.py88
-rw-r--r--pyload/lib/simplejson/tests/test_bigint_as_string.py67
-rw-r--r--pyload/lib/simplejson/tests/test_bitsize_int_as_string.py73
-rw-r--r--pyload/lib/simplejson/tests/test_check_circular.py30
-rw-r--r--pyload/lib/simplejson/tests/test_decimal.py71
-rw-r--r--pyload/lib/simplejson/tests/test_decode.py99
-rw-r--r--pyload/lib/simplejson/tests/test_default.py9
-rw-r--r--pyload/lib/simplejson/tests/test_dump.py121
-rw-r--r--pyload/lib/simplejson/tests/test_encode_basestring_ascii.py47
-rw-r--r--pyload/lib/simplejson/tests/test_encode_for_html.py30
-rw-r--r--pyload/lib/simplejson/tests/test_errors.py51
-rw-r--r--pyload/lib/simplejson/tests/test_fail.py176
-rw-r--r--pyload/lib/simplejson/tests/test_float.py35
-rw-r--r--pyload/lib/simplejson/tests/test_for_json.py97
-rw-r--r--pyload/lib/simplejson/tests/test_indent.py86
-rw-r--r--pyload/lib/simplejson/tests/test_item_sort_key.py20
-rw-r--r--pyload/lib/simplejson/tests/test_namedtuple.py122
-rw-r--r--pyload/lib/simplejson/tests/test_pass1.py71
-rw-r--r--pyload/lib/simplejson/tests/test_pass2.py14
-rw-r--r--pyload/lib/simplejson/tests/test_pass3.py20
-rw-r--r--pyload/lib/simplejson/tests/test_recursion.py67
-rw-r--r--pyload/lib/simplejson/tests/test_scanstring.py194
-rw-r--r--pyload/lib/simplejson/tests/test_separators.py42
-rw-r--r--pyload/lib/simplejson/tests/test_speedups.py39
-rw-r--r--pyload/lib/simplejson/tests/test_tool.py97
-rw-r--r--pyload/lib/simplejson/tests/test_tuple.py51
-rw-r--r--pyload/lib/simplejson/tests/test_unicode.py153
-rw-r--r--pyload/lib/simplejson/tool.py42
-rw-r--r--pyload/lib/thrift/TSCons.py35
-rw-r--r--pyload/lib/thrift/TSerialization.py38
-rw-r--r--pyload/lib/thrift/TTornado.py153
-rw-r--r--pyload/lib/thrift/Thrift.py170
-rw-r--r--pyload/lib/thrift/__init__.py (renamed from module/lib/thrift/__init__.py)0
-rw-r--r--pyload/lib/thrift/protocol/TBase.py81
-rw-r--r--pyload/lib/thrift/protocol/TBinaryProtocol.py260
-rw-r--r--pyload/lib/thrift/protocol/TCompactProtocol.py403
-rw-r--r--pyload/lib/thrift/protocol/TJSONProtocol.py550
-rw-r--r--pyload/lib/thrift/protocol/TProtocol.py406
-rw-r--r--pyload/lib/thrift/protocol/__init__.py20
-rw-r--r--pyload/lib/thrift/protocol/fastbinary.c1219
-rw-r--r--pyload/lib/thrift/server/THttpServer.py87
-rw-r--r--pyload/lib/thrift/server/TNonblockingServer.py346
-rw-r--r--pyload/lib/thrift/server/TProcessPoolServer.py118
-rw-r--r--pyload/lib/thrift/server/TServer.py269
-rw-r--r--pyload/lib/thrift/server/__init__.py (renamed from module/lib/thrift/server/__init__.py)0
-rw-r--r--pyload/lib/thrift/transport/THttpClient.py149
-rw-r--r--pyload/lib/thrift/transport/TSSLSocket.py214
-rw-r--r--pyload/lib/thrift/transport/TSocket.py176
-rw-r--r--pyload/lib/thrift/transport/TTransport.py330
-rw-r--r--pyload/lib/thrift/transport/TTwisted.py221
-rw-r--r--pyload/lib/thrift/transport/TZlibTransport.py248
-rw-r--r--pyload/lib/thrift/transport/__init__.py20
-rw-r--r--pyload/lib/wsgiserver.py2299
-rw-r--r--pyload/manager/AccountManager.py173
-rw-r--r--pyload/manager/CaptchaManager.py158
-rw-r--r--pyload/manager/HookManager.py305
-rw-r--r--pyload/manager/PluginManager.py356
-rw-r--r--pyload/manager/RemoteManager.py91
-rw-r--r--pyload/manager/ThreadManager.py317
-rw-r--r--pyload/manager/__init__.py (renamed from module/plugins/accounts/__init__.py)0
-rw-r--r--pyload/manager/event/PullEvents.py120
-rw-r--r--pyload/manager/event/Scheduler.py141
-rw-r--r--pyload/manager/event/__init__.py (renamed from module/plugins/captcha/__init__.py)0
-rw-r--r--pyload/manager/thread/PluginThread.py675
-rw-r--r--pyload/manager/thread/ServerThread.py108
-rw-r--r--pyload/manager/thread/__init__.py (renamed from module/plugins/container/__init__.py)0
-rw-r--r--pyload/network/Browser.py132
-rw-r--r--pyload/network/Bucket.py59
-rw-r--r--pyload/network/CookieJar.py50
-rw-r--r--pyload/network/HTTPChunk.py292
-rw-r--r--pyload/network/HTTPDownload.py325
-rw-r--r--pyload/network/HTTPRequest.py303
-rw-r--r--pyload/network/RequestFactory.py126
-rw-r--r--pyload/network/XDCCRequest.py159
-rw-r--r--pyload/network/__init__.py (renamed from module/network/__init__.py)0
-rw-r--r--pyload/plugins/Account.py281
-rw-r--r--pyload/plugins/Container.py61
-rw-r--r--pyload/plugins/Crypter.py64
-rw-r--r--pyload/plugins/Hook.py149
-rw-r--r--pyload/plugins/Hoster.py20
-rw-r--r--pyload/plugins/OCR.py299
-rw-r--r--pyload/plugins/Plugin.py629
-rw-r--r--pyload/plugins/README.md16
-rw-r--r--pyload/plugins/__init__.py (renamed from module/plugins/crypter/__init__.py)0
-rw-r--r--pyload/plugins/accounts/AlldebridCom.py58
-rw-r--r--pyload/plugins/accounts/BayfilesCom.py36
-rw-r--r--pyload/plugins/accounts/BitshareCom.py31
-rw-r--r--pyload/plugins/accounts/CramitIn.py15
-rw-r--r--pyload/plugins/accounts/CyberlockerCh.py35
-rw-r--r--pyload/plugins/accounts/CzshareCom.py41
-rw-r--r--pyload/plugins/accounts/DebridItaliaCom.py36
-rw-r--r--pyload/plugins/accounts/DepositfilesCom.py32
-rw-r--r--pyload/plugins/accounts/EasybytezCom.py61
-rw-r--r--pyload/plugins/accounts/EgoFilesCom.py44
-rw-r--r--pyload/plugins/accounts/EuroshareEu.py41
-rw-r--r--pyload/plugins/accounts/FastixRu.py36
-rw-r--r--pyload/plugins/accounts/FastshareCz.py41
-rw-r--r--pyload/plugins/accounts/File4safeCom.py18
-rw-r--r--pyload/plugins/accounts/FilecloudIo.py57
-rw-r--r--pyload/plugins/accounts/FilefactoryCom.py46
-rw-r--r--pyload/plugins/accounts/FilejungleCom.py47
-rw-r--r--pyload/plugins/accounts/FilerNet.py49
-rw-r--r--pyload/plugins/accounts/FilerioCom.py15
-rw-r--r--pyload/plugins/accounts/FilesMailRu.py27
-rw-r--r--pyload/plugins/accounts/FileserveCom.py43
-rw-r--r--pyload/plugins/accounts/FourSharedCom.py30
-rw-r--r--pyload/plugins/accounts/FreakshareCom.py39
-rw-r--r--pyload/plugins/accounts/FreeWayMe.py52
-rw-r--r--pyload/plugins/accounts/FshareVn.py59
-rw-r--r--pyload/plugins/accounts/Ftp.py16
-rw-r--r--pyload/plugins/accounts/HellshareCz.py74
-rw-r--r--pyload/plugins/accounts/HotfileCom.py74
-rw-r--r--pyload/plugins/accounts/Http.py16
-rw-r--r--pyload/plugins/accounts/LetitbitNet.py33
-rw-r--r--pyload/plugins/accounts/LinksnappyCom.py49
-rw-r--r--pyload/plugins/accounts/MegaDebridEu.py37
-rw-r--r--pyload/plugins/accounts/MegasharesCom.py46
-rw-r--r--pyload/plugins/accounts/MovReelCom.py21
-rw-r--r--pyload/plugins/accounts/MultishareCz.py44
-rw-r--r--pyload/plugins/accounts/MyfastfileCom.py34
-rw-r--r--pyload/plugins/accounts/NetloadIn.py38
-rw-r--r--pyload/plugins/accounts/OboomCom.py53
-rw-r--r--pyload/plugins/accounts/OneFichierCom.py48
-rw-r--r--pyload/plugins/accounts/OverLoadMe.py35
-rw-r--r--pyload/plugins/accounts/PremiumTo.py30
-rw-r--r--pyload/plugins/accounts/PremiumizeMe.py46
-rw-r--r--pyload/plugins/accounts/QuickshareCz.py39
-rw-r--r--pyload/plugins/accounts/RPNetBiz.py49
-rw-r--r--pyload/plugins/accounts/RapidgatorNet.py56
-rw-r--r--pyload/plugins/accounts/RapidshareCom.py54
-rw-r--r--pyload/plugins/accounts/RarefileNet.py15
-rw-r--r--pyload/plugins/accounts/RealdebridCom.py35
-rw-r--r--pyload/plugins/accounts/RehostTo.py37
-rw-r--r--pyload/plugins/accounts/RyushareCom.py23
-rw-r--r--pyload/plugins/accounts/ShareRapidCom.py52
-rw-r--r--pyload/plugins/accounts/ShareonlineBiz.py42
-rw-r--r--pyload/plugins/accounts/SimplyPremiumCom.py45
-rw-r--r--pyload/plugins/accounts/SimplydebridCom.py33
-rw-r--r--pyload/plugins/accounts/StahnuTo.py34
-rw-r--r--pyload/plugins/accounts/TurbobitNet.py41
-rw-r--r--pyload/plugins/accounts/UlozTo.py45
-rw-r--r--pyload/plugins/accounts/UnrestrictLi.py43
-rw-r--r--pyload/plugins/accounts/UploadedTo.py53
-rw-r--r--pyload/plugins/accounts/UploadheroCom.py40
-rw-r--r--pyload/plugins/accounts/UploadingCom.py40
-rw-r--r--pyload/plugins/accounts/UptoboxCom.py17
-rw-r--r--pyload/plugins/accounts/YibaishiwuCom.py38
-rw-r--r--pyload/plugins/accounts/ZeveraCom.py54
-rw-r--r--pyload/plugins/accounts/__init__.py (renamed from module/plugins/hooks/__init__.py)0
-rw-r--r--pyload/plugins/container/CCF.py43
-rw-r--r--pyload/plugins/container/DLC_25.pyc (renamed from module/plugins/container/DLC_25.pyc)bin8340 -> 8340 bytes
-rw-r--r--pyload/plugins/container/DLC_26.pyc (renamed from module/plugins/container/DLC_26.pyc)bin8313 -> 8313 bytes
-rw-r--r--pyload/plugins/container/DLC_27.pyc (renamed from module/plugins/container/DLC_27.pyc)bin8237 -> 8237 bytes
-rw-r--r--pyload/plugins/container/LinkList.py73
-rw-r--r--pyload/plugins/container/RSDF.py51
-rw-r--r--pyload/plugins/container/__init__.py (renamed from module/plugins/hoster/__init__.py)0
-rw-r--r--pyload/plugins/crypter/BitshareComFolder.py18
-rw-r--r--pyload/plugins/crypter/C1neonCom.py15
-rw-r--r--pyload/plugins/crypter/ChipDe.py27
-rw-r--r--pyload/plugins/crypter/CrockoComFolder.py17
-rw-r--r--pyload/plugins/crypter/CryptItCom.py15
-rw-r--r--pyload/plugins/crypter/CzshareComFolder.py31
-rw-r--r--pyload/plugins/crypter/DDLMusicOrg.py48
-rw-r--r--pyload/plugins/crypter/DailymotionBatch.py98
-rw-r--r--pyload/plugins/crypter/DataHuFolder.py43
-rw-r--r--pyload/plugins/crypter/DdlstorageComFolder.py18
-rw-r--r--pyload/plugins/crypter/DepositfilesComFolder.py17
-rw-r--r--pyload/plugins/crypter/Dereferer.py24
-rw-r--r--pyload/plugins/crypter/DlProtectCom.py62
-rw-r--r--pyload/plugins/crypter/DontKnowMe.py26
-rw-r--r--pyload/plugins/crypter/DuckCryptInfo.py59
-rw-r--r--pyload/plugins/crypter/DuploadOrgFolder.py17
-rw-r--r--pyload/plugins/crypter/EasybytezComFolder.py20
-rw-r--r--pyload/plugins/crypter/EmbeduploadCom.py55
-rw-r--r--pyload/plugins/crypter/FilebeerInfoFolder.py15
-rw-r--r--pyload/plugins/crypter/FilecloudIoFolder.py18
-rw-r--r--pyload/plugins/crypter/FilefactoryComFolder.py25
-rw-r--r--pyload/plugins/crypter/FilerNetFolder.py22
-rw-r--r--pyload/plugins/crypter/FileserveComFolder.py37
-rw-r--r--pyload/plugins/crypter/FilestubeCom.py18
-rw-r--r--pyload/plugins/crypter/FiletramCom.py18
-rw-r--r--pyload/plugins/crypter/FiredriveComFolder.py28
-rw-r--r--pyload/plugins/crypter/FourChanOrg.py25
-rw-r--r--pyload/plugins/crypter/FreakhareComFolder.py35
-rw-r--r--pyload/plugins/crypter/FreetexthostCom.py25
-rw-r--r--pyload/plugins/crypter/FshareVnFolder.py17
-rw-r--r--pyload/plugins/crypter/GooGl.py29
-rw-r--r--pyload/plugins/crypter/HoerbuchIn.py57
-rw-r--r--pyload/plugins/crypter/HotfileFolderCom.py30
-rw-r--r--pyload/plugins/crypter/ILoadTo.py15
-rw-r--r--pyload/plugins/crypter/ImgurComAlbum.py24
-rw-r--r--pyload/plugins/crypter/LetitbitNetFolder.py32
-rw-r--r--pyload/plugins/crypter/LinkSaveIn.py225
-rw-r--r--pyload/plugins/crypter/LinkdecrypterCom.py91
-rw-r--r--pyload/plugins/crypter/LixIn.py59
-rw-r--r--pyload/plugins/crypter/LofCc.py15
-rw-r--r--pyload/plugins/crypter/MBLinkInfo.py15
-rw-r--r--pyload/plugins/crypter/MediafireComFolder.py56
-rw-r--r--pyload/plugins/crypter/Movie2kTo.py15
-rw-r--r--pyload/plugins/crypter/MultiUpOrg.py35
-rw-r--r--pyload/plugins/crypter/MultiloadCz.py42
-rw-r--r--pyload/plugins/crypter/MultiuploadCom.py64
-rw-r--r--pyload/plugins/crypter/NCryptIn.py303
-rw-r--r--pyload/plugins/crypter/NetfolderIn.py73
-rw-r--r--pyload/plugins/crypter/NosvideoCom.py18
-rw-r--r--pyload/plugins/crypter/OneKhDe.py38
-rw-r--r--pyload/plugins/crypter/OronComFolder.py15
-rw-r--r--pyload/plugins/crypter/PastebinCom.py18
-rw-r--r--pyload/plugins/crypter/QuickshareCzFolder.py31
-rw-r--r--pyload/plugins/crypter/RSLayerCom.py15
-rw-r--r--pyload/plugins/crypter/RelinkUs.py263
-rw-r--r--pyload/plugins/crypter/SafelinkingNet.py82
-rw-r--r--pyload/plugins/crypter/SecuredIn.py15
-rw-r--r--pyload/plugins/crypter/SerienjunkiesOrg.py324
-rw-r--r--pyload/plugins/crypter/ShareLinksBiz.py269
-rw-r--r--pyload/plugins/crypter/ShareRapidComFolder.py17
-rw-r--r--pyload/plugins/crypter/SpeedLoadOrgFolder.py15
-rw-r--r--pyload/plugins/crypter/StealthTo.py15
-rw-r--r--pyload/plugins/crypter/TnyCz.py24
-rw-r--r--pyload/plugins/crypter/TrailerzoneInfo.py15
-rw-r--r--pyload/plugins/crypter/TurbobitNetFolder.py39
-rw-r--r--pyload/plugins/crypter/TusfilesNetFolder.py40
-rw-r--r--pyload/plugins/crypter/UlozToFolder.py45
-rw-r--r--pyload/plugins/crypter/UploadableChFolder.py21
-rw-r--r--pyload/plugins/crypter/UploadedToFolder.py38
-rw-r--r--pyload/plugins/crypter/WiiReloadedOrg.py15
-rw-r--r--pyload/plugins/crypter/XupPl.py23
-rw-r--r--pyload/plugins/crypter/YoutubeBatch.py138
-rw-r--r--pyload/plugins/crypter/__init__.py (renamed from module/plugins/internal/__init__.py)0
-rw-r--r--pyload/plugins/hooks/AlldebridCom.py28
-rw-r--r--pyload/plugins/hooks/BypassCaptcha.py127
-rw-r--r--pyload/plugins/hooks/Captcha9kw.py156
-rw-r--r--pyload/plugins/hooks/CaptchaBrotherhood.py157
-rw-r--r--pyload/plugins/hooks/Checksum.py175
-rw-r--r--pyload/plugins/hooks/ClickAndLoad.py76
-rw-r--r--pyload/plugins/hooks/DeathByCaptcha.py202
-rw-r--r--pyload/plugins/hooks/DebridItaliaCom.py29
-rw-r--r--pyload/plugins/hooks/DeleteFinished.py69
-rw-r--r--pyload/plugins/hooks/DownloadScheduler.py75
-rw-r--r--pyload/plugins/hooks/EasybytezCom.py37
-rw-r--r--pyload/plugins/hooks/Ev0InFetcher.py81
-rw-r--r--pyload/plugins/hooks/ExpertDecoders.py94
-rw-r--r--pyload/plugins/hooks/ExternalScripts.py104
-rw-r--r--pyload/plugins/hooks/ExtractArchive.py320
-rw-r--r--pyload/plugins/hooks/FastixRu.py28
-rw-r--r--pyload/plugins/hooks/FreeWayMe.py26
-rw-r--r--pyload/plugins/hooks/HotFolder.py65
-rw-r--r--pyload/plugins/hooks/IRCInterface.py404
-rw-r--r--pyload/plugins/hooks/ImageTyperz.py143
-rw-r--r--pyload/plugins/hooks/LinkdecrypterCom.py55
-rw-r--r--pyload/plugins/hooks/LinksnappyCom.py28
-rw-r--r--pyload/plugins/hooks/MegaDebridEu.py31
-rw-r--r--pyload/plugins/hooks/MergeFiles.py76
-rw-r--r--pyload/plugins/hooks/MultiHome.py75
-rw-r--r--pyload/plugins/hooks/MultishareCz.py27
-rw-r--r--pyload/plugins/hooks/MyfastfileCom.py29
-rw-r--r--pyload/plugins/hooks/OverLoadMe.py31
-rw-r--r--pyload/plugins/hooks/PremiumTo.py35
-rw-r--r--pyload/plugins/hooks/PremiumizeMe.py54
-rw-r--r--pyload/plugins/hooks/RPNetBiz.py52
-rw-r--r--pyload/plugins/hooks/RealdebridCom.py28
-rw-r--r--pyload/plugins/hooks/RehostTo.py40
-rw-r--r--pyload/plugins/hooks/RestartFailed.py42
-rw-r--r--pyload/plugins/hooks/SimplyPremiumCom.py30
-rw-r--r--pyload/plugins/hooks/SimplydebridCom.py23
-rw-r--r--pyload/plugins/hooks/UnSkipOnFail.py85
-rw-r--r--pyload/plugins/hooks/UnrestrictLi.py31
-rw-r--r--pyload/plugins/hooks/UpdateManager.py281
-rw-r--r--pyload/plugins/hooks/WindowsPhoneToastNotify.py59
-rw-r--r--pyload/plugins/hooks/XFileSharingPro.py78
-rw-r--r--pyload/plugins/hooks/XMPPInterface.py233
-rw-r--r--pyload/plugins/hooks/ZeveraCom.py23
-rw-r--r--pyload/plugins/hooks/__init__.py (renamed from module/remote/thriftbackend/__init__.py)0
-rw-r--r--pyload/plugins/hoster/AlldebridCom.py87
-rw-r--r--pyload/plugins/hoster/BasePlugin.py116
-rw-r--r--pyload/plugins/hoster/BayfilesCom.py84
-rw-r--r--pyload/plugins/hoster/BezvadataCz.py87
-rw-r--r--pyload/plugins/hoster/BillionuploadsCom.py23
-rw-r--r--pyload/plugins/hoster/BitshareCom.py151
-rw-r--r--pyload/plugins/hoster/BoltsharingCom.py18
-rw-r--r--pyload/plugins/hoster/CatShareNet.py44
-rw-r--r--pyload/plugins/hoster/CloudzerNet.py18
-rw-r--r--pyload/plugins/hoster/CramitIn.py27
-rw-r--r--pyload/plugins/hoster/CrockoCom.py75
-rw-r--r--pyload/plugins/hoster/CyberlockerCh.py18
-rw-r--r--pyload/plugins/hoster/CzshareCom.py148
-rw-r--r--pyload/plugins/hoster/DailymotionCom.py111
-rw-r--r--pyload/plugins/hoster/DataHu.py41
-rw-r--r--pyload/plugins/hoster/DataportCz.py56
-rw-r--r--pyload/plugins/hoster/DateiTo.py83
-rw-r--r--pyload/plugins/hoster/DdlstorageCom.py18
-rw-r--r--pyload/plugins/hoster/DebridItaliaCom.py49
-rw-r--r--pyload/plugins/hoster/DepositfilesCom.py129
-rw-r--r--pyload/plugins/hoster/DlFreeFr.py205
-rw-r--r--pyload/plugins/hoster/DuploadOrg.py22
-rw-r--r--pyload/plugins/hoster/EasybytezCom.py31
-rw-r--r--pyload/plugins/hoster/EdiskCz.py54
-rw-r--r--pyload/plugins/hoster/EgoFilesCom.py89
-rw-r--r--pyload/plugins/hoster/EpicShareNet.py26
-rw-r--r--pyload/plugins/hoster/EuroshareEu.py64
-rw-r--r--pyload/plugins/hoster/ExtabitCom.py77
-rw-r--r--pyload/plugins/hoster/FastixRu.py71
-rw-r--r--pyload/plugins/hoster/FastshareCz.py88
-rw-r--r--pyload/plugins/hoster/File4safeCom.py40
-rw-r--r--pyload/plugins/hoster/FileApeCom.py18
-rw-r--r--pyload/plugins/hoster/FileParadoxIn.py25
-rw-r--r--pyload/plugins/hoster/FileStoreTo.py34
-rw-r--r--pyload/plugins/hoster/FilebeerInfo.py18
-rw-r--r--pyload/plugins/hoster/FilecloudIo.py115
-rw-r--r--pyload/plugins/hoster/FilefactoryCom.py106
-rw-r--r--pyload/plugins/hoster/FilejungleCom.py28
-rw-r--r--pyload/plugins/hoster/FileomCom.py39
-rw-r--r--pyload/plugins/hoster/FilepostCom.py129
-rw-r--r--pyload/plugins/hoster/FilerNet.py109
-rw-r--r--pyload/plugins/hoster/FilerioCom.py27
-rw-r--r--pyload/plugins/hoster/FilesMailRu.py101
-rw-r--r--pyload/plugins/hoster/FileserveCom.py209
-rw-r--r--pyload/plugins/hoster/FileshareInUa.py83
-rw-r--r--pyload/plugins/hoster/FilezyNet.py42
-rw-r--r--pyload/plugins/hoster/FiredriveCom.py51
-rw-r--r--pyload/plugins/hoster/FlyFilesNet.py46
-rw-r--r--pyload/plugins/hoster/FourSharedCom.py59
-rw-r--r--pyload/plugins/hoster/FreakshareCom.py173
-rw-r--r--pyload/plugins/hoster/FreeWayMe.py35
-rw-r--r--pyload/plugins/hoster/FreevideoCz.py18
-rw-r--r--pyload/plugins/hoster/FshareVn.py120
-rw-r--r--pyload/plugins/hoster/Ftp.py76
-rw-r--r--pyload/plugins/hoster/GamefrontCom.py84
-rw-r--r--pyload/plugins/hoster/GigapetaCom.py64
-rw-r--r--pyload/plugins/hoster/GooIm.py36
-rw-r--r--pyload/plugins/hoster/HellshareCz.py47
-rw-r--r--pyload/plugins/hoster/HellspyCz.py18
-rw-r--r--pyload/plugins/hoster/HotfileCom.py18
-rw-r--r--pyload/plugins/hoster/HugefilesNet.py25
-rw-r--r--pyload/plugins/hoster/HundredEightyUploadCom.py26
-rw-r--r--pyload/plugins/hoster/IFileWs.py23
-rw-r--r--pyload/plugins/hoster/IcyFilesCom.py18
-rw-r--r--pyload/plugins/hoster/IfileIt.py62
-rw-r--r--pyload/plugins/hoster/IfolderRu.py75
-rw-r--r--pyload/plugins/hoster/JumbofilesCom.py36
-rw-r--r--pyload/plugins/hoster/Keep2shareCC.py110
-rw-r--r--pyload/plugins/hoster/LemUploadsCom.py26
-rw-r--r--pyload/plugins/hoster/LetitbitNet.py160
-rw-r--r--pyload/plugins/hoster/LinksnappyCom.py72
-rw-r--r--pyload/plugins/hoster/LoadTo.py69
-rw-r--r--pyload/plugins/hoster/LomafileCom.py61
-rw-r--r--pyload/plugins/hoster/LuckyShareNet.py75
-rw-r--r--pyload/plugins/hoster/MediafireCom.py125
-rw-r--r--pyload/plugins/hoster/MegaDebridEu.py89
-rw-r--r--pyload/plugins/hoster/MegaFilesSe.py23
-rw-r--r--pyload/plugins/hoster/MegaNz.py132
-rw-r--r--pyload/plugins/hoster/MegacrypterCom.py53
-rw-r--r--pyload/plugins/hoster/MegareleaseOrg.py22
-rw-r--r--pyload/plugins/hoster/MegasharesCom.py105
-rw-r--r--pyload/plugins/hoster/MovReelCom.py24
-rw-r--r--pyload/plugins/hoster/MultishareCz.py72
-rw-r--r--pyload/plugins/hoster/MyfastfileCom.py45
-rw-r--r--pyload/plugins/hoster/MyvideoDe.py45
-rw-r--r--pyload/plugins/hoster/NarodRu.py60
-rw-r--r--pyload/plugins/hoster/NetloadIn.py258
-rw-r--r--pyload/plugins/hoster/NosuploadCom.py42
-rw-r--r--pyload/plugins/hoster/NovafileCom.py33
-rw-r--r--pyload/plugins/hoster/NowDownloadEu.py60
-rw-r--r--pyload/plugins/hoster/OboomCom.py132
-rw-r--r--pyload/plugins/hoster/OneFichierCom.py90
-rw-r--r--pyload/plugins/hoster/OverLoadMe.py82
-rw-r--r--pyload/plugins/hoster/PandaPlanet.py28
-rw-r--r--pyload/plugins/hoster/PornhostCom.py76
-rw-r--r--pyload/plugins/hoster/PornhubCom.py85
-rw-r--r--pyload/plugins/hoster/PotloadCom.py22
-rw-r--r--pyload/plugins/hoster/PremiumTo.py75
-rw-r--r--pyload/plugins/hoster/PremiumizeMe.py55
-rw-r--r--pyload/plugins/hoster/PromptfileCom.py45
-rw-r--r--pyload/plugins/hoster/QuickshareCz.py92
-rw-r--r--pyload/plugins/hoster/RPNetBiz.py80
-rw-r--r--pyload/plugins/hoster/RapidgatorNet.py191
-rw-r--r--pyload/plugins/hoster/RapidshareCom.py223
-rw-r--r--pyload/plugins/hoster/RarefileNet.py39
-rw-r--r--pyload/plugins/hoster/RealdebridCom.py91
-rw-r--r--pyload/plugins/hoster/RedtubeCom.py58
-rw-r--r--pyload/plugins/hoster/RehostTo.py41
-rw-r--r--pyload/plugins/hoster/RemixshareCom.py59
-rw-r--r--pyload/plugins/hoster/RgHostNet.py32
-rw-r--r--pyload/plugins/hoster/RyushareCom.py85
-rw-r--r--pyload/plugins/hoster/SecureUploadEu.py23
-rw-r--r--pyload/plugins/hoster/SendmywayCom.py23
-rw-r--r--pyload/plugins/hoster/SendspaceCom.py60
-rw-r--r--pyload/plugins/hoster/Share4webCom.py21
-rw-r--r--pyload/plugins/hoster/Share76Com.py18
-rw-r--r--pyload/plugins/hoster/ShareFilesCo.py18
-rw-r--r--pyload/plugins/hoster/ShareRapidCom.py66
-rw-r--r--pyload/plugins/hoster/SharebeesCom.py18
-rw-r--r--pyload/plugins/hoster/ShareonlineBiz.py199
-rw-r--r--pyload/plugins/hoster/ShareplaceCom.py84
-rw-r--r--pyload/plugins/hoster/ShragleCom.py18
-rw-r--r--pyload/plugins/hoster/SimplyPremiumCom.py81
-rw-r--r--pyload/plugins/hoster/SimplydebridCom.py62
-rw-r--r--pyload/plugins/hoster/SockshareCom.py88
-rw-r--r--pyload/plugins/hoster/SoundcloudCom.py57
-rw-r--r--pyload/plugins/hoster/SpeedLoadOrg.py18
-rw-r--r--pyload/plugins/hoster/SpeedfileCz.py18
-rw-r--r--pyload/plugins/hoster/SpeedyshareCom.py43
-rw-r--r--pyload/plugins/hoster/StreamCz.py70
-rw-r--r--pyload/plugins/hoster/StreamcloudEu.py124
-rw-r--r--pyload/plugins/hoster/TurbobitNet.py167
-rw-r--r--pyload/plugins/hoster/TurbouploadCom.py18
-rw-r--r--pyload/plugins/hoster/TusfilesNet.py31
-rw-r--r--pyload/plugins/hoster/TwoSharedCom.py39
-rw-r--r--pyload/plugins/hoster/UlozTo.py158
-rw-r--r--pyload/plugins/hoster/UloziskoSk.py70
-rw-r--r--pyload/plugins/hoster/UnibytesCom.py71
-rw-r--r--pyload/plugins/hoster/UnrestrictLi.py89
-rw-r--r--pyload/plugins/hoster/UploadStationCom.py18
-rw-r--r--pyload/plugins/hoster/UploadedTo.py240
-rw-r--r--pyload/plugins/hoster/UploadheroCom.py77
-rw-r--r--pyload/plugins/hoster/UploadingCom.py99
-rw-r--r--pyload/plugins/hoster/UpstoreNet.py75
-rw-r--r--pyload/plugins/hoster/UptoboxCom.py69
-rw-r--r--pyload/plugins/hoster/VeehdCom.py79
-rw-r--r--pyload/plugins/hoster/VeohCom.py51
-rw-r--r--pyload/plugins/hoster/VidPlayNet.py27
-rw-r--r--pyload/plugins/hoster/VimeoCom.py72
-rw-r--r--pyload/plugins/hoster/Vipleech4uCom.py18
-rw-r--r--pyload/plugins/hoster/WarserverCz.py18
-rw-r--r--pyload/plugins/hoster/WebshareCz.py60
-rw-r--r--pyload/plugins/hoster/WrzucTo.py51
-rw-r--r--pyload/plugins/hoster/WuploadCom.py18
-rw-r--r--pyload/plugins/hoster/X7To.py18
-rw-r--r--pyload/plugins/hoster/XFileSharingPro.py324
-rw-r--r--pyload/plugins/hoster/XHamsterCom.py123
-rw-r--r--pyload/plugins/hoster/XVideosCom.py28
-rw-r--r--pyload/plugins/hoster/Xdcc.py205
-rw-r--r--pyload/plugins/hoster/YibaishiwuCom.py54
-rw-r--r--pyload/plugins/hoster/YoupornCom.py56
-rw-r--r--pyload/plugins/hoster/YourfilesTo.py81
-rw-r--r--pyload/plugins/hoster/YoutubeCom.py180
-rw-r--r--pyload/plugins/hoster/ZDF.py56
-rw-r--r--pyload/plugins/hoster/ZeveraCom.py108
-rw-r--r--pyload/plugins/hoster/ZippyshareCom.py74
-rw-r--r--pyload/plugins/hoster/__init__.py (renamed from module/remote/thriftbackend/thriftgen/__init__.py)0
-rw-r--r--pyload/plugins/internal/AbstractExtractor.py (renamed from module/plugins/internal/AbstractExtractor.py)0
-rw-r--r--pyload/plugins/internal/CaptchaService.py (renamed from module/plugins/internal/CaptchaService.py)0
-rw-r--r--pyload/plugins/internal/DeadCrypter.py19
-rw-r--r--pyload/plugins/internal/DeadHoster.py27
-rw-r--r--pyload/plugins/internal/MultiHoster.py192
-rw-r--r--pyload/plugins/internal/SimpleCrypter.py118
-rw-r--r--pyload/plugins/internal/SimpleHoster.py292
-rw-r--r--pyload/plugins/internal/UnRar.py212
-rw-r--r--pyload/plugins/internal/UnZip.py38
-rw-r--r--pyload/plugins/internal/XFSPAccount.py69
-rw-r--r--pyload/plugins/internal/__init__.py (renamed from module/web/__init__.py)0
-rw-r--r--pyload/plugins/ocr/GigasizeCom.py23
-rw-r--r--pyload/plugins/ocr/LinksaveIn.py149
-rw-r--r--pyload/plugins/ocr/NetloadIn.py27
-rw-r--r--pyload/plugins/ocr/ShareonlineBiz.py38
-rw-r--r--pyload/plugins/ocr/__init__.py0
-rw-r--r--pyload/remote/ClickAndLoadBackend.py170
-rw-r--r--pyload/remote/SocketBackend.py25
-rw-r--r--pyload/remote/ThriftBackend.py56
-rw-r--r--pyload/remote/__init__.py3
-rw-r--r--pyload/remote/socketbackend/__init__.py0
-rw-r--r--pyload/remote/socketbackend/create_ttypes.py86
-rw-r--r--pyload/remote/socketbackend/ttypes.py381
-rw-r--r--pyload/remote/thriftbackend/Processor.py (renamed from module/remote/thriftbackend/Processor.py)0
-rw-r--r--pyload/remote/thriftbackend/Protocol.py30
-rw-r--r--pyload/remote/thriftbackend/Socket.py129
-rw-r--r--pyload/remote/thriftbackend/ThriftClient.py87
-rw-r--r--pyload/remote/thriftbackend/ThriftTest.py91
-rw-r--r--pyload/remote/thriftbackend/Transport.py37
-rw-r--r--pyload/remote/thriftbackend/__init__.py0
-rw-r--r--pyload/remote/thriftbackend/pyload.thrift337
-rw-r--r--pyload/remote/thriftbackend/thriftgen/__init__.py0
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote570
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py5533
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/__init__.py3
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/constants.py10
-rw-r--r--pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py834
-rw-r--r--pyload/utils/JsEngine.py238
-rw-r--r--pyload/utils/__init__.py244
-rw-r--r--pyload/utils/packagetools.py136
-rw-r--r--pyload/utils/printer.py15
-rw-r--r--pyload/utils/pylgettext.py60
-rw-r--r--pyload/webui/__init__.py146
-rw-r--r--pyload/webui/app/__init__.py3
-rw-r--r--pyload/webui/app/api.py101
-rw-r--r--pyload/webui/app/cnl.py168
-rw-r--r--pyload/webui/app/json.py311
-rw-r--r--pyload/webui/app/pyload.py544
-rw-r--r--pyload/webui/app/utils.py138
-rw-r--r--pyload/webui/filters.py61
-rw-r--r--pyload/webui/middlewares.py132
-rw-r--r--pyload/webui/servers/lighttpd_default.conf153
-rw-r--r--pyload/webui/servers/nginx_default.conf (renamed from module/web/servers/nginx_default.conf)0
-rw-r--r--pyload/webui/themes/dark/css/MooDialog.css94
-rw-r--r--pyload/webui/themes/dark/css/dark.css962
-rw-r--r--pyload/webui/themes/dark/css/log.css75
-rw-r--r--pyload/webui/themes/dark/css/pathchooser.css (renamed from module/web/media/default/css/pathchooser.css)0
-rw-r--r--pyload/webui/themes/dark/css/window.css92
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-close.png (renamed from module/web/media/img/dialog-close.png)bin689 -> 689 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-question.png (renamed from module/web/media/img/dialog-question.png)bin2073 -> 2073 bytes
-rw-r--r--pyload/webui/themes/dark/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/dark/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/dark/img/dark-bg.jpgbin0 -> 40930 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/add_folder.png (renamed from module/web/media/default/img/add_folder.png)bin571 -> 571 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/ajax-loader.gif (renamed from module/web/media/default/img/ajax-loader.gif)bin404 -> 404 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/arrow_refresh.png (renamed from module/web/media/default/img/arrow_refresh.png)bin685 -> 685 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/arrow_right.png (renamed from module/web/media/default/img/arrow_right.png)bin349 -> 349 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/big_button.gif (renamed from module/web/media/default/img/big_button.gif)bin1905 -> 1905 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/big_button_over.gif (renamed from module/web/media/default/img/big_button_over.gif)bin728 -> 728 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/body.png (renamed from module/web/media/default/img/body.png)bin402 -> 402 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/closebtn.gif (renamed from module/web/media/default/img/closebtn.gif)bin254 -> 254 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/cog.png (renamed from module/web/media/default/img/cog.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_add.png (renamed from module/web/media/default/img/control_add.png)bin446 -> 446 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_add_blue.png (renamed from module/web/media/default/img/control_add_blue.png)bin845 -> 845 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_cancel.png (renamed from module/web/media/default/img/control_cancel.png)bin3349 -> 3349 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_cancel_blue.png (renamed from module/web/media/default/img/control_cancel_blue.png)bin787 -> 787 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_pause.png (renamed from module/web/media/default/img/control_pause.png)bin598 -> 598 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_pause_blue.png (renamed from module/web/media/default/img/control_pause_blue.png)bin721 -> 721 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_play.png (renamed from module/web/media/default/img/control_play.png)bin592 -> 592 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_play_blue.png (renamed from module/web/media/default/img/control_play_blue.png)bin717 -> 717 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_stop.png (renamed from module/web/media/default/img/control_stop.png)bin403 -> 403 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/control_stop_blue.png (renamed from module/web/media/default/img/control_stop_blue.png)bin695 -> 695 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/delete.png (renamed from module/web/media/default/img/delete.png)bin715 -> 715 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/drag_corner.gif (renamed from module/web/media/default/img/drag_corner.gif)bin76 -> 76 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/error.png (renamed from module/web/media/default/img/error.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/folder.png (renamed from module/web/media/default/img/folder.png)bin537 -> 537 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/full.png (renamed from module/web/media/default/img/full.png)bin3543 -> 3543 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-login.png (renamed from module/web/media/default/img/head-login.png)bin1288 -> 1288 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-collector.png (renamed from module/web/media/default/img/head-menu-collector.png)bin1953 -> 1953 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-config.png (renamed from module/web/media/default/img/head-menu-config.png)bin1802 -> 1802 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-development.png (renamed from module/web/media/default/img/head-menu-development.png)bin876 -> 876 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-download.png (renamed from module/web/media/default/img/head-menu-download.png)bin721 -> 721 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-home.png (renamed from module/web/media/default/img/head-menu-home.png)bin920 -> 920 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-index.png (renamed from module/web/media/default/img/head-menu-index.png)bin482 -> 482 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-news.png (renamed from module/web/media/default/img/head-menu-news.png)bin628 -> 628 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-queue.png (renamed from module/web/media/default/img/head-menu-queue.png)bin2629 -> 2629 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-recent.png (renamed from module/web/media/default/img/head-menu-recent.png)bin932 -> 932 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-menu-wiki.png (renamed from module/web/media/default/img/head-menu-wiki.png)bin1204 -> 1204 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head-search-noshadow.png (renamed from module/web/media/default/img/head-search-noshadow.png)bin1187 -> 1187 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/head_bg1.png (renamed from module/web/media/default/img/head_bg1.png)bin125 -> 125 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/images.png (renamed from module/web/media/default/img/images.png)bin661 -> 661 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/notice.png (renamed from module/web/media/default/img/notice.png)bin778 -> 778 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/package_go.png (renamed from module/web/media/default/img/package_go.png)bin898 -> 898 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-backlinks.png (renamed from module/web/media/default/img/page-tools-backlinks.png)bin540 -> 540 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-edit.png (renamed from module/web/media/default/img/page-tools-edit.png)bin574 -> 574 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/page-tools-revisions.png (renamed from module/web/media/default/img/page-tools-revisions.png)bin603 -> 603 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/parseUri.png (renamed from module/web/media/default/img/parseUri.png)bin666 -> 666 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/pencil.png (renamed from module/web/media/default/img/pencil.png)bin450 -> 450 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/reconnect.png (renamed from module/web/media/default/img/reconnect.png)bin755 -> 755 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_None.png (renamed from module/web/media/default/img/status_None.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_downloading.png (renamed from module/web/media/default/img/status_downloading.png)bin943 -> 943 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_failed.png (renamed from module/web/media/default/img/status_failed.png)bin701 -> 701 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_finished.png (renamed from module/web/media/default/img/status_finished.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_offline.png (renamed from module/web/media/default/img/status_offline.png)bin700 -> 700 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_proc.png (renamed from module/web/media/default/img/status_proc.png)bin512 -> 512 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_queue.png (renamed from module/web/media/default/img/status_queue.png)bin7613 -> 7613 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/status_waiting.png (renamed from module/web/media/default/img/status_waiting.png)bin889 -> 889 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/success.png (renamed from module/web/media/default/img/success.png)bin781 -> 781 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/tabs-border-bottom.png (renamed from module/web/media/default/img/tabs-border-bottom.png)bin163 -> 163 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-actions-logout.png (renamed from module/web/media/default/img/user-actions-logout.png)bin799 -> 799 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-actions-profile.png (renamed from module/web/media/default/img/user-actions-profile.png)bin628 -> 628 bytes
-rw-r--r--pyload/webui/themes/dark/img/default/user-info.png (renamed from module/web/media/default/img/user-info.png)bin3963 -> 3963 bytes
-rw-r--r--pyload/webui/themes/dark/img/pyload-logo.pngbin0 -> 6947 bytes
-rw-r--r--pyload/webui/themes/dark/img/tab-background.pngbin0 -> 3044 bytes
-rw-r--r--pyload/webui/themes/dark/js/render/admin.coffee58
-rw-r--r--pyload/webui/themes/dark/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/dark/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/dark/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/dark/js/render/package.js376
-rw-r--r--pyload/webui/themes/dark/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/dark/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/dark/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/dark/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/dark/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/dark/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/dark/js/static/purr.js309
-rw-r--r--pyload/webui/themes/dark/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/dark/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/dark/js/static/tinytab.min.js1
-rw-r--r--pyload/webui/themes/dark/tml/admin.html98
-rw-r--r--pyload/webui/themes/dark/tml/base.html177
-rw-r--r--pyload/webui/themes/dark/tml/captcha.html42
-rw-r--r--pyload/webui/themes/dark/tml/downloads.html29
-rw-r--r--pyload/webui/themes/dark/tml/folder.html15
-rw-r--r--pyload/webui/themes/dark/tml/home.html263
-rw-r--r--pyload/webui/themes/dark/tml/info.html76
-rw-r--r--pyload/webui/themes/dark/tml/login.html37
-rw-r--r--pyload/webui/themes/dark/tml/logout.html9
-rw-r--r--pyload/webui/themes/dark/tml/logs.html41
-rw-r--r--pyload/webui/themes/dark/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/dark/tml/queue.html104
-rw-r--r--pyload/webui/themes/dark/tml/settings.html204
-rw-r--r--pyload/webui/themes/dark/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/dark/tml/window.html52
-rw-r--r--pyload/webui/themes/default/css/MooDialog.css91
-rw-r--r--pyload/webui/themes/default/css/default.css902
-rw-r--r--pyload/webui/themes/default/css/log.css71
-rw-r--r--pyload/webui/themes/default/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/default/css/window.css (renamed from module/web/media/default/css/window.css)0
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/default/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/default/img/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/default/img/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/default/img/arrow_refresh.pngbin0 -> 685 bytes
-rw-r--r--pyload/webui/themes/default/img/arrow_right.pngbin0 -> 349 bytes
-rw-r--r--pyload/webui/themes/default/img/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/default/img/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/default/img/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/default/img/button.png (renamed from module/web/media/default/img/button.png)bin452 -> 452 bytes
-rw-r--r--pyload/webui/themes/default/img/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/default/img/cog.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/default/img/control_add.pngbin0 -> 446 bytes
-rw-r--r--pyload/webui/themes/default/img/control_add_blue.pngbin0 -> 845 bytes
-rw-r--r--pyload/webui/themes/default/img/control_cancel.pngbin0 -> 3349 bytes
-rw-r--r--pyload/webui/themes/default/img/control_cancel_blue.pngbin0 -> 787 bytes
-rw-r--r--pyload/webui/themes/default/img/control_pause.pngbin0 -> 598 bytes
-rw-r--r--pyload/webui/themes/default/img/control_pause_blue.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/default/img/control_play.pngbin0 -> 592 bytes
-rw-r--r--pyload/webui/themes/default/img/control_play_blue.pngbin0 -> 717 bytes
-rw-r--r--pyload/webui/themes/default/img/control_stop.pngbin0 -> 403 bytes
-rw-r--r--pyload/webui/themes/default/img/control_stop_blue.pngbin0 -> 695 bytes
-rw-r--r--pyload/webui/themes/default/img/delete.pngbin0 -> 715 bytes
-rw-r--r--pyload/webui/themes/default/img/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/default/img/error.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/default/img/folder.pngbin0 -> 537 bytes
-rw-r--r--pyload/webui/themes/default/img/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/default/img/head-login.pngbin0 -> 1288 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-collector.pngbin0 -> 1953 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-config.pngbin0 -> 1802 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-development.pngbin0 -> 876 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-download.pngbin0 -> 721 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-home.pngbin0 -> 920 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-index.pngbin0 -> 482 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-news.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-queue.pngbin0 -> 2629 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/default/img/head-menu-wiki.pngbin0 -> 1204 bytes
-rw-r--r--pyload/webui/themes/default/img/head-search-noshadow.pngbin0 -> 1187 bytes
-rw-r--r--pyload/webui/themes/default/img/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/default/img/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/default/img/notice.pngbin0 -> 778 bytes
-rw-r--r--pyload/webui/themes/default/img/package_go.pngbin0 -> 898 bytes
-rw-r--r--pyload/webui/themes/default/img/page-tools-backlinks.pngbin0 -> 540 bytes
-rw-r--r--pyload/webui/themes/default/img/page-tools-edit.pngbin0 -> 574 bytes
-rw-r--r--pyload/webui/themes/default/img/page-tools-revisions.pngbin0 -> 603 bytes
-rw-r--r--pyload/webui/themes/default/img/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/default/img/pencil.pngbin0 -> 450 bytes
-rw-r--r--pyload/webui/themes/default/img/pyload-logo.png (renamed from module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png)bin8457 -> 8457 bytes
-rw-r--r--pyload/webui/themes/default/img/reconnect.pngbin0 -> 755 bytes
-rw-r--r--pyload/webui/themes/default/img/status_None.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/default/img/status_downloading.pngbin0 -> 943 bytes
-rw-r--r--pyload/webui/themes/default/img/status_failed.pngbin0 -> 701 bytes
-rw-r--r--pyload/webui/themes/default/img/status_finished.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/default/img/status_offline.pngbin0 -> 700 bytes
-rw-r--r--pyload/webui/themes/default/img/status_proc.pngbin0 -> 512 bytes
-rw-r--r--pyload/webui/themes/default/img/status_queue.pngbin0 -> 7613 bytes
-rw-r--r--pyload/webui/themes/default/img/status_waiting.pngbin0 -> 889 bytes
-rw-r--r--pyload/webui/themes/default/img/success.pngbin0 -> 781 bytes
-rw-r--r--pyload/webui/themes/default/img/tab-background.png (renamed from module/web/media/default/img/tab-background.png)bin179 -> 179 bytes
-rw-r--r--pyload/webui/themes/default/img/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/default/img/user-actions-logout.pngbin0 -> 799 bytes
-rw-r--r--pyload/webui/themes/default/img/user-actions-profile.pngbin0 -> 628 bytes
-rw-r--r--pyload/webui/themes/default/img/user-info.pngbin0 -> 3963 bytes
-rw-r--r--pyload/webui/themes/default/js/render/admin.coffee58
-rw-r--r--pyload/webui/themes/default/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/default/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/default/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/default/js/render/filemanager.js291
-rw-r--r--pyload/webui/themes/default/js/render/package.js376
-rw-r--r--pyload/webui/themes/default/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/default/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/default/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/default/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/default/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/default/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/default/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/default/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/default/js/static/purr.js309
-rw-r--r--pyload/webui/themes/default/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/default/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/default/js/static/tinytab.min.js1
-rw-r--r--pyload/webui/themes/default/tml/admin.html98
-rw-r--r--pyload/webui/themes/default/tml/base.html180
-rw-r--r--pyload/webui/themes/default/tml/captcha.html42
-rw-r--r--pyload/webui/themes/default/tml/downloads.html29
-rw-r--r--pyload/webui/themes/default/tml/filemanager.html78
-rw-r--r--pyload/webui/themes/default/tml/folder.html15
-rw-r--r--pyload/webui/themes/default/tml/home.html266
-rw-r--r--pyload/webui/themes/default/tml/info.html81
-rw-r--r--pyload/webui/themes/default/tml/login.html36
-rw-r--r--pyload/webui/themes/default/tml/logout.html9
-rw-r--r--pyload/webui/themes/default/tml/logs.html41
-rw-r--r--pyload/webui/themes/default/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/default/tml/queue.html104
-rw-r--r--pyload/webui/themes/default/tml/settings.html204
-rw-r--r--pyload/webui/themes/default/tml/settings_item.html48
-rw-r--r--pyload/webui/themes/default/tml/window.html46
-rw-r--r--pyload/webui/themes/flat/css/MooDialog.css84
-rw-r--r--pyload/webui/themes/flat/css/flat.css863
-rw-r--r--pyload/webui/themes/flat/css/log.css72
-rw-r--r--pyload/webui/themes/flat/css/pathchooser.css68
-rw-r--r--pyload/webui/themes/flat/css/window.css73
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-close.pngbin0 -> 689 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-error.pngbin0 -> 1472 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-question.pngbin0 -> 2073 bytes
-rw-r--r--pyload/webui/themes/flat/img/MooDialog/dialog-warning.pngbin0 -> 1651 bytes
-rw-r--r--pyload/webui/themes/flat/img/arrow_refresh.pngbin0 -> 119032 bytes
-rw-r--r--pyload/webui/themes/flat/img/arrow_right.pngbin0 -> 136967 bytes
-rw-r--r--pyload/webui/themes/flat/img/button.pngbin0 -> 569 bytes
-rw-r--r--pyload/webui/themes/flat/img/cog.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_add.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_add_blue.pngbin0 -> 116941 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_cancel.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_cancel_blue.pngbin0 -> 116939 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_pause.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_pause_blue.pngbin0 -> 134855 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_play.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_play_blue.pngbin0 -> 134904 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_stop.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/flat/img/control_stop_blue.pngbin0 -> 134835 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/add_folder.pngbin0 -> 571 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/ajax-loader.gifbin0 -> 404 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/big_button.gifbin0 -> 1905 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/big_button_over.gifbin0 -> 728 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/body.pngbin0 -> 402 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/closebtn.gifbin0 -> 254 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/drag_corner.gifbin0 -> 76 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/full.pngbin0 -> 3543 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/head-menu-recent.pngbin0 -> 932 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/head_bg1.pngbin0 -> 125 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/images.pngbin0 -> 661 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/parseUri.pngbin0 -> 666 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/pyload-logo.pngbin0 -> 8457 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/tab-background.pngbin0 -> 179 bytes
-rw-r--r--pyload/webui/themes/flat/img/default/tabs-border-bottom.pngbin0 -> 163 bytes
-rw-r--r--pyload/webui/themes/flat/img/delete.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/flat/img/error.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/flat/img/folder.pngbin0 -> 134669 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-login.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-collector.pngbin0 -> 134985 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-config.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-development.pngbin0 -> 135818 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-download.pngbin0 -> 137664 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-home.pngbin0 -> 139387 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-index.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-news.pngbin0 -> 136511 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-queue.pngbin0 -> 136269 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-menu-wiki.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/flat/img/head-search-noshadow.pngbin0 -> 137217 bytes
-rw-r--r--pyload/webui/themes/flat/img/notice.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/flat/img/package_go.pngbin0 -> 136299 bytes
-rw-r--r--pyload/webui/themes/flat/img/page-tools-backlinks.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/page-tools-edit.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/page-tools-revisions.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/pencil.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/reconnect.pngbin0 -> 3063 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_None.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_downloading.pngbin0 -> 3061 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_failed.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_finished.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_offline.pngbin0 -> 137673 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_proc.pngbin0 -> 137406 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_queue.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/status_waiting.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/success.pngbin0 -> 117658 bytes
-rw-r--r--pyload/webui/themes/flat/img/user-actions-logout.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/user-actions-profile.pngbin0 -> 138112 bytes
-rw-r--r--pyload/webui/themes/flat/img/user-info.pngbin0 -> 3080 bytes
-rw-r--r--pyload/webui/themes/flat/js/render/admin.coffee58
-rw-r--r--pyload/webui/themes/flat/js/render/admin.min.js3
-rw-r--r--pyload/webui/themes/flat/js/render/base.coffee177
-rw-r--r--pyload/webui/themes/flat/js/render/base.min.js3
-rw-r--r--pyload/webui/themes/flat/js/render/package.js376
-rw-r--r--pyload/webui/themes/flat/js/render/settings.coffee107
-rw-r--r--pyload/webui/themes/flat/js/render/settings.min.js3
-rw-r--r--pyload/webui/themes/flat/js/static/MooDialog.js140
-rw-r--r--pyload/webui/themes/flat/js/static/MooDialog.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/MooDropMenu.js86
-rw-r--r--pyload/webui/themes/flat/js/static/MooDropMenu.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-core.js5977
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-core.min.js491
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-more.js2856
-rw-r--r--pyload/webui/themes/flat/js/static/mootools-more.min.js226
-rw-r--r--pyload/webui/themes/flat/js/static/purr.js309
-rw-r--r--pyload/webui/themes/flat/js/static/purr.min.js1
-rw-r--r--pyload/webui/themes/flat/js/static/tinytab.js43
-rw-r--r--pyload/webui/themes/flat/js/static/tinytab.min.js1
-rw-r--r--pyload/webui/themes/flat/tml/admin.html98
-rw-r--r--pyload/webui/themes/flat/tml/base.html177
-rw-r--r--pyload/webui/themes/flat/tml/captcha.html42
-rw-r--r--pyload/webui/themes/flat/tml/downloads.html29
-rw-r--r--pyload/webui/themes/flat/tml/folder.html15
-rw-r--r--pyload/webui/themes/flat/tml/home.html263
-rw-r--r--pyload/webui/themes/flat/tml/info.html81
-rw-r--r--pyload/webui/themes/flat/tml/login.html36
-rw-r--r--pyload/webui/themes/flat/tml/logout.html9
-rw-r--r--pyload/webui/themes/flat/tml/logs.html41
-rw-r--r--pyload/webui/themes/flat/tml/pathchooser.html76
-rw-r--r--pyload/webui/themes/flat/tml/queue.html104
-rw-r--r--pyload/webui/themes/flat/tml/settings.html204
-rw-r--r--pyload/webui/themes/flat/tml/settings_item.html (renamed from module/web/templates/default/settings_item.html)0
-rw-r--r--pyload/webui/themes/flat/tml/window.html46
-rw-r--r--scripts/Readme.txt25
-rw-r--r--systemCheck.py142
-rw-r--r--testlinks.txt26
-rw-r--r--tests/APIExerciser.py157
-rw-r--r--tests/clonedigger.sh4
-rw-r--r--tests/code_analysis.sh25
-rw-r--r--tests/test_api.py3
-rw-r--r--tests/test_json.py2
1778 files changed, 224423 insertions, 86159 deletions
diff --git a/.gitattributes b/.gitattributes
index e34533a0d..d3aa9bed2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,21 +1,38 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
-# Explicitly declare text files we want to always be normalized and converted
+# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
-*.py text
-*.js text
-*.po text
+*.bat text
+*.c text
*.cfg text
-*.md text
+*.cmd text
+*.coffee text
+*.conf text
+*.htm text
+*.html text
*.in text
-*.txt text
-*.sh text
+*.js text
*.json text
-*.html text
-.htm text
+*.md text
+*.po text
+*.pot text
+*.py text
+*.rst text
+*.sh text
+*.thrift text
+*.txt text
+*.xml text
+*.yaml text
# Denote all files that are truly binary and should not be modified.
-*.png binary
+*.gif binary
+*.ico binary
*.jpg binary
-*.mo binary \ No newline at end of file
+*.mo binary
+*.pyc binary
+*.pyx binary
+*.png binary
+*.rar binary
+*.svg binary
+*.zip binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..e035516d1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,85 @@
+# Common packaging directories
+bin/
+build/
+dist/
+env/
+lib/
+lib64/
+parts/
+sdist/
+var/
+temp/
+!/pyload/lib/
+
+# Common directories
+.*/
+__pycache__/
+Downloads/
+Logs/
+tmp/
+userplugins/
+
+# Common files
+*.backup
+*.db
+*.pid
+/*.conf
+/*.version
+/*.txt
+/*.xml
+/*.zip
+/pyload/config/core.xml
+/pyload/config/plugin.xml
+
+# Common MacOS & Windows files
+*.DS_Store
+Thumbs.db
+desktop.ini
+
+# Certificate files
+cert.pem
+ssl.crt
+ssl.key
+
+# Common compiled files
+*.mo
+*.so
+*.py[cod]
+!/pyload/plugins/container/DLC_*.py[cod]
+
+# Installer files
+.installed.cfg
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+nosetests.xml
+
+# Python Eggs
+*.egg
+*.egg-info
+develop-eggs
+eggs
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# PyCharm
+.idea
+
+# Other development stuff
+.svn
+*~
+*.orig
+*.pidaproject
+*.prefs
+*.project
+*.pydevproject
+*.rej
+_build/
+/module/
+DLC_*.py
+paver-minilib.zip
diff --git a/.hgignore b/.hgignore
deleted file mode 100644
index 1215b241d..000000000
--- a/.hgignore
+++ /dev/null
@@ -1,36 +0,0 @@
-# ignoreing unneeded files, using glob syntax
-syntax: glob
-*.pyc
-*~
-*.pidaproject
-.svn
-*.DS_Store
-*.egg-info
-*.project
-*.pydevproject
-Downloads/*
-container/*
-Logs/*
-docs/module/
-docs/_build
-module/plugins/container/DLC_*.py
-failed_links.txt
-module/config/gui.xml
-module/config/core.xml
-module/config/plugin.xml
-links.txt
-ssl.crt
-ssl.key
-cert.pem
-module/web/pyload.db
-*.svg
-*.prefs
-*.po
-*.orig
-*.rej
-pyload/*
-dist/*
-build/*
-setup.py
-paver-minilib.zip
-env/*
diff --git a/CREDITS.md b/CREDITS.md
new file mode 100644
index 000000000..8e0f86740
--- /dev/null
+++ b/CREDITS.md
@@ -0,0 +1,38 @@
+Credits
+-------
+
+### pyLoad Team ###
+
+*(alphabetically sorted)*
+
+ - himbrr <himbrr@himbrr.ws>
+ - Marius <mkaay@mkaay.de>
+ - RaNaN <Mast3rRaNaN@hotmail.de>
+ - sebnapi
+ - spoob <spoob@gmx.de>
+ - Stefano <l.stickell@yahoo.it>
+ - Walter Purcaro <vuolter@gmail.com>
+ - zoidberg10 <zoidberg@mujmail.cz>
+
+A special thanks to spoob, sebnapi and RaNaN who created pyLoad!
+
+
+### Contributors ###
+
+The list of developers who have kindly contributed to the pyLoad project is constantly updated.
+You can found it here: <https://github.com/pyload/pyload/graphs/contributors>.
+
+
+### Translators ###
+
+Many users contribute everyday to translate pyLoad in more and more languages.
+You can see their recent activities here: <http://translate.pyload.org/project/pyload/activity_stream>.
+
+
+### Supporters ###
+
+Sadly, is really hard to list all the users who helped the pyLoad Team in these last years testing code, reporting issues,
+opening topic to share tips and advices, fix troubles or simply discuss about this project...
+
+
+#### The pyLoad Team can be only forever thankful to all of you for your amazing efforts! ####
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index bc08fe2e4..000000000
--- a/LICENSE
+++ /dev/null
@@ -1,619 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
- The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works. By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users. We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors. You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
- To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights. Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received. You must make sure that they, too, receive
-or can get the source code. And you must show them these terms so they
-know their rights.
-
- Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
- For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software. For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
- Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so. This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software. The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable. Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products. If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
- Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary. To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- TERMS AND CONDITIONS
-
- 0. Definitions.
-
- "This License" refers to version 3 of the GNU General Public License.
-
- "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
- "The Program" refers to any copyrightable work licensed under this
-License. Each licensee is addressed as "you". "Licensees" and
-"recipients" may be individuals or organizations.
-
- To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy. The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
- A "covered work" means either the unmodified Program or a work based
-on the Program.
-
- To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy. Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
- To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies. Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
- An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License. If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
- 1. Source Code.
-
- The "source code" for a work means the preferred form of the work
-for making modifications to it. "Object code" means any non-source
-form of a work.
-
- A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
- The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form. A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
- The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities. However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work. For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
- The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
- The Corresponding Source for a work in source code form is that
-same work.
-
- 2. Basic Permissions.
-
- All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met. This License explicitly affirms your unlimited
-permission to run the unmodified Program. The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work. This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
- You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force. You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright. Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
- Conveying under any other circumstances is permitted solely under
-the conditions stated below. Sublicensing is not allowed; section 10
-makes it unnecessary.
-
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
- No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
- When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
- 4. Conveying Verbatim Copies.
-
- You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
- You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
- 5. Conveying Modified Source Versions.
-
- You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
- a) The work must carry prominent notices stating that you modified
- it, and giving a relevant date.
-
- b) The work must carry prominent notices stating that it is
- released under this License and any conditions added under section
- 7. This requirement modifies the requirement in section 4 to
- "keep intact all notices".
-
- c) You must license the entire work, as a whole, under this
- License to anyone who comes into possession of a copy. This
- License will therefore apply, along with any applicable section 7
- additional terms, to the whole of the work, and all its parts,
- regardless of how they are packaged. This License gives no
- permission to license the work in any other way, but it does not
- invalidate such permission if you have separately received it.
-
- d) If the work has interactive user interfaces, each must display
- Appropriate Legal Notices; however, if the Program has interactive
- interfaces that do not display Appropriate Legal Notices, your
- work need not make them do so.
-
- A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit. Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
- 6. Conveying Non-Source Forms.
-
- You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
- a) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by the
- Corresponding Source fixed on a durable physical medium
- customarily used for software interchange.
-
- b) Convey the object code in, or embodied in, a physical product
- (including a physical distribution medium), accompanied by a
- written offer, valid for at least three years and valid for as
- long as you offer spare parts or customer support for that product
- model, to give anyone who possesses the object code either (1) a
- copy of the Corresponding Source for all the software in the
- product that is covered by this License, on a durable physical
- medium customarily used for software interchange, for a price no
- more than your reasonable cost of physically performing this
- conveying of source, or (2) access to copy the
- Corresponding Source from a network server at no charge.
-
- c) Convey individual copies of the object code with a copy of the
- written offer to provide the Corresponding Source. This
- alternative is allowed only occasionally and noncommercially, and
- only if you received the object code with such an offer, in accord
- with subsection 6b.
-
- d) Convey the object code by offering access from a designated
- place (gratis or for a charge), and offer equivalent access to the
- Corresponding Source in the same way through the same place at no
- further charge. You need not require recipients to copy the
- Corresponding Source along with the object code. If the place to
- copy the object code is a network server, the Corresponding Source
- may be on a different server (operated by you or a third party)
- that supports equivalent copying facilities, provided you maintain
- clear directions next to the object code saying where to find the
- Corresponding Source. Regardless of what server hosts the
- Corresponding Source, you remain obligated to ensure that it is
- available for as long as needed to satisfy these requirements.
-
- e) Convey the object code using peer-to-peer transmission, provided
- you inform other peers where the object code and Corresponding
- Source of the work are being offered to the general public at no
- charge under subsection 6d.
-
- A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
- A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling. In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage. For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product. A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
- "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source. The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
- If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information. But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
- The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed. Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
- Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
- 7. Additional Terms.
-
- "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law. If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
- When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it. (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.) You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
- Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
- a) Disclaiming warranty or limiting liability differently from the
- terms of sections 15 and 16 of this License; or
-
- b) Requiring preservation of specified reasonable legal notices or
- author attributions in that material or in the Appropriate Legal
- Notices displayed by works containing it; or
-
- c) Prohibiting misrepresentation of the origin of that material, or
- requiring that modified versions of such material be marked in
- reasonable ways as different from the original version; or
-
- d) Limiting the use for publicity purposes of names of licensors or
- authors of the material; or
-
- e) Declining to grant rights under trademark law for use of some
- trade names, trademarks, or service marks; or
-
- f) Requiring indemnification of licensors and authors of that
- material by anyone who conveys the material (or modified versions of
- it) with contractual assumptions of liability to the recipient, for
- any liability that these contractual assumptions directly impose on
- those licensors and authors.
-
- All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10. If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term. If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
- If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
- Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
- 8. Termination.
-
- You may not propagate or modify a covered work except as expressly
-provided under this License. Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
- However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
- Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
- Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License. If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
- 9. Acceptance Not Required for Having Copies.
-
- You are not required to accept this License in order to receive or
-run a copy of the Program. Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance. However,
-nothing other than this License grants you permission to propagate or
-modify any covered work. These actions infringe copyright if you do
-not accept this License. Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
- 10. Automatic Licensing of Downstream Recipients.
-
- Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License. You are not responsible
-for enforcing compliance by third parties with this License.
-
- An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations. If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
- You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License. For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
- 11. Patents.
-
- A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based. The
-work thus licensed is called the contributor's "contributor version".
-
- A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version. For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
- Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
- In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement). To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
- If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients. "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
- If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
- A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License. You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
- Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
- 12. No Surrender of Others' Freedom.
-
- If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all. For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
- 13. Use with the GNU Affero General Public License.
-
- Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work. The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
- 14. Revised Versions of this License.
-
- The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation. If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
- If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
- Later license versions may give you additional or different
-permissions. However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
- 15. Disclaimer of Warranty.
-
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. Limitation of Liability.
-
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
- 17. Interpretation of Sections 15 and 16.
-
- If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 000000000..78f62ade3
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,596 @@
+GNU GENERAL PUBLIC LICENSE
+==========================
+
+Version 3, 29 June 2007
+
+Copyright &copy; 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+## Preamble
+
+The GNU General Public License is a free, copyleft license for software and other
+kinds of works.
+
+The licenses for most software and other practical works are designed to take away
+your freedom to share and change the works. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change all versions of a
+program--to make sure it remains free software for all its users. We, the Free
+Software Foundation, use the GNU General Public License for most of our software; it
+applies also to any other work released this way by its authors. You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our General
+Public Licenses are designed to make sure that you have the freedom to distribute
+copies of free software (and charge for them if you wish), that you receive source
+code or can get it if you want it, that you can change the software or use pieces of
+it in new free programs, and that you know you can do these things.
+
+To protect your rights, we need to prevent others from denying you these rights or
+asking you to surrender the rights. Therefore, you have certain responsibilities if
+you distribute copies of the software, or if you modify it: responsibilities to
+respect the freedom of others.
+
+For example, if you distribute copies of such a program, whether gratis or for a fee,
+you must pass on to the recipients the same freedoms that you received. You must make
+sure that they, too, receive or can get the source code. And you must show them these
+terms so they know their rights.
+
+Developers that use the GNU GPL protect your rights with two steps: (1) assert
+copyright on the software, and (2) offer you this License giving you legal permission
+to copy, distribute and/or modify it.
+
+For the developers' and authors' protection, the GPL clearly explains that there is
+no warranty for this free software. For both users' and authors' sake, the GPL
+requires that modified versions be marked as changed, so that their problems will not
+be attributed erroneously to authors of previous versions.
+
+Some devices are designed to deny users access to install or run modified versions of
+the software inside them, although the manufacturer can do so. This is fundamentally
+incompatible with the aim of protecting users' freedom to change the software. The
+systematic pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we have designed
+this version of the GPL to prohibit the practice for those products. If such problems
+arise substantially in other domains, we stand ready to extend this provision to
+those domains in future versions of the GPL, as needed to protect the freedom of
+users.
+
+Finally, every program is threatened constantly by software patents. States should
+not allow patents to restrict development and use of software on general-purpose
+computers, but in those that do, we wish to avoid the special danger that patents
+applied to a free program could make it effectively proprietary. To prevent this, the
+GPL assures that patents cannot be used to render the program non-free.
+
+The precise terms and conditions for copying, distribution and modification follow.
+
+## TERMS AND CONDITIONS
+
+### 0. Definitions.
+
+&ldquo;This License&rdquo; refers to version 3 of the GNU General Public License.
+
+&ldquo;Copyright&rdquo; also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+&ldquo;The Program&rdquo; refers to any copyrightable work licensed under this
+License. Each licensee is addressed as &ldquo;you&rdquo;. &ldquo;Licensees&rdquo; and
+&ldquo;recipients&rdquo; may be individuals or organizations.
+
+To &ldquo;modify&rdquo; a work means to copy from or adapt all or part of the work in
+a fashion requiring copyright permission, other than the making of an exact copy. The
+resulting work is called a &ldquo;modified version&rdquo; of the earlier work or a
+work &ldquo;based on&rdquo; the earlier work.
+
+A &ldquo;covered work&rdquo; means either the unmodified Program or a work based on
+the Program.
+
+To &ldquo;propagate&rdquo; a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for infringement under
+applicable copyright law, except executing it on a computer or modifying a private
+copy. Propagation includes copying, distribution (with or without modification),
+making available to the public, and in some countries other activities as well.
+
+To &ldquo;convey&rdquo; a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through a computer
+network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays &ldquo;Appropriate Legal Notices&rdquo; to the
+extent that it includes a convenient and prominently visible feature that (1)
+displays an appropriate copyright notice, and (2) tells the user that there is no
+warranty for the work (except to the extent that warranties are provided), that
+licensees may convey the work under this License, and how to view a copy of this
+License. If the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+### 1. Source Code.
+
+The &ldquo;source code&rdquo; for a work means the preferred form of the work for
+making modifications to it. &ldquo;Object code&rdquo; means any non-source form of a
+work.
+
+A &ldquo;Standard Interface&rdquo; means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of interfaces
+specified for a particular programming language, one that is widely used among
+developers working in that language.
+
+The &ldquo;System Libraries&rdquo; of an executable work include anything, other than
+the work as a whole, that (a) is included in the normal form of packaging a Major
+Component, but which is not part of that Major Component, and (b) serves only to
+enable use of the work with that Major Component, or to implement a Standard
+Interface for which an implementation is available to the public in source code form.
+A &ldquo;Major Component&rdquo;, in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system (if any) on which
+the executable work runs, or a compiler used to produce the work, or an object code
+interpreter used to run it.
+
+The &ldquo;Corresponding Source&rdquo; for a work in object code form means all the
+source code needed to generate, install, and (for an executable work) run the object
+code and to modify the work, including scripts to control those activities. However,
+it does not include the work's System Libraries, or general-purpose tools or
+generally available free programs which are used unmodified in performing those
+activities but which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for the work, and
+the source code for shared libraries and dynamically linked subprograms that the work
+is specifically designed to require, such as by intimate data communication or
+control flow between those subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate
+automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+
+### 2. Basic Permissions.
+
+All rights granted under this License are granted for the term of copyright on the
+Program, and are irrevocable provided the stated conditions are met. This License
+explicitly affirms your unlimited permission to run the unmodified Program. The
+output from running a covered work is covered by this License only if the output,
+given its content, constitutes a covered work. This License acknowledges your rights
+of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey, without
+conditions so long as your license otherwise remains in force. You may convey covered
+works to others for the sole purpose of having them make modifications exclusively
+for you, or provide you with facilities for running those works, provided that you
+comply with the terms of this License in conveying all material for which you do not
+control copyright. Those thus making or running the covered works for you must do so
+exclusively on your behalf, under your direction and control, on terms that prohibit
+them from making any copies of your copyrighted material outside their relationship
+with you.
+
+Conveying under any other circumstances is permitted solely under the conditions
+stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+
+### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+No covered work shall be deemed part of an effective technological measure under any
+applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
+adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
+of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention of
+technological measures to the extent such circumvention is effected by exercising
+rights under this License with respect to the covered work, and you disclaim any
+intention to limit operation or modification of the work as a means of enforcing,
+against the work's users, your or third parties' legal rights to forbid circumvention
+of technological measures.
+
+### 4. Conveying Verbatim Copies.
+
+You may convey verbatim copies of the Program's source code as you receive it, in any
+medium, provided that you conspicuously and appropriately publish on each copy an
+appropriate copyright notice; keep intact all notices stating that this License and
+any non-permissive terms added in accord with section 7 apply to the code; keep
+intact all notices of the absence of any warranty; and give all recipients a copy of
+this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you may offer
+support or warranty protection for a fee.
+
+### 5. Conveying Modified Source Versions.
+
+You may convey a work based on the Program, or the modifications to produce it from
+the Program, in the form of source code under the terms of section 4, provided that
+you also meet all of these conditions:
+
+* **a)** The work must carry prominent notices stating that you modified it, and giving a
+relevant date.
+* **b)** The work must carry prominent notices stating that it is released under this
+License and any conditions added under section 7. This requirement modifies the
+requirement in section 4 to &ldquo;keep intact all notices&rdquo;.
+* **c)** You must license the entire work, as a whole, under this License to anyone who
+comes into possession of a copy. This License will therefore apply, along with any
+applicable section 7 additional terms, to the whole of the work, and all its parts,
+regardless of how they are packaged. This License gives no permission to license the
+work in any other way, but it does not invalidate such permission if you have
+separately received it.
+* **d)** If the work has interactive user interfaces, each must display Appropriate Legal
+Notices; however, if the Program has interactive interfaces that do not display
+Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works, which are
+not by their nature extensions of the covered work, and which are not combined with
+it such as to form a larger program, in or on a volume of a storage or distribution
+medium, is called an &ldquo;aggregate&rdquo; if the compilation and its resulting
+copyright are not used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work in an aggregate
+does not cause this License to apply to the other parts of the aggregate.
+
+### 6. Conveying Non-Source Forms.
+
+You may convey a covered work in object code form under the terms of sections 4 and
+5, provided that you also convey the machine-readable Corresponding Source under the
+terms of this License, in one of these ways:
+
+* **a)** Convey the object code in, or embodied in, a physical product (including a
+physical distribution medium), accompanied by the Corresponding Source fixed on a
+durable physical medium customarily used for software interchange.
+* **b)** Convey the object code in, or embodied in, a physical product (including a
+physical distribution medium), accompanied by a written offer, valid for at least
+three years and valid for as long as you offer spare parts or customer support for
+that product model, to give anyone who possesses the object code either (1) a copy of
+the Corresponding Source for all the software in the product that is covered by this
+License, on a durable physical medium customarily used for software interchange, for
+a price no more than your reasonable cost of physically performing this conveying of
+source, or (2) access to copy the Corresponding Source from a network server at no
+charge.
+* **c)** Convey individual copies of the object code with a copy of the written offer to
+provide the Corresponding Source. This alternative is allowed only occasionally and
+noncommercially, and only if you received the object code with such an offer, in
+accord with subsection 6b.
+* **d)** Convey the object code by offering access from a designated place (gratis or for
+a charge), and offer equivalent access to the Corresponding Source in the same way
+through the same place at no further charge. You need not require recipients to copy
+the Corresponding Source along with the object code. If the place to copy the object
+code is a network server, the Corresponding Source may be on a different server
+(operated by you or a third party) that supports equivalent copying facilities,
+provided you maintain clear directions next to the object code saying where to find
+the Corresponding Source. Regardless of what server hosts the Corresponding Source,
+you remain obligated to ensure that it is available for as long as needed to satisfy
+these requirements.
+* **e)** Convey the object code using peer-to-peer transmission, provided you inform
+other peers where the object code and Corresponding Source of the work are being
+offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from the
+Corresponding Source as a System Library, need not be included in conveying the
+object code work.
+
+A &ldquo;User Product&rdquo; is either (1) a &ldquo;consumer product&rdquo;, which
+means any tangible personal property which is normally used for personal, family, or
+household purposes, or (2) anything designed or sold for incorporation into a
+dwelling. In determining whether a product is a consumer product, doubtful cases
+shall be resolved in favor of coverage. For a particular product received by a
+particular user, &ldquo;normally used&rdquo; refers to a typical or common use of
+that class of product, regardless of the status of the particular user or of the way
+in which the particular user actually uses, or expects or is expected to use, the
+product. A product is a consumer product regardless of whether the product has
+substantial commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+&ldquo;Installation Information&rdquo; for a User Product means any methods,
+procedures, authorization keys, or other information required to install and execute
+modified versions of a covered work in that User Product from a modified version of
+its Corresponding Source. The information must suffice to ensure that the continued
+functioning of the modified object code is in no case prevented or interfered with
+solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically for
+use in, a User Product, and the conveying occurs as part of a transaction in which
+the right of possession and use of the User Product is transferred to the recipient
+in perpetuity or for a fixed term (regardless of how the transaction is
+characterized), the Corresponding Source conveyed under this section must be
+accompanied by the Installation Information. But this requirement does not apply if
+neither you nor any third party retains the ability to install modified object code
+on the User Product (for example, the work has been installed in ROM).
+
+The requirement to provide Installation Information does not include a requirement to
+continue to provide support service, warranty, or updates for a work that has been
+modified or installed by the recipient, or for the User Product in which it has been
+modified or installed. Access to a network may be denied when the modification itself
+materially and adversely affects the operation of the network or violates the rules
+and protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord with
+this section must be in a format that is publicly documented (and with an
+implementation available to the public in source code form), and must require no
+special password or key for unpacking, reading or copying.
+
+### 7. Additional Terms.
+
+&ldquo;Additional permissions&rdquo; are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions. Additional
+permissions that are applicable to the entire Program shall be treated as though they
+were included in this License, to the extent that they are valid under applicable
+law. If additional permissions apply only to part of the Program, that part may be
+used separately under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any
+additional permissions from that copy, or from any part of it. (Additional
+permissions may be written to require their own removal in certain cases when you
+modify the work.) You may place additional permissions on material, added by you to a
+covered work, for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you add to a
+covered work, you may (if authorized by the copyright holders of that material)
+supplement the terms of this License with terms:
+
+* **a)** Disclaiming warranty or limiting liability differently from the terms of
+sections 15 and 16 of this License; or
+* **b)** Requiring preservation of specified reasonable legal notices or author
+attributions in that material or in the Appropriate Legal Notices displayed by works
+containing it; or
+* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
+modified versions of such material be marked in reasonable ways as different from the
+original version; or
+* **d)** Limiting the use for publicity purposes of names of licensors or authors of the
+material; or
+* **e)** Declining to grant rights under trademark law for use of some trade names,
+trademarks, or service marks; or
+* **f)** Requiring indemnification of licensors and authors of that material by anyone
+who conveys the material (or modified versions of it) with contractual assumptions of
+liability to the recipient, for any liability that these contractual assumptions
+directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered &ldquo;further
+restrictions&rdquo; within the meaning of section 10. If the Program as you received
+it, or any part of it, contains a notice stating that it is governed by this License
+along with a term that is a further restriction, you may remove that term. If a
+license document contains a further restriction but permits relicensing or conveying
+under this License, you may add to a covered work material governed by the terms of
+that license document, provided that the further restriction does not survive such
+relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place, in
+the relevant source files, a statement of the additional terms that apply to those
+files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form of a
+separately written license, or stated as exceptions; the above requirements apply
+either way.
+
+### 8. Termination.
+
+You may not propagate or modify a covered work except as expressly provided under
+this License. Any attempt otherwise to propagate or modify it is void, and will
+automatically terminate your rights under this License (including any patent licenses
+granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from a
+particular copyright holder is reinstated (a) provisionally, unless and until the
+copyright holder explicitly and finally terminates your license, and (b) permanently,
+if the copyright holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently
+if the copyright holder notifies you of the violation by some reasonable means, this
+is the first time you have received notice of violation of this License (for any
+work) from that copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses of
+parties who have received copies or rights from you under this License. If your
+rights have been terminated and not permanently reinstated, you do not qualify to
+receive new licenses for the same material under section 10.
+
+### 9. Acceptance Not Required for Having Copies.
+
+You are not required to accept this License in order to receive or run a copy of the
+Program. Ancillary propagation of a covered work occurring solely as a consequence of
+using peer-to-peer transmission to receive a copy likewise does not require
+acceptance. However, nothing other than this License grants you permission to
+propagate or modify any covered work. These actions infringe copyright if you do not
+accept this License. Therefore, by modifying or propagating a covered work, you
+indicate your acceptance of this License to do so.
+
+### 10. Automatic Licensing of Downstream Recipients.
+
+Each time you convey a covered work, the recipient automatically receives a license
+from the original licensors, to run, modify and propagate that work, subject to this
+License. You are not responsible for enforcing compliance by third parties with this
+License.
+
+An &ldquo;entity transaction&rdquo; is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an organization, or
+merging organizations. If propagation of a covered work results from an entity
+transaction, each party to that transaction who receives a copy of the work also
+receives whatever licenses to the work the party's predecessor in interest had or
+could give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if the predecessor
+has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights granted or
+affirmed under this License. For example, you may not impose a license fee, royalty,
+or other charge for exercise of rights granted under this License, and you may not
+initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
+that any patent claim is infringed by making, using, selling, offering for sale, or
+importing the Program or any portion of it.
+
+### 11. Patents.
+
+A &ldquo;contributor&rdquo; is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The work thus
+licensed is called the contributor's &ldquo;contributor version&rdquo;.
+
+A contributor's &ldquo;essential patent claims&rdquo; are all patent claims owned or
+controlled by the contributor, whether already acquired or hereafter acquired, that
+would be infringed by some manner, permitted by this License, of making, using, or
+selling its contributor version, but do not include claims that would be infringed
+only as a consequence of further modification of the contributor version. For
+purposes of this definition, &ldquo;control&rdquo; includes the right to grant patent
+sublicenses in a manner consistent with the requirements of this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
+under the contributor's essential patent claims, to make, use, sell, offer for sale,
+import and otherwise run, modify and propagate the contents of its contributor
+version.
+
+In the following three paragraphs, a &ldquo;patent license&rdquo; is any express
+agreement or commitment, however denominated, not to enforce a patent (such as an
+express permission to practice a patent or covenant not to sue for patent
+infringement). To &ldquo;grant&rdquo; such a patent license to a party means to make
+such an agreement or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the
+Corresponding Source of the work is not available for anyone to copy, free of charge
+and under the terms of this License, through a publicly available network server or
+other readily accessible means, then you must either (1) cause the Corresponding
+Source to be so available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner consistent with
+the requirements of this License, to extend the patent license to downstream
+recipients. &ldquo;Knowingly relying&rdquo; means you have actual knowledge that, but
+for the patent license, your conveying the covered work in a country, or your
+recipient's use of the covered work in a country, would infringe one or more
+identifiable patents in that country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement, you
+convey, or propagate by procuring conveyance of, a covered work, and grant a patent
+license to some of the parties receiving the covered work authorizing them to use,
+propagate, modify or convey a specific copy of the covered work, then the patent
+license you grant is automatically extended to all recipients of the covered work and
+works based on it.
+
+A patent license is &ldquo;discriminatory&rdquo; if it does not include within the
+scope of its coverage, prohibits the exercise of, or is conditioned on the
+non-exercise of one or more of the rights that are specifically granted under this
+License. You may not convey a covered work if you are a party to an arrangement with
+a third party that is in the business of distributing software, under which you make
+payment to the third party based on the extent of your activity of conveying the
+work, and under which the third party grants, to any of the parties who would receive
+the covered work from you, a discriminatory patent license (a) in connection with
+copies of the covered work conveyed by you (or copies made from those copies), or (b)
+primarily for and in connection with specific products or compilations that contain
+the covered work, unless you entered into that arrangement, or that patent license
+was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied
+license or other defenses to infringement that may otherwise be available to you
+under applicable patent law.
+
+### 12. No Surrender of Others' Freedom.
+
+If conditions are imposed on you (whether by court order, agreement or otherwise)
+that contradict the conditions of this License, they do not excuse you from the
+conditions of this License. If you cannot convey a covered work so as to satisfy
+simultaneously your obligations under this License and any other pertinent
+obligations, then as a consequence you may not convey it at all. For example, if you
+agree to terms that obligate you to collect a royalty for further conveying from
+those to whom you convey the Program, the only way you could satisfy both those terms
+and this License would be to refrain entirely from conveying the Program.
+
+### 13. Use with the GNU Affero General Public License.
+
+Notwithstanding any other provision of this License, you have permission to link or
+combine any covered work with a work licensed under version 3 of the GNU Affero
+General Public License into a single combined work, and to convey the resulting work.
+The terms of this License will continue to apply to the part which is the covered
+work, but the special requirements of the GNU Affero General Public License, section
+13, concerning interaction through a network will apply to the combination as such.
+
+### 14. Revised Versions of this License.
+
+The Free Software Foundation may publish revised and/or new versions of the GNU
+General Public License from time to time. Such new versions will be similar in spirit
+to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies that
+a certain numbered version of the GNU General Public License &ldquo;or any later
+version&rdquo; applies to it, you have the option of following the terms and
+conditions either of that numbered version or of any later version published by the
+Free Software Foundation. If the Program does not specify a version number of the GNU
+General Public License, you may choose any version ever published by the Free
+Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of the GNU
+General Public License can be used, that proxy's public statement of acceptance of a
+version permanently authorizes you to choose that version for the Program.
+
+Later license versions may give you additional or different permissions. However, no
+additional obligations are imposed on any author or copyright holder as a result of
+your choosing to follow a later version.
+
+### 15. Disclaimer of Warranty.
+
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM &ldquo;AS IS&rdquo; WITHOUT WARRANTY OF ANY KIND, EITHER
+EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
+QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
+DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+### 16. Limitation of Liability.
+
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
+COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
+INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
+OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
+WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+### 17. Interpretation of Sections 15 and 16.
+
+If the disclaimer of warranty and limitation of liability provided above cannot be
+given local legal effect according to their terms, reviewing courts shall apply local
+law that most closely approximates an absolute waiver of all civil liability in
+connection with the Program, unless a warranty or assumption of liability accompanies
+a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+## How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible use to
+the public, the best way to achieve this is to make it free software which everyone
+can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach them
+to the start of each source file to most effectively state the exclusion of warranty;
+and each file should have at least the &ldquo;copyright&rdquo; line and a pointer to
+where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program does terminal interaction, make it output a short notice like this
+when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type 'show c' for details.
+
+The hypothetical commands 'show w' and 'show c' should show the appropriate parts of
+the General Public License. Of course, your program's commands might be different;
+for a GUI interface, you would use an &ldquo;about box&rdquo;.
+
+You should also get your employer (if you work as a programmer) or school, if any, to
+sign a &ldquo;copyright disclaimer&rdquo; for the program, if necessary. For more
+information on this, and how to apply and follow the GNU GPL, see
+&lt;<http://www.gnu.org/licenses/>&gt;.
+
+The GNU General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may consider it
+more useful to permit linking proprietary applications with the library. If this is
+what you want to do, use the GNU Lesser General Public License instead of this
+License. But first, please read
+&lt;<http://www.gnu.org/philosophy/why-not-lgpl.html>&gt;.
diff --git a/README b/README
deleted file mode 100644
index be6200ead..000000000
--- a/README
+++ /dev/null
@@ -1,88 +0,0 @@
-Description
-===========
-
-pyLoad is a free and open source downloader for 1-click-hosting sites
-like rapidshare.com or uploaded.to.
-It supports link decryption as well as all important container formats.
-
-pyLoad is written entirely in Python and is currently under heavy development.
-
-For news, downloads, wiki, forum and further information visit http://pyload.org/
-
-To report bugs, suggest features, ask a question, get the developer version
-or help us out, visit http://github.com/pyload/pyload
-
-Documentation about extending pyLoad can be found at http://docs.pyload.org or join us at #pyload on irc.freenode.net
-
-Dependencies
-============
-
-You need at least python 2.5 to run pyLoad and all of these required libaries.
-They should be automatically installed when using pip install.
-The prebuilt pyload packages also install these dependencies or have them included, so manuall install
-is only needed when installing pyLoad from source.
-
-Required
---------
-
-- pycurl a.k.a python-curl
-- jinja2
-- beaker
-- thrift
-- simplejson (for python 2.5)
-
-Some plugins require additional packages, only install these when needed.
-
-Optional
---------
-
-- pycrypto: RSDF/CCF/DLC support
-- tesseract, python-pil a.k.a python-imaging: Automatic captcha recognition for a small amount of plugins
-- jsengine (spidermonkey, ossp-js, pyv8, rhino): Used for several hoster, ClickNLoad
-- feedparser
-- BeautifulSoup
-- pyOpenSSL: For SSL connection
-
-First start
-===========
-
-Note: If you installed pyload via package-manager `python pyLoadCore.py` is probably equivalent to `pyLoadCore`
-
-Run::
-
- python pyLoadCore.py
-
-and follow the instructions of the setup assistent.
-
-For a list of options use::
-
- python pyLoadCore.py -h
-
-Configuration
-=============
-
-After finishing the setup assistent pyLoad is ready to use and more configuration can be done via webinterface.
-Additionally you could simply edit the config files located in your pyLoad home dir (defaults to: ~/.pyload)
-with your favorite editor and edit the appropriate options. For a short description of
-the options take a look at http://pyload.org/configuration.
-
-To restart the configure assistent run::
-
- python pyLoadCore.py -s
-
-Adding downloads
-----------------
-
-To start the CLI and connect to a local server, run::
-
- python pyLoadCli.py -l
-
-for more options refer to::
-
- python pyLoadCli.py -h
-
-The webinterface can be accessed when pointing your webbrowser to the ip and configured port, defaults to http://localhost:8000
-
-Notes
-=====
-For more information, see http://pyload.org/
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..c0ddbc416
--- /dev/null
+++ b/README.md
@@ -0,0 +1,139 @@
+[![pyLoad](/docs/resources/banner.png "pyLoad")](http://pyload.org/)
+====================================================================
+
+[![Translation Status](http://translate.pyload.org/badges/pyload/localized.png "Translation Status")](http://translate.pyload.org/project/pyload/)
+
+**pyLoad** is a Free and Open Source download manager written entirely in Python and designed to be extremely lightweight.
+
+pyLoad is available for all kind of operating systems and devices:
+You could install it on a computer, but also on a headless servers, a router, a smart usb-stick running linux... almost whatever you want!
+
+You can control it entirely by web.
+Its web user interface allows full managing and easily remote access to your download from anywhere, online, or in your personal network..
+
+All common video-sites, one-click-hosters, container formats and well known web standards are supported to allow you to download files.
+Additionaly, pyLoad has a great variety of plugins to automate common tasks and make unattended running possible.
+
+pyLoad has a fully featured and well documented API, it's easily extendable and accessible by external tools, cross-platform apps or other softwares.
+
+
+Dependencies
+------------
+
+ - **You need at least Python 2.5 or at most Python 2.7 to run pyLoad and its required software libraries**
+ - **Python 3 and PyPy are not yet supported**
+
+### Required ###
+
+ - **Beaker**
+ - **bottle**
+ - **Getch**
+ - **jinja2**
+ - **markupsafe**
+ - **MultipartPostHandler**
+ - **pycurl** (python-curl)
+ - **rename_process**
+ - **SafeEval**
+ - **simplejson** *required for JSON speedup*
+ - **thrift**
+ - **wsgiserver**
+
+Some addition features require additional packages.
+
+
+### Optional ###
+
+ - **BeautifulSoup**
+ - **bjoern** (<https://github.com/jonashaag/bjoern>) *required for a better webui experience*
+ - **feedparser** *required for RSS parsing support*
+ - **node.js** or **ossp-js** or **pyv8** or **rhino** or **spidermonkey** (JS Engines) *required by plugins like ClickNLoad to work correctly*
+ - **PIL** (python-imaging) *required for captcha recognition support*
+ - **pycrypto** *required for RSDF/CCF/DLC container decrypting*
+ - **pyOpenSSL** *required for SSL connection support*
+ - **tesseract** *required for captcha ocr support*
+
+You can install them using the Python Package Index:
+
+ pip install <package-name>
+
+
+### Included ###
+
+ - **Beaker**
+ - **BeautifulSoup**
+ - **bottle**
+ - **feedparser**
+ - **Getch**
+ - **jinja2**
+ - **markupsafe**
+ - **MultipartPostHandler**
+ - **rename_process**
+ - **SafeEval**
+ - **simplejson**
+ - **thrift**
+ - **wsgiserver**
+
+> **Note:**
+Pre-build packages should yet include all.
+
+
+Usage
+-----
+
+### First start ###
+
+Run:
+
+ python pyload.py
+
+and follow the setup assistant instructions.
+
+> **Note:**
+If you installed pyLoad by package-manager, command `python pyload.py` is probably equivalent to `pyload`.
+
+Additionally, you can whenever restart the setup assistant typing:
+
+ python pyload.py -s
+
+Or you can even edit configuration files located in your pyLoad home directory (default to `~/.pyload`)
+with your favorite editor.
+For a short description of all the configuration options available visit <http://pyload.org/configuration>.
+
+
+### Web User Interface ###
+
+Run:
+
+ python pyload.py
+
+So, to retrieve it point your web browser to the socket address configured by setup (default to `http://localhost:8000`).
+
+You can get the list of accepted arguments typing:
+
+ python pyload.py -h
+
+
+### Command Line Interface ###
+
+Run:
+
+ python pyload-cli.py -l
+
+You can get the list of accepted arguments typing:
+
+ python pyload-cli.py -h
+
+
+Notes
+-----
+
+For news, downloads, wiki, forum and further information visit <http://pyload.org/>.
+
+To report bugs, suggest features, ask a question, get the developer version
+or help us out, visit <https://github.com/pyload/pyload>.
+
+Documentation about extending pyLoad can be found at <http://docs.pyload.org> or joining us at `#pyload` on `irc.freenode.net`.
+
+
+------------------------------
+###### pyLoad Team 2008-2014 ######
diff --git a/docs/access_api.rst b/docs/access_api.rst
index df69da8b2..1d013c2c0 100644
--- a/docs/access_api.rst
+++ b/docs/access_api.rst
@@ -13,12 +13,12 @@ First of all, you need to know what you can do with our API. It lets you do all
retrieving download status, manage queue, manage accounts, modify config and so on.
This document is not intended to explain every function in detail, for a complete listing
-see :class:`Api <module.Api.Api>`.
+see :class:`Api <pyload.api.Api>`.
-Of course its possible to access the ``core.api`` attribute in plugins and hooks, but much more
+Of course its possible to access the ``core.api`` attribute in plugins and addons, but much more
interesting is the possibillity to call function from different programs written in many different languages.
-pyLoad uses thrift as backend and provides its :class:`Api <module.Api.Api>` as service.
+pyLoad uses thrift as backend and provides its :class:`Api <pyload.api.Api>` as service.
More information about thrift can be found here http://wiki.apache.org/thrift/.
@@ -26,7 +26,7 @@ Using Thrift
------------
Every thrift service has to define all data structures and declare every method which should be usable via rpc.
-This file is located :file:`module/remote/thriftbackend/pyload.thrift`, its very helpful to inform about
+This file is located :file:`pyload/remote/thriftbackend/pyload.thrift`, its very helpful to inform about
arguments and detailed structure of return types. However it does not contain any information about what the functions does.
Assuming you want to use the API in any other language than python than check if it is
@@ -48,7 +48,7 @@ In case you want to use python, pyload has already all files included to access
A basic script that prints out some information: ::
- from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
+ from pyload.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin
try:
client = ThriftClient(host="127.0.0.1", port=7227, user="User", password="yourpw")
@@ -92,14 +92,14 @@ so pyLoad can authenticate you.
Calling Methods
===============
-In general you can use any method listed at the :class:`Api <module.Api.Api>` documentation, which is also available to
+In general you can use any method listed at the :class:`Api <pyload.api.Api>` documentation, which is also available to
the thriftbackend.
Access works simply via ``http://pyload-core/api/methodName``, where ``pyload-core`` is the ip address
or hostname including the webinterface port. By default on local access this would be `localhost:8000`.
The return value will be formatted in JSON, complex data types as dictionaries.
-As mentionted above for a documentation about the return types look at the thrift specification file :file:`module/remote/thriftbackend/pyload.thrift`.
+As mentionted above for a documentation about the return types look at the thrift specification file :file:`pyload/remote/thriftbackend/pyload.thrift`.
==================
Passing parameters
@@ -107,7 +107,7 @@ Passing parameters
To pass arguments you have two choices.
Either use positional arguments, eg ``http://pyload-core/api/getFileData/1``, where 1 is the FileID, or use keyword arguments
-supplied via GET or POST ``http://pyload-core/api/getFileData?fid=1``. You can find the argument names in the :class:`Api <module.Api.Api>`
+supplied via GET or POST ``http://pyload-core/api/getFileData?fid=1``. You can find the argument names in the :class:`Api <pyload.api.Api>`
documentation.
It is important that *all* arguments are in JSON format. So ``http://pyload-core/api/getFileData/1`` is valid because
@@ -118,4 +118,4 @@ Strings are wrapped in double qoutes, because `"username"` represents a string i
every container type like lists and dicts are possible. You usually don't have to convert them. just use a json encoder before using them
in the HTTP request.
-Please note that the data have to be urlencoded at last. (Most libaries will do that automatically) \ No newline at end of file
+Please note that the data have to be urlencoded at last. (Most libaries will do that automatically)
diff --git a/docs/conf.py b/docs/conf.py
index 9d2cf98f9..62ad33bb7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,7 +16,7 @@ from os.path import dirname, join, abspath
dir_name = join(dirname(abspath("")))
sys.path.append(dir_name)
-sys.path.append(join(dir_name, "module", "lib"))
+sys.path.append(join(dir_name, "pyload", "lib"))
# If extensions (or modules to document with autodoc) are in another directory,
@@ -52,7 +52,7 @@ master_doc = 'index'
# General information about the project.
project = u'pyLoad'
-copyright = u'2011, pyLoad Team'
+copyright = u'2008-2014, pyLoad Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -66,7 +66,7 @@ cog.outl("version = '%s'" % ".".join(v[:2]))
cog.outl("release = '%s'" % ".".join(v))
]]]"""
version = '0.4'
-release = '0.4.9'
+release = '0.4.10'
# [[[end]]]
@@ -128,12 +128,12 @@ html_theme = 'default'
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-html_logo = join(dir_name, "module", "web", "media", "default", "img", "pyload-logo-edited3.5-new-font-small.png")
+html_logo = join(dir_name, "pyload", "web", "media", "default", "img", "pyload-logo-edited3.5-new-font-small.png")
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-html_favicon = join(dir_name, "icons", "pyload2.ico")
+html_favicon = join(dir_name, "docs", "resources", "icon.ico")
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
diff --git a/docs/docs.conf b/docs/docs.conf
index e197cfa43..fd2987784 100644
--- a/docs/docs.conf
+++ b/docs/docs.conf
@@ -3,12 +3,12 @@
[epydoc]
-modules: pyLoadCore.py, pyLoadCli.py, pyloadGui.py, module
+modules: pyload.py, pyload-cli.py, pyload
output: html
target: docs
docformat: restructuredtext
-exclude: module\.lib|module\.remote\.thriftbackend\.thriftgen|PyQt4|\.pyc|\.pyo|module\.plugins\.(accounts|captcha|container|crypter|hooks|hoster)
+exclude: pyload\.lib|pyload\.remote\.thriftbackend\.thriftgen|\.pyc|\.pyo|pyload\.plugins\.(accounts|container|crypter|hooks|hoster|internal|ocr)
name: pyLoad Documentation
url: http://docs.pyload.org
diff --git a/docs/extend_pyload.rst b/docs/extend_pyload.rst
index 337cb6854..ccd4ac203 100755
--- a/docs/extend_pyload.rst
+++ b/docs/extend_pyload.rst
@@ -1,7 +1,7 @@
.. _extend_pyload:
********************
-How to extend pyLoad
+How to extend pyLoad
********************
In general there a two different plugin types. These allow everybody to write powerful, modular plugins without knowing
@@ -9,5 +9,6 @@ every detail of the pyLoad core. However you should have some basic knowledge of
.. toctree::
- write_hooks.rst
- write_plugins.rst \ No newline at end of file
+ write_addons.rst
+ write_plugins.rst
+ write_scripts.rst
diff --git a/docs/index.rst b/docs/index.rst
index 757fd7537..ff40ff3cb 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -20,7 +20,7 @@ Contents:
extend_pyload.rst
module_overview.rst
-.. currentmodule:: module
+.. currentmodule:: pyload
==================
diff --git a/docs/module_overview.rst b/docs/module_overview.rst
index d51202c88..fce9ecfca 100644
--- a/docs/module_overview.rst
+++ b/docs/module_overview.rst
@@ -1,17 +1,19 @@
+.. _module_overview:
+
Module Overview
===============
You can find an overview of some important classes here:
.. autosummary::
- :toctree: module
+ :toctree: pyload
- module.Api.Api
- module.plugins.Plugin.Base
- module.plugins.Plugin.Plugin
- module.plugins.Crypter.Crypter
- module.plugins.Account.Account
- module.plugins.Hook.Hook
- module.HookManager.HookManager
- module.PyFile.PyFile
- module.PyPackage.PyPackage
+ pyload.api.Api
+ pyload.plugins.Plugin.Base
+ pyload.plugins.Plugin.Plugin
+ pyload.plugins.Crypter.Crypter
+ pyload.plugins.Account.Account
+ pyload.plugins.Addon.Addon
+ pyload.manager.AddonManager.AddonManager
+ pyload.datatypes.PyFile.PyFile
+ pyload.datatypes.PyPackage.PyPackage
diff --git a/docs/plugin_licensing.rst b/docs/plugin_licensing.rst
new file mode 100644
index 000000000..0e8590d62
--- /dev/null
+++ b/docs/plugin_licensing.rst
@@ -0,0 +1,18 @@
+.. _plugin_licensing:
+
+Plugin Licensing
+================
+
+According to the terms of the GNU General Public License,
+pyload's plugins must be treated as an extension of the main program.
+This means the plugins must be released under the GPL or a GPL-compatible
+free software license, and that the terms of the GPL must be followed when
+those plugins are distributed.
+
+ * Any plugin published **without a license notice** is intend published under the **GNU GPLv3**.
+ * A different license can be used but it **must be GPL-compatible** and the license notice must be put in the plugin
+ file.
+ * Any plugin published **with a GPL incompatible license** will be rejected.
+ This includes *copyright all right reserved*.
+ * Is recommended to put the license notice at the top of the plugin file.
+ * Is recommended to **not** put the license notice when plugin is published under the GNU GPLv3.
diff --git a/docs/resources/banner.png b/docs/resources/banner.png
new file mode 100644
index 000000000..4e2d718f7
--- /dev/null
+++ b/docs/resources/banner.png
Binary files differ
diff --git a/docs/resources/icon.ico b/docs/resources/icon.ico
new file mode 100644
index 000000000..b5fb55686
--- /dev/null
+++ b/docs/resources/icon.ico
Binary files differ
diff --git a/docs/resources/logo.png b/docs/resources/logo.png
new file mode 100644
index 000000000..d20084847
--- /dev/null
+++ b/docs/resources/logo.png
Binary files differ
diff --git a/docs/resources/logo.svg b/docs/resources/logo.svg
new file mode 100644
index 000000000..f67815678
--- /dev/null
+++ b/docs/resources/logo.svg
@@ -0,0 +1,317 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="svg2"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:version="0.32"
+ sodipodi:docname="pyload_logo.svg"
+ x="0px"
+ y="0px"
+ width="1000"
+ height="1000"
+ viewBox="0 0 1000 1000"
+ enable-background="new 0 0 1000 1000"
+ xml:space="preserve"
+ inkscape:export-filename="/Users/christian/Development/pyload/docs/resources/pyload_logo.png"
+ inkscape:export-xdpi="9.2647057"
+ inkscape:export-ydpi="9.2647057"><title
+ id="title3794">pyLoad Logo</title><metadata
+ id="metadata33"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>pyLoad Logo</dc:title><dc:creator><cc:Agent><dc:title>pyLoad Team</dc:title></cc:Agent></dc:creator><cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-nc-sa/3.0/" /><dc:subject><rdf:Bag><rdf:li>pyload logo</rdf:li></rdf:Bag></dc:subject><dc:description>Official pyLoad logo with some optional filters</dc:description></cc:Work><cc:License
+ rdf:about="http://creativecommons.org/licenses/by-nc-sa/3.0/"><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
+ rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
+ rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><defs
+ id="defs31"><color-profile
+ name="Generic-RGB-Profile"
+ xlink:href="/System/Library/ColorSync/Profiles/Generic RGB Profile.icc"
+ id="color-profile3792" /><linearGradient
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"
+ y2="752.55798"
+ x2="783.30792"
+ y1="134.5405"
+ x1="1220.123"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3778">
+ <stop
+ id="stop3780"
+ style="stop-color:#ffd856;stop-opacity:1;"
+ offset="0" />
+ <stop
+ id="stop3782"
+ style="stop-color:#ffc836;stop-opacity:1;"
+ offset="1" />
+ </linearGradient><linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3778"
+ id="linearGradient3005"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0266298,0,0,1,-7.2192914,0)"
+ spreadMethod="pad"
+ x1="271.09799"
+ y1="499.06601"
+ x2="1001.7615"
+ y2="499.06601" /><linearGradient
+ inkscape:collect="always"
+ xlink:href="#path1948_3_"
+ id="linearGradient3783"
+ x1="25.991835"
+ y1="239.76074"
+ x2="763.24133"
+ y2="239.76074"
+ gradientUnits="userSpaceOnUse" /><filter
+ id="filter3020"
+ inkscape:label="Drop shadow"
+ width="1.5"
+ height="1.5"
+ x="-.25"
+ y="-.25"><feGaussianBlur
+ id="feGaussianBlur3022"
+ in="SourceAlpha"
+ stdDeviation="25"
+ result="blur" /><feColorMatrix
+ id="feColorMatrix3024"
+ result="bluralpha"
+ type="matrix"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " /><feOffset
+ id="feOffset3026"
+ in="bluralpha"
+ dx="15"
+ dy="30"
+ result="offsetBlur" /><feMerge
+ id="feMerge3028"><feMergeNode
+ id="feMergeNode3030"
+ in="offsetBlur" /><feMergeNode
+ id="feMergeNode3032"
+ in="SourceGraphic" /></feMerge></filter><filter
+ id="filter3023"
+ inkscape:label="Outline"
+ inkscape:menu="Morphology"
+ inkscape:menu-tooltip="Draws a colored outline around"
+ width="1.5"
+ height="1.5"
+ x="-0.25"
+ y="-0.25"
+ color-interpolation-filters="sRGB"><feGaussianBlur
+ id="feGaussianBlur3025"
+ in="SourceAlpha"
+ stdDeviation="10"
+ result="blur" /><feColorMatrix
+ id="feColorMatrix3027"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1 "
+ result="result4" /><feFlood
+ id="feFlood3029"
+ result="result1"
+ flood-color="rgb(0,0,0)"
+ in="blur"
+ flood-opacity="1" /><feComposite
+ id="feComposite3031"
+ in2="result4"
+ result="result2"
+ in="result1"
+ operator="atop" /><feComposite
+ id="feComposite3033"
+ in2="result2"
+ in="SourceGraphic"
+ result="result3"
+ operator="atop" /></filter><filter
+ id="filter3140"
+ inkscape:label="3D Effect"
+ inkscape:menu="Bevels"
+ inkscape:menu-tooltip="Basic specular bevel to use for building textures"
+ color-interpolation-filters="sRGB"><feGaussianBlur
+ id="feGaussianBlur3142"
+ stdDeviation="6"
+ in="SourceGraphic"
+ result="result0" /><feDiffuseLighting
+ id="feDiffuseLighting3144"
+ lighting-color="rgb(255,255,255)"
+ diffuseConstant="1"
+ surfaceScale="4"
+ result="result5"><feDistantLight
+ id="feDistantLight3146"
+ elevation="45"
+ azimuth="235" /></feDiffuseLighting><feComposite
+ id="feComposite3148"
+ in2="SourceGraphic"
+ k1="1.4"
+ in="result5"
+ result="fbSourceGraphic"
+ operator="arithmetic" /><feGaussianBlur
+ id="feGaussianBlur3150"
+ result="result0"
+ in="fbSourceGraphic"
+ stdDeviation="6" /><feSpecularLighting
+ id="feSpecularLighting3152"
+ specularExponent="25"
+ specularConstant="1"
+ surfaceScale="4"
+ lighting-color="rgb(255,255,255)"
+ result="result1"
+ in="result0"><feDistantLight
+ id="feDistantLight3154"
+ azimuth="235"
+ elevation="45" /></feSpecularLighting><feComposite
+ id="feComposite3156"
+ in2="result91"
+ k3="1"
+ k2="1"
+ operator="arithmetic"
+ result="result4"
+ in="fbSourceGraphic" /><feComposite
+ id="feComposite3158"
+ in2="SourceGraphic"
+ operator="in"
+ result="result2"
+ in="result4" /></filter></defs><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1352"
+ inkscape:window-height="766"
+ id="namedview29"
+ showgrid="false"
+ inkscape:zoom="0.5"
+ inkscape:cx="556.17192"
+ inkscape:cy="487.74566"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g2303_1_"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ showborder="true"
+ borderlayer="false"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+<g
+ id="g2303"
+ display="none"
+ style="display:none"
+ transform="translate(-21.799529,1.0000288)">
+
+ <linearGradient
+ id="path1948_2_"
+ gradientUnits="userSpaceOnUse"
+ x1="-338.45169"
+ y1="1559.3135"
+ x2="627.55139"
+ y2="728.19678"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#5A9FD4"
+ id="stop5" />
+ <stop
+ offset="1"
+ style="stop-color:#306998"
+ id="stop7" />
+ </linearGradient>
+ <path
+ id="path1948"
+ display="inline"
+ d="M 493.674,-0.868 H 256.098 166.937 c -69.042,0 -129.5,41.498 -148.412,120.447 -21.813,90.495 -22.783,146.964 0,241.457 16.887,70.339 57.219,120.447 126.26,120.447 h 81.686 V 372.939 c 0,-78.413 67.856,-147.585 148.416,-147.585 h 237.298 c 66.057,0 118.787,-54.385 118.787,-120.727 L 730.846,-0.89"
+ inkscape:connector-curvature="0"
+ style="fill:url(#path1948_2_);display:inline" />
+
+ <linearGradient
+ id="path1950_2_"
+ gradientUnits="userSpaceOnUse"
+ x1="1186.2559"
+ y1="133.9731"
+ x2="749.44067"
+ y2="751.99072"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#FFD43B"
+ id="stop11" />
+ <stop
+ offset="1"
+ style="stop-color:#FFE873"
+ id="stop13" />
+ </linearGradient>
+ <path
+ id="path1950"
+ display="inline"
+ d="m 755.602,0.132 v 105.496 c 0,81.799 -69.346,150.631 -148.418,150.631 H 369.886 c -65.002,0 -118.788,55.632 -118.788,120.727 v 226.221 c 0,64.39 55.985,102.262 118.788,120.727 C 412.959,736.601 500,1000 500,1000 c 0,0 68.736,-264.907 107.184,-276.066 59.809,-17.313 118.787,-52.168 118.787,-120.727 V 512.666 H 488.673 V 482.483 H 725.97 844.761 c 69.049,0 94.783,-48.163 118.787,-120.447 24.809,-74.417 23.752,-145.985 0,-241.457 C 946.486,51.85 913.898,0.132 844.762,0.132 h -89.16 z M 622.137,573.026 c 24.627,0 44.578,20.179 44.578,45.137 0,25.041 -19.951,45.409 -44.578,45.409 -24.541,0 -44.58,-20.368 -44.58,-45.409 0,-24.958 20.039,-45.137 44.58,-45.137 z"
+ inkscape:connector-curvature="0"
+ style="fill:url(#path1950_2_);display:inline" />
+</g>
+<g
+ id="g2303_1_"
+ transform="translate(-21.799529,1.0000288)">
+
+ <linearGradient
+ id="path1948_3_"
+ gradientUnits="userSpaceOnUse"
+ x1="-303.33109"
+ y1="1560.5684"
+ x2="662.672"
+ y2="729.45172"
+ gradientTransform="matrix(0.57252813,0,0,-0.568,127.65196,708.7776)">
+ <stop
+ offset="0"
+ style="stop-color:#3778b0;stop-opacity:1;"
+ id="stop18" />
+ <stop
+ offset="1"
+ style="stop-color:#356b97;stop-opacity:1;"
+ id="stop20" />
+ </linearGradient>
+ <path
+ style="fill:url(#linearGradient3783);fill-opacity:1;"
+ d="M 187.6338,-0.828786 C 142.54691,-0.4970408 97.509189,20.00164 69.766894,55.506872 50.404869,79.267481 40.287236,108.86774 34.426911,138.63795 22.416163,189.64033 17.78885,242.92961 25.780677,294.93728 c 5.106096,32.23417 11.057028,64.60794 22.392916,95.27781 13.479342,35.90012 38.974607,69.46226 76.144837,82.91501 27.93718,10.93473 54.60541,6.72414 83.9641,7.53695 13.63487,0 24.96913,0 38.604,0 0.2713,-39.75457 -0.23921,-79.53817 0.7,-119.27522 4.44911,-60.56565 57.97765,-113.83582 116.49553,-129.10577 28.59598,-7.98504 58.75298,-3.97369 88.12613,-4.95196 65.78932,-0.1699 131.59413,0.33658 197.37399,-0.24727 46.56452,-2.36951 89.41253,-33.74388 105.96031,-76.7788 8.4927,-20.25194 9.04154,-42.50123 8.36849,-64.136866 -0.0327,-28.519022 -0.0719,-58.538168 -0.10062,-87.0571928 -160.2708,-0.8222611 -320.54247,-0.1692627 -480.8154,0.027 -31.78694,0.021047 -63.57472,-0.0424765 -95.36116,0.0302428 z"
+ id="path1948_1_"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+
+ <linearGradient
+ id="path1950_3_"
+ gradientUnits="userSpaceOnUse"
+ x1="1220.123"
+ y1="134.5405"
+ x2="783.30792"
+ y2="752.55798"
+ gradientTransform="matrix(0.5625,0,0,-0.568,125.7979,708.7776)"
+ xlink:href="#linearGradient3778">
+ <stop
+ offset="0"
+ style="stop-color:#fae13f;stop-opacity:1;"
+ id="stop24" />
+ <stop
+ offset="1"
+ style="stop-color:#facc2b;stop-opacity:1;"
+ id="stop26" />
+ </linearGradient>
+ <path
+ id="path1950_1_"
+ d="m 789.03685,-0.868 v 105.496 c 0,81.799 -71.19267,150.631 -152.37034,150.631 H 393.0493 c -66.73298,0 -121.9513,55.632 -121.9513,120.727 v 226.221 c 0,64.39 255.53022,396.793 255.53022,396.793 0,0 231.98858,-328.234 231.98858,-396.793 V 511.666 H 515.00061 V 481.483 H 758.6168 880.57116 c 70.88777,0 97.30706,-48.163 121.95034,-120.447 25.4697,-74.417 24.3844,-145.985 0,-241.457 C 985.00406,50.85 951.54825,-0.868 880.57116,-0.868 H 789.03685 z M 652.0177,572.026 c 25.28282,0 45.76511,20.179 45.76511,45.137 0,25.041 -20.48229,45.409 -45.76511,45.409 -25.19452,0 -45.76716,-20.368 -45.76716,-45.409 0,-24.958 20.57264,-45.137 45.76716,-45.137 z"
+ style="fill:url(#linearGradient3005);fill-opacity:1;"
+ inkscape:connector-curvature="0" />
+</g>
+</svg> \ No newline at end of file
diff --git a/docs/write_addons.rst b/docs/write_addons.rst
new file mode 100644
index 000000000..c9f050ebc
--- /dev/null
+++ b/docs/write_addons.rst
@@ -0,0 +1,158 @@
+.. _write_addons:
+
+Addons
+======
+
+A Addon is a python file which is located at :file:`pyload/plugins/addon`.
+The :class:`AddonManager <pyload.manager.AddonManager.AddonManager>` will load it automatically on startup. Only one instance exists
+over the complete lifetime of pyload. Your addon can interact on various events called by the :class:`AddonManager <pyload.manager.AddonManager.AddonManager>`,
+do something complete autonomic and has full access to the :class:`Api <pyload.api.Api>` and every detail of pyLoad.
+The UpdateManager, CaptchaTrader, UnRar and many more are realised as addon.
+
+Addon header
+-----------
+
+Your addon needs to subclass :class:`Addon <pyload.plugins.Addon.Addon>` and will inherit all of its method, make sure to check its documentation!
+
+All addons should start with something like this: ::
+
+ from pyload.plugins.Addon import Addon
+
+ class YourAddon(Addon):
+ __name__ = "YourAddon"
+ __version__ = "0.1"
+ __description__ = "Does really cool stuff"
+ __config__ = [ ("activated" , "bool" , "Activated" , "True" ) ]
+ __author_name__ = ("Me")
+ __author_mail__ = ("me@has-no-mail.com")
+
+All meta-data is defined in the header, you need at least one option at ``__config__`` so the user can toggle your
+addon on and off. Dont't overwrite the ``init`` method if not neccesary, use ``setup`` instead.
+
+Using the Config
+----------------
+
+We are taking a closer look at the ``__config__`` parameter.
+You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``.
+When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name, value``.
+
+
+Interacting on Events
+---------------------
+
+The next step is to think about where your Addon action takes places.
+
+The easiest way is to overwrite specific methods defined by the :class:`Addon <pyload.plugins.Addon.Addon>` base class.
+The name is indicating when the function gets called.
+See :class:`Addon <pyload.plugins.Addon.Addon>` page for a complete listing.
+
+You should be aware of the arguments the Addon is called with, whether its a :class:`PyFile <pyload.datatype.PyFile.PyFile>`
+or :class:`PyPackage <pyload.datatype.PyPackage.PyPackage>` you should read its related documentation to know how to access her great power and manipulate them.
+
+A basic excerpt would look like: ::
+
+ from pyload.plugins.Addon import Addon
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ def activate(self):
+ print "Yay, the core is ready let's do some work."
+
+ def downloadFinished(self, pyfile):
+ print "A Download just finished."
+
+Another and more flexible and powerful way is to use event listener.
+All addon methods exists as event and very useful additional events are dispatched by the core. For a little overview look
+at :class:`AddonManager <pyload.manager.AddonManager.AddonManager>`. Keep in mind that you can define own events and other people may listen on them.
+
+For your convenience it's possible to register listeners automatical via the ``event_map`` attribute.
+It requires a `dict` that maps event names to function names or a `list` of function names. It's important that all names are strings ::
+
+ from pyload.plugins.Addon import Addon
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+ event_map = {'downloadFinished': "doSomeWork",
+ 'allDownloadsFnished': "someMethod",
+ 'activate': "initialize"}
+
+ def initialize(self):
+ print "Initialized."
+
+ def doSomeWork(self, pyfile):
+ print "This is equivalent to the above example."
+
+ def someMethod(self):
+ print "The underlying event (allDownloadsFinished) for this method is not available through the base class"
+
+An advantage of the event listener is that you are able to register and remove the listeners at runtime.
+Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name", function)` and see doc for
+:class:`AddonManager <pyload.manager.AddonManager.AddonManager>`. Contrary to ``event_map``, ``function`` has to be a reference
+and **not** a `string`.
+
+We introduced events because it scales better if there a a huge amount of events and addons. So all future interaction will be exclusive
+available as event and not accessible through overwriting addon methods. However you can safely do this, it will not be removed and is easier to implement.
+
+
+Providing RPC services
+----------------------
+
+You may noticed that pyLoad has an :class:`Api <pyload.api.Api>`, which can be used internal or called by clients via RPC.
+So probably clients want to be able to interact with your addon to request it's state or invoke some action.
+
+Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: ::
+
+ from pyload.plugins.Addon import Addon, Expose
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ @Expose
+ def invoke(self, arg):
+ print "Invoked with", arg
+
+Thats all, it's available via the :class:`Api <pyload.api.Api>` now. If you want to use it read :ref:`access_api`.
+Here is a basic example: ::
+
+ #Assuming client is a ThriftClient or Api object
+
+ print client.getServices()
+ print client.call(ServiceCall("YourAddon", "invoke", "an argument"))
+
+Providing status information
+----------------------------
+Your addon can store information in a ``dict`` that can easily be retrievied via the :class:`Api <pyload.api.Api>`.
+
+Just store everything in ``self.info``. ::
+
+ from pyload.plugins.Addon import Addon
+
+ class YourAddon(Addon):
+ """
+ Your Addon code here.
+ """
+
+ def setup(self):
+ self.info = {'running': False}
+
+ def activate(self):
+ self.info['running'] = True
+
+Usable with: ::
+
+ #Assuming client is a ThriftClient or Api object
+
+ print client.getAllInfo()
+
+Example
+-------
+ Sorry but you won't find an example here ;-)
+
+ Look at :file:`pyload/plugins/addon` and you will find plenty examples there.
diff --git a/docs/write_hooks.rst b/docs/write_hooks.rst
deleted file mode 100644
index dd60367b7..000000000
--- a/docs/write_hooks.rst
+++ /dev/null
@@ -1,162 +0,0 @@
-.. _write_hooks:
-
-Hooks
-=====
-
-A Hook is a python file which is located at :file:`module/plugins/hooks`.
-The :class:`HookManager <module.HookManager.HookManager>` will load it automatically on startup. Only one instance exists
-over the complete lifetime of pyload. Your hook can interact on various events called by the :class:`HookManager <module.HookManager.HookManager>`,
-do something complete autonomic and has full access to the :class:`Api <module.Api.Api>` and every detail of pyLoad.
-The UpdateManager, CaptchaTrader, UnRar and many more are realised as hooks.
-
-Hook header
------------
-
-Your hook needs to subclass :class:`Hook <module.plugins.Hook.Hook>` and will inherit all of its method, make sure to check its documentation!
-
-All Hooks should start with something like this: ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- __name__ = "YourHook"
- __version__ = "0.1"
- __description__ = "Does really cool stuff"
- __config__ = [ ("activated" , "bool" , "Activated" , "True" ) ]
- __threaded__ = ["downloadFinished"]
- __author_name__ = ("Me")
- __author_mail__ = ("me@has-no-mail.com")
-
-All meta-data is defined in the header, you need at least one option at ``__config__`` so the user can toggle your
-hook on and off. Dont't overwrite the ``init`` method if not neccesary, use ``setup`` instead.
-
-Using the Config
-----------------
-
-We are taking a closer look at the ``__config__`` parameter.
-You can add more config values as desired by adding tuples of the following format to the config list: ``("name", "type", "description", "default value")``.
-When everything went right you can access the config values with ``self.getConfig(name)`` and ``self.setConfig(name,value``.
-
-
-Interacting on Events
----------------------
-
-The next step is to think about where your Hook action takes places.
-
-The easiest way is to overwrite specific methods defined by the :class:`Hook <module.plugins.Hook.Hook>` base class.
-The name is indicating when the function gets called.
-See :class:`Hook <module.plugins.Hook.Hook>` page for a complete listing.
-
-You should be aware of the arguments the Hooks are called with, whether its a :class:`PyFile <module.PyFile.PyFile>`
-or :class:`PyPackage <module.PyPackage.PyPackage>` you should read its related documentation to know how to access her great power and manipulate them.
-
-A basic excerpt would look like: ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- def coreReady(self):
- print "Yay, the core is ready let's do some work."
-
- def downloadFinished(self, pyfile):
- print "A Download just finished."
-
-Another important feature to mention can be seen at the ``__threaded__`` parameter. Function names listed will be executed
-in a thread, in order to not block the main thread. This should be used for all kind of longer processing tasks.
-
-Another and more flexible and powerful way is to use event listener.
-All hook methods exists as event and very useful additional events are dispatched by the core. For a little overview look
-at :class:`HookManager <module.HookManager.HookManager>`. Keep in mind that you can define own events and other people may listen on them.
-
-For your convenience it's possible to register listeners automatical via the ``event_map`` attribute.
-It requires a `dict` that maps event names to function names or a `list` of function names. It's important that all names are strings ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
- event_map = {"downloadFinished" : "doSomeWork",
- "allDownloadsFnished": "someMethod",
- "coreReady": "initialize"}
-
- def initialize(self):
- print "Initialized."
-
- def doSomeWork(self, pyfile):
- print "This is equivalent to the above example."
-
- def someMethod(self):
- print "The underlying event (allDownloadsFinished) for this method is not available through the base class"
-
-An advantage of the event listener is that you are able to register and remove the listeners at runtime.
-Use `self.manager.addEvent("name", function)`, `self.manager.removeEvent("name", function)` and see doc for
-:class:`HookManager <module.HookManager.HookManager>`. Contrary to ``event_map``, ``function`` has to be a reference
-and **not** a `string`.
-
-We introduced events because it scales better if there a a huge amount of events and hooks. So all future interaction will be exclusive
-available as event and not accessible through overwriting hook methods. However you can safely do this, it will not be removed and is easier to implement.
-
-
-Providing RPC services
-----------------------
-
-You may noticed that pyLoad has an :class:`Api <module.Api.Api>`, which can be used internal or called by clients via RPC.
-So probably clients want to be able to interact with your hook to request it's state or invoke some action.
-
-Sounds complicated but is very easy to do. Just use the ``Expose`` decorator: ::
-
- from module.plugins.Hook import Hook, Expose
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- @Expose
- def invoke(self, arg):
- print "Invoked with", arg
-
-Thats all, it's available via the :class:`Api <module.Api.Api>` now. If you want to use it read :ref:`access_api`.
-Here is a basic example: ::
-
- #Assuming client is a ThriftClient or Api object
-
- print client.getServices()
- print client.call(ServiceCall("YourHook", "invoke", "an argument"))
-
-Providing status information
-----------------------------
-Your hook can store information in a ``dict`` that can easily be retrievied via the :class:`Api <module.Api.Api>`.
-
-Just store everything in ``self.info``. ::
-
- from module.plugins.Hook import Hook
-
- class YourHook(Hook):
- """
- Your Hook code here.
- """
-
- def setup(self):
- self.info = {"running": False}
-
- def coreReady(self):
- self.info["running"] = True
-
-Usable with: ::
-
- #Assuming client is a ThriftClient or Api object
-
- print client.getAllInfo()
-
-Example
--------
- Sorry but you won't find an example here ;-)
-
- Look at :file:`module/plugins/hooks` and you will find plenty examples there.
diff --git a/docs/write_plugins.rst b/docs/write_plugins.rst
index b513a5978..f624b2fb5 100644
--- a/docs/write_plugins.rst
+++ b/docs/write_plugins.rst
@@ -3,11 +3,11 @@
Plugins
=======
-A Plugin is a python file located at one of the subfolders in :file:`module/plugins/`. Either :file:`hoster`, :file:`crypter`
+A Plugin is a python file located at one of the subfolders in :file:`pyload/plugins/`. Either :file:`hoster`, :file:`crypter`
or :file:`container`, depending of it's type.
There are three kinds of different plugins: **Hoster**, **Crypter**, **Container**.
-All kind of plugins inherit from the base :class:`Plugin <module.plugins.Plugin.Plugin>`. You should know its
+All kind of plugins inherit from the base :class:`Plugin <pyload.plugins.Plugin.Plugin>`. You should know its
convenient methods, they make your work easier ;-)
Every plugin defines a ``__pattern__`` and when the user adds urls, every url is matched against the pattern defined in
@@ -19,7 +19,7 @@ Plugin header
How basic hoster plugin header could look like: ::
- from module.plugin.Hoster import Hoster
+ from pyload.plugins.Hoster import Hoster
class MyFileHoster(Hoster):
__name__ = "MyFileHoster"
@@ -30,7 +30,7 @@ How basic hoster plugin header could look like: ::
You have to define these meta-data, ``__pattern__`` has to be a regexp that sucessfully compiles with
``re.compile(__pattern__)``.
-Just like :ref:`write_hooks` you can add and use config values exatly the same way.
+Just like :ref:`write_addons` you can add and use config values exatly the same way.
If you want a Crypter or Container plugin, just replace the word Hoster with your desired plugin type.
@@ -41,13 +41,13 @@ We head to the next important section, the ``process`` method of your plugin.
In fact the ``process`` method is the only functionality your plugin has to provide, but its always a good idea to split up tasks to not produce spaghetti code.
An example ``process`` function could look like this ::
- from module.plugin.Hoster import Hoster
+ from pyload.plugins.Hoster import Hoster
class MyFileHoster(Hoster):
"""
plugin code
"""
-
+
def process(self, pyfile):
html = self.load(pyfile.url) # load the content of the orginal pyfile.url to html
@@ -58,7 +58,7 @@ An example ``process`` function could look like this ::
# download the file, destination is determined by pyLoad
self.download(parsed_url)
-You need to know about the :class:`PyFile <module.PyFile.PyFile>` class, since an instance of it is given as parameter to every pyfile.
+You need to know about the :class:`PyFile <pyload.datatype.PyFile.PyFile>` class, since an instance of it is given as parameter to every pyfile.
Some tasks your plugin should handle: proof if file is online, get filename, wait if needed, download the file, etc..
Wait times
@@ -71,7 +71,7 @@ Captcha decrypting
__________________
To handle captcha input just use ``self.decryptCaptcha(url, ...)``, it will be send to clients
-or handled by :class:`Hook <module.plugins.Hook.Hook>` plugins
+or handled by :class:`Addon <pyload.plugins.Addon.Addon>` plugins
Crypter
-------
@@ -81,7 +81,7 @@ Well, they work nearly the same, only that the function they have to provide is
Example: ::
- from module.plugin.Crypter import Crypter
+ from pyload.plugins.Crypter import Crypter
class MyFileCrypter(Crypter):
"""
@@ -93,11 +93,11 @@ Example: ::
self.packages.append(("pyLoad packages", urls, "pyLoad packages")) # urls list of urls
-They can access all the methods from :class:`Plugin <module.plugins.Plugin.Plugin>`, but the important thing is they
+They can access all the methods from :class:`Plugin <pyload.plugins.Plugin.Plugin>`, but the important thing is they
have to append all packages they parsed to the `self.packages` list. Simply append tuples with `(name, urls, folder)`,
where urls is the list of urls contained in the packages. Thats all of your work, pyLoad will know what to do with them.
Examples
--------
-Best examples are already existing plugins in :file:`module/plugins/`. \ No newline at end of file
+Best examples are already existing plugins in :file:`pyload/plugins/`.
diff --git a/docs/write_scripts.rst b/docs/write_scripts.rst
new file mode 100644
index 000000000..55c0e1665
--- /dev/null
+++ b/docs/write_scripts.rst
@@ -0,0 +1,25 @@
+.. _write_scripts:
+
+Scripts
+=======
+
+pyLoad is able to start any kind of scripts at given events.
+Simply put your script in a suitable folder and pyLoad will execute it at the given events and pass some arguments to them.
+
+
+***Note:***
+**Every script starting with symbol `#` will be ignored!** (ex.: `#converter.sh`)
+
+***Note:***
+You have to restart pyload when you change script names or locations.
+
+
+Below you see the list of arguments, which are passed to the scripts.
+
+### Argument list ###
+
+ - `download_preparing`: **`pluginname` `url`**
+ - `download_finished`: **`pluginname` `url` `filename` `filelocation`**
+ - `package_finshed`: **`packagename` `packagelocation`**
+ - `before_reconnect`: **`oldip`**
+ - `after_reconnect`: **`newip`**
diff --git a/icons/abort.png b/icons/abort.png
deleted file mode 100644
index 66170aae7..000000000
--- a/icons/abort.png
+++ /dev/null
Binary files differ
diff --git a/icons/add_small.png b/icons/add_small.png
deleted file mode 100644
index 4dc88b09b..000000000
--- a/icons/add_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/clipboard.png b/icons/clipboard.png
deleted file mode 100644
index 9ba608eba..000000000
--- a/icons/clipboard.png
+++ /dev/null
Binary files differ
diff --git a/icons/close.png b/icons/close.png
deleted file mode 100644
index 66170aae7..000000000
--- a/icons/close.png
+++ /dev/null
Binary files differ
diff --git a/icons/edit_small.png b/icons/edit_small.png
deleted file mode 100644
index eb76e21b4..000000000
--- a/icons/edit_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/logo-gui.png b/icons/logo-gui.png
deleted file mode 100644
index 5994b274d..000000000
--- a/icons/logo-gui.png
+++ /dev/null
Binary files differ
diff --git a/icons/logo.png b/icons/logo.png
deleted file mode 100644
index 72a95b740..000000000
--- a/icons/logo.png
+++ /dev/null
Binary files differ
diff --git a/icons/pull_small.png b/icons/pull_small.png
deleted file mode 100644
index 432ad321f..000000000
--- a/icons/pull_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/push_small.png b/icons/push_small.png
deleted file mode 100644
index 701fc69e3..000000000
--- a/icons/push_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/pyload-gui.ico b/icons/pyload-gui.ico
deleted file mode 100644
index 00a1a53ff..000000000
--- a/icons/pyload-gui.ico
+++ /dev/null
Binary files differ
diff --git a/icons/pyload.ico b/icons/pyload.ico
deleted file mode 100644
index 58b1f4b89..000000000
--- a/icons/pyload.ico
+++ /dev/null
Binary files differ
diff --git a/icons/pyload2.ico b/icons/pyload2.ico
deleted file mode 100644
index c2b497986..000000000
--- a/icons/pyload2.ico
+++ /dev/null
Binary files differ
diff --git a/icons/refresh1_small.png b/icons/refresh1_small.png
deleted file mode 100644
index ce4f24efc..000000000
--- a/icons/refresh1_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/refresh_small.png b/icons/refresh_small.png
deleted file mode 100644
index 1ffd18d97..000000000
--- a/icons/refresh_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/remove_small.png b/icons/remove_small.png
deleted file mode 100644
index bf99763e8..000000000
--- a/icons/remove_small.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_add.png b/icons/toolbar_add.png
deleted file mode 100644
index 17003e9f0..000000000
--- a/icons/toolbar_add.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_pause.png b/icons/toolbar_pause.png
deleted file mode 100644
index b7a727b71..000000000
--- a/icons/toolbar_pause.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_remove.png b/icons/toolbar_remove.png
deleted file mode 100644
index 1e9c00e16..000000000
--- a/icons/toolbar_remove.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_start.png b/icons/toolbar_start.png
deleted file mode 100644
index 1123266e6..000000000
--- a/icons/toolbar_start.png
+++ /dev/null
Binary files differ
diff --git a/icons/toolbar_stop.png b/icons/toolbar_stop.png
deleted file mode 100644
index b388e3d72..000000000
--- a/icons/toolbar_stop.png
+++ /dev/null
Binary files differ
diff --git a/locale/README.md b/locale/README.md
new file mode 100644
index 000000000..2751611f7
--- /dev/null
+++ b/locale/README.md
@@ -0,0 +1,55 @@
+# Localization [![Crowdin](http://translate.pyload.org/badges/pyload/localized.png)](http://translate.pyload.org/project/pyload)
+
+The localization process take place on Crowdin:
+
+http://translate.pyload.org
+
+or
+
+http://crowdin.net/project/pyload
+
+## Add a tip for translators
+If you want to explain a translatable string to make the translation process easier you can do that using comment block starting with `L10N:`. For example:
+
+```python
+# L10N: Here the tip for translators
+# Thanks
+print _("A translatable string")
+```
+
+Translators will see:
+
+```
+L10N: Here the tip for translators
+Thanks
+```
+
+## Updating templates
+
+To update POT files run:
+
+`paver generate_locale`
+
+to automatically upload the updated POTs on Crowdin for the localization process just run:
+
+`paver upload_translations -k [api_key]`
+
+the API Key can be retrieved in the Settings panel of the project on Crowdin. This is allowed only to administrators.
+
+## Retrieve updated PO files
+
+Updated PO files can be automatically download from Crowdin using:
+
+`paver download_translations -k [api_key]`
+
+This is allowed only to administrators, users can download the last version of the translations using the Crowdin web interface.
+
+## Compile PO files
+
+MO files can be generated using:
+
+`paver compile_translations`
+
+To compile a single file just use `msgfmt`. For example to compile a core.po file run:
+
+`msgfmt -o core.mo core.po`
diff --git a/locale/af/LC_MESSAGES/django.po b/locale/af/LC_MESSAGES/django.po
new file mode 100644
index 000000000..e6f126a57
--- /dev/null
+++ b/locale/af/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Afrikaans\n"
+"Language: af_ZA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/af/LC_MESSAGES/pyLoad.po b/locale/af/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..d29dad5d0
--- /dev/null
+++ b/locale/af/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Afrikaans\n"
+"Language: af_ZA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/af/LC_MESSAGES/pyLoadCli.po b/locale/af/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..6760512f1
--- /dev/null
+++ b/locale/af/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Afrikaans\n"
+"Language: af_ZA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/af/LC_MESSAGES/setup.po b/locale/af/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..a66cfd176
--- /dev/null
+++ b/locale/af/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Afrikaans\n"
+"Language: af_ZA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/ar/LC_MESSAGES/django.po b/locale/ar/LC_MESSAGES/django.po
new file mode 100644
index 000000000..875577bad
--- /dev/null
+++ b/locale/ar/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Arabic\n"
+"Language: ar_SA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "طُلِؚت كاؚت؎ا جديدة"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "فضلاً اقرأ النصّ الموجود في الكاؚت؎ا."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "أعيد ت؎غيل pyLoad"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "ايقاف"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "نجح"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "ت؎غيل"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "أمتأكد من أنك تريد الخروج من pyLoad ؟"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "أعد ؚدء الراؚط"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "احذف الراؚط"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "أدخل اسماً للحزمة."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "فضلاً اضغط على الموقع الصحيح للكاؚت؎ا."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "حدث خطأ."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "المجلد فارغ"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "ف؎ل"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "لا تتوفر كاؚت؎ا لأقرأها."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "لم تتطاؚق كلمتا المرور."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "حُفِ؞َت الإعدادات"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "مجلد جديد"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "أمتأكد من أنك تريد إعادة ت؎غيل pyLoad؟"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "انت؞ار %"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "التنزيلات الن؎طة"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "المنزل"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "قا؊مة الإنت؞ار"
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "جامع الرواؚط"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "التنزيلات"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "السجلات"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "الضؚط"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "اسم"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "الحالة"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "معلومات"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "الحجم"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "التقدم"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "تسجيل الدخول"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "اسم المستخدم"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "كلمة المرور"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "لم يتطاؚق اسم المستخدم وكلمة المرور الّذان أدخلتهما. رجاءً حاول مجدّداً."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "لإعادة ضؚط ؚيانات الولوج أو إضافة مستخدم نفّذ:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "تمّ الحذف"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "ف؎لت إعادة الت؎غيل"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "المجلد:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "كلمة السر:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "حرّر الحزمة"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "حرّر تفاصيل الحزمة أدناه."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "اسم الحزمة."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "مجلد"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "اسم المجلّد الفرعيّ لهذه التنزيلات."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "قا؊مة كلمات السر المستخدمة لفكّ ضغط rar"
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "اقترح"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "امحُ"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "لقد خرجت ؚنجاح."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "المسار"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "مُطلَق"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "نسؚي"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "الاسم"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "الحجم"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "النوع"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "آخر تغيير"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "المجلد الأؚ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "لا محتوى"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "عام"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "الملحقات"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "الحساؚات"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "اختر قِسماً من القا؊مة"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "الاضافات"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "صالحة حتى"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "الوقت"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "أقصى عدد للتنزيلات المتزامنة"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "حذف؟"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "صالحة"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "غير صالح"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "نعم"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "لا"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "إضافة"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "إضافة حساؚ"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "أضف معلومات حساؚك لتستخدم مزايا الحساؚ المتقدّم"
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "اسم حساؚك."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "كلمة السرّ لهذا الحساؚ."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "نوع"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "اختر المضيف لحساؚك."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "اؚدأ"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "الساؚق"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "التالي"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "انتهى"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "أخؚار"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "الدعم"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "الن؞ام"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr "ؚايثون:"
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "ن؞ام الت؎غيل:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "إصدار pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "مجلد التثؚيت:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "مجلد الإعدادات:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "مجلد التنزيلات:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "المساحة الحرّة:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "اللُّغة:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "منفذ واجهة ال؎نكؚوتية:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "منفذ الواجهة الؚعيدة:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "ؚرنامج الإعداد"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "مدير الملفات"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "أضف حزمة"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "الصق راؚطأ أو ارفع حاوياً. "
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "اسم الحزمة الجديدة."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "رواؚط"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "الصق الرواؚط أو أيّ نصّ هنا ثم اضغط زرّ استخراج الرواؚط."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "ر؎ّح الرواؚط"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "كلمة سر أر؎يفات RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "ملف"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "ارفع حاوياً."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "الوجهة"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "النص"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "إغلاق"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "واجهة ال؎نكؚوتية"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "يتوفّر تحديث للؚرنامج!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "تمّ تحديث الإضافة، يرجى إعادة ت؎غيل الؚرنامج!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "كاؚت؎ا تنت؞ر"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "اخرج"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "أدِر"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "المعلومات"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "يرجى الولوج!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "توقّف"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "الغاء"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "التنزيل:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "إعادة الاتصال:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "السرعة:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "الن؎ط:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "أعد تحميل الصفحة"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "يحمّل"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "العودة لأعلى"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "اخرج من pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "أعد ت؎غيل pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "لإضافة مستخدم أو تغيير كلمات مرور استخدم:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "هامّ: حساؚ المدير Admin له دا؊ماً الصلاحيات الكاملة!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "غيّر كلمة السرّ"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "الم؎رف"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "الأذون"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "غيّر"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "أدخل كلمة السرّ الحاليّة والجديدة."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "المستخدم"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "كلمة السرّ الحالية"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "كلمة سرّ جديدة"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "كلمة السرّ الجديدة."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "كلمة السرّ الجديدة (إعادة)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "فضلاً أعد إدخال كلمة السرّ."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "لا يوجد لديك صلاحيات للوصول لهذه الصفحه"
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "مجلد التحميلات غير موجود"
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "غير محدود"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "غير متوفر"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "قم ؚت؎غيل pyload.py -s لتؚداء التثؚيت"
+
diff --git a/locale/ar/LC_MESSAGES/pyLoad.po b/locale/ar/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..2f37f4a1b
--- /dev/null
+++ b/locale/ar/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Arabic\n"
+"Language: ar_SA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "تم استقؚال ا؎ارة انهاء"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "ؚاي لود يعمل حاليا ؚمعرف العملية %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "ف؎ل في تغيير المجموعه: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "ف؎ل في تغيير المستخدم: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "مجلد للتقارير"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "ؚدء"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "استخدام مجلد المنزل: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "مجلد للملفات الم؀قتة"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "مجلد للتحميلات"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL لاتصال امن"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "نقل اعدادت المستخدم القديم الى قاعدة الؚيانات"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "يرجى التاكد من ؚيانات الدخول ؚستخدام ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "تم مسح جميع الرواؚط"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "وقت التحميل: %"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "المساحه الحره: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "تفعيل الحساؚات..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "تفعيل الاضافات..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "ؚاي ولد محدث و يعمل"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "اعادة ت؎غيل ؚاي لود"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "اغلاق ؚاي لود"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "التثؚيت %"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "لا يمكن ايجاد %(وصف): %(اسم)"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "لا يمكن ان؎اء %(وصف): %(اسم)"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "جاري ايقاف الت؎غيل..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "خطاء اثناء ايقاف الت؎غيل "
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "انهاء ؚاي لود من الطرفيه"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "تم الانتهاء"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "غير متصل"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "متصل"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "في قا؊مة الانت؞ار"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "تخطي"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "انت؞ار"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "م؀قت. غير متصل"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "الؚدء"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "ف؎ل"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "الغاء"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "فك ت؎فير"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "مخصص"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "جاري التنزيل"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "تجهيز"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "غير معروف"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "انتهت الحزمه: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "خطاء في الواجهة الخلفيه للؚرنامج "
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "ؚدء %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "ف؎ل في تحميل الواجهه الخلفيه للؚرنامج %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "انت؞ار %"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "؎هادة SSL غير موجوده."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "نأسف, لقد اوقفنا الدعم للؚد؊ % مؚا؎ره من ؚاي لود "
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "عينات الاعدادات موجودة في المجلد pyload/webui/servers "
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "لايمكن استخدام %(خادم), لم يتم تثؚيت python-flup!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "تحتاج لتحميل و تثؚيت bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "انسخ boern.so الى pyload/lib او استخدم setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "ؚالطؚع يجؚ ان تكون معتاد على لنكس وتعرف كيف تثؚت الؚرامج"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "هذا المخدم لا يقدم خدمة SSL، يرجى الن؞ر في استخدام ؚديلاً من ذلك"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "لا يوجد لديك صلاحيات للوصول لهذه الصفحه"
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "مجلد التحميلات غير موجود"
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "غير محدود"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "غير متوفر"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "قم ؚت؎غيل pyload.py -s لتؚداء التثؚيت"
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "ف؎ل التنزيل على اجزاء, العودة الى التنزيل على اتصال واحد | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "ؚدء التنزيل: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "تم الانتهاء من التنزيل: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "الاضافة %s تفتقد الى و؞يفة."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "تم الغاء التنزيل: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "تم اعادة ت؎غيل التنزيل: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "التنزيل غير متصل: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "التنزيل غير متصل م؀قتا: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "ف؎ل التنزيل: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "لايمكن الوصول الى الخادم او اعادة الاتصال, الانت؞ار 1 دقيقة و اعادة المحاولة."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "تم تخطي التنزيل: %(name)s ؚسؚؚ %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "ف؎ل جلؚ المعلومات ل %(name)s | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "ف؎ل التفعيل %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "الاضافات المفعلة: %"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "الاضافات المعطلة: %"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "ف؎ل في اعادة الاتصال: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "الؚرنامج النصي لاعادة الاتصال غير متوفر!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "ؚدء اعادة الاتصال"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "ف؎ل في تنفيذ الؚرنامج النصي لاعادة الاتصال!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "تمت اعادة الاتصال, IP جديد: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "لم يتؚقى مساحة كافية على الجهاز"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "لا يمكن تسجيل الدخول ؚهذا الحساؚ %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "كلمة مرور خاط؊ة"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "الحساؚ %s ليس فية كمية ؚيانات كافية, اعد التحقق خلال 30 دقيقة"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "انتهت مدة الحساؚ %s, تحقق مرة اخرى في 1 ساعة"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "لقد وصلت الى الحد الاقصى للتنزيل"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "خطأ في استيراد %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "لا مستضيف محمل"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "قم ؚتفعيل التحميل المؚا؎ر في حساؚ Bitshare لديك"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "مطلوؚ تصريح (اسم مستخدم: كلمة مرور)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "الرجاء إدخال حساؚك في %s أو إلغاء تن؎يط هذة الاضافة"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "هناك رمز HTML في الملفات اللتي تم تنزيلها (%s)...خطاء اعادة توجية؟سوف يتم اعادة ت؎غيل التنزيل."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "الملف غير متوفر م؀قتا"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "ضغط ال؎ؚكة: الانت؞ار ؚين التنزيلات %d."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "ضغط ال؎ؚكة: الانت؞ار من اجل كلمة التحقق%d."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "الملف اللذي تم تنزيلة فارغ"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "مفتاح واحهة ؚرمجة التطؚيقات غير صالح"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: لم يتؚقى لديك ؚيانات كافية"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "تم تجاوز الؚيانات المتوفرة"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: ؚيانات الم؎اركة (تحميل مؚا؎ر)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "جاري التحميل من عنوان الانترنت هذا, انت؞ر ل 60 ثانية"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "رمز تاكيد غير صالح, سوف يتم اعادة التنزيل"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: لا يوجد مساحات فارغة"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "تحناج لحساؚ مدفوع من اجل هذا الملف"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "تم الاؚلاغ عن اسم الملف غير صالح"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "خطاء تنزيلات متوازية, الان انت؞ر 60 ثانية."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "لم يتم تسجيل الدخول."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "ف؎ل في فك الت؎فير"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "لا يوجد مفتاح ملف مقدم في عنوان الانترنت"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "رمز خطاء:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** تم تحديث الاضافات, يرجى اعادة ت؎غيل ؚاي لود ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "تم تحديث الاضافات و اعادة ت؎غيلها"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "لا يوجد تحديثات للاضافات"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "لايوجد تحديثات ل ؚاي لود"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** نسخه جديده %s من ؚاي لود متوفره ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** احصل عليها من هنا: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "غير قادر على الاتصال ؚالخادم من اجل التحديث"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "نسخه جديدة من %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "حصل خطاء عند التحديث %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "نسخة غير متطاؚقة"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "انتهى التنزيل: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "طلؚ كلمة تحقق جديد: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "اجؚ Øš 'c %s نص على كلمة التحقق'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "اضافه %s من HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "لا %s مثؚت"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "لا يمكن تفعيل %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "تم التفعيل"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "لا اضافات استخراج مفعلة"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "الحزمه %s في قا؊مة الانت؞ار للاستخراج لاحقا"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "تفحص الحزم %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "استخراج الى %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "لم يتم العثور على ملفات للاستخراج"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "فك ال؞غط"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "محمي ؚكلمة مرور"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "كلمة مرور خاط؊ة"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "حذف %s الملفات"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "تم الانتهاء من الاستخراج"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "خطأ في الأر؎يف"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC غير متطاؚق"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "خطاء غير معروف"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "ف؎ل في تحديد المستخدم و المجموعة"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "ضغط و تحميل: منفذ 9666 قيد الاستخدام"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%sرصيد متؚقي"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "لايمكن ارسال رد."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "حساؚك في CaptchaTrader لا يحتوي رصيد كافي"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "لم يتم العثور على قا؊مة Crypter"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "قا؊مة Crypter فارغة"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "انتهى التحميل: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "هوية تحقق جديدة من upload: %s :%s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "حساؚك للتحقق 9kw.eu لا يحتوي على رصيد كافي"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "سكرؚت مثؚت ل %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "سكرؚت غير قاؚل للتنفيذ:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "خطاء في %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "لم يتؚقى لديك رصيد كافي في حساؚ ExpertDecoders لديك"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "يرجى اضافة حساؚ rehost.to اولا ثم اعاده ت؎غيل ؚاي لود"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d رصيد متؚقي"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "pil و tesseract غير مثؚت ولايوجد عميل متصل لفك كلمة التحقق"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "ف؎ل في اعداد المستخدم و المجموعه: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "لايوجد عميل متصل لفك ت؎فير الكاؚات؎ا"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "حزم مضافه %(اسم) تحتوي %(عدد) رواؚط"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "مضافه %(عدد) رواؚط الى الحزمه #%(حزم)"
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "لم يتم العثور على محرك js, يرجى تثؚيت Spidermonkey, ossp-js, pyv8 او rhino"
+
diff --git a/locale/ar/LC_MESSAGES/pyLoadCli.po b/locale/ar/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..fabbb0d85
--- /dev/null
+++ b/locale/ar/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Arabic\n"
+"Language: ar_SA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " واجهة سطر الأوامر"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s التنزيلات:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " السرعة: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " الحجم: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " الانتهاء في: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " معرف: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "في انت؞ار: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "الحالة:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "تم إيقاف م؀قتاً"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "ت؎غيل"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "السرعة الإجمالية"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "الملفات الموجودة في قا؊مة الانت؞ار"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "المجموع"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "القا؊مة:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " اضافة رواؚط"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " إدارة قا؊مة الانت؞ار"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " إدارة جامع الرواؚط"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " (عدم) ايقاف م؀قت للخادم"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " قتل الخادم"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " إنهاء"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "الرجاء استخدام ؚناء الجملة التالي: إضافة <Package name><link><link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "التحقق %d من الرواؚط:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "الملف غير موجود."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "تم انهاء ؚاي لود"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "اطؚع حالة الخادم"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "اكتؚ التنزيلات اللتي في قا؊مة الانت؞ار"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "اكتؚ التنزيلات اللتي في جامع الرواؚط"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "اضافة حزمة الى قا؊مة الانت؞ار"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "اضافة حزمة إلى جامع الرواؚط"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "حذف الملفات من قا؊مة الانت؞ار/جامع الرواؚط"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "حذف حزم من قا؊مة الانت؞ار/جامع الرواؚط"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "نقل الحزم من قا؊مة الانت؞ار إلى جامع الرواؚط أو العكس ؚالعكس"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "إعادة ت؎غيل الملفات"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "إعادة ت؎غيل الحزم"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "تحقق من حالة الاتصال، يعمل مع المحتوى المحلي"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "التحقق من حالة الاتصال من الملف المحتوى"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "إيقاف الخادم م؀قتا"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "متاؚعة التنزيلات"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "تؚديل إيقاف/عدم ايقاف الإيقاف الم؀قت"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "قتل الخادم"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "قا؊مة الأوامر:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "لا يمكن كتاؚة ملف اعدادات المستخدم"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "العنوان: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "المنفذ: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "اسم المستخدم: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "كلمة المرور: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "ؚيانات تسجيل الدخول خاط؊ة."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "لا يمكن ان؎اء اتصال %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "أنت ؚحاجة إلى py-openssl للاتصال ؚهذا الاساس من ؚاي لود."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "الوضع التؚادلي تم تجاهله حيث انك مررت ؚعض الاوامر."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "إضافة حزمة:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "أدخل اسماً للحزمة الجديدة"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "الحزمة: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "تحليل الرواؚط التي تريد إضافتها."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "اكتؚ %s عند الانتهاء."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "تم اضافة الرواؚط: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " العودة إلى القا؊مة الر؊يسية"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "إدارة الحزم:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "إدارة الرواؚط:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "ما اللذي تريد نقلة ؟"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "ما اللذي تريد حذفة ؟"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "ما اللذي تريد اعادة ت؎غيلة ؟"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "حذف"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "نقل"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "إعادة ت؎غيل"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " -الساؚقة"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " -التالي"
+
diff --git a/locale/ar/LC_MESSAGES/setup.po b/locale/ar/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..00bfae831
--- /dev/null
+++ b/locale/ar/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Arabic\n"
+"Language: ar_SA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "مرحؚا ؚكم في مساعد اعداد ؚاي لود ."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "سوف يتم فحص ن؞امك وعمل تثؚيت اولي من اجل ت؎غيل ؚاي لود."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "القيمة الموجودة في الأقواس [] هي دا؊ماً القيمة الافتراضية،"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "في حال كنت لا ترغؚ في تغييرها أو كنت غير متأكد من ما تختار، فقط اضغط أدخال."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "لا تنسى: يمكنك دا؊ماً إعادة ت؎غيل هذا المساعد ؚستخدام--setup أو-s المعلمة، عند ؚدء ت؎غيل pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "للانهاء، وعدم السماح له الؚد؊ مع pyload.py تلقا؊ياً ؚعد الآن."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "عندما تكون على استعداد للتحقق من الن؞ام، اضغط مفتاح الادخال."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "واجهة ال؎نكؚوتية"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "الاستمرار في التنصيؚ؟"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "هل تريد تغير مكان الاعدادات؟ الحالي هو %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "تغيير مسار الاعداد؟"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "هل تريد اعداد الاعدادات الأساسية وؚيانات الدخول؟"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "هذا مستحسن من اجل الت؎غيلة الاولى."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "صنع الاعدادات الاساسية؟"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "هل تريد اعداد ssl؟"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "اعداد ssl؟"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "هل تريد اعداد واحهة الويؚ؟"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "اعداد واجهة الويؚ؟"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "تم انهاء التثؚيت ؚنجاح."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "اضغ؞ مفتاح الادخال للانهاء و اعادة ت؎غيل ؚاي لود"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "# # الإعداد الأساسي # #"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "ؚيانات تسجيل الدخول التالية صالحة لواجهة سطر الاوامر, واجهة المستخدم و واجهة الويؚ."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "اسم المستخدم"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "اللغة"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "العدد الاقصى التنزيلات المتوازيية (التنزيلات في نفس الوقت)"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "استخدام إعادة الاتصال؟"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "موقع الؚرنامج النصي لاعادة الاتصال"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "# # اعداد واجهة الويؚ # #"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "تن؎يط واجهة الويؚ؟"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "عنوان الواجهة, اذا قمت ؚستخدام 127.0.0.1 او localhost, واجهة الويؚ سوف تكون قاؚلة للوصول محليا فقط."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "عنوان"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "المنفذ"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "يقدم ؚاي لود خوادم متعددة للواجهة الخلفية، الآن ؚعد ؎رحاً موجزاً."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "يمكن استخدامها ؚواسطة أؚات؎ي، lighttpd، يتطلؚ منك اعدادهم, وهي ليست مهمة سهلة جداً."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "يمكنك الحصول علية من هنا : https://github.com/jonashaag/bjoern, قم ؚتجميعة ؚرمجيا"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "انتؚاه: في ؚعض الحالات النادرة الخادم المدمج لا يعمل، إذا لاح؞ت وجود م؎اكل مع واجهة الويؚ"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "عد إلى هنا وقم ؚتغير الخادم مدمج لذالك المذكور هنا."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "خادم"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "# # إعداد SSL # #"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "تنفيذ هذه الأوامر من مجلد اعداد ؚاي لود لانتاج ؎هادات ssl:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "اذا انتهيت وكل ؎يء جرى ؚ؎كل صحيح, تستطيع تفعيل ssl الان."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "تفعيل SSL؟"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "حدد الإجراء"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1-إن؎اء/تحرير المستخدم"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2-قا؊مة المستخدمين"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3-إزالة المستخدم"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4-إنهاء"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "المستخدمين"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "اضغط Enter للإنهاء."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "ف؎ل الإعداد مسار التكوين: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "كلمة المرور: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "كلمة المرور قصيرة جداً. استخدام 4 رموز على الاقل."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "كلمة المرور (مرة أخرى): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "لم تتطاؚق كلمتا المرور."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "نعم"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "صحيح"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "لا"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "خاط؊ة"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "الإدخال غير صالح"
+
diff --git a/locale/bn/LC_MESSAGES/django.po b/locale/bn/LC_MESSAGES/django.po
new file mode 100644
index 000000000..759264ae6
--- /dev/null
+++ b/locale/bn/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Bengali\n"
+"Language: bn_BD\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/bn/LC_MESSAGES/pyLoad.po b/locale/bn/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..99e491023
--- /dev/null
+++ b/locale/bn/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Bengali\n"
+"Language: bn_BD\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/bn/LC_MESSAGES/pyLoadCli.po b/locale/bn/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..dc7a6deaf
--- /dev/null
+++ b/locale/bn/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Bengali\n"
+"Language: bn_BD\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/bn/LC_MESSAGES/setup.po b/locale/bn/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..cc40924e3
--- /dev/null
+++ b/locale/bn/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Bengali\n"
+"Language: bn_BD\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/ca/LC_MESSAGES/django.po b/locale/ca/LC_MESSAGES/django.po
new file mode 100644
index 000000000..e91b2fe3f
--- /dev/null
+++ b/locale/ca/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Catalan\n"
+"Language: ca_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Sol·licitud d'un nou Captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Si us plau introdueixi el text que apareix al captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad ha estat reiniciat"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "apagat"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Èxit"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "encÚs"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Estas segur que vols sortir del pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Reiniciar Enllaç"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Elimina enllaç"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Introdueixi el nom del paquet."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Clica a la posició correcta del captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "S'ha produït un error."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "El directori és buit"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Ha fallat"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "No hi ha captchas per llegir."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Les contrasenyes no coincideixen."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Paràmetres desats."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nou directori"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Estas segur que vols reiniciar pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperant %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Descàrregues actives"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Inici"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Descàrregues"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Registres"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuració"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nom"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Estat"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informació"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Mida"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Progrés"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Inici de sessió"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Nom d'usuari"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Contrasenya"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "El teu usuari d'usuari i contrasenya no concorden. Torna-ho a provar."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Per reiniciar les teves dades de login o afegir usuari executa:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Eliminar acabat"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Reiniciar fallit"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Carpeta:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Contrasenya:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Editar paquet"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Editar detalls del paquet segÃŒent."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Nom del paquet."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Carpeta"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nom de la subcarpeta per aquestes descàrregues."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Llista de contrasenyes emprades per l'unrar."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Enviar"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Reiniciar"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Has tancat la sessió correctament."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Ruta"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relatiu"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nom"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "mida"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tipus"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "última modificació"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "directori pare"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "sense contingut"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "General"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Comptes"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Tria una secció del menú"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Connector"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Vàlid fins"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Tràfic restant"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Temps"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max Paral·lel"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Eliminar?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "vàlid"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "invàlid"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "sí"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Afegir"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Afegir compte"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Introdueix les dades del teu compte per emprar les característiques premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "El seu nom d'usuari."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "La contrasenya és per aquest compte."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Tipus"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Tria proveïdor del teu compte."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Començar"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "anterior"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "segÃŒent"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fi"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Notícies"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Suport"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistema"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "S.O.:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "versió del pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Carpeta de instal·lació:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Carpeta de configuració:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Carpeta de descàrregues:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Espai lliure:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Idioma:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Port de la interfície Web:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Port de la interfície remota:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Configuració"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Gestor de Fitxers"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Afegir paquet"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Aferra els teus enllaços o puja un fitxer contenidor."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Nom del nou paquet."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Enllaços"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Aferra els teus enllaços aquó o qualsevol text i pitja el botó de filtrat."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtra urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Contrasenya per l'arxiu RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Fitxer"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Puja un contenidor."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Destinació"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Llegint captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "El captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Introdueix el text del captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Tancar"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interfície web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Hi ha una actualització de pyLoad disponible!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins actualitzats, si us plau reinicia!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Esperant captcha"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Sortir"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrar"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Informació"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Si us plau fes login!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Aturar"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Cancel·lar"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Descàrrega:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Reconnectar:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Velocitat:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Actiu:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Recarregar pàgina"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "carregant"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Tornar a l'inici"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Sortir de pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Reiniciar pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Per afegir usuari o canviar contrasenya emprar:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Important: L'usuari administrador sempre te tots els permisos!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Canviar Contrasenya"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Administrador"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Permisos"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "canviar"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Introdueixi la contrasenya actual i la desitjada."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Usuari"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Contrasenya actual"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Contrasenya nova"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "La nova contrasenya."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nova contrasenya (repetir)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Siusplau repeteix la nova contrasenya."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "No tens permisos per accedir a aquesta pàgina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "No s'ha trobat el directori de descàrregues."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "il·limitat"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "no disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Executa pyload.py -s per accedir a l'instal·lació."
+
diff --git a/locale/ca/LC_MESSAGES/pyLoad.po b/locale/ca/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..732ef9ec8
--- /dev/null
+++ b/locale/ca/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Catalan\n"
+"Language: ca_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "S'ha rebut la senyal de Sortida"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "ja s'estat executant pyLoad amb el pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Ha fallat el canvi de el grup: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Ha fallat el canvi d'usuari: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "carpeta de registre"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Començant"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Emprant el directori d'inici: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto descodificarà el contenidor de fitxers"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "carpeta per fitxers temporals"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "carpeta de descàrregues"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL per connexions segures"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Movent l'antiga configuració d'usuari a la DB"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Comprova les teves dades de login amb ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Tots els enllaços eliminats"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Temps de descàrrega: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Espai lliure: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Activant Comptes..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Activant Plugins..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad està en funcionament"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "reiniciant pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad està sortint"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Instal·la %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "no s'ha pogut trobar %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "no s'ha pogut crear %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "apagant..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "ha succeït un error mentre s'apagava"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "matat pyLoad des de el Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "L'arxiu de la base de dades ha estat eliminat per una incompatiblitat de la versió."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "El fitxer de la base de dades NO pot ser convertit."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "La base de dades ha estat convertida de la v2 a la v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "La base de dades ha estat convertida de la v3 a la v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Convertint l'antiga BD de Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "finalitzat"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "fora de línia"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "en línia"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "en cua"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "saltat"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "esperant"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temporalment fora de línia"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "començant"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "fallit"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "avortat"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "desxifrant"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personalitzat"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "descarregant"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "processant"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "deconegut"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paquet finalitzat: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Emprant SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Error al backend remot: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Iniciant %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "S'ha fallat la carrega del backend %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperant %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "No s'han trobat els certificats SSL."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Ho sent molt, ja no permetem iniciar %s directament emprant pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Pots emprar el servidor amb fils que ofereix millor rendiment i ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "per suposat encara pots emprar el teu %s amb el servidor fastcgi de pyLoad"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "les configuracions d'exemple estan al directori pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "No s'ha pogut emprar %(server)s, python-flup no està instal·lat!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Error important el servidor lleuger: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Has de descarregar i compilar el bjoern. https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Copia el fitxer boern.so a la carpeta pyload/lib o empra setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Per suposat hauràs d'estar familiaritzat amb GNU/Linux i sabre com compilar programes"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "El servidor està en mode multi-fil degut als problemes de rendiment a windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Aquest servidor no ofereix SSL, si us plau considera l'opció d'emprar el servidor amb fils"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Iniciant el servidor incorporat: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Iniciant el servidor web SSL amb fils: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Iniciant el servidor web amb fils: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Iniciant el servidor fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Iniciant el servidor web lleuger (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "No tens permisos per accedir a aquesta pàgina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "No s'ha trobat el directori de descàrregues."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "il·limitat"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "no disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Executa pyload.py -s per accedir a l'instal·lació."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Descàrrega per trossos fallida, tornant a la connexió única | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Iniciant descàrrega: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Descàrrega finalitzada: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "El plugin %s troba a faltar una funció."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Descàrrega avortada: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Descàrrega reiniciada: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "La descàrrega està fora de línia: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "La descàrrega està temporalment fora de línia: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Descàrrega fallida: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "No s'ha pogut connectar amb el servidor o la connexió s'ha reiniciat, esperant 1 minuts per tornar-ho a provar."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Descàrrega omitida: %(name)s due to %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Desxifrant comença en: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Desxifrat fallit: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Re-intentant %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Obtenint informació per %(name)s failed | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Error executant ganxos: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "L'activació de %(name)s ha fallat"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Plugins activats: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Plugins desactivats: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Reconnexió fallida: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "No s'ha trobat l'script de reconnexió!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Iniciant reconnexió"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "L'execució de l'script de reconnexió ha fallat!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Reconnectat, nova IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "No queda suficient espai lliure al dispositiu"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "No s'ha pogut iniciar sessió amb el compte %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Contrasenya Errònia"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "L'hora %s està en un format incorrecte, empra: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "El compte %s no te suficient tràfic, es tornarà a comprovar d'aquí 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "El compte %s està caducat, es tornarà a comprovar d'aquí 1h"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Límit de descàrrega assolit"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s té un patró invàlid."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Error important %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "No s'ha carregat cap proveïdor"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Activa la descàrrega directa al teu compte de Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList no pot ser netejat."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Els ajustaments del compte han estat eliminats degut al nou format de la configuració."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorització requerida (usuari:contrasenya)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Si us plau, introdueixi el seu compte de %s o desactivar aquest plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Hi havia Codi HTML en el fitxer (%s) descarregat... error de re-direcció? Es re-iniciarà la descàrrega."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Fitxer temporalment no disponible"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: esperant entre descàrregues %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: esperant per captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "El fitxer descarregat estava buit"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Clau de API invàlida"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: No queda suficient tràfic"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Tràfic excedit"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Tràfic Compartit (descàrrega directa)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Ja s'està descarregant des de aquesta adreça IP, esperant 60 segons"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Codi d'Autenticació invàlid, la descàrrega serà reiniciada"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: No hi ha espais lliures"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Necessites un compte premium per aquest fitxer"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Nom de fitxer informat invàlid"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Error de descàrrega paral·lela, esperant 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "No connectat."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Desxifrat ha fallat"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Cap clau de fitxer proporcionat a la URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Codi d'error:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "El fitxer no existeix."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "* * * Plugins s'han actualitzat, si us plau reinicieu pyLoad * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins actualitzats i recarregats"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "No hi ha actualitzacions de plugins disponibles"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "No hi ha actualitzacions del pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** La nova Versió %s de pyLoad està disponible ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Obtengui'l aquí: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "No es pot connectar al servidor per actualitzacions"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nova versió de %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "S'ha produït un error mentre s'actualitzava %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "La versió no coincideix"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Descàrrega finalitzada: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nova petició de Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Constesta amb 'c %s text del captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Si us plau, afegeiu el vostre compte premium.to i torneu a iniciar pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Afegit %s des de HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "El %s no està instal·lat"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "No s'ha pogut activar %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Activat"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "No hi han connectors d'extracció activats"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "El paquet %s s'ha posat a la cua per una posterior extracció"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Revisa el paquet %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Extreu a %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "No s'han trobat fitxers per extreure"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "extraient"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Protegit amb contrasenya"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Contrasenya incorrecta"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Eliminant els fitxers %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Extracció finalitzada"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Error d'arxiu"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "El CRC no coincideix"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Error desconegut"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Configuració d'usuari i el grup ha fallat"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: El Port 9666 ja s'està en ús"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s crÚdits restants"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "No s'ha pogut enviar la resposta."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "El teu compte de CaptchaTrader no te crÚdits suficients"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Llista de Crypter no trobada"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "La llista de Crypter està buida"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Descàrrega finalitzada : %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nou CaptchaID de càrrega: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "El teu compte de 9kw.eu no te crÚdits suficients"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Scripts instal·lats per %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script no executable:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Error en %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "El teu compte de ExpertDecoders no te crÚdits suficients"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Si us plau afegir el seu compte de rehost.to primer i reinicieu pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Si us plau afegiu un compte vàlid premiumize.me primer i reinicieu pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d crÚdits restants"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil i tesseract no estan instal·lats i no hi ha cap Client connectat per desxifrar captchas"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Cap plugin ha pogut obtenir resultats apropiats d'aquest captcha en un temps assignat."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Aplicació d'Usuari i el Grup ha fallat: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "No hi ha cap client connectat per des-encriptar el captcha"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Afegit paquet %(name)s que conté les enllaços %(count)d"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Afegits %(count)d enllaços al paquet #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "No s'ha trobat cap motor js, si us plau instal·la Spidermonkey, ossp-js, pyv8 o rhino"
+
diff --git a/locale/ca/LC_MESSAGES/pyLoadCli.po b/locale/ca/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..da383b6a7
--- /dev/null
+++ b/locale/ca/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Catalan\n"
+"Language: ca_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Línia de Comandes"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Baixades:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Velocitat: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Mida: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Acabarà en: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "esperant: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Estat:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "pausat"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "executant"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "velocitat Total"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Fitxers en cua"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Menú:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Afegeix enllaços"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Gestiona Cua"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Gestiona Col·lector"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "ReprÚn/Pausa Servidor"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Mata Servidor"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Surt"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Si us plau empreu aquesta sintaxi: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Comprovant %d enllaç(os):"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "El fitxer no existeix."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad ha finalitzat"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Mostra estat del servidor"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Mostra descàrregues en cua"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Mostra descàrreges en el col·lector"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Afegeix paquet a la cua"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Afegeix paquet al col·lector"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Elimina Fitxers de la Cua/Col·lector"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Elimina Paquets de la Cua/Col·lector"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Mou Paquets de la Cua al Col·lector o viceversa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Reinicia fitxers"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Reinicia paquets"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Comprova l'estat online, funciona amb contenidor local"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Comprova l'estat online d'un fitxer contenidor"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pausa el servidor"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continua les descàrregues"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Canvia pausa/reprÚn"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "mata servidor"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Llista de comandes:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "No s'ha pogut escriure el fitxer de configuració d'usuari"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Necessites py-openssl per connectar-te al nucli del pyLoad."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adreça: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Nom d'usuari: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Contrasenya: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Dades d'accés incorrectes."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "No s'ha pogut establir la connexió a %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Necessites py-openssl per connectar-te al nucli del pyLoad."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Mode interactiu ignorat ja que has especificat algunes comandes."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Afegeix Paquet:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Introdueix el nom per el nou paquet"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paquet: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analitza dels enllaços que vols introduir."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Introdueix %s en acabar."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Enllaços afegits: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " torna al menú principal"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Gestiona Paquets:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Gestiona Enllaços:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "QuÚ vols moure?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "QuÚ vols eliminar?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "QuÚ vols reiniciar?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Tria el que vols fer o introdueix un número de paquet."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "elimina"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "mou"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "reinicia"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - anterior"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - segÃŒent"
+
diff --git a/locale/ca/LC_MESSAGES/setup.po b/locale/ca/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..8f6fbeb8c
--- /dev/null
+++ b/locale/ca/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Catalan\n"
+"Language: ca_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "s"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Benvingut a l'Assistent de Configuració de pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Es comprovarà el teu equip i es farà una configuració bàsica per executar pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "El valor entre claudàtors [] sempre és el valor per defecte,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "en cas de no voler canviar-ho o si estas insegur de que triar simplement pitja enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "No oblidis: Sempre pots tornar a l'assistent amb els paràmetres --setup o -s quan inicies el pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Si tens cap problema amb l'assistent pitja Control-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "per avortar i no permetre que s'iniciï automàticament amb pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Quan estiguis llest per la comprovació del sistema pitja enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Necessites pycurl, squlit i python 2.5, 2.6 o 2.7 per executar pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Si us plau corregeix aixo i torna a executar el pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "L'assistent ara es tancarà."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "La comprovació del sistema ha acabat, pitja enter per veure l'informe de l'estat."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Estat ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "desxifrant contenidor"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "connexió SSL"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "desxifrat automàtic de captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interfície web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "extensió Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Funcionalitats disponibles:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Funcionalitats no disponibles: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crytop no disponible"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Això es necessari si vols desxifrar els contenidors de fitxers."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL no disponible"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Això es necessari si vols establir connexions segures amb el nucli o la interfície web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Si només voleu accedir a pyLoad localment el SSL no és necessari."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "reconeixedor de Captchas no disponible"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Només necessari per alguns proveïdors com a usuari gratuït."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI no disponible"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "La Interfície Gràfica d'Usuari."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "no s'ha trobat el motor JavaScript"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Es necessari algun d'aquests paquets per els enllaços Click'N'Load. Instal.la Spidermonkey, ossp-js, pyv8 o rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Ara pots avortar la instal·lació i arreglar les dependÚncies, si així ho prefereixes."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Continuar amb la instal·lació?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Vols canviar la ruta de configuració? Actualment és %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Si estas emprant pyLoad en un servidor o en una partició a un dispositiu flash intern és una bona idea canviar-ho."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Canviar la ruta de configuració?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Vols configurar les dades d'accés i la configuració bàsica?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Això és recomanant en la primera execució."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Establir una configuració bàsica?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Vols configurar el SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Configurar SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Vols configurar l'interíicie web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Configurar interfície web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Configuració finalitzada satisfactòriament."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Pitja enter i reinicia pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Comprovació del Sistema ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "La teva versió de python és massa nova. Si us plau empra Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "La teva versió de python és massa antiga. Si us plau empra com a mínim Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Versió de Python: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "La teva versió %s de jinja2 es massa antiga."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Pots continuar tranquil·lament encara que la interfície web no funcioni,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "si us plau actualitza o desinstal·la-ho, pylLoad ja inclou una biblioteca jinja2."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "Motor JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Configuració bàsica ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Les dades d'accés segÌents són vàlides per CLI, GUI i la interfície web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Nom d'usuari"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Els clients externs (GUI, CLI i altres) necessiten accés remot a través de la xarxa per funcionar."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "No obstant, si només vos emprar la interfície web pots deshabilitar-lo per estalviar memòria ram."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Activar accés remot"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Idioma"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Directori de descàrregues"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Nombre màxim de descàrregues paral·leles"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Emprar el reconnectar?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Ubicació de l'script de reconnexió"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Configuració de la interfície Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Activar interfície web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adreça d'escolta, si empres 127.0.0.1 o localhost la interfície web només serà accessible localment."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adreça"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad ofereix uns quants backends, ara se'n farà una breu explicació."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "El servidor per defecte es la millor opció si no saps quin triar."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Aquest servidor ofereix SSL i es una bona alternativa a l'integrat."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Pots emprar apache, lighttpd, però requereixen ser configurats i no sempre és una tasca fàcil."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Una alternativa escrita en C, requereix libev i coneixements de GNU/Linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Aconsegueix-lo aquí: https://github.com/jonashaag/bjoern, compila'l"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "i copia bjoren.so a pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Atenció: En alguns casos estranys el servidor integrat no funciona, ho notareu amb problemes a la interfície web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "torna aquí i canvia el servidor integrat per el servidor amb fils."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Servidor"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Configuració SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Executa aquesta comanada des de la carpeta de configuració de pyLoad per fer els certificats SSL:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Si has acabat i tot ha anat bé ara podràs activar el SSL."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Activar SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Tria una opció"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Crear/editar usuari"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Llistar usuaris"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Eliminar usuari"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Sortir"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Usuaris"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Configurant nova ruta de configuració, la configuració actual no serà transferida!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Ruta de configuració"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Ruta de configuració canviada, la instal·lació es tancarà, si us plau reinicia per continuar."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Pitja enter per sortir."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "L'ajustament de la ruta de configuració ha fallat: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: perdut"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Contrasenya: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "La contrasenya és massa curta. Utilitzeu almenys 4 caràcters."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Contrasenya (altre cop): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Les contrasenyes no coincideixen."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "sí"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "cert"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "c"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "fals"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Entrada invàlida"
+
diff --git a/locale/cli.pot b/locale/cli.pot
index 646c6c70e..db8033d31 100644
--- a/locale/cli.pot
+++ b/locale/cli.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+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"
@@ -17,283 +17,283 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pyLoadCli.py:75 pyLoadCli.py:133
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
msgid " Command Line Interface"
msgstr ""
-#: pyLoadCli.py:165
+#: pyload/cli/Cli.py:165
#, python-format
msgid "%s Downloads:"
msgstr ""
-#: pyLoadCli.py:177
+#: pyload/cli/Cli.py:177
msgid " Speed: "
msgstr ""
-#: pyLoadCli.py:177
+#: pyload/cli/Cli.py:177
msgid " Size: "
msgstr ""
-#: pyLoadCli.py:178
+#: pyload/cli/Cli.py:178
msgid " Finished in: "
msgstr ""
-#: pyLoadCli.py:179
+#: pyload/cli/Cli.py:179
msgid " ID: "
msgstr ""
-#: pyLoadCli.py:184
+#: pyload/cli/Cli.py:184
msgid "waiting: "
msgstr ""
-#: pyLoadCli.py:191 pyLoadCli.py:193
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
msgid "Status:"
msgstr ""
-#: pyLoadCli.py:191
+#: pyload/cli/Cli.py:191
msgid "paused"
msgstr ""
-#: pyLoadCli.py:193
+#: pyload/cli/Cli.py:193
msgid "running"
msgstr ""
-#: pyLoadCli.py:196
+#: pyload/cli/Cli.py:196
msgid "total Speed"
msgstr ""
-#: pyLoadCli.py:196
+#: pyload/cli/Cli.py:196
msgid "Files in queue"
msgstr ""
-#: pyLoadCli.py:197
+#: pyload/cli/Cli.py:197
msgid "Total"
msgstr ""
-#: pyLoadCli.py:203
+#: pyload/cli/Cli.py:203
msgid "Menu:"
msgstr ""
-#: pyLoadCli.py:205
+#: pyload/cli/Cli.py:205
msgid " Add Links"
msgstr ""
-#: pyLoadCli.py:206
+#: pyload/cli/Cli.py:206
msgid " Manage Queue"
msgstr ""
-#: pyLoadCli.py:207
+#: pyload/cli/Cli.py:207
msgid " Manage Collector"
msgstr ""
-#: pyLoadCli.py:208
+#: pyload/cli/Cli.py:208
msgid " (Un)Pause Server"
msgstr ""
-#: pyLoadCli.py:209
+#: pyload/cli/Cli.py:209
msgid " Kill Server"
msgstr ""
-#: pyLoadCli.py:210
+#: pyload/cli/Cli.py:210
msgid " Quit"
msgstr ""
-#: pyLoadCli.py:289 pyLoadCli.py:296
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
msgid "Please use this syntax: add <Package name> <link> <link2> ..."
msgstr ""
-#: pyLoadCli.py:315
+#: pyload/cli/Cli.py:315
#, python-format
msgid "Checking %d links:"
msgstr ""
-#: pyLoadCli.py:324
+#: pyload/cli/Cli.py:324
msgid "File does not exists."
msgstr ""
-#: pyLoadCli.py:385
+#: pyload/cli/Cli.py:385
msgid "pyLoad was terminated"
msgstr ""
-#: pyLoadCli.py:443
+#: pyload/cli/Cli.py:443
msgid "Prints server status"
msgstr ""
-#: pyLoadCli.py:444
+#: pyload/cli/Cli.py:444
msgid "Prints downloads in queue"
msgstr ""
-#: pyLoadCli.py:445
+#: pyload/cli/Cli.py:445
msgid "Prints downloads in collector"
msgstr ""
-#: pyLoadCli.py:446
+#: pyload/cli/Cli.py:446
msgid "Adds package to queue"
msgstr ""
-#: pyLoadCli.py:447
+#: pyload/cli/Cli.py:447
msgid "Adds package to collector"
msgstr ""
-#: pyLoadCli.py:448
+#: pyload/cli/Cli.py:448
msgid "Delete Files from Queue/Collector"
msgstr ""
-#: pyLoadCli.py:449
+#: pyload/cli/Cli.py:449
msgid "Delete Packages from Queue/Collector"
msgstr ""
-#: pyLoadCli.py:450
+#: pyload/cli/Cli.py:450
msgid "Move Packages from Queue to Collector or vice versa"
msgstr ""
-#: pyLoadCli.py:451
+#: pyload/cli/Cli.py:451
msgid "Restart files"
msgstr ""
-#: pyLoadCli.py:452
+#: pyload/cli/Cli.py:452
msgid "Restart packages"
msgstr ""
-#: pyLoadCli.py:453
+#: pyload/cli/Cli.py:453
msgid "Check online status, works with local container"
msgstr ""
-#: pyLoadCli.py:454
+#: pyload/cli/Cli.py:454
msgid "Checks online status of a container file"
msgstr ""
-#: pyLoadCli.py:455
+#: pyload/cli/Cli.py:455
msgid "Pause the server"
msgstr ""
-#: pyLoadCli.py:456
+#: pyload/cli/Cli.py:456
msgid "continue downloads"
msgstr ""
-#: pyLoadCli.py:457
+#: pyload/cli/Cli.py:457
msgid "Toggle pause/unpause"
msgstr ""
-#: pyLoadCli.py:458
+#: pyload/cli/Cli.py:458
msgid "kill server"
msgstr ""
-#: pyLoadCli.py:460
+#: pyload/cli/Cli.py:460
msgid "List of commands:"
msgstr ""
-#: pyLoadCli.py:473
+#: pyload/cli/Cli.py:473
msgid "Couldn't write user config file"
msgstr ""
-#: pyLoadCli.py:548
+#: pyload/cli/Cli.py:548
msgid "You need py-openssl to connect to this pyLoad Core."
msgstr ""
-#: pyLoadCli.py:555
+#: pyload/cli/Cli.py:555
msgid "Address: "
msgstr ""
-#: pyLoadCli.py:556
+#: pyload/cli/Cli.py:556
msgid "Port: "
msgstr ""
-#: pyLoadCli.py:557
+#: pyload/cli/Cli.py:557
msgid "Username: "
msgstr ""
-#: pyLoadCli.py:561
+#: pyload/cli/Cli.py:561
msgid "Password: "
msgstr ""
-#: pyLoadCli.py:566 pyLoadCli.py:575
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
msgid "Login data is wrong."
msgstr ""
-#: pyLoadCli.py:568 pyLoadCli.py:577
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
#, python-format
msgid "Could not establish connection to %(addr)s:%(port)s."
msgstr ""
-#: pyLoadCli.py:580
+#: pyload/cli/Cli.py:580
msgid "You need py-openssl to connect to this pyLoad core."
msgstr ""
-#: pyLoadCli.py:582
+#: pyload/cli/Cli.py:582
msgid "Interactive mode ignored since you passed some commands."
msgstr ""
-#: module/cli/ManageFiles.py:97
-msgid "Manage Packages:"
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
msgstr ""
-#: module/cli/ManageFiles.py:99
-msgid "Manage Links:"
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
msgstr ""
-#: module/cli/ManageFiles.py:104
-msgid "What do you want to move?"
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
msgstr ""
-#: module/cli/ManageFiles.py:106
-msgid "What do you want to delete?"
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
msgstr ""
-#: module/cli/ManageFiles.py:108
-msgid "What do you want to restart?"
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
msgstr ""
-#: module/cli/ManageFiles.py:113
-msgid "Choose what yout want to do or enter package number."
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "delete"
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "move"
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
msgstr ""
-#: module/cli/ManageFiles.py:115
-msgid "restart"
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
msgstr ""
-#: module/cli/ManageFiles.py:148
-msgid " - previous"
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
msgstr ""
-#: module/cli/ManageFiles.py:148
-msgid " - next"
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
msgstr ""
-#: module/cli/ManageFiles.py:149 module/cli/AddPackage.py:64
-msgid " back to main menu"
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
msgstr ""
-#: module/cli/AddPackage.py:48
-msgid "Add Package:"
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
msgstr ""
-#: module/cli/AddPackage.py:53
-msgid "Enter a name for the new package"
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
msgstr ""
-#: module/cli/AddPackage.py:57
-#, python-format
-msgid "Package: %s"
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
msgstr ""
-#: module/cli/AddPackage.py:58
-msgid "Parse the links you want to add."
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
msgstr ""
-#: module/cli/AddPackage.py:59
-#, python-format
-msgid "Type %s when done."
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
msgstr ""
-#: module/cli/AddPackage.py:60
-msgid "Links added: "
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
msgstr ""
diff --git a/locale/core.pot b/locale/core.pot
index 546f0e4d3..47550a6aa 100644
--- a/locale/core.pot
+++ b/locale/core.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+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"
@@ -17,850 +17,883 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: pyLoadCore.py:185
+#: pyload/Core.py:181
msgid "Received Quit signal"
msgstr ""
-#: pyLoadCore.py:301
+#: pyload/Core.py:302
#, python-format
msgid "pyLoad already running with pid %s"
msgstr ""
-#: pyLoadCore.py:315
+#: pyload/Core.py:316
#, python-format
msgid "Failed changing group: %s"
msgstr ""
-#: pyLoadCore.py:325
+#: pyload/Core.py:326
#, python-format
msgid "Failed changing user: %s"
msgstr ""
-#: pyLoadCore.py:327
+#: pyload/Core.py:328
msgid "folder for logs"
msgstr ""
-#: pyLoadCore.py:338
+#: pyload/Core.py:339
msgid "Starting"
msgstr ""
-#: pyLoadCore.py:339
+#: pyload/Core.py:340
#, python-format
msgid "Using home directory: %s"
msgstr ""
-#: pyLoadCore.py:348
+#: pyload/Core.py:349
msgid "pycrypto to decode container files"
msgstr ""
-#: pyLoadCore.py:351
+#: pyload/Core.py:352
msgid "folder for temporary files"
msgstr ""
-#: pyLoadCore.py:356
+#: pyload/Core.py:357
msgid "folder for downloads"
msgstr ""
-#: pyLoadCore.py:359
+#: pyload/Core.py:360
msgid "OpenSSL for secure connection"
msgstr ""
-#: pyLoadCore.py:363
+#: pyload/Core.py:364
msgid "Moving old user config to DB"
msgstr ""
-#: pyLoadCore.py:366
-msgid "Please check your logindata with ./pyLoadCore.py -u"
+#: pyload/Core.py:367
+msgid "Please check your logindata with ./pyload.py -u"
msgstr ""
-#: pyLoadCore.py:369
+#: pyload/Core.py:370
msgid "All links removed"
msgstr ""
-#: pyLoadCore.py:400
+#: pyload/Core.py:401
#, python-format
msgid "Downloadtime: %s"
msgstr ""
-#: pyLoadCore.py:410
+#: pyload/Core.py:411
#, python-format
msgid "Free space: %s"
msgstr ""
-#: pyLoadCore.py:430
+#: pyload/Core.py:431
msgid "Activating Accounts..."
msgstr ""
-#: pyLoadCore.py:436
+#: pyload/Core.py:437
msgid "Activating Plugins..."
msgstr ""
-#: pyLoadCore.py:439
+#: pyload/Core.py:440
msgid "pyLoad is up and running"
msgstr ""
-#: pyLoadCore.py:458
+#: pyload/Core.py:459
msgid "restarting pyLoad"
msgstr ""
-#: pyLoadCore.py:462
+#: pyload/Core.py:463
msgid "pyLoad quits"
msgstr ""
-#: pyLoadCore.py:519
+#: pyload/Core.py:520
#, python-format
msgid "Install %s"
msgstr ""
-#: pyLoadCore.py:555
+#: pyload/Core.py:556
#, python-format
msgid "could not find %(desc)s: %(name)s"
msgstr ""
-#: pyLoadCore.py:557
+#: pyload/Core.py:558
#, python-format
msgid "could not create %(desc)s: %(name)s"
msgstr ""
-#: pyLoadCore.py:578
+#: pyload/Core.py:579
msgid "shutting down..."
msgstr ""
-#: pyLoadCore.py:595
+#: pyload/Core.py:596
msgid "error while shutting down"
msgstr ""
-#: pyLoadCore.py:659
+#: pyload/Core.py:660
msgid "killed pyLoad from Terminal"
msgstr ""
-#: module/common/JsEngine.py:156
-msgid ""
-"No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or "
-"rhino"
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
msgstr ""
-#: module/remote/ThriftBackend.py:39
-msgid "Using SSL ThriftBackend"
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
msgstr ""
-#: module/remote/RemoteManager.py:35
-#, python-format
-msgid "Remote backend error: %s"
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
msgstr ""
-#: module/remote/RemoteManager.py:82
-#, python-format
-msgid "Starting %(name)s: %(addr)s:%(port)s"
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
msgstr ""
-#: module/remote/RemoteManager.py:84
-#, python-format
-msgid "Failed loading backend %(name)s | %(error)s"
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
msgstr ""
-#: module/ThreadManager.py:137
-#, python-format
-msgid "Reconnect Failed: %s"
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
msgstr ""
-#: module/ThreadManager.py:176
-msgid "Reconnect script not found!"
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
msgstr ""
-#: module/ThreadManager.py:182
-msgid "Starting reconnect"
+#: pyload/database/FileDatabase.py:45
+msgid "online"
msgstr ""
-#: module/ThreadManager.py:196
-msgid "Failed executing reconnect script!"
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
msgstr ""
-#: module/ThreadManager.py:208
-#, python-format
-msgid "Reconnected, new IP: %s"
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
msgstr ""
-#: module/ThreadManager.py:288
-msgid "Not enough space left on device"
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
msgstr ""
-#: module/HookManager.py:90 module/plugins/Hook.py:102
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
#, python-format
-msgid "Error executing hooks: %s"
+msgid "Package finished: %s"
msgstr ""
-#: module/HookManager.py:140
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
#, python-format
-msgid "Failed activating %(name)s"
+msgid "Remote backend error: %s"
msgstr ""
-#: module/HookManager.py:144
+#: pyload/remote/RemoteManager.py:82
#, python-format
-msgid "Activated plugins: %s"
+msgid "Starting %(name)s: %(addr)s:%(port)s"
msgstr ""
-#: module/HookManager.py:145
+#: pyload/remote/RemoteManager.py:84
#, python-format
-msgid "Deactivate plugins: %s"
+msgid "Failed loading backend %(name)s | %(error)s"
msgstr ""
-#: module/CaptchaManager.py:78 module/interaction/InteractionManager.py:82
-msgid "No Client connected for captcha decrypting"
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
msgstr ""
-#: module/web/ServerThread.py:35
+#: pyload/threads/ServerThread.py:36
msgid "SSL certificates not found."
msgstr ""
-#: module/web/ServerThread.py:39
+#: pyload/threads/ServerThread.py:40
#, python-format
msgid "Sorry, we dropped support for starting %s directly within pyLoad"
msgstr ""
-#: module/web/ServerThread.py:40
+#: pyload/threads/ServerThread.py:41
msgid "You can use the threaded server which offers good performance and ssl,"
msgstr ""
-#: module/web/ServerThread.py:41
+#: pyload/threads/ServerThread.py:42
#, python-format
msgid ""
"of course you can still use your existing %s with pyLoads fastcgi server"
msgstr ""
-#: module/web/ServerThread.py:42
-msgid "sample configs are located in the module/web/servers directory"
+#: pyload/threads/ServerThread.py:43
+msgid "sample configs are located in the pyload/webui/servers directory"
msgstr ""
-#: module/web/ServerThread.py:49
+#: pyload/threads/ServerThread.py:49
#, python-format
msgid "Can't use %(server)s, python-flup is not installed!"
msgstr ""
-#: module/web/ServerThread.py:56
+#: pyload/threads/ServerThread.py:58
#, python-format
msgid "Error importing lightweight server: %s"
msgstr ""
-#: module/web/ServerThread.py:57
+#: pyload/threads/ServerThread.py:59
msgid ""
"You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
msgstr ""
-#: module/web/ServerThread.py:58
-msgid "Copy the boern.so to module/lib folder or use setup.py install"
+#: pyload/threads/ServerThread.py:60
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
msgstr ""
-#: module/web/ServerThread.py:59
+#: pyload/threads/ServerThread.py:61
msgid ""
"Of course you need to be familiar with linux and know how to compile software"
msgstr ""
-#: module/web/ServerThread.py:63
+#: pyload/threads/ServerThread.py:64
msgid "Server set to threaded, due to known performance problems on windows."
msgstr ""
-#: module/web/ServerThread.py:80 module/web/ServerThread.py:103
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
msgid "This server offers no SSL, please consider using threaded instead"
msgstr ""
-#: module/web/ServerThread.py:82
+#: pyload/threads/ServerThread.py:82
#, python-format
msgid "Starting builtin webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:87
+#: pyload/threads/ServerThread.py:87
#, python-format
msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:91
+#: pyload/threads/ServerThread.py:91
#, python-format
msgid "Starting threaded webserver: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:97
+#: pyload/threads/ServerThread.py:97
#, python-format
msgid "Starting fastcgi server: %(host)s:%(port)d"
msgstr ""
-#: module/web/ServerThread.py:105
+#: pyload/threads/ServerThread.py:105
#, python-format
msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
msgstr ""
-#: module/web/pyload_app.py:125
+#: pyload/webui/app/pyload.py:127
msgid "You dont have permission to access this page."
msgstr ""
-#: module/web/pyload_app.py:193
+#: pyload/webui/app/pyload.py:195
msgid "Download directory not found."
msgstr ""
-#: module/web/pyload_app.py:260 module/web/pyload_app.py:267
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
msgid "unlimited"
msgstr ""
-#: module/web/pyload_app.py:262 module/web/pyload_app.py:269
+#: pyload/webui/app/pyload.py:264 pyload/webui/app/pyload.py:271
msgid "not available"
msgstr ""
-#: module/web/pyload_app.py:509
-msgid "Run pyLoadCore.py -s to access the setup."
+#: pyload/webui/app/pyload.py:509
+msgid "Run pyload.py -s to access the setup."
msgstr ""
-#: module/web/json_app.py:60
+#: pyload/network/HTTPDownload.py:245
#, python-format
-msgid "waiting %s"
+msgid "Download chunks failed, fallback to single connection | %s"
msgstr ""
-#: module/Api.py:329
+#: pyload/threads/PluginThread.py:183
#, python-format
-msgid "Added package %(name)s containing %(count)d links"
+msgid "Download starts: %s"
msgstr ""
-#: module/Api.py:592
+#: pyload/threads/PluginThread.py:189
#, python-format
-msgid "Added %(count)d links to package #%(package)d "
-msgstr ""
-
-#: module/plugins/crypter/SerienjunkiesOrg.py:125
-msgid "Downloadlimit reached"
-msgstr ""
-
-#: module/plugins/hooks/ClickAndLoad.py:74
-msgid "Click'N'Load: Port 9666 already in use"
+msgid "Download finished: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:91
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
#, python-format
-msgid "No %s installed"
+msgid "Plugin %s is missing a function."
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:93
-#: module/plugins/hooks/ExtractArchive.py:98
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
#, python-format
-msgid "Could not activate %s"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:103
-msgid "Activated"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:105
-msgid "No Extract plugins activated"
+msgid "Download aborted: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:117
+#: pyload/threads/PluginThread.py:222
#, python-format
-msgid "Package %s queued for later extracting"
+msgid "Download restarted: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:142
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
#, python-format
-msgid "Check package %s"
+msgid "Download is offline: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:179
+#: pyload/threads/PluginThread.py:234
#, python-format
-msgid "Extract to %s"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:198
-msgid "extracting"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:209
-msgid "Password protected"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:229
-msgid "Wrong password"
+msgid "Download is temporary offline: %s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:237
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
#, python-format
-msgid "Deleting %s files"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:242
-msgid "Extracting finished"
-msgstr ""
-
-#: module/plugins/hooks/ExtractArchive.py:249
-msgid "Archive Error"
+msgid "Download failed: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:251
-msgid "CRC Mismatch"
+#: pyload/threads/PluginThread.py:254
+msgid ""
+"Couldn't connect to host or connection reset, waiting 1 minute and retry."
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:255
-msgid "Unknown Error"
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
msgstr ""
-#: module/plugins/hooks/ExtractArchive.py:307
-msgid "Setting User and Group failed"
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
msgstr ""
-#: module/plugins/hooks/CaptchaTrader.py:71
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
#, python-format
-msgid "%s credits left"
+msgid "Decrypting failed: %(name)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/CaptchaTrader.py:132
-msgid "Your CaptchaTrader Account has not enough credits"
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:74
-#: module/plugins/hooks/XMPPInterface.py:82
-#: module/database/FileDatabase.py:507
+#: pyload/threads/PluginThread.py:635
#, python-format
-msgid "Package finished: %s"
+msgid "Info Fetching for %(name)s failed | %(err)s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:81
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:103
#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s "
+msgid "Error executing hooks: %s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:93
+#: pyload/HookManager.py:140
#, python-format
-msgid "New Captcha Request: %s"
+msgid "Failed activating %(name)s"
msgstr ""
-#: module/plugins/hooks/IRCInterface.py:94
+#: pyload/HookManager.py:144
#, python-format
-msgid "Answer with 'c %s text on the captcha'"
+msgid "Activated plugins: %s"
msgstr ""
-#: module/plugins/hooks/XMPPInterface.py:90
+#: pyload/HookManager.py:145
#, python-format
-msgid "Download finished: %(name)s @ %(plugin)s"
+msgid "Deactivate plugins: %s"
msgstr ""
-#: module/plugins/hooks/RehostTo.py:32
-msgid "Please add your rehost.to account first and restart pyLoad"
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
msgstr ""
-#: module/plugins/hooks/HotFolder.py:82
-#, python-format
-msgid "Added %s from HotFolder"
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:54
-#, python-format
-msgid "Installed scripts for %s: "
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:70
-msgid "Script not executable:"
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
msgstr ""
-#: module/plugins/hooks/ExternalScripts.py:80
+#: pyload/ThreadManager.py:208
#, python-format
-msgid "Error in %(script)s: %(error)s"
+msgid "Reconnected, new IP: %s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:68
-msgid "No Updates for pyLoad"
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:73
-msgid "*** Plugins have been updated, please restart pyLoad ***"
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:90
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:75
-msgid "Plugins updated and reloaded"
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:78
-msgid "No plugin updates available"
+#: pyload/plugins/Account.py:243
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:93
+#: pyload/plugins/Account.py:269
#, python-format
-msgid "*** New pyLoad Version %s available ***"
+msgid "Account %s has not enough traffic, checking again in 30min"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:94
-msgid "*** Get it here: http://pyload.org/download ***"
+#: pyload/plugins/Account.py:276
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:97
-#: module/plugins/hooks/UpdateManager.py:110
-msgid "Not able to connect server for updates"
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:126
+msgid "Downloadlimit reached"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:141
+#: pyload/plugins/PluginManager.py:153
#, python-format
-msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgid "%s has a invalid pattern."
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:150
-#: module/plugins/hooks/UpdateManager.py:155
+#: pyload/plugins/PluginManager.py:272
#, python-format
-msgid "Error when updating %s"
+msgid "Error importing %(name)s: %(msg)s"
msgstr ""
-#: module/plugins/hooks/UpdateManager.py:155
-msgid "Version mismatch"
+#: pyload/plugins/internal/MultiHoster.py:132
+msgid "No Hoster loaded"
msgstr ""
-#: module/plugins/hoster/BasePlugin.py:53
-msgid "Authorization required (username:password)"
+#: pyload/plugins/accounts/BitshareCom.py:38
+msgid "Activate direct Download in your Bitshare Account"
msgstr ""
-#: module/plugins/hoster/OronCom.py:135
-msgid "Not enough traffic left"
+#: pyload/plugins/container/LinkList.py:64
+msgid "LinkList could not be cleared."
msgstr ""
-#: module/plugins/hoster/OronCom.py:137
-#: module/plugins/hoster/UploadedTo.py:158
-msgid "Traffic exceeded"
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:135
-msgid "You should enable direct Download in your Megaupload Account settings"
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:158
+#: pyload/plugins/hoster/SimplydebridCom.py:23
+#: pyload/plugins/hoster/RealdebridCom.py:40
+#: pyload/plugins/hoster/FreeWayMe.py:39 pyload/plugins/hoster/ZeveraCom.py:21
+#: pyload/plugins/hoster/UnrestrictLi.py:52
+#: pyload/plugins/hoster/Vipleech4uCom.py:30
+#: pyload/plugins/hoster/Premium4Me.py:27 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/SimplyPremiumCom.py:52
+#: pyload/plugins/hoster/MegaDebridEu.py:46
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/LinksnappyCom.py:29
+#: pyload/plugins/hoster/DebridItaliaCom.py:39
+#: pyload/plugins/hoster/RPNetBiz.py:28
+#: pyload/plugins/hoster/MultiDebridCom.py:40
+#: pyload/plugins/hoster/ReloadCc.py:26 pyload/plugins/hoster/OverLoadMe.py:38
+#: pyload/plugins/hoster/RehostTo.py:25
+#: pyload/plugins/hoster/PremiumizeMe.py:24
+#: pyload/plugins/hooks/RPNetBiz.py:45
#, python-format
-msgid "Megaupload: waiting %d minutes"
-msgstr ""
-
-#: module/plugins/hoster/MegauploadCom.py:172
-msgid "You need premium to download files larger than 1 GB"
+msgid "Please enter your %s account or deactivate this plugin"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:177
-msgid "The file is password protected, enter a password and restart."
+#: pyload/plugins/hoster/FilesMailRu.py:98
+#, python-format
+msgid ""
+"There was HTML Code in the Downloaded File (%s)...redirect error? The "
+"Download will be restarted."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:194
-msgid "Megaupload is currently blocking your IP. Try again later, manually."
+#: pyload/plugins/hoster/NetloadIn.py:145
+#: pyload/plugins/hoster/NetloadIn.py:169
+msgid "File temporarily not available"
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:269
-msgid ""
-"Looks like the file is still not available. Retry downloading later, "
-"manually."
+#: pyload/plugins/hoster/NetloadIn.py:182
+#, python-format
+msgid "Netload: waiting between downloads %d s."
msgstr ""
-#: module/plugins/hoster/MegauploadCom.py:272
-msgid "Wrong password for download link."
+#: pyload/plugins/hoster/NetloadIn.py:213
+#, python-format
+msgid "Netload: waiting for captcha %d s."
msgstr ""
-#: module/plugins/hoster/UploadedTo.py:131
-msgid "API key invalid"
+#: pyload/plugins/hoster/NetloadIn.py:251
+msgid "Downloaded File was empty"
msgstr ""
-#: module/plugins/hoster/UploadedTo.py:155
+#: pyload/plugins/hoster/MegaDebridEu.py:49
#, python-format
-msgid "%s: Not enough traffic left"
-msgstr ""
-
-#: module/plugins/hoster/ShareonlineBiz.py:106
-msgid "Parallel download issue"
+msgid "Impossible to connect to %s"
msgstr ""
-#: module/plugins/hoster/ShareonlineBiz.py:121
-msgid "Invalid download ticket"
+#: pyload/plugins/hoster/MegaDebridEu.py:89
+#, python-format
+msgid "Impossible to debrid %s"
msgstr ""
-#: module/plugins/hoster/RehostTo.py:26
-msgid "Please enter your rehost.to account or deactivate this plugin"
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
msgstr ""
-#: module/plugins/hoster/FileserveCom.py:87
-msgid "Not logged in."
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
msgstr ""
-#: module/plugins/hoster/FileserveCom.py:112
-msgid "Parallel download error, now waiting 60s."
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:99
+#: pyload/plugins/hoster/RapidshareCom.py:99
msgid "Rapidshare: Traffic Share (direct download)"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:126
-#: module/plugins/hoster/RapidshareCom.py:192
+#: pyload/plugins/hoster/RapidshareCom.py:126
+#: pyload/plugins/hoster/RapidshareCom.py:193
msgid "Already downloading from this ip address, waiting 60 seconds"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:130
+#: pyload/plugins/hoster/RapidshareCom.py:130
msgid "Invalid Auth Code, download will be restarted"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:196
+#: pyload/plugins/hoster/RapidshareCom.py:198
msgid "RapidShareCom: No free slots"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:199
+#: pyload/plugins/hoster/RapidshareCom.py:201
msgid "You need a premium account for this file"
msgstr ""
-#: module/plugins/hoster/RapidshareCom.py:201
+#: pyload/plugins/hoster/RapidshareCom.py:203
msgid "Filename reported invalid"
msgstr ""
-#: module/plugins/hoster/FilesMailRu.py:99
-msgid "There was HTML Code in the Downloaded File("
+#: pyload/plugins/hoster/FileserveCom.py:100
+msgid "Parallel download error, now waiting 60s."
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:141
-#: module/plugins/hoster/NetloadIn.py:161
-msgid "File temporarily not available"
+#: pyload/plugins/hoster/FileserveCom.py:216
+msgid "Not logged in."
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:174
-#, python-format
-msgid "Netload: waiting between downloads %d s."
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:203
-#, python-format
-msgid "Netload: waiting for captcha %d s."
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
msgstr ""
-#: module/plugins/hoster/NetloadIn.py:242
-msgid "Downloaded File was empty"
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
msgstr ""
-#: module/plugins/hoster/RealdebridCom.py:37
-msgid "Please enter your Real-debrid account or deactivate this plugin"
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
msgstr ""
-#: module/plugins/container/LinkList.py:54
-msgid "LinkList could not be cleared."
+#: pyload/plugins/hooks/UpdateManager.py:97
+msgid "Not able to connect server to get updates"
msgstr ""
-#: module/plugins/Plugin.py:381
-msgid ""
-"Pil and tesseract not installed and no Client connected for captcha "
-"decrypting"
+#: pyload/plugins/hooks/UpdateManager.py:111
+msgid "No pyLoad version available"
msgstr ""
-#: module/plugins/Plugin.py:385
-msgid "No captcha result obtained in appropiate time by any of the plugins."
+#: pyload/plugins/hooks/UpdateManager.py:118
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:119
+msgid "*** Get it here: https://github.com/pyload/pyload/releases ***"
msgstr ""
-#: module/plugins/Plugin.py:490 module/plugins/Plugin.py:520
+#: pyload/plugins/hooks/UpdateManager.py:170
#, python-format
-msgid "Setting User and Group failed: %s"
+msgid "New version of [%(type)s] %(name)s (v%(oldver)s -> v%(newver)s)"
msgstr ""
-#: module/plugins/Container.py:68
-msgid "File not exists."
+#: pyload/plugins/hooks/UpdateManager.py:180
+#: pyload/plugins/hooks/UpdateManager.py:185
+#, python-format
+msgid "Error when updating plugin %s"
msgstr ""
-#: module/plugins/accounts/MegauploadCom.py:41
-msgid "Activate direct Download in your MegaUpload Account"
+#: pyload/plugins/hooks/UpdateManager.py:185
+msgid "Version mismatch"
msgstr ""
-#: module/plugins/accounts/FilesonicCom.py:49
-msgid "Invalid login retrieving user details"
+#: pyload/plugins/hooks/UpdateManager.py:196
+#, python-format
+msgid "Removed blacklisted plugin: [%(type)s] %(name)s"
msgstr ""
-#: module/plugins/accounts/BitshareCom.py:36
-msgid "Activate direct Download in your Bitshare Account"
+#: pyload/plugins/hooks/UpdateManager.py:204
+msgid "Plugins updated and reloaded"
msgstr ""
-#: module/plugins/PluginManager.py:153
-#, python-format
-msgid "%s has a invalid pattern."
+#: pyload/plugins/hooks/UpdateManager.py:206
+msgid "*** Plugins have been updated, pyLoad will be restarted now ***"
msgstr ""
-#: module/plugins/PluginManager.py:272
-#, python-format
-msgid "Error importing %(name)s: %(msg)s"
+#: pyload/plugins/hooks/UpdateManager.py:211
+msgid "No plugin updates available"
msgstr ""
-#: module/plugins/AccountManager.py:88
-msgid "Account settings deleted, due to new config format."
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
msgstr ""
-#: module/plugins/internal/MultiHoster.py:60
-msgid "No Hoster loaded"
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
msgstr ""
-#: module/plugins/Account.py:85 module/plugins/Account.py:91
+#: pyload/plugins/hooks/IRCInterface.py:96
#, python-format
-msgid "Could not login with account %(user)s | %(msg)s"
+msgid "Answer with 'c %s text on the captcha'"
msgstr ""
-#: module/plugins/Account.py:86
-msgid "Wrong Password"
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
msgstr ""
-#: module/plugins/Account.py:240
+#: pyload/plugins/hooks/HotFolder.py:81
#, python-format
-msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgid "Added %s from HotFolder"
msgstr ""
-#: module/plugins/Account.py:266
+#: pyload/plugins/hooks/ExtractArchive.py:94
#, python-format
-msgid "Account %s has not enough traffic, checking again in 30min"
+msgid "No %s installed"
msgstr ""
-#: module/plugins/Account.py:273
+#: pyload/plugins/hooks/ExtractArchive.py:96
+#: pyload/plugins/hooks/ExtractArchive.py:101
#, python-format
-msgid "Account %s is expired, checking again in 1h"
+msgid "Could not activate %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "finished"
+#: pyload/plugins/hooks/ExtractArchive.py:106
+msgid "Activated"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "offline"
+#: pyload/plugins/hooks/ExtractArchive.py:108
+msgid "No Extract plugins activated"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "online"
+#: pyload/plugins/hooks/ExtractArchive.py:120
+#, python-format
+msgid "Package %s queued for later extracting"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "queued"
+#: pyload/plugins/hooks/ExtractArchive.py:143
+#, python-format
+msgid "Check package %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "skipped"
+#: pyload/plugins/hooks/ExtractArchive.py:184
+#, python-format
+msgid "Extract to %s"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "waiting"
+#: pyload/plugins/hooks/ExtractArchive.py:199
+msgid "No files found to extract"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "temp. offline"
+#: pyload/plugins/hooks/ExtractArchive.py:206
+msgid "extracting"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "starting"
+#: pyload/plugins/hooks/ExtractArchive.py:217
+msgid "Password protected"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "failed"
+#: pyload/plugins/hooks/ExtractArchive.py:238
+msgid "Wrong password"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "aborted"
+#: pyload/plugins/hooks/ExtractArchive.py:246
+#, python-format
+msgid "Deleting %s files"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "decrypting"
+#: pyload/plugins/hooks/ExtractArchive.py:253
+msgid "Extracting finished"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "custom"
+#: pyload/plugins/hooks/ExtractArchive.py:259
+msgid "Archive Error"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "downloading"
+#: pyload/plugins/hooks/ExtractArchive.py:261
+msgid "CRC Mismatch"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "processing"
+#: pyload/plugins/hooks/ExtractArchive.py:265
+msgid "Unknown Error"
msgstr ""
-#: module/database/FileDatabase.py:47
-msgid "unknown"
+#: pyload/plugins/hooks/ExtractArchive.py:317
+msgid "Setting User and Group failed"
msgstr ""
-#: module/database/DatabaseBackend.py:174
-msgid "Filedatabase was deleted due to incompatible version."
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
msgstr ""
-#: module/database/DatabaseBackend.py:189
-msgid "Filedatabase could NOT be converted."
+#: pyload/plugins/hooks/CaptchaTrader.py:70
+#: pyload/plugins/hooks/Captcha9kw.py:60
+#: pyload/plugins/hooks/ExpertDecoders.py:51
+#, python-format
+msgid "%s credits left"
msgstr ""
-#: module/database/DatabaseBackend.py:198
-msgid "Database was converted from v2 to v3."
+#: pyload/plugins/hooks/CaptchaTrader.py:118
+msgid "Could not send response."
msgstr ""
-#: module/database/DatabaseBackend.py:206
-msgid "Database was converted from v3 to v4."
+#: pyload/plugins/hooks/CaptchaTrader.py:136
+msgid "Your CaptchaTrader Account has not enough credits"
msgstr ""
-#: module/database/DatabaseBackend.py:252
-msgid "Converting old Django DB"
+#: pyload/plugins/hooks/LinkdecrypterCom.py:45
+msgid "Crypter list not found"
msgstr ""
-#: module/network/HTTPDownload.py:245
-#, python-format
-msgid "Download chunks failed, fallback to single connection | %s"
+#: pyload/plugins/hooks/LinkdecrypterCom.py:59
+msgid "Crypter list is empty"
msgstr ""
-#: module/PluginThread.py:183
+#: pyload/plugins/hooks/XMPPInterface.py:91
#, python-format
-msgid "Download starts: %s"
+msgid "Download finished: %(name)s @ %(plugin)s"
msgstr ""
-#: module/PluginThread.py:189
+#: pyload/plugins/hooks/Captcha9kw.py:94
#, python-format
-msgid "Download finished: %s"
+msgid "New CaptchaID from upload: %s : %s"
msgstr ""
-#: module/PluginThread.py:194 module/PluginThread.py:366
-#, python-format
-msgid "Plugin %s is missing a function."
+#: pyload/plugins/hooks/Captcha9kw.py:130
+msgid "Your Captcha 9kw.eu Account has not enough credits"
msgstr ""
-#: module/PluginThread.py:202 module/PluginThread.py:265
-#: module/PluginThread.py:383
+#: pyload/plugins/hooks/ExternalScripts.py:54
#, python-format
-msgid "Download aborted: %s"
+msgid "Installed scripts for %s: "
msgstr ""
-#: module/PluginThread.py:222
-#, python-format
-msgid "Download restarted: %(name)s | %(msg)s"
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
msgstr ""
-#: module/PluginThread.py:231 module/PluginThread.py:374
+#: pyload/plugins/hooks/ExternalScripts.py:80
#, python-format
-msgid "Download is offline: %s"
+msgid "Error in %(script)s: %(error)s"
msgstr ""
-#: module/PluginThread.py:234
-#, python-format
-msgid "Download is temporary offline: %s"
+#: pyload/plugins/hooks/ExpertDecoders.py:96
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
msgstr ""
-#: module/PluginThread.py:237 module/PluginThread.py:304
+#: pyload/plugins/hooks/PremiumizeMe.py:50
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:70
#, python-format
-msgid "Download failed: %(name)s | %(msg)s"
+msgid "%d credits left"
msgstr ""
-#: module/PluginThread.py:254
+#: pyload/plugins/Plugin.py:389
msgid ""
-"Couldn't connect to host or connection reset, waiting 1 minute and retry."
+"Pil and tesseract not installed and no Client connected for captcha "
+"decrypting"
msgstr ""
-#: module/PluginThread.py:290
-#, python-format
-msgid "Download skipped: %(name)s due to %(plugin)s"
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
msgstr ""
-#: module/PluginThread.py:362
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
#, python-format
-msgid "Decrypting starts: %s"
+msgid "Setting User and Group failed: %s"
msgstr ""
-#: module/PluginThread.py:377 module/PluginThread.py:395
-#, python-format
-msgid "Decrypting failed: %(name)s | %(msg)s"
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
msgstr ""
-#: module/PluginThread.py:389
+#: pyload/Api.py:330
#, python-format
-msgid "Retrying %s"
+msgid "Added package %(name)s containing %(count)d links"
msgstr ""
-#: module/PluginThread.py:636
+#: pyload/Api.py:593
#, python-format
-msgid "Info Fetching for %(name)s failed | %(err)s"
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid ""
+"No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or "
+"rhino"
msgstr ""
diff --git a/locale/crowdin.yaml b/locale/crowdin.yaml
new file mode 100644
index 000000000..96898d500
--- /dev/null
+++ b/locale/crowdin.yaml
@@ -0,0 +1,15 @@
+project_identifier: pyload
+preserve_hierarchy: true
+api_key: {key}
+base_path: {tmp}
+
+files:
+ -
+ source: 'pyLoad/*.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/%file_name%.po'
+ -
+ source: 'pyLoad/cli.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/pyLoadCli.po'
+ -
+ source: 'pyLoad/core.pot'
+ translation: 'pyLoad/%two_letters_code%/LC_MESSAGES/pyLoad.po'
diff --git a/locale/cs/LC_MESSAGES/django.mo b/locale/cs/LC_MESSAGES/django.mo
deleted file mode 100644
index 3d771c127..000000000
--- a/locale/cs/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/django.po b/locale/cs/LC_MESSAGES/django.po
new file mode 100644
index 000000000..1136362de
--- /dev/null
+++ b/locale/cs/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Czech\n"
+"Language: cs_CZ\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Nová captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Prosím přečtěte text captchy."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad restartován"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "vypnuto"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Úspěch"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "zapnuto"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Opravdu chcete ukončit pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Restartovat odkaz"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Smazat odkaz"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Prosím zadejte jméno balíčku."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Prosím, klikněte na správnou pozici captchy."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Nastala chyba."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Sloşka je prázdná"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Selhalo"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Ŝádné Captchy k přečtení."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Hesla se neshodují."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Nastavení uloşeno."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nová sloşka"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Opravdu chcete restartovat pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "cekam %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktivní Stahování"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Domů"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Stahování"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Logy"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Konfigurace"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Jméno"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Stav"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informace"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Velikost"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Postup"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Přihlásit se"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Uşivatelské jméno"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Heslo"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Vaše uşivatelské jméno a heslo se neshodují s údaji v databázi. Prosím zkuste to znovu."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Pro reset vaÅ¡ich přihlaÅ¡ovacích údajů nebo přidání uÅŸivatele spusÅ¥te:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Smazat dokončené"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Restartovat nezdařené"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "SloÅŸka:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Heslo:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Upravit Balíček"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Níşe upravte vlastnosti balíčku."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Jméno balíčku."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "SloÅŸka"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Jméno podsloşky pro toto stahování."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Seznam hesel pouşitÜch pro unrar."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Odeslat"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Byli jste úspěšně odhlášeni."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Cesta"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolutní"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativní"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "jméno"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "velikost"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "typ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "naposledy upraveno"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "nadřazenÜ adresář"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "şádnÜ obsah"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Obecné"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Pluginy"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Účty"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Vyberte sekci z menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Doplněk"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "PlatnÜ do"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "ZbÜvá traffic"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Čas"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max Současně"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Smazat?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "platnÜ"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "neplatnÜ"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "ano"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "ne"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Přidat"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Přidat Účet"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Pro přístup k prémiovÜm funkcím vloÅŸte vaÅ¡e přístupová data."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Vaše uşivatelské jméno."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Heslo pro tento účet."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Typ"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Vyberte filehosting vašeho účtu."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "předchozí"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "další"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Konec"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Novinky"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Podpora"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Systém"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad verze:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "SloÅŸka instalace:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Sloşka nastavení:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Sloşka stahování:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Volné Místo:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Jazyk:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Port Vzdaleneho Rozhrani:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Nastavení"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "SouborovÜ Manaşer"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Přidat Balíček"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "VloÅŸte vaÅ¡e odkazy nebo uploadněte kontejner."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Jméno nového balíčku."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Odkazy"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Sem vloÅŸte vaÅ¡e odkazy nebo jakÜkoli text a stiskněte tlačítko filtrovat."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrovat URL"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Heslo pro archiv RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Soubor"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Upload kontejneru."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Cíl"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Čtení captchy"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "VloÅŸte text captchy."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Zavřít"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Webové rozhraní"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad Aktualizace k dispozici!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Pluginy aktualizovány, restartuj pyLoad!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha čeká"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Odhlásit"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrace"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Informace"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Přihlašte se prosím!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Zastavit"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Zrušit"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Stahování:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Rychlost:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktivní:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Obnovit stránku"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "načítám"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Nahoru"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Ukončit pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Restartovat pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Pro přidání uÅŸivatele nebo změnu hesla pouÅŸijte:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Důleşité: Admin má vşdy plná práva!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Změnit heslo"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Oprávnění"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "změnit"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Zadejte aktuální a poşadované heslo."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "UÅŸivatel"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Aktuální heslo"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nové heslo"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Nové heslo."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nové heslo (znovu)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Prosím zadej nové heslo znovu."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Pro zobrazeni teto stranky nemate opravneni."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Adresar pro stahovani nenalezen."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "neomezenÜ"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "neni k dispozici"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Pro setup spustte pyload.py -s."
+
diff --git a/locale/cs/LC_MESSAGES/pyLoad.mo b/locale/cs/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index a0c01d4e9..000000000
--- a/locale/cs/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/pyLoad.po b/locale/cs/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..842f2fc6b
--- /dev/null
+++ b/locale/cs/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Czech\n"
+"Language: cs_CZ\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Prijat Quit signal"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad je jiz spusten pod pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Zmena skupiny selhala: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Zmena uzivatele selhala: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "slozka pro logy"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Spoustim"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Nastaven domovsky adresar: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto pro dekodovani kontejnerovych souboru"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "slozka pro docasne soubory"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "slozka pro stahovani"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL pro zabezpecene pripojeni"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Presunuji stara nastaveni uzivatele do DB."
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Prosim zkontrolujte sve prihlasovaci udaje pres ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Všechny odkazy odstraněny"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Cas stahovani: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Volne misto: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Aktivuji Ucty..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Aktivuji Pluginy..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad je spusten"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "restartuji pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad se ukonci"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Instalujte %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "nelze najit %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "nelze vytvorit %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "vypinani..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "chyba pri vypinani"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad zastaven z terminalu"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Databaze souboru byla smazana pro nekompatibilni verzi."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Databáze nemůşe bÜt převedena"
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Databaze byla konvertovana z v2 na v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Databaze byla konvertovana z v3 na v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Konvertuji starou databazi Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "hotovo"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "ve fronte"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "preskoceno"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "ceka"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "doc. nedostupne"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "spoustim"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "selhalo"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "zruseno"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "dekoduji"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "vlastni"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "stahuji"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "zpracovavam"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "neznamy"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Balicek dokoncen: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Pouzivam SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Vzdalena chyba systemu: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Zahajuji %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Spousteni backendu %(name)s selhalo | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "cekam %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certifikaty SSL nenalezeny."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Omlouvame se, ale spousteni %s v ramci pyLoad jiz neni podporovano"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Je mozne pouzit server podporujici vlakna, ktery nabizi dobry vykon a ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "a samozrejme je take mozne pouzit vas jiz existujici %s se serverem fastcgi pyLoadu"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "vzorove konfiguracni soubory jsou umisteny v adresari pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Nelze pouzit %(server)s, python-flup neni nainstalovan!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Chyba pri importu lightweight serveru: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Musíte si stáhnout a zkompilovat bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Zkopirujte soubor boern.so do adresare pyload/lib nebo pouzijte setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Samozrejme budete potrebovat znalost linuxu a kompilace software"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Server nyni pracuje s vlakny i pres zname obtize s vykonem na Windows"
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Tento server nenabizi SSL, zvazte pouziti rezimu s vlakny"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Spoustim vestaveny Webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Spoustim SSL webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Spoustim webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Spoustim fastcgi server: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Startuji lightweight server (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Pro zobrazeni teto stranky nemate opravneni."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Adresar pro stahovani nenalezen."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "neomezenÜ"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "neni k dispozici"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Pro setup spustte pyload.py -s."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Stahování částí selhalo, přecházím na jediné připojení | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Zahajuji stahovani: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Stahovani dokonceno: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "V pluginu %s chybi funkce."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Stahovani zruseno: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Stahovani obnoveno: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Zdroj stahovani je offline: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Zdroj stahovani je docasne nedostupny: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Stahovani selhalo: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "K hostiteli se nelze pripojit, nebo bylo pripojeni resetovano, cekam 1 minutu do dalsiho pokusu."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Stahovani preskoceno: %(name)s v dusledku %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Zacina dekodovani: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Dekodovani selhalo: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Zkousim znovu %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Ziskavani informaci pro %(name)s selhalo | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Chyba pri provadeni zaveseni: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Aktivace %(name)s selhala"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Aktivovane pluginy: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Deaktivovat pluginy: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Reconnect selhal: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Skript pro reconnect nenalezen!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Spoustim reconnect"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Provadeni skriptu pro Reconnect selhalo!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Reconnectnuto, nova IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Není dostatek místa na zařízení"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Nezdařílo se přihlášení k účtu %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Špatné heslo"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Vas cas %s ma nespravny format, pouzijte 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Účet %s nemá nedostatek trafficu, další kontrola za 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Účet %s vypršel, další kontrola za 1h"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Dosaşen limit pro stahování"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s ma neplatny vzor."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Chyba pri importu %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Nenačten şádnÜ plugin Hoster"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Aktivovat přímé stahování na vašem Bitshare accountu"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList nemohl byt vyprazdnen."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Pro novy format konfiguracniho souboru bylo nastaveni uctu smazano."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Je vyşadována autorizace (username:password)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Prosím vloşte svůj %s účet nebo tento plugin deaktivujte"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Ve staÅŸeném souboru (%s) byl nalezen HTML kód... Chyba přesměrování? Stahování bude restartováno."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Soubor není dočasně k dispozici"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: čekání mezi stahováním %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: čekám na captchu %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "StaşenÜ soubor byl prázdnÜ"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "NeplatnÜ API klíč"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: NezbÜvá dostatečnÜ traffic"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Překročen Traffic"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "RapidShare: Traffic Share (přímé staÅŸení)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Z této ip adresy jiş stahování probíhá, čekám 60 sekund"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "ŠpatnÜ autentizační kód, stahování bude restartováno"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Ŝádné volné sloty"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Pro tento soubor potřebujete prémiovÜ účet"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Ohlášeno neplatné jméno souboru"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Chyba souběşného stahování, čekám 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Nejste přihlášeni."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Dešifrování selhalo"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "URL neobsahuje klíč souboru"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Kód chyby:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Soubor neexistuje."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Pluginy byly aktualizovány, prosím restartujte pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Pluginy aktualizovány a znovu načteny"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Ŝádné aktualizace rozšíření"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Ŝádné aktualizace pro pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Je dostupná nové verze pyLoad (%s) ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Získejte ji zde: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Nelze se připojit k update serveru"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nová verze %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Chyba při aktualizaci %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Nesprávná verze"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Stahování dokončeno: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "NovÜ poşadavek na Captchu: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Odpovězte \"c %s text z captchy\""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Nejdříve prosím přidejte váš premium.to účet a restartujte pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Přidán %s ze sledované sloÅŸky"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s není nainstalován"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Nelze aktivovat %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Aktivní"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Nejsou aktivovány şádné rozbalovací pluginy"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Balíček %s zařazen do fronty pro pozdější rozbalení"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Kontrola balíčku %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Rozbalit do %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Nenalezeny şádné soubory k rozbalení"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "rozbaluji"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Chráněno heslem"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Chybné heslo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Maşu %s souborů"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Rozbalení dokončeno"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Chyba archivu"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC nesouhlasí"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Neznámá chyba"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Nastavení Uşivatele a Skupiny selhalo"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Port 9666 se jiş pouşívá"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s kreditů zbÜvá"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Nelze odeslat odpověď."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Na vašem CaptchaTrader účtu není dostatek kreditu"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Seznam pluginů Crypter nenalezen"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Seznam pluginů Crypter je prázdnÜ"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Stahování dokončeno: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nové CaptchaID z uploadu: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Na vašem captcha účtu 9kw.eu není dostatek kreditu"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Nainstalované skripty pro %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Skript není spustitelnÜ:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Chyba v %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Na vašm ExpertDecoders účtu není dostatek kreditu"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Nejdříve prosím přidejte váš rehost.to účet a restartujte pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Nejdříve prosím přidejte váš premiumize.me účet a restartujte pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "ZbÜvá %d kreditů"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil a tesseract nejsou nainstalovány a není připojen şádnÜ klient pro rozpoznávání Captchy"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Zadny plugin nedodal vysledek Captchy v prijatelnem case."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Nastavení uÅŸivatele a skupiny se nezdařilo: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Neni pripojen zadny klient pro dekodovani Captchy"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Pridan balicek %(name)s obsahujici %(count)d odkazu"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Do balicku #%(package)d pridano %(count)d odkazu "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Nenalezen zadny js engine, prosim naistalujte Spidermonkey, ossp-js, pyv8 nebo rhino"
+
diff --git a/locale/cs/LC_MESSAGES/pyLoadCli.mo b/locale/cs/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 6f8b94b67..000000000
--- a/locale/cs/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/pyLoadCli.po b/locale/cs/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..bd97917d6
--- /dev/null
+++ b/locale/cs/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Czech\n"
+"Language: cs_CZ\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Příkazová řádka"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Stahování:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Rychlost: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Velikost: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Dokončeno za: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "čeká: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Stav:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "pozastaveno"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "probíhá"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "celková rychlost"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Souborů ve frontě"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Celkem"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Přidat Odkazy"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Správa fronty"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Správa Sběrače"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Pozastavit/Spustit server"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Ukončit server"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Konec"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Prosím pouşijte tuto syntaxi: add <Jmeno balicku> <odkaz> <odkaz2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Kontroluji %d odkazů:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Soubor neexistuje."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad byl ukončen"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Vypíše stav serveru"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Vypíše stahování ve frontě"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Vypíše stahování ve sběrači"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Přidá balíček do fronty"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Přidá balíček do sběrače"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Smazat soubory z fronty/sběrače"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Smazat balíčky z fronty/sběrače"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Přesun balíčku z fronty do sběrače nebo naopak"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Restart souborů"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Restart balíčků"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Zkontrolovat stav online - funguje s místním zásobníkem"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Zkontroluje online stav souboru ze zásobníku"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pozastavit server"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "pokracovat ve stahovani"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Prepnout pozastaveni/beh"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "ukoncit server"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Seznam prikazu:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Nemohu zapsat nastavení uşivatele"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Pro pripojeni k tomuto jadru pyLoad je vyzadovano py-openssl."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adresa: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Uzivatelske jmeno: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Heslo: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Prihlasovaci udaje jsou chybne."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Nelze vytvorit pripojeni k %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "K pripojeni na toto pyLoad jadro potrebujete py-openssl."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Byly zadany prikazy, interaktivni mod je ignorovan."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Přidat Balíček:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Zadej název nového balíčku"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Balíček: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analyzuje odkazy které chcete přidat."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Pokud jste hotovi, napište %s"
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Přidáno odkazů: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " zpět do hlavního menu"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Správa balíčků:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr " Spravovat Odkazy:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Co chcete přesunout?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Co chcete smazat?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Co chcete restartovat?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Vyberte si, co chcete dělat, nebo zadejte číslo balíčku."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "smazat"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "přesunout"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "restartovat"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - předchozí"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - další"
+
diff --git a/locale/cs/LC_MESSAGES/pyLoadGui.mo b/locale/cs/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index c913703f1..000000000
--- a/locale/cs/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/setup.mo b/locale/cs/LC_MESSAGES/setup.mo
deleted file mode 100644
index 91ab321ec..000000000
--- a/locale/cs/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/cs/LC_MESSAGES/setup.po b/locale/cs/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..f5b2ae040
--- /dev/null
+++ b/locale/cs/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Czech\n"
+"Language: cs_CZ\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "a"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Vitejte v konfiguracnim pruvodci programu pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Zkontroluje vas system a provede zakladni nastaveni pro spusteni programu pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Hodnota v zavorkach [] je vzdy vychozi,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "v pripade, ze ji nechcete zmenit nebo si nejste jisti co vybrat, jen stisknete enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Nezapomente: Tohoto pruvodce muzete kdykoli sputit znovu pouzitim parametru --setup nebo -s pri startu pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Pokud mate s pruvodcem jakekoli problemy, stisknete CTRL-C"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "pro zruseni a zamezeni jeho automatickeho spusteni pri dalsim startu pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Az budete pripraveni na kontrolu systemu, stisknete enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Pro beh pyLoad je potreba pycurl a python verze 2.5, 2.6 nebo 2.7."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Prosim odstrante problem a znovu spustte pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Setup bude nyni ukoncen."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Kontrola systemu ukoncena, stisknete enter pro zobrazeni zpravy o stavu."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Stav ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "dekodovani kontejneru"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "pripojeni ssl"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "automaticke rozpoznavani captchy"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Webové rozhraní"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "rozsirene Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Dostupne funkce:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Chybejici funkce: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto nedostupne"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Toto potrebujede k dekodovani souboru kontejneru."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL nedostupne"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Je potreba k vytvoreni zabezpeceneho pripojeni k jadru nebo webovemu rozhrani."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Pokud budete pyLoad pouzivat pouze lokalne, ssl neni nutne."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "rozpoznavani Caprtchy nedostupne"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Je potreba jen pro nektere filehostingy pro neplaceny pristup."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Gui nedostupne"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Graficke Uzivatelske Rozhrani"
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "nenalezeno zadne jadro JavaScriptu"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Toto budete potrebovat pro nektere Click'N'Load linky. Naistalujte Spidermonkey, ossp-js, pyv8 nebo rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Pokud chcete, muzete nyni zrusit instalaci a opravit nektere zavislosti."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Pokracovat v instalaci?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Chcete zmenit cestu k nastaveni? Stavajici je %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Pokud pouzivate pyLoad na serveru, nebo je domovsky oddil na vnitrni flash, muze byt dobre ji zmenit."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Zmenit cestu k nastaveni?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Chcete nastavit prihlasovaci udaje a zakladni volby?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Doporuceno pri prvnim spusteni."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Vytvorit zakladni nastaveni?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Prejete si nastavit ssl?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Nastavit ssl?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Prejete si nastavit webove rozhrani?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Nastavit webove rozhrani?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Nastaveni uspesne dokonceno."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Stiknete enter pro ukonceni a restartujte pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Kontrola Systemu ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Vase verze pythonu je prilis nova, prosim pouzijte verzi 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Vase verze pythonu je prilis stara, prosim pouzijte alespon Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Verze Pythonu: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Vase nainstalovana verze %s jinja2 je prilis stara."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Muzete bezpecne pokracovat, nicmene pokud bude webove rozhani nefunkcni,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "bude treba ji upgradovat nebo odistalovat, pyLoad jiz obsahuje knihovnu jinja2."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "jadro JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Zakladni nastaveni ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Nasledujici prihlasovaci udaje jsou platne pro CLI, GUI a webove rozhrani."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Uşivatelské jméno"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Externí klientské aplikace (např. GUI a CLI) vyÅŸadují vzdálenÜ přístup pro fungovaní přes síť."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Pokud vÅ¡ak chcete pouşívat pouze webové rozhraní, můşete jej zakázat a uÅ¡etřit tak paměť."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Povolit vzdálenÜ přístup"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Jazyk"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Slozka pro stahovani"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Max soubeznych stahovani"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Pouzivat Reconnect?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Umisteni skriptu pro Reconnect"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Nastaveni Weboveho rozhrani ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Aktivovat webove rozhrani?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adresa pro naslouchani, pokud pouzijete 127.0.0.1 nebo localhost, bude webove rozhrani pristupne pouze lokalne."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adresa"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad nabizi mnoho vezri administrace, nize kratke vysvetleni."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Vychozi server, nejlepsi volba, pokud nevite, ktery vybrat."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Tento server nabizi SSl a je dobrou alternativou k vestavenemu."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Muze byt pouzit apachem, lighttpd, vyzaduje vsak nastaveni, ktere nemusi byt snadne."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Velmi svizna alternativa napsana v C, vyzaduje znalost linuxu a libev."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Stahnete jej zde: https://github.com/jonashaag/bjoern a zkompilujte"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "a zkopirujte bjoern.so do adresare pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Pozor: V nekterych zvlastnich pripadech neni vestaveny server funkcni. Pokud mate problemy s webovym rozhranim,"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "vratte se sem a zmente vestaveny server za server s vlakny."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Server"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Nastaveni SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Pro vytvoreni ssl certifikatu spustte tyto prikazy v konfiguracni slozce pyLoad:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Pokud jste skoncili a vse probehlo v poradku, muzete aktivovat SSL."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Aktivovat SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Zvolte akci"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Vytvorit/Upravit uzivatele"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Seznam uzivatelu"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Odstranit uzivatele"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Konec"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Uzivatele"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Nastavuji novou cestu ke konfiguracnim souborum, stavajici konfigurace nebude prenesena!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Cesta ke konfiguracnim souborum"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Cesta ke konfiguracnim souborum byla zmenena, instalace se nyni ukonci, pro pokracovani ji prosim restartujte."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Stisknete enter pro ukonceni."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Nastaveni cesty ke konfiguracnim souborum selhalo: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: chybi"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Heslo: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Heslo je příliÅ¡ krátké. PouÅŸijte nejméně 4 znaky."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Heslo (znovu): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Hesla se neshodují."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "ano"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "pravda"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "p"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "ne"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "nepravda"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "n"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Neplatne zadani"
+
diff --git a/locale/da/LC_MESSAGES/django.po b/locale/da/LC_MESSAGES/django.po
new file mode 100644
index 000000000..b131d9408
--- /dev/null
+++ b/locale/da/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Danish\n"
+"Language: da_DK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Ny Captcha anmodning"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "LÊs venligst teksten i captcha"
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad genstartet"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Succes"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Er du virkelig sikker på du vil lukke pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Genstart Link"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Slet link"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Indtast venligst et pakkenavn"
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Tryk venligst på den hÞjre captcha"
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Fejl opstod"
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Mappen er tom"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Ingen Captcha at lÊse"
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Adgangskoden stemte ikke overens"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Indstillinger gemt."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Ny mappe"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Er du sikker på du vil genstarte pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktive hentninger"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Hjem"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Logfiler"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Konfiguration"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Fremskridt"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Dit brugernavn og adgangskode stemte ikke overens. PrÞv venligst igen."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "For at nulstille dine logind data eller tilfÞje en bruger kÞr:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Slet afsluttede"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Genstart mislykkedes"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Mappe:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Adgangskode:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Redigér pakke"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Redigér pakke detaljerne nedenfor."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Pakkens navn."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Navnet på disse nedhentnigers undermappe."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Liste over adgangskoder brugt til unrar."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Nulstil"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Du er logget ud med succes."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Sti"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "navn"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "stÞrrelse"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "indtast"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "sidst Êndret"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "forrige mappe"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "intet indhold"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "VÊlg en sektion fra menuen"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Gyldig indtil"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Trafik tilbage"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tid"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Maks paralelle"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Slet?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "ikke gyldig"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "ja"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nej"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Indtast dine konto data for at benytte premium egenskaber."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Dit brugernavn."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Adgangskoden til denne konto."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "VÊlg udbyderen for din konto"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "forrige"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "nÊste"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Slut"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Nyheder"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Installations mappe:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Konfigurations mappe:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Download mappe:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Fri plads:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Sprog:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Webinterface port:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Fjerninterface port:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "FilManager"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "TilfÞj pakke:"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "IndsÊt dine links eller overfÞr en beholder."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Navnet på den nye pakke"
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "IndsÊt dine links eller hvilkensomhelst tekst her og tryk filtrer knappen."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrér urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Adgangskode for RAR-arkiv"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Fil"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "OverfÞr en beholder."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha lÊser"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha'en"
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Tekst"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "IndsÊt teksten i captcha'en"
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Webbrugerflade"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "TilgÊngelig pyLoad opdatering!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins blev opdateret, genstart venligst!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha venter"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Log af"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrér"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "info"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Log venligst ind!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Genstart:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Hastighed:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktiv:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Genhent side"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "indlÊser"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Tilbage til toppen"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Luk pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Genstart pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "For at tilfÞje bruger eller Êndre adgangskode brug:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Vigtigt: Admin brugere har altid alle rettigheder"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Skift adgangskode"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Rettigheder"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "skift"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Indtast din nuvÊrende og Þnskede adgangskode "
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Bruger"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "NuvÊrende adgangskode"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Ny adgangskode"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Den nye adgangskode."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Ny adgangskode (gentag)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Gentag venligst den nye adgangskode."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "ikke tilgÊngelig"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/da/LC_MESSAGES/pyLoad.po b/locale/da/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..8b0ccab04
--- /dev/null
+++ b/locale/da/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Danish\n"
+"Language: da_DK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Starter"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "mappe til midlertidige filer"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL til sikker forbindelse"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Fri plads: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Aktiverer konti..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "genstarter pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad afslutter"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installér %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "lukker ned..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "fejl ved nedlukning"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "drÊbte pyLoad fra terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "Starter"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "ikke tilgÊngelig"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/da/LC_MESSAGES/pyLoadCli.po b/locale/da/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..c87fd85ab
--- /dev/null
+++ b/locale/da/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Danish\n"
+"Language: da_DK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Hentes:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "Hastighed:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "StÞrrelse:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "FÊrdig om:"
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "Venter:"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "TilfÞj Links"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "Genoptag/Pause Server"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "Afslut Server"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "Afslut"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Brug venligst denne syntaks: add <Pakkens Navn> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad var afslutte"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Udskriver server status"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Flyt pakker fra kÞ til samler og opmvendt"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Genstart filer"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Genstart pakker"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pause serveren"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "FortsÊt hentning"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Pause/Genoptag"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "Afslut server"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Kommando liste"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Kunne ikke skrive til bruger config filen"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Du mangler py-openssl for at tilslutte til denne pyLoad Kerne"
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adresse:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Brugernavn:"
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Kode:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Login oplysninger er forkerte"
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Can ikke etablere forbindelse til %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Du mangler py-openssl for at tilslutte til denne pyLoad Kerne"
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Interaktiv tilstand ignoreres da du gik nogle kommandoer."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "IndsÊt de links du Þnsker at tilfÞje"
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Skriv %s når du er fÊrdig."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Links tilfÞjet:"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "tilbage til hovedmenu"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Håndtér pakker:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Håndtér Links:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Hvad vil du flytte?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Hvad vil du slette?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Hvad vil du genstarte?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "slet"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "flyt"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "genstart"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr "- forrige "
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "- nÊste"
+
diff --git a/locale/da/LC_MESSAGES/setup.po b/locale/da/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..a14bdf05f
--- /dev/null
+++ b/locale/da/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Danish\n"
+"Language: da_DK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Webbrugerflade"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Kode:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Adgangskoden stemte ikke overens"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "ja"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nej"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo
deleted file mode 100644
index 017f373dc..000000000
--- a/locale/de/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 000000000..f40ad98a7
--- /dev/null
+++ b/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: German\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Neue CAPTCHA-Anfrage"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Bitte lies den Text auf dem Captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad wurde neu gestartet"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "aus"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Erfolgreich"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "ein"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Bist du sicher, dass du pyLoad beenden möchtest?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Link neu starten"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Link löschen"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Bitte gib einen Paketnamen ein."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Bitte klicke auf die richtige Position im Captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Ein Fehler ist aufgetreten."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Ordner ist leer"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Fehlgeschlagen"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Keine CAPTCHA's verfÃŒgbar."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Passwörter stimmen nicht Ìberein."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Einstellungen gespeichert"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Neuer Ordner"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Sicher, dass du pyLoad neustarten willst?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "Warte %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktiv"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Start"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "Warteschlange"
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "Linksammler"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Downloads"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Log"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Einstellungen"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Name"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Status"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informationen"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Größe"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Fortschritt"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Anmeldung"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Benutzername"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Passwort"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Dein Benutzername und/oder Passwort sind falsch. Bitte versuche es noch einmal."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Um deine Login-Informationen zu resetten oder un einen Benutzer hinzuzufÃŒgen, starte:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Fertige löschen"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Fehlgeschlagene neustarten"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Ordner:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Passwort:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Paket bearbeiten"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Bearbeiten Sie hier die Paketdetails."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Name des Pakets."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Verzeichnis"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Name des Unterordners fÃŒr diese Downloads."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Liste der Passwörter, welche fÌr das Entpacken genutzt werden."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Übernehmen"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Abbrechen"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Du wurdest erfolgreich abgemeldet."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Pfad"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "Name"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "Größe"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "Typ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "zuletzt verÀndert"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "ÃŒbergeordnetes Verzeichnis"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "kein Inhalt"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Allgemein"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Zusatzprogramme"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Konten"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "WÀhle einen Abschnitt aus dem MenÌ."
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Erweiterung"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "Premium"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "GÃŒltig bis:"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Verbleibender Traffic:"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Zeit:"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max Parallel"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Löschen?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "gÃŒltig"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "ungÃŒltig"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "ja"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nein"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "HinzufÃŒgen"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Konto hinzufÃŒgen"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Gib deine Benutzerdaten ein, um die Premiumfunktionen zu nutzen."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Dein Benutzername."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Das Password fÃŒr diesen Account."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Typ"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "WÀhle den Hoster fÌr dein Benutzerkonto."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Starten"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "zurÃŒck"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "weiter"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Ende"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Neuigkeiten"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Support"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "System"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr "Python:"
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "Betriebssystem:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad Version:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Installationsordner:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Konfigurationsordner:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Downloadordner:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Freier Speicherplatz:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Sprache:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Webinterface Port:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Remoteinterface Port:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Konfiguration"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Dateimanager"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Paket hinzufÃŒgen"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Links einfÃŒgen oder Container hochladen."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Name des neuen Pakets."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Links"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "FÃŒge deine Links oder einen Text hier ein und drÃŒck den 'Filter' Knopf"
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Links filtern"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Passwörter fÌr RAR-Dateien"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Datei"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Container hochladen."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Ziel"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captchas lesen"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "Captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Das Captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Text"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Gib den Text aus dem Captcha ein."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Schließen"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "WeboberflÀche"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad Update verfÃŒgbar!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins aktualisiert, bitte pyLoad neustarten!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captchas verfÃŒgbar"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Abmelden"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrieren"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Info"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Bitte anmelden!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Stoppen"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Download:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Erneut verbinden:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Geschwindigkeit:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktiv:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Aktualisieren"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "lade"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Nach oben"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "pyLoad beenden"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "pyLoad neustarten"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Um Benutzer hinzuzufÌgen oder Passwörter zu Àndern benutze:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Wichtig: Admin User haben immer alle Berechtigungen!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Passwort Àndern"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Admin"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Berechtigungen"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "Àndern"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Gib dein momentanes und dein neues Passwort ein."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Benutzer"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Momentanes Passwort"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Neues Passwort"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Das neue Passwort."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Neues Passwort (wiederholen)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Bitte wiederhole das neue Passwort."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Sie haben nicht die Berechtigung diese Seite zu besuchen."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Downloadverzeichnis nicht gefunden."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "unbegrenzt"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "nicht verfÃŒgbar"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Starte pyload.py -s um auf dieses Setup zuzugreifen."
+
diff --git a/locale/de/LC_MESSAGES/pyLoad.mo b/locale/de/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index e1e29cebc..000000000
--- a/locale/de/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/pyLoad.po b/locale/de/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..61c5eaa15
--- /dev/null
+++ b/locale/de/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: German\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Signal zum Beenden erhalten"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad ist bereits gestartet mit der pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Fehler beim Ändern der Gruppe: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Fehler beim Ändern des Benutzers: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "Ordner fÃŒr Logs"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "starte"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Benutze Home-Verzeichnis: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pyCrypto um Containerdateien zu öffnen"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "Ordner fÌr temporÀre Dateien"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "Downloadordner"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL fÃŒr eine sichere Verbindung"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Migriere alte Benutzereinstellungen in die Datenbank..."
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Bitte ÃŒberprÃŒfe deine Login-Informationen mit './pyload.py -u'"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Alle Links entfernt"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Downloadzeit: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Freier Speicher: %sGB"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Aktiviere Konten..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Aktiviere Plugins..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad ist gestartet und lÀuft"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "starte pyLoad neu"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad wird beendet"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installiere %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "konnte %(desc)s nicht finden: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "konnte %(desc)s nicht erstellen: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "Beenden..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "Fehler beim Beenden"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad vom Terminal beendet"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Link Datenbank wurde aufgrund inkompatibler Version gelöscht."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Link Datenbank konnte nicht konvertiert werden."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Datenbank wurde von v2 nach v3 konvertiert."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Datenbank wurde von v3 nach v4 konvertiert."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Konvertiere alte Django DB"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "Fertig"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "Offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "Online"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "eingereiht"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "ÃŒbersprungen"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "wartend"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "kurzzeitig offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "starte"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "fehlgeschlagen"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "abgebrochen"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "entschlÃŒsseln"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "benutzerdefiniert"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "downloade"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "verarbeite"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "unbekannt"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paket fertiggestellt: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Nutze SSL Thrift Backend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Fehler beim Fernzugriff: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Starte %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Konnte Backend %(name)s nicht laden | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "Warte %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL Zertifikat nicht gefunden."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Entschuldigung! Wir haben den Support fÃŒr das Starten von %s direkt in pyLoad eingestellt"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Du kannst den multithreaded-server benutzen, welcher eine gute Performance, SSL und"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "natÃŒrlich kannst du weiterhin deine existierende %s mit pyLoads fastcgi server benutzen"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "Beispiel-Konfigurationsdateien befinden sich in dem Verzeichnis 'pyload/webui/servers'"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Kann %(server)s nicht benutzen, weil python-flup nicht installiert ist!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Fehler beim Laden vom lightweight Server: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Du musst 'bjoern' herunterladen und kompilieren, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Kopiere 'boern.so' in 'pyload/lib' oder benutze 'setup.py install'"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "NatÃŒrlich musst musst etwas Ahnung von Linux haben und wie man Software kompiliert"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Der 'threaded' Server wird wegen Performance Problemen unter Windows benutzt."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "SSL funktioniert mit diesem Server nicht, benutze bitte alternativ den 'threaded' Server"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Starte den eingebauten Webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Starte den threaded SSL Webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Starte den threaded Webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Starte fastcgi Webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Starte bjoern Webserver: %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Sie haben nicht die Berechtigung diese Seite zu besuchen."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Downloadverzeichnis nicht gefunden."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "unbegrenzt"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "nicht verfÃŒgbar"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Starte pyload.py -s um auf dieses Setup zuzugreifen."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Eine Download Verbindung ist fehlggeschlagen, Falle zurÃŒck auf eine Verbindung | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Starte Download: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Download fertiggestellt: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Dem Plugin %s fehlt eine Funktion."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Download abgebrochen: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Download erneut gestartet: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Download ist nicht verfÃŒgbar: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Download ist vorÃŒbergehend nicht verfÃŒgbar: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Download fehlgeschlagen: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Verbindungsaufbau zum Host fehlgeschlagen oder die Verbindung wurde zurÃŒckgesetzt. Erneuter Versuch in 1 Minute..."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Download %(name)s wegen %(plugin)s ÃŒbersprungen"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Decrypting startet: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Decrypting fehlgeschlagen: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Wiederhole %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Holen von Informationen fÃŒr %(name)s fehlgeschlagen | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Fehler beim ausfÃŒhren des Hook: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Konnte %(name)s nicht aktivieren"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Aktivierte Plugins: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Deaktivierte Plugins: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "erneute Verbindung fehlgeschlagen: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Skript fÃŒr erneute Verbindung nicht gefunden!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "versuche erneute Verbindung"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Fehler beim AusfÃŒhren des Skriptes fÃŒr die erneute Verbindung!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Verbindung erneut aufgebaut, IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Auf dem GerÀt ist nicht mehr genÌgend Speicherplatz vorhanden"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Fehler beim Anmelden mit Konto %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Falsches Passwort"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Ihre Zeit %s hat ein falsches Format, nutzen Sie dieses: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Konto %s hat nicht genÃŒgend Traffic, erneuter Versuch in 30 min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Konto %s ist abgelaufen, erneuter Versuch in 1Std"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Downloadlimit erreicht"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s hat ein ungÃŒltiges Muster."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Fehler beim Importieren von %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Kein Hoster geladen"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Aktiviere direkten Download in deinem Bitshare Account"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "links.txt konnte nicht geleert werden."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Account-Einstellungen aufgrund neuem Konfigurationsformat gelöscht."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Authorisierung erforderlich (Benutzername:Passwort)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Bitte gib deine %s Kontodaten ein oder deaktiviere dieses Plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Es gab HTML-Code in der heruntergeladen Datei (%s)... Umleitungsfehler? Der Download wird neu gestartet."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Datei zur Zeit nicht verfÃŒgbar"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: warte zwischen den Downloads %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: Warte auf Captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Die heruntergeladene Datei ist leer"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API SchlÃŒssel ungÃŒltig"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Nicht genÃŒgend Traffic ÃŒbrig"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Traffic ÃŒberschritten"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Traffic Share (direkter Download)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Von dieser IP Adresse wird bereits heruntergeladen, warte 60 Sekunden"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "UngÃŒltiger Authorization-Code, Download wird neugestartet"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Keine freien Slots"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Du benötigst ein Premium-Konto fÌr diese Datei"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Dateiname als ungÃŒltig gemeldet"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Parallel-Download Fehler, warte 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Nicht eingeloggt."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "EntschlÃŒsselung fehlgeschlagen"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "FÃŒr die Datei wurde kein SchlÃŒssel in der URL ÃŒbertragen"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Fehler Code:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Datei existiert nicht."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Plugins wurden aktualisiert, bitte starte pyLoad neu ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins aktualisiert und neu geladen"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Keine Plugin-Updates verfÃŒgbar"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Keine Updates fÃŒr pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Neue pyLoad Version %s verfÃŒgbar ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Download hier: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Konnte keine Verbindung zum Server herstellen, um auf Updates zu prÃŒfen"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Neue Version von %(type)s | %(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Fehler beim Aktualisieren von %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Versionskonflikt"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Download fertig: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Neue Captcha Anfrage: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Beantworte Captcha mit \"c %s Text\""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Bitte fÃŒge erst dein premium.to Konto hinzu und starte pyLoad neu"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "%s aus HotFolder hinzugefÃŒgt"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "Kein %s installiert"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Konnte %s nicht aktivieren"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Aktiviert"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Keine Entpacken-Plugins aktiviert"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Paket %s eingereiht fÌr spÀteres entpacken"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "ÜberprÃŒfe Paket %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Entpacke nach %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Keine Dateien zum entpacken gefunden"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "entpacke"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "PasswortgeschÃŒtzt"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Falsches Passwort"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Lösche %s Dateien"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Entpacken abgeschlossen"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Archiv-Fehler"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC Fehler"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Unbekannter Fehler"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Setzen von Benutzer und Gruppe fehlgeschlagen"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Port 9666 wird bereits verwendet"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s Credits verbleibend"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Konnte keine Antwort senden."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Dein CaptchaTrader Konto hat nicht genÃŒgend Credits"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Liste von Cryptern nicht gefunden"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Liste von Cryptern ist leer"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Download abgeschlossen: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Neue CaptchaID von Upload: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Dein Captcha 9kw.eu Account hat nicht genÃŒgend Credits"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Installierte Skripte fÃŒr %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script nicht ausfÃŒhrbar:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Fehler in %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Dein ExpertDecoders Account hat nicht genÃŒgend Credits"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Bitte fÃŒgen Sie zuerst Ihr rehost.to Konto hinzu und starten Sie pyLoad danach neu"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Bitte fÃŒge erst ein gÃŒltiges premiumize.me Konto hinzu und starte pyLoad neu."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d Credits verbleibend"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil und Tesseract sind nicht installiert und kein Client ist verbunden fÃŒr Captcha-Entzifferung"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Es konnte in angemessener zeit kein Captcha-Ergebnis durch dies Plugins ermittelt werden."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Benutzer und Gruppe setzen fehlgeschlagen: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Kein Client verbunden zum eingeben des Captchas"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Paket %(name)s mit %(count)d Links hinzugefÃŒgt"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "%(count)d Links wurden zum Paket #%(package)d hinzugefÃŒgt "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Keine JS Engine erkannt. Bitte installiere entweder Spidermonkey, ossp-js, pyv8 oder rhino"
+
diff --git a/locale/de/LC_MESSAGES/pyLoadCli.mo b/locale/de/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index e46503121..000000000
--- a/locale/de/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/pyLoadCli.po b/locale/de/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..2e4bd9056
--- /dev/null
+++ b/locale/de/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: German\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Kommandozeile"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Downloads:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Geschwindigkeit: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Größe: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Fertig in: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "wartend: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Status:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "pausiert"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "lÀuft"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Gesamtgeschwindigkeit"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Dateien in Warteschlange"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Gesamt"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "MenÌ:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Links hinzufÃŒgen"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Warteschlange verwalten"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Linksammler anpassen"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Server fortsetzen/pausieren"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Server beenden"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Beenden"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Bitte benutze folgende Syntax: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "ÜberprÃŒfe %d Links:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Datei existiert nicht."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad wurde beendet"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Zeigt den Server-Status"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Zeigt die Warteschlange"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Zeigt den Linksammler"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "FÃŒgt Paket der Warteschlange hinzu"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "FÃŒgt Paket zum Linksammler hinzu"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Löscht Dateien aus der Warteschlange/Linksammler"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Löscht Pakete aus der Warteschlange/Linksammler"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Verschiebt Pakete aus der Warteschlange in den Linksammler oder umgekehrt"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Dateien neustarten"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Pakete neustarten"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "ÜberprÃŒfe Onlinestatus, funktioniert mit lokalen Containern"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Onlinestatus eines Containers prÃŒfen"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "pausiert den Server"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "Downloads fortsetzen"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Pause/Fortsetzen"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "Server beenden"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Liste aller Befehle:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Konnte Benutzer-Konfigurationsdatei nicht schreiben"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Du benötigst py-openssl um dich zu diesem pyLoad Server verbinden zu können."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adresse: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Benutzername: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Passwort: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Logindaten sind falsch."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Konnte keine Verbindung zu %(addr)s:%(port)s aufbauen."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Du benötigst py-openssl um dich zu diesem pyLoad Server verbinden zu können."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Befehle im interaktiven Modus werden ignoriert, wenn zusÀtzliche Befehle Ìbergeben wurden."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Paket hinzufÃŒgen:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Gib einen Namen fÃŒr das neue Paket ein"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paket: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Kopiere die Links, die du hinzufÃŒgen willst."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "DrÃŒcke %s wenn du fertig bist."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "HinzugefÃŒgte Links: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " ZurÌck zum MenÌ"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Pakete verwalten:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Links verwalten:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Was möchtest du verschieben?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Was möchtest du löschen?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Was möchtest du neustarten?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "WÀhle, was du machen möchtest oder gib eine Paketnummer an."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "löschen"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "verschieben"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "neustarten"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - vorige Seite"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - nÀchste Seite"
+
diff --git a/locale/de/LC_MESSAGES/pyLoadGui.mo b/locale/de/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 8ead8383c..000000000
--- a/locale/de/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/setup.mo b/locale/de/LC_MESSAGES/setup.mo
deleted file mode 100644
index 80ca3553e..000000000
--- a/locale/de/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/de/LC_MESSAGES/setup.po b/locale/de/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..41992ce3b
--- /dev/null
+++ b/locale/de/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: German\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "j"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "n"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Willkommen im pyLoad Konfigurations Assistenten."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Er wird dein System prÌfen und einige Grundeinstellungen vornehmen um pyLoad ausfÌhren zu können,"
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Der Wert in Klammern [] ist immer der Standardwert,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "Falls du sie nicht Àndern möchtest oder unsicher bist, drÌcke einfach Enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Beachte: Du kannst diesen Assistenten jederzeit wieder mit dem --setup oder -s Parameter starten."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Falls du beenden willst, drÃŒcke STRG-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "um abzubrechen und ihn nicht mehr automatisch zu starten."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Wenn du fÃŒr die SystemÃŒberprÃŒfung bereit bist, drÃŒcke Enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Du brauchst pycurl, sqlite und python 2.5, 2.6 oder 2.7 um pyLoad zu starten."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Bitte korrigiere das und starte pyLoad neu."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Das Setup wird sich nun beenden."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "System-Check beendet, drÃŒcke Enter um deinen Status Bericht zu sehen."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Status ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "Container decrypting"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "SSL Verbindung"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "Automatisches Captcha einlesen"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "GUI"
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "WeboberflÀche"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "erweitertes Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "VerfÃŒgbare Funktionen:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Fehlende Funktionen: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "kein py-crypto verfÃŒgbar"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Du brauchst es, um Container Dateien zu öffnen."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "Kein SSL verfÃŒgbar"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Wird gebraucht falls du eine SSL Verbindung zu Core oder Webinterface einstellen willst."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Falls du nur lokal zugreifen willst, ist SSL ÃŒberflÃŒssig."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "keine Captcha Erkennung verfÃŒgbar"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Wird fÌr einige Hoster als Freeuser benötigt."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI nicht verfÃŒgbar"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Die Grafische Benutzer OberflÀche."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "keine JavaScript Engine gefunden"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Du benötigst das fÌr einige Click'n'Load links. Installiere Spidermonkey, ossp-js, pyv8 oder rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Du kannst das Setup nun abbrechen und AbhÀngigkeiten fixen, falls du willst."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Mit Setup fortfahren?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Möchtest du den Pfad zu den Konfigurationsdateien Àndern? Jetziger ist %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Falls du pyLoad auf einem Server benutzt, oder die home Partition auf einem internen Flashspeicher liegt, wÀr es eine gute Idee ihn zu Àndern."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Konfigurationspfad Àndern?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Wollen Sie die Anmeldedaten und Grundeinstellungen festlegen?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Wird fÃŒr den ersten Start empfohlen."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Grundeinstellungen vornehmen?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Willst du SSL konfigurieren?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "SSL konfigurieren?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Willst du das Webinterface konfigurieren?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Webinterface konfigurieren?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Setup erfolgreich beendet."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "DrÃŒcke Enter zum beenden und starte pyLoad neu"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## System Check ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Deine python version ist zu neu, benutze python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Deine Python Version ist zu alt, benutze mindestens 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python Version: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Ihre installierte Version %s von jinja2 scheint veraltet zu sein."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Sie können problemlos fortfahren, sollte jedoch das Webinterface nicht funktionieren,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "sollten Sie es upgrade oder deinstallieren, pyload bringt eine ausreichende jinja2 Bibliothek mit."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS engine"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Grundeinstellungen ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Die folgenden Anmeldedaten sind fÃŒr CLI, GUI und Webinterface gÃŒltig."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Benutzername"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Externe Clients (GUI, CLI und andere) benötigen Fernzugriff, um via Netzwerk zugreifen zu können."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Solltest Du jedoch nur das Webinterface nutzen, kannst Du Ihn deaktivieren, um den Speicherverbrauch zu verringern."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Aktiviere Fernzugriff"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Sprache"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Download Ordner"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Maximale parallele Downloads"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Benutze Reconnect?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Reconnect Script Pfad"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Webinterface Setup ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Aktiviere Webinterface?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adresse des Webservers. Falls du 127.0.0.1 oder localhost eintrÀgst wird das Webinterface nur lokal erreichbar sein."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adresse"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad verfÌgt Ìber verschiedene Webserver, eine kurze ErklÀrung folgt."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Standardserver, beste Wahl wenn du nicht weißt welchen du wÀhlen sollst."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Dieser Server unterstÃŒtzt SSL und ist eine gute Alternative zu builtin."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Kann von apache, lighttpd benutzt werden. Muss konfiguriert werden, welches aber nicht sehr einfach ist."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Sehr schnelle Alternative geschrieben in C, benötigt libev und Linux-Kenntnisse."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Downloaden von: https://github.com/jonashaag/bjoern, danach kompilieren."
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "und kopiere die bjoern.so nach pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Achtung: In manchen FÀllen funktioniert der builtin Server nicht, wenn du Probleme mit dem Webinterface bemerkst."
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "komme zurÌck und Àndere den builtin server zu threaded hier"
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Server"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Einstellungen fÃŒr SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "FÃŒhren Sie die folgenden Kommandos im pyLoad Konfigurationsordner aus, um ein SSL-Zertifikate zu erstellen:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Falls du fertig bist und alles erfolgreich war, kannst du nun SSL aktivieren."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "SSL aktivieren?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Aktion wÀhlen"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Erstelle/Bearbeite Nutzer"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Liste Nutzer auf"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Entferne Nutzer"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Verlassen"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Benutzer"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Setze neuen Config Pfad, momentane Konfiguration wird nicht ÃŒbernommen!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Config Pfad"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Config Pfad geÀndert, Setup wird nun beenden, bitte starte neu um weiterzumachen."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "DrÃŒcke Enter zum Beenden."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Fehler beim Setzen des Pfades fÃŒr die Konfiguration: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr "%s: OK"
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: fehlt"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Passwort: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Passwort zu kurz. Benutze mindestens 4 Zeichen."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Passwort (bestÀtigen): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Passwörter stimmen nicht Ìberein."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "ja"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "ja"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "j"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nein"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "nein"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "n"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "UngÃŒltige Eingabe"
+
diff --git a/locale/django.pot b/locale/django.pot
index 81c9c7b6b..277108e5b 100644
--- a/locale/django.pot
+++ b/locale/django.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+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"
@@ -17,670 +17,673 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: module/web/translations.js:1 module/web/templates/default/base.html:123
-#: module/web/templates/default/base.html:124
-#: module/web/templates/default/settings_item.html:14
-msgid "on"
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
msgstr ""
-#: module/web/translations.js:2 module/web/templates/default/captcha.html:7
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
msgid "Please read the text on the captcha."
msgstr ""
-#: module/web/translations.js:3
-msgid "Settings saved."
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
msgstr ""
-#: module/web/translations.js:4 module/web/templates/default/base.html:123
-#: module/web/templates/default/base.html:124
-#: module/web/templates/default/settings_item.html:16
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
msgid "off"
msgstr ""
-#: module/web/translations.js:5
+#: pyload/webui/translations.js:5
msgid "Success"
msgstr ""
-#: module/web/translations.js:6
-msgid "Passwords did not match."
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
msgstr ""
-#: module/web/translations.js:7
-msgid "Delete Link"
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
msgstr ""
-#: module/web/translations.js:8
-msgid "pyLoad restarted"
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
msgstr ""
-#: module/web/translations.js:9
-msgid "You are really sure you want to quit pyLoad?"
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
msgstr ""
-#: module/web/translations.js:10
+#: pyload/webui/translations.js:10
msgid "Please Enter a packagename."
msgstr ""
-#: module/web/translations.js:11
+#: pyload/webui/translations.js:11
msgid "Please click on the right captcha position."
msgstr ""
-#: module/web/translations.js:12
+#: pyload/webui/translations.js:12
msgid "Error occured."
msgstr ""
-#: module/web/translations.js:13
-msgid "New Captcha Request"
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
msgstr ""
-#: module/web/translations.js:14
+#: pyload/webui/translations.js:14
msgid "Failed"
msgstr ""
-#: module/web/translations.js:15
+#: pyload/webui/translations.js:15
msgid "No Captchas to read."
msgstr ""
-#: module/web/translations.js:16
-#: module/web/templates/default/filemanager.html:65
-#: module/web/templates/default/folder.html:14
-msgid "Folder is empty"
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
msgstr ""
-#: module/web/translations.js:17
-msgid "Restart Link"
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
msgstr ""
-#: module/web/translations.js:18
+#: pyload/webui/translations.js:18
msgid "New folder"
msgstr ""
-#: module/web/translations.js:19
+#: pyload/webui/translations.js:19
msgid "Are you sure you want to restart pyLoad?"
msgstr ""
-#: module/web/pyload_app.py:125
-msgid "You dont have permission to access this page."
-msgstr ""
-
-#: module/web/pyload_app.py:193
-msgid "Download directory not found."
-msgstr ""
-
-#: module/web/pyload_app.py:260 module/web/pyload_app.py:267
-msgid "unlimited"
-msgstr ""
-
-#: module/web/pyload_app.py:262 module/web/pyload_app.py:269
-msgid "not available"
-msgstr ""
-
-#: module/web/pyload_app.py:509
-msgid "Run pyLoadCore.py -s to access the setup."
-msgstr ""
-
-#: module/web/json_app.py:60
+#: pyload/webui/app/json.py:57
#, python-format
msgid "waiting %s"
msgstr ""
-#: module/web/templates/default/info.html:14
-#: module/web/templates/default/info.html:15
-#: module/web/templates/default/home.html:239
-msgid "Information"
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
msgstr ""
-#: module/web/templates/default/info.html:18
-msgid "News"
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
msgstr ""
-#: module/web/templates/default/info.html:21
-msgid "Support"
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
msgstr ""
-#: module/web/templates/default/info.html:37
-msgid "System"
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
msgstr ""
-#: module/web/templates/default/info.html:40
-msgid "Python:"
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
msgstr ""
-#: module/web/templates/default/info.html:44
-msgid "OS:"
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
msgstr ""
-#: module/web/templates/default/info.html:48
-msgid "pyLoad version:"
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
msgstr ""
-#: module/web/templates/default/info.html:52
-msgid "Installation Folder:"
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
msgstr ""
-#: module/web/templates/default/info.html:56
-msgid "Config Folder:"
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
msgstr ""
-#: module/web/templates/default/info.html:60
-msgid "Download Folder:"
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
msgstr ""
-#: module/web/templates/default/info.html:64
-msgid "Free Space:"
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
msgstr ""
-#: module/web/templates/default/info.html:68
-msgid "Language:"
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
msgstr ""
-#: module/web/templates/default/info.html:72
-msgid "Webinterface Port:"
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
msgstr ""
-#: module/web/templates/default/info.html:76
-msgid "Remote Interface Port:"
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
msgstr ""
-#: module/web/templates/default/downloads.html:6
-#: module/web/templates/default/base.html:93
-#: module/web/templates/default/home.html:220
-msgid "Downloads"
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
msgstr ""
-#: module/web/templates/default/filemanager.html:19
-msgid "FileManager"
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
msgstr ""
-#: module/web/templates/default/admin.html:8
-#: module/web/templates/default/admin.html:9
-#: module/web/templates/default/base.html:59
-msgid "Administrate"
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
msgstr ""
-#: module/web/templates/default/admin.html:13
-msgid "Quit pyLoad"
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
msgstr ""
-#: module/web/templates/default/admin.html:14
-msgid "Restart pyLoad"
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
msgstr ""
-#: module/web/templates/default/admin.html:18
-msgid "To add user or change passwords use:"
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
msgstr ""
-#: module/web/templates/default/admin.html:19
-msgid "Important: Admin user have always all permissions!"
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
msgstr ""
-#: module/web/templates/default/admin.html:25
-#: module/web/templates/default/settings.html:91
-#: module/web/templates/default/queue.html:82
-#: module/web/templates/default/window.html:7
-#: module/web/templates/default/home.html:237
-msgid "Name"
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
msgstr ""
-#: module/web/templates/default/admin.html:28
-#: module/web/templates/default/admin.html:67
-msgid "Change Password"
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
msgstr ""
-#: module/web/templates/default/admin.html:31
-msgid "Admin"
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
msgstr ""
-#: module/web/templates/default/admin.html:34
-msgid "Permissions"
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
msgstr ""
-#: module/web/templates/default/admin.html:41
-msgid "change"
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
msgstr ""
-#: module/web/templates/default/admin.html:61
-#: module/web/templates/default/admin.html:91
-#: module/web/templates/default/settings.html:167
-#: module/web/templates/default/queue.html:97
-#: module/web/templates/default/captcha.html:33
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
msgid "Submit"
msgstr ""
-#: module/web/templates/default/admin.html:69
-msgid "Enter your current and desired Password."
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
msgstr ""
-#: module/web/templates/default/admin.html:70
-msgid "User"
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
msgstr ""
-#: module/web/templates/default/admin.html:71
-#: module/web/templates/default/settings.html:179
-msgid "Your username."
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
msgstr ""
-#: module/web/templates/default/admin.html:75
-msgid "Current password"
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
msgstr ""
-#: module/web/templates/default/admin.html:76
-#: module/web/templates/default/settings.html:184
-msgid "The password for this account."
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
msgstr ""
-#: module/web/templates/default/admin.html:80
-msgid "New password"
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
msgstr ""
-#: module/web/templates/default/admin.html:81
-msgid "The new password."
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
msgstr ""
-#: module/web/templates/default/admin.html:85
-msgid "New password (repeat)"
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
msgstr ""
-#: module/web/templates/default/admin.html:86
-msgid "Please repeat the new password."
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
msgstr ""
-#: module/web/templates/default/admin.html:92
-#: module/web/templates/default/settings.html:198
-#: module/web/templates/default/queue.html:98
-#: module/web/templates/default/window.html:41
-msgid "Reset"
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
msgstr ""
-#: module/web/templates/default/settings.html:3
-#: module/web/templates/default/settings.html:4
-#: module/web/templates/default/base.html:102
-#: module/web/templates/default/home.html:229
-msgid "Config"
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
msgstr ""
-#: module/web/templates/default/settings.html:16
+#: pyload/webui/themes/default/tml/settings.html:16
msgid "General"
msgstr ""
-#: module/web/templates/default/settings.html:17
+#: pyload/webui/themes/default/tml/settings.html:17
msgid "Plugins"
msgstr ""
-#: module/web/templates/default/settings.html:18
+#: pyload/webui/themes/default/tml/settings.html:18
msgid "Accounts"
msgstr ""
-#: module/web/templates/default/settings.html:45
-#: module/web/templates/default/settings.html:74
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
msgid "Choose a section from the menu"
msgstr ""
-#: module/web/templates/default/settings.html:90
+#: pyload/webui/themes/default/tml/settings.html:90
msgid "Plugin"
msgstr ""
-#: module/web/templates/default/settings.html:92
-#: module/web/templates/default/settings.html:183
-#: module/web/templates/default/login.html:19
-#: module/web/templates/default/queue.html:92
-#: module/web/templates/default/window.html:21
-msgid "Password"
-msgstr ""
-
-#: module/web/templates/default/settings.html:93
-#: module/web/templates/default/home.html:238
-msgid "Status"
-msgstr ""
-
-#: module/web/templates/default/settings.html:94
+#: pyload/webui/themes/default/tml/settings.html:94
msgid "Premium"
msgstr ""
-#: module/web/templates/default/settings.html:95
+#: pyload/webui/themes/default/tml/settings.html:95
msgid "Valid until"
msgstr ""
-#: module/web/templates/default/settings.html:96
+#: pyload/webui/themes/default/tml/settings.html:96
msgid "Traffic left"
msgstr ""
-#: module/web/templates/default/settings.html:97
+#: pyload/webui/themes/default/tml/settings.html:97
msgid "Time"
msgstr ""
-#: module/web/templates/default/settings.html:98
+#: pyload/webui/themes/default/tml/settings.html:98
msgid "Max Parallel"
msgstr ""
-#: module/web/templates/default/settings.html:99
+#: pyload/webui/themes/default/tml/settings.html:99
msgid "Delete?"
msgstr ""
-#: module/web/templates/default/settings.html:121
+#: pyload/webui/themes/default/tml/settings.html:121
msgid "valid"
msgstr ""
-#: module/web/templates/default/settings.html:124
+#: pyload/webui/themes/default/tml/settings.html:124
msgid "not valid"
msgstr ""
-#: module/web/templates/default/settings.html:131
+#: pyload/webui/themes/default/tml/settings.html:131
msgid "yes"
msgstr ""
-#: module/web/templates/default/settings.html:134
+#: pyload/webui/themes/default/tml/settings.html:134
msgid "no"
msgstr ""
-#: module/web/templates/default/settings.html:168
-#: module/web/templates/default/settings.html:197
-#: module/web/templates/default/base.html:117
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
msgid "Add"
msgstr ""
-#: module/web/templates/default/settings.html:176
+#: pyload/webui/themes/default/tml/settings.html:176
msgid "Add Account"
msgstr ""
-#: module/web/templates/default/settings.html:177
+#: pyload/webui/themes/default/tml/settings.html:177
msgid "Enter your account data to use premium features."
msgstr ""
-#: module/web/templates/default/settings.html:178
-#: module/web/templates/default/login.html:3
-msgid "Login"
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
msgstr ""
-#: module/web/templates/default/settings.html:188
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
msgid "Type"
msgstr ""
-#: module/web/templates/default/settings.html:189
+#: pyload/webui/themes/default/tml/settings.html:189
msgid "Choose the hoster for your account."
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "Path"
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "absolute"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
msgstr ""
-#: module/web/templates/default/pathchooser.html:39
-#: module/web/templates/default/pathchooser.html:41
-msgid "relative"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
msgstr ""
-#: module/web/templates/default/pathchooser.html:46
-msgid "name"
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
msgstr ""
-#: module/web/templates/default/pathchooser.html:47
-msgid "size"
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
msgstr ""
-#: module/web/templates/default/pathchooser.html:48
-msgid "type"
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
msgstr ""
-#: module/web/templates/default/pathchooser.html:49
-msgid "last modified"
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
msgstr ""
-#: module/web/templates/default/pathchooser.html:54
-msgid "parent directory"
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
msgstr ""
-#: module/web/templates/default/pathchooser.html:70
-msgid "no content"
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
msgstr ""
-#: module/web/templates/default/setup.html:3
-#: module/web/templates/default/setup.html:4
-msgid "Setup"
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
msgstr ""
-#: module/web/templates/default/login.html:14
-msgid "Username"
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
msgstr ""
-#: module/web/templates/default/login.html:29
-msgid "Your username and password didn't match. Please try again."
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
msgstr ""
-#: module/web/templates/default/login.html:30
-msgid "To reset your login data or add an user run:"
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
msgstr ""
-#: module/web/templates/default/base.html:20
-#: module/web/templates/default/base.html:139
-msgid "Webinterface"
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
msgstr ""
-#: module/web/templates/default/base.html:39
-msgid "pyLoad Update available!"
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
msgstr ""
-#: module/web/templates/default/base.html:46
-msgid "Plugins updated, please restart!"
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
msgstr ""
-#: module/web/templates/default/base.html:52
-msgid "Captcha waiting"
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
msgstr ""
-#: module/web/templates/default/base.html:57
-msgid "Logout"
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
msgstr ""
-#: module/web/templates/default/base.html:61
-msgid "Info"
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
msgstr ""
-#: module/web/templates/default/base.html:65
-msgid "Please Login!"
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
msgstr ""
-#: module/web/templates/default/base.html:84
-#: module/web/templates/default/home.html:211
-msgid "Home"
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
msgstr ""
-#: module/web/templates/default/base.html:87
-#: module/web/templates/default/queue.html:15
-#: module/web/templates/default/window.html:34
-#: module/web/templates/default/home.html:214
-msgid "Queue"
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
msgstr ""
-#: module/web/templates/default/base.html:90
-#: module/web/templates/default/queue.html:17
-#: module/web/templates/default/window.html:36
-#: module/web/templates/default/home.html:217
-msgid "Collector"
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
msgstr ""
-#: module/web/templates/default/base.html:99
-#: module/web/templates/default/logs.html:3
-#: module/web/templates/default/logs.html:4
-#: module/web/templates/default/home.html:226
-msgid "Logs"
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
msgstr ""
-#: module/web/templates/default/base.html:114
-#: module/web/templates/default/logs.html:12
-msgid "Start"
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
msgstr ""
-#: module/web/templates/default/base.html:115
-msgid "Stop"
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
msgstr ""
-#: module/web/templates/default/base.html:116
-msgid "Cancel"
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
msgstr ""
-#: module/web/templates/default/base.html:123
-msgid "Download:"
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
msgstr ""
-#: module/web/templates/default/base.html:124
-msgid "Reconnect:"
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
msgstr ""
-#: module/web/templates/default/base.html:125
-msgid "Speed:"
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
msgstr ""
-#: module/web/templates/default/base.html:126
-msgid "Active:"
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
msgstr ""
-#: module/web/templates/default/base.html:127
-msgid "Reload page"
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
msgstr ""
-#: module/web/templates/default/base.html:157
-msgid "loading"
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
msgstr ""
-#: module/web/templates/default/base.html:166
-msgid "Back to top"
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "prev"
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "next"
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
msgstr ""
-#: module/web/templates/default/logs.html:12
-msgid "End"
+#: pyload/webui/themes/default/tml/base.html:39
+#, python-format
+msgid "New pyLoad version %s available!"
msgstr ""
-#: module/web/templates/default/logout.html:8
-msgid "You were successfully logged out."
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
msgstr ""
-#: module/web/templates/default/queue.html:25
-msgid "Delete Finished"
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
msgstr ""
-#: module/web/templates/default/queue.html:26
-msgid "Restart Failed"
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
msgstr ""
-#: module/web/templates/default/queue.html:65
-msgid "Folder:"
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
msgstr ""
-#: module/web/templates/default/queue.html:65
-msgid "Password:"
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
msgstr ""
-#: module/web/templates/default/queue.html:79
-msgid "Edit Package"
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
msgstr ""
-#: module/web/templates/default/queue.html:80
-msgid "Edit the package detais below."
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
msgstr ""
-#: module/web/templates/default/queue.html:83
-msgid "The name of the package."
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
msgstr ""
-#: module/web/templates/default/queue.html:87
-msgid "Folder"
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
msgstr ""
-#: module/web/templates/default/queue.html:88
-msgid "Name of subfolder for these downloads."
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
msgstr ""
-#: module/web/templates/default/queue.html:93
-msgid "List of passwords used for unrar."
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
msgstr ""
-#: module/web/templates/default/window.html:5
-#: module/web/templates/default/window.html:40
-msgid "Add Package"
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
msgstr ""
-#: module/web/templates/default/window.html:6
-msgid "Paste your links or upload a container."
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
msgstr ""
-#: module/web/templates/default/window.html:8
-msgid "The name of the new package."
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
msgstr ""
-#: module/web/templates/default/window.html:12
-msgid "Links"
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
msgstr ""
-#: module/web/templates/default/window.html:13
-msgid "Paste your links here or any text and press the filter button."
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
msgstr ""
-#: module/web/templates/default/window.html:14
-msgid "Filter urls"
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
msgstr ""
-#: module/web/templates/default/window.html:22
-msgid "Password for RAR-Archive"
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
msgstr ""
-#: module/web/templates/default/window.html:26
-msgid "File"
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
msgstr ""
-#: module/web/templates/default/window.html:27
-msgid "Upload a container."
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
msgstr ""
-#: module/web/templates/default/window.html:31
-msgid "Destination"
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
msgstr ""
-#: module/web/templates/default/home.html:206
-msgid "Active Downloads"
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
msgstr ""
-#: module/web/templates/default/home.html:240
-msgid "Size"
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
msgstr ""
-#: module/web/templates/default/home.html:241
-msgid "Progress"
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
msgstr ""
-#: module/web/templates/default/captcha.html:6
-msgid "Captcha reading"
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
msgstr ""
-#: module/web/templates/default/captcha.html:13
-msgid "Captcha"
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
msgstr ""
-#: module/web/templates/default/captcha.html:14
-msgid "The captcha."
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
msgstr ""
-#: module/web/templates/default/captcha.html:20
-msgid "Text"
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
msgstr ""
-#: module/web/templates/default/captcha.html:21
-msgid "Input the text on the captcha."
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
msgstr ""
-#: module/web/templates/default/captcha.html:34
-msgid "Close"
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:127
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:195
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:264 pyload/webui/app/pyload.py:271
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:509
+msgid "Run pyload.py -s to access the setup."
msgstr ""
diff --git a/locale/el/LC_MESSAGES/django.po b/locale/el/LC_MESSAGES/django.po
new file mode 100644
index 000000000..c7fc04fc0
--- /dev/null
+++ b/locale/el/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Greek\n"
+"Language: el_GR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Αίτηση για Μέο Captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Παρακαλώ Ύιαβάστε το κείΌεΜο στο captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "ΈγιΜε επαΜεκκίΜηση στο pyLoad "
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "αΜεΜεργό"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Επιτυχία"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "εΜεργό"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Είστε σίγουροι ότι Ξέλετε Μα τερΌατίσετε το pyLoad ;"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "ΕπαΜεκκίΜηση συΜΎέσΌου"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Διαγραφή συΜΎέσΌου"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Παρακαλώ εισάγετε έΜα όΜοΌα για το πακέτο"
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Παρακαλώ πατήστε στη σωστή Ξέση captcha"
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Παρουσιάστηκε έΜα σφάλΌα"
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Ο φάκελος είΜαι άΎειος"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "ΑπέτυχαΜ"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "ΔεΜ υπάρχουΜ Captchas για αΜάγΜωση."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Οι κωΎικοί ΎεΜ ταιριάζουΜ"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Οι ρυΞΌίσεις αποΞηκεύτηκαΜ"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Νέος φάκελος"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Είσαστε σίγουροι ότι Ξέλετε Μα κάΜετε επαΜεκκίΜηση στο pyLoad ;"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "αΜαΌοΜή %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "ΕΜεργές Μεταφορτώσεις "
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Αρχική"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Λήψεις"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Αρχεία καταγραφής"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "ΡυΞΌίσεις"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "ΌΜοΌα"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Κατάσταση"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Πληροφορία"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "ΜέγεΞος"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "ΠρόοΎος"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "ΣύΜΎεση"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "ΌΜοΌα Χρήστη"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "ΚωΎικός πρόσβασης"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "΀ο όΜοΌα χρήστη και ο κωΎικός πρόσβασης ΎεΜ ταιριάζουΜ. ΔοκιΌάστε ΟαΜά."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Για Μα επαΜαφέρετε τα στοιχεία εισόΎου ή για Μα προσΞέστε χρήστη τρέΟτε: "
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Διαγραφή ΀ελείωσε"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "ΕπαΜεκκίΜηση Απέτυχε"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Ίάκελος:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "ΚωΎικός Πρόσβασης:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "ΕπεΟεργασία Πακέτου"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "ΕπεΟεργαστείτε λεπτοΌέρειες του πακέτου παρακάτω. "
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "΀ο όΜοΌα του πακέτου."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Ίάκελος"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "ΌΜοΌα υποφακέλου για αυτές τις Όεταφορτώσεις."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Λίστα κωΎικώΜ που χρησιΌοποιούΜται για το unrar."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Υποβολή"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "ΕπαΜαφορά"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "ΑποσυΜΎεΞήκατε επιτυχής."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "ΔιαΎροΌή"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "απόλυτη"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "σχετική"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "όΜοΌα"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "ΌέγεΞος"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "τύπος"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "τελευταία αλλαγή"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "γοΜικός φάκελος"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "χωρίς περιεχόΌεΜο"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "ΓεΜικά"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "ΠρόσΞετα"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "ΛογαριασΌοί"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "ΕπιλέΟτε από το menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "ΠρόσΞετα"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Έγκυρο εως"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "ΕΜαποΌείΜουσα κίΜηση"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "ΧρόΜος"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Μέγιστος αριΞΌός ταυτόχροΜωΜ"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Διαγραφή;"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "έγκυρο"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "άκυρο"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "Μαι"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "όχι"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "ΠροσΞήκη"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "ΠροσΞήκη ΛογαριασΌού"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Εισάγετε τα στοιχεία του λογαριασΌού σας για Μα χρησιΌοποιήσετε τα επιπλέοΜ χαρακτηριστικά."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "΀ο όΜοΌα χρήστη σας."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Ο κωΎικός χρήστης για αυτόΜ το λογαριασΌό."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "΀ύπος"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "ΧρησιΌοποιήστε άλλο hoster για τοΜ λογαριασΌό σας. "
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "ΕκκίΜηση"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "προηγ."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "επόΌ."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "΀έλος"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Νέα"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "ΥποστήριΟη"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "ΣύστηΌα"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "έκΎοση pyLoad: "
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Ίάκελος Εγκατάστασης: "
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Ίάκελος ΡυΞΌίσεωΜ: "
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Ίάκελος Λήψης: "
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "ΕλεύΞερος Χώρος: "
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Γλώσσα: "
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Θύρα Webinterface: "
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Θύρα ΑποΌακρυσΌέΜης Πρόσβασης: "
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Εγκατάσταση"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Διαχείριση ΑρχείωΜ"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "ΠροσΞήκη Πακέτου "
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Επικολλήστε συΜΎέσΌους η φορτώστε αρχείο container."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "΀ο όΜοΌα του Μέου πακέτου."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "ΣύΜΎεσΌοι"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Επικολλήστε τους συΜΎέσΌους σας εΎώ ή οποιοΎήποτε κείΌεΜο και πατήστε το πλήκτρο φίλτρου."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "ΊιλτράρισΌα urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "ΚωΎικός για Aρχείο RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Aρχείο"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "ΑΜεβάστε αρχείο container."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "ΠροορισΌός"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "ΑΜάλυση Captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "΀ο captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "ΚείΌεΜο"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Εισάγετε το κείΌεΜο που φαίΜεται στο captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "ΚλείσιΌο"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Διεπαφή ιστού"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Υπάρχει ΎιαΞέσιΌη αΜαβάΞΌιση για το pyLoad!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "΀α πρόσΞετα αΜαβαΞΌίστηκαΜ, παρακαλούΌε κάΜτε επαΜεκκίΜηση!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "ΠεριΌέΜει Captcha"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "ΑποσύΜΎεση"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Διαχείριση"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Πληροφορίες"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Παρακαλώ ΣυΜΎεΞείτε!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Διακοπή"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Ακύρωση"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Λήψη:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "ΕπαΜασύΜΎεση:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "΀αχύτητα:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "ΕΜεργό:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "ΕπαΜαφόρτιση σελίΎας"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "φορτώΜει "
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Επιστροφή στηΜ κορυφή"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "ΣταΌάτηΌα του pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "ΕπαΜεκκίΜηση του pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Για Μα προσΞέσετε χρήστες ή Μα αλλάΟετε κωΎικούς πρόσβασης χρησιΌοποιήσετε: "
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "ΕπισήΌαΜση: Ο Διαχειριστής έχει πάΜτα όλα τα ΎικαιώΌατα!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Αλλαγή ΚωΎικού Πρόσβασης"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Διαχειριστής"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "ΔικαιώΌατα"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "αλλαγή"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Εισάγετε το παρόΜ και το επιΞυΌητό κωΎικό χρήστη."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "ΌΜοΌα χρήστη"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "ΠαρόΜ κωΎικός χρήστη"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Νέος κωΎικός χρήστη"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Ο Μέος κωΎικός χρήστη."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Νέος κωΎικός χρήστη (επαΜάληψη)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Παρακαλώ επαΜαλάβετε το Μέο κωΎικό χρήστη."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "ΔεΜ σας επιτρέπετε η πρόσβαση σε αυτή τη σελίΎα"
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Ο κατάλογος ΌεταφορτώσεωΜ ΎεΜ βρέΞηκε"
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "απεριόριστο"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "Όη ΎιαΞέσιΌο"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Εκτελέστε τηΜ εΜτολή pyload.py -s για πρόσβαση στηΜ εγκατάσταση."
+
diff --git a/locale/el/LC_MESSAGES/pyLoad.po b/locale/el/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..cb4826ca9
--- /dev/null
+++ b/locale/el/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Greek\n"
+"Language: el_GR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "ΕλήφΞη σήΌα εγκατάλειψης"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "΀ο pyLoad εκτελείται ήΎη Όε pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Αποτυχία αλλαγής οΌάΎας: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Αποτυχία αλλαγής χρήστη: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "φάκελος για αρχεία καταγραφής"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Σε εκκίΜηση"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Χρήση γοΜικού καταλόγου: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "το pycrypto Ξα αποκωΎικοποιήσει τα περιέχοΜτα αρχεία"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "φάκελος για προσωριΜά αρχεία"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "φάκελος για κατεβασΌέΜα"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL για ασφαλή σύΜΎεση"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Παρακαλώ ελέγΟτε τα στοιχεία σύΜΎεσης Όε τη χρήση του ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Όλοι οι συΜΎεσΌοι αφαιρέΞηκαΜ"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "ΧρόΜος κατεβάσΌατος: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "ΕλέυΞερος χώρος στο Ύίσκο: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "ΕΜεργοποίηση ΛογαριασΌώΜ..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "ΕΜεργοποίηση ΠρόσΞετωΜ..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "΀ο pyLoad είΜαι εΜεργό και εκτελείται "
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "το pyLoad επαΜεκκιΜείται"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "το pyLoad σταΌατάει"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Εγκατάσταση %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "ΎεΜ βρεΞηκε(καΜ) %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "ΎεΜ ΎιΌιουργήΞηκε(καΜ) %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "κλείσιΌο..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "σφάλΌα κατα το κλείσιΌο"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "το pyLoad σταΌατήΞηκε απο το ΀ερΌατικο"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "τελείωσε"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "αποσυΜΎεΌέΜος"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "συΜΎεΌέΜος"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "στηΜ ουρά"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "προσπεράστηκε"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "σε αΜαΌοΜή"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "προσωριΜά χωρίς σύΜΎεση"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "ΟεκιΜάει"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "απέτυχε"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "ΌαταιώΞηκε"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "αποκρυπτογραφείται"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "προσαρΌοσΌέΜο"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "σε λήψη"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "σε εΟέλιΟη"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "άγΜωστο"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Ολοκλήρωση πακέτου: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "ΣφάλΌα αποΌακρυσΌέΜου backend: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "ΓίΜεται εκκίΜηση %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Απέτυχε η φόρτωση του backend %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "αΜαΌοΜή %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "ΔεΜ βρέΞηκαΜ πιστοποιητικά SSL."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Μπορείτε Μα χρησιΌοποιήσετε τοΜ threaded server που προσφέρει καλή απόΎοση και ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "παραΎείγΌατα αρχείωΜ ρυΞΌίσεωΜ βρίσκοΜται Όέσα στο φάκελο pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Αποτυχία χρήσης του %(server)s, ΎεΜ είΜαι εγκατεστηΌέΜο το πακέτο python-flup!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Χρειάζεται Μα κατεβλασετε και Μα συΜτάΟετε το bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Μα φυσικά και χρειάζετε Μα είστε οικείος Όε τα linux και Μα Οέρετε πως Μα συΜτάσσεται λογισΌικό"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Ο ΎιακοΌιστής άλλαΟε σε threaded, λόγω κάποιωΜ γΜωστώΜ προβληΌάτωΜ απόΎοσης στα windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Αυτός ο ΎιακοΌιστής ΎεΜ προσφέρει SSL, αΜτί για αυτόΜ παρακαλώ σκεφτείτε τηΜ περίπτωση χρήσης του threaded"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "ΕκκίΜηση του εΜσωΌατωΌέΜου ΎιακοΌιστή web: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "ΕκκίΜηση του threaded SSL ΎιακοΌιστή web: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "ΕκκίΜηση του threaded ΎιακοΌιστή web: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "ΕκκίΜηση του ΎιακοΌιστή fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "ΔεΜ σας επιτρέπετε η πρόσβαση σε αυτή τη σελίΎα"
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Ο κατάλογος ΌεταφορτώσεωΜ ΎεΜ βρέΞηκε"
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "απεριόριστο"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "Όη ΎιαΞέσιΌο"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Εκτελέστε τηΜ εΜτολή pyload.py -s για πρόσβαση στηΜ εγκατάσταση."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Η λήψη τωΜ κοΌΌατιώΜ απέτυχε, επιστροφή σε κατάσταση ΌοΜής σύΜΎεσης | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Η λήψη ΟεκιΜά: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Η λήψη ολοκληρώΞηκε: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Στο πρόσΞετο %s λείπει Όια λειτουργία."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Η λήψη ΌαταιώΞηκε: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Η λήψη επαΜεκκιΜήΞηκε: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Η λήψη είΜαι εκτός σύΜΎεσης: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Η λήψη είΜαι προσωριΜά εκτός σύΜΎεσης: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Η λήψη απέτυχε: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Αποτυχία σύΜΎεσης Όε το ΎιακοΌιστή ή Ύιακοπή σύΜΎεσης, αΜαΌοΜή 1 λεπτό πριΜ γίΜει κι άλλη προσπάΞεια."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "ΈγιΜε παράλειψη στη λήψη: %(name)s εΟαιτίας του %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Η αΜάκτηση πληροφοριώΜ για το %(name)s απέτυχε | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Αποτυχία εΜεργοποίησης %(name)s "
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "ΕΜεργοποιηΌέΜα πρόσΞετα: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "ΑπεΜεργοποίηση πρόσΞετωΜ: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "ΕπαΜασύΜΎεση απέτυχε %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "ΔεΜ βρέΞηκε αρχείο εΜτολώΜ για επαΜασύΜΎεση!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "ΞεκιΜάει η επαΜασύΜΎεση"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Αποτυχία εκτέλεσης αρχείου εΜτολώΜ επαΜασύΜΎεσης!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "ΕπαΜασυΜΎέΞηκε, Μέα ΎιέυΞυΜση IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "ΔεΜ έχει αποΌείΜει αρκετός χώρος στηΜ συσκευή"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Αποτυχία σύΜΎεσης Όε τοΜ λογαριασΌό %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "ΛάΞος κωΎικός"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "ΣτοΜ λογαριασΌό %s ΎεΜ έχει αποΌείΜει αρκετή κίΜηση ΎεΎοΌέΜωΜ, Ξα γίΜει πάλι έλεγχος σε 30 λεπτά"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Ο λογαριασΌός %s έχει λήΟει, Ξα γίΜει ΟαΜά έλεγχος σε 1 ώρα"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Ίτάσατε στο Όέγιστο όριο λήψεωΜ"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "ΣφάλΌα κατά τηΜ εισαγωγή %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "ΔεΜ φορτώΞηκε κάποιος hoster"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "ΕΜεργοποιήστε τις απευΞείας λήψεις στοΜ Bitshare λογαριασΌό σας"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Απαιτείται πιστοποίηση (όΜοΌα χρήστη:κωΎικός)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Παρακαλώ εισάγετε το λογαριασΌό σας %s ή απεΜεργοποιήστε αυτό το πρόσΞετο"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Υπήρχε κώΎικας HTML στο αρχείο που ΌεταφορτώΞηκε (%s)...πρόβληΌα αΜακατεύΞυΜσης ίσως; Η Όεταφόρτωση Ξα επαΜεκκιΜήσει."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "΀ο αρχείο είΜαι προσωριΜά Όη ΎιαΞέσιΌο"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: αΜαΌοΜή ΌεταΟύ τωΜ λήψεωΜ %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: αΜαΌοΜή για captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "΀ο ληφΞέΜ αρχείο ήταΜ άΎειο"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "΀ο κλειΎί API ΎεΜ είΜαι έγκυρο"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: ΔεΜ απέΌειΜε αρκετή κίΜηση ΎεΎοΌέΜωΜ"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Υπέρβαση ορίου κίΜησης ΎεΎοΌέΜωΜ"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Traffic Share (απευΞείας λήψη)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Κατεβάζετε ήΎη από αυτή τη ΎιεύΞυΜση ΙΡ, αΜαΌοΜή 60 ΎευτερολέπτωΜ"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Μη έγκυρος κώΎικας , η λήψη Ξα γίΜει επαΜεκκίΜηση"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: ΔεΜ υπάρχουΜ ελέυΞερα slots"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Χρειάζεστε έΜαΜ λογαριασΌό premium για αυτό το αρχείο"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "΀ο όΜοΌα αρχείου αΜαφέρΞηκε ως Όη έγκυρο"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "ΣφάλΌα ταυτόχροΜης λήψης, αΜαΌοΜή 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "ΔεΜ έχετε συΜΎεΞεί."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Η αποκρυπτογράφηση απέτυχε"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Η ΎιεύΞυΜση URL ΎεΜ παρείχε καΜέΜα κλειΎί αρχείου"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "ΚωΎικός σφάλΌατος:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "΀ο αρχείο ΎεΜ υπάρχει"
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** ΀α πρόσΞετα έχουΜ αΜαβαΞΌιστεί, παρακαλώ επαΜεκκιΜήστε το pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "΀α πρόσΞετα αΜαβαΞΌίστηκαΜ και ΟαΜαφορτώΞηκαΜ"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "ΔεΜ υπάρχουΜ αΜαβαΞΌίσεις για τα πρόσΞετα"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "ΔεΜ βρέΞηκε αΜαβάΞΌιση για το pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** ΕίΜαι ΎιαΞέσιΌη Όια καιΜούρια έκΎοση (%s) του pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Κατεβάστε απο εΎώ: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "ΑΎυΜαΌία σύΜΎεσης στοΜ κεΜτρικό ΎιακοΌιστή για έλεγχο εΜηΌερώσεωΜ"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Νέα έκΎοση του%(type)s | %(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "ΣφάλΌα κατά τηΜ αΜαβάΞΌιση του %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "ΑσυΌφωΜία έκΎοσης"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Η λήψη ολοκληρώΞηκε: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Αίτηση για Μέο Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "ΑπάΜτηση Όε 'c %s κείΌεΜο για το captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Παρακαλώ προσΞέστε πρώτα τοΜ λογαριαΌό σας στο premium.to και επαΜεκκιΜήστε το pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "ΠροστέΞηκε το %s από HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "ΔεΜ έχει εγκατασταΞεί το %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Αποτυχία εΜεργοποίησης του %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "ΕΜεργοποιήΞηκε"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "ΔεΜ έχουΜ εΜεργοποιηΞεί πρόσΞετα για αποσυΌπίεση"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "΀ο πακέτο %s έχει ΎροΌολογηΞεί για αποσυΌπίεση αργότερα"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Έλεγχος του πακέτου %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "ΑποσυΌπίεση στο %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "ΔεΜ βρέΞηκαΜ αρχεία για αποσυΌπίεση"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "αποσυΌπιέζεται"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "ΠροστατευΌέΜο Όε κωΎικό"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "ΛάΞος κωΎικός"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Διαγραφή %s αρχείωΜ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Η αποσυΌπίεση τελείωσε"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "ΣφάλΌα στο συΌπιεσΌέΜο αρχείο"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "ΑσυΌφωΜία CRC"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "ΆγΜωστο ΣφάλΌα"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Ο ορισΌός του Χρήστη και της ΟΌάΎας απέτυχαΜ"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Η Ξύρα 9666 χρησιΌοποιείται ήΎη"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "αποΌέΜουΜ %s πόΜτοι"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Αποτυχία αποστολής απάΜτησης."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Ο λογαρισΌός σας CaptchaTrader ΎεΜ έχει αρκετούς πόΜτους"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Η λίστα Crypter ΎεΜ βρέΞηκε"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Η λίστα Crypter είΜαι κεΜή"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Η λήψη ολοκληρώΞηκε: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Νέο CaptchaID από τη Όεταφόρτωση: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Ο λογαριασΌός σας Captcha 9kw.eu ΎεΜ έχει αρκετούς πόΜτους"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "ΕγκατεστηΌέΜα αρχεία εΜτολώΜ για το %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "΀ο αρχείο εΜτολώΜ ΎεΜ είΜαι εκτελέσιΌο:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "ΣφάλΌα στο %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Ο λογαριασΌός στο ExpertDecoders ΎεΜ έχει αρκετούς πόΜτους"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Παρακαλώ προσΞέστε πρώτα τοΜ λογαριαΌό σας στο rehost.to και επαΜεκκιΜήστε το pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Παρακαλώ προσΞέστε πρώτα έΜαΜ έγκυρο λογαριαΌό στο premiumize.me και επαΜεκκιΜήστε το pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "αποΌέΜουΜ %d πόΜτοι"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "΀ο PIL και το tesseract ΎεΜ είΜαι εγκαταστηΌέΜα και ΎεΜ υπάρχει πρόγραΌΌα-πελάτης συΜΎεΌέΜος για αποκρυπτογράφηση captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Η ρύΞΌιση χρηστώΜ και οΌάΎωΜ απέτυχε: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "ΠροστέΞηκαΜ %(count)d σύΜΎεσΌοι στο πακέτο #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "ΚαΌία ΌηχαΜή js ΎεΜ βρέΞηκε, παρακαλώ εγκαταστήσετε Όια απο τις παρακάτω Spidermonkey, ossp-js, pyv8, rhino "
+
diff --git a/locale/el/LC_MESSAGES/pyLoadCli.po b/locale/el/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..bec3abe08
--- /dev/null
+++ b/locale/el/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Greek\n"
+"Language: el_GR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "ΔιασύΜΎεση ΓραΌΌής ΕΜτολώΜ"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Λήψεις:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "΀αχύτητα:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "ΜέγεΞος:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "Ολοκλήρωση σε: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "αΜαΌοΜή: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Κατάσταση:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "σε παύση"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "εκτελείται"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "συΜολική ταχύτητα"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Αρχεία στηΜ ουρά"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "ΣύΜολο"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr " ΜεΜού:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " ΠροσΞήκη ΣυΜΎέσΌωΜ"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Διαχείριση Ουράς"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Διαχείριση Συλλέκτη "
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " ΕΜεργοποίηση/ΑΎραΜοποίηση"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " ΀ερΌατισΌός ΔιακοΌιστή"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " ΈΟοΎος"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Χρήση Όε: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Έλεγχος %d συΜΎέσΌωΜ:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "΀ο αρχείο ΎεΜ υπάρχει."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "΀ο pyLoad τερΌατίστηκε"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "ΕΌφαΜίζει τηΜ κατάσταση του ΎιακοΌιστή"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "ΕΌφαΜίζει τις λήψεις σε ουρά"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "ΕΌφαΜίζει τις λήψεις στο συλλέκτη"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "ΠροσΞέτει το πακέτο στηΜ ουρά"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "ΠροσΞέτει το πακέτο στο συλλέκτη"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Διαγραφή αρχείου από Ουρά/Συλλεκτη"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Διαγραφή πακέτωΜ από Ουρά/Συλλέκτη"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "ΜετακίΜηση πακέτωΜ από τηΜ Ουρά στο Συλλέκτη και αΜτίστροφα"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "ΕπαΜεκκίΜηση αρχείωΜ"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "ΕπαΜεκκίΜηση πακέτωΜ"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Έλεγχος κατάστασης σύΜΎεσης. Λειτουργεί Όε τοπικά container"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Έλεγχος κατάστασης σύΜΎεσης εΜός αρχείου container"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Παύση του ΎιακοΌιστή"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "συΜέχιση λήψεωΜ"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "ΕΜαλλαγή παύσης/εκτέλεσης"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "τερΌατισΌός ΎιακοΌιστή"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Λίστα εΜτολώΜ:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "ΑΎυΜαΌία εγγραφής του αρχείου ρυΞΌίσεωΜ του χρήστη"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Απαιτείται το py-openssl για σύΜΎεση σε αυτόΜ το pyLoad Core."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "ΔιεύΞυΜση:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Θύρα:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "ΌΜοΌα χρήστη:"
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "ΚωΎικός χρήστη:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "΀α στοιχεία σύΜΎεσης είΜαι λάΞος."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "ΑΎυΜαΌία σύΜΎεσης στη ΎιεύΞυΜση %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Απαιτείται το py-openssl για σύΜΎεση σε αυτόΜ τοΜ pyLoad Core."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "H αλληλεπιΎραστική λειτουργία αγΜοήΞηκε εφόσοΜ Ύώσατε κάποιες εΜτολές."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "ΠροσΞήκη Πακέτου:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Εισάγετε όΜοΌα για το καιΜούργιο πακέτο"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Πακέτο: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "ΑΜάλυση τωΜ συΜΎέσΌωΜ που Ξέλετε Μα προσΞέσετε. "
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Πληκτρολογείστε %s όταΜ τελειώσετε. "
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "ΣύΜΎεσΌοι που προστέΞηκαΜ:"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "επιστροφή στο αρχικό ΌεΜού"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Διαχείριση πακέτωΜ:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Διαχείριση ΣυΜΎέσΌωΜ:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "΀ι Ξέλετε Μα ΌετακιΜήσετε;"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "΀ί Ξέλετε Μα Ύιαγράψετε;"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "΀ί Ξέλετε Μα επαΜεκκιΜήσετε;"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "ΕπιλέΟτε εΜέργεια ή πληκτρολογήστε αριΞΌό πακέτου."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "Ύιαγραφή"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "ΌετακίΜηση"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "επαΜεκκίΜηση"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - προηγούΌεΜο"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - επόΌεΜο"
+
diff --git a/locale/el/LC_MESSAGES/setup.po b/locale/el/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..0a5ba97a6
--- /dev/null
+++ b/locale/el/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Greek\n"
+"Language: el_GR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Καλώς ήλΞατε στο ΒοηΞό ΡύΞΌισης του pyLoad"
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Θα γίΜει έλεγχος και βασική ρύΞΌιση του συστήΌατός σας, ώστε Μα εκτελεστεί το pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Η τιΌή στις αγκύλες [] είΜαι πάΜτα η προεπιλεγΌέΜη τιΌή,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "σε περίπτωση που ΎεΜ επιΞυΌείτε αλλαγή ή αΌφιβάλλετε για τηΜ επιλογή σας, απλά πατήστε enter. "
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "ΜηΜ ΟεχΜάτε: Μπορείτε πάΜτα Μα εκτελέσετε ΟαΜά τοΜ ΒοηΞό, εισάγοΜτας τη παράΌετρο --setup ή -s, κατά τηΜ εκτέλεση του pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "ΑΜ αΜτιΌετωπίσετε προβλήΌατα Όε το ΒοηΞό, πατήστε CTRL-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "για Μα εγκαταλείψετε και Μα αποτρέψετε τηΜ αυτόΌατη εκτέλεσή του."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "ΌταΜ είστε έτοιΌος για τοΜ έλεγχο του συστήΌατος, πατήστε enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "ΑπαιτούΜται τα pycurl, sqlite και python 2.5, 2.6 ή 2.7 για Μα εκτελέσετε το pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Παρακαλώ κάΜετε τις ΎιορΞώσεις και εκτελέστε ΟαΜά το pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Η εγκατάσταση Ξα τερΌατιστεί."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Ο έλεγχος του συστήΌατος ολοκληρώΞηκε, πατήστε το enter για Μα Ύείτε τα αποτελέσΌατα."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Κατάσταση ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "αποκρυπτογράφηση περιεχόΌεΜου"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "σύΜΎεση ssl"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "αυτόΌατη αποκρυπτογράφηση captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Διεπαφή ιστού"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Χαρακτηριστικά ΎιαΞέσιΌα:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Χαρακτηριστικά που λείπουΜ:"
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "ΎεΜ είΜαι ΎιαΞέσιΌο το py-crypto"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Αυτό απαιτείται αΜ Ξέλετε Μα αποκρυπτογραφήσετε αρχεία container."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "ΎεΜ είΜαι ΎιαΞέσιΌο το SSL"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Αυτό απαιτείται για τηΜ πραγΌατοποίηση ασφαλής σύΜΎεσης Όε τοΜ πυρήΜα ή το Webιnterface."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "ΑΜ Ξέλετε Μα έχετε πρόσβαση ΌόΜο τοπικά στο pyLoad, το ssl ΎεΜ είΜαι χρήσιΌο."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "ΎεΜ υπάρχει ΎιαΞέσιΌη αΜαγΜώριση Captcha"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Χρειάζεται ΌόΜο από Όερικούς ΎιακοΌιστές ΎιαΌοιρασΌού αρχείωΜ και σαΜ ΎωρεάΜ χρήστης."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "΀ο GUI ΎεΜ είΜαι ΎιαΞέσιΌο"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "΀ο Γραφικό ΠεριβάλλοΜ Χρήστη."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "ΎεΜ βρέΞηκε ΌηχαΜή JavaScript"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Θα το χρειαστείτε για Όερικούς συΜΎέσΌους Click'N'Load. Εγκαταστήστε Spidermonkey, ossp-js, pyv8 ή rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "ΑΜ Ξέλετε Όπορείτε Μα ακυρώσετε τηΜ εγκατάσταση τώρα και Μα ΎιορΞώσετε κάποιες εΟαρτήσεις."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "ΣυΜέχιση Όε τηΜ εγκατάσταση;"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Θέλετε Μα αλλάΟετε τη ΎιαΎροΌή του αρχείου αποΞήκευσης τωΜ ρυΞΌίσεωΜ; Η τρέχουσα είΜαι %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "ΑΜ χρησιΌοποιήτε το pyLoad σε εΟυπηρετητή ή το ΎιαΌέρισΌα home βρίσκεται σε εσωτερική ΌΜήΌη flash, Ξα ήταΜ καλή ιΎέα Μα το αλλάΟετε."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Αλλαγή ΎιαΎροΌής αποΞήκευσης αρχείου ρυΞΌίσεωΜ;"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Θέλετε Μα ορίσετε στοιχεία σύΜΎεσης και Μα κάΜετε τις βασικές ρυΞΌίσεις;"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Αυτό συΜιστάται για τηΜ πρώτη εκτέλεση."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "ΔηΌιουργία βασικής ρύΞΌισης;"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Θέλετε Μα ρυΞΌίσετε το SSL;"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "ΡύΞΌιση SSL;"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Θέλετε Μα ρυΞΌίσετε το περιβάλλοΜ Web;"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "ΡύΞΌιση του Web Interface;"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Η εγκατάσταση ολοκληρώΞηκε Όε επιτυχία."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Πατήστε Enter για Μα εΟέλΞετε και Μα επαΜεκκιΜήσετε το pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Έλεγχος ΣυστήΌατος ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Η έκΎοση της python που χρησιΌοποιείτε είΜαι πολύ Μέα, παρακαλώ χρησιΌοποιήστε Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Η έκΎοση της python που χρησιΌοποιείτε είΜαι πολύ παλιά, παρακαλώ χρησιΌοποιήστε τουλάχιστοΜ Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "ΈκΎοση Python: ΟΚ"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Η έκΎοση %s του jinja2 που έχετε εγκατεστηΌέΜη φαίΜεται πολύ παλιά."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Μπορείτε Μα συΜεχίστε, αλλά αΜ ΎεΜ Ύουλεύει το Web Interface,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "παρακαλώ αΜαβαΞΌίστε το ή απεγκαταστήστε το, το pyLoad συΌπεριλαΌβάΜει Όια επαρκή βιβλιοΞήκη jinja2."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "ΜηχαΜή JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Βασική Εγκατάσταση ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "΀α ακόλουΞα στοιχεία σύΜΎεσης είΜαι έγκυρα για τη ΓραΌΌή ΕΜτολώΜ, το GUI και το περιβάλλοΜ Web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "ΌΜοΌα Χρήστη"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Οι εΟωτερικοί πελάτες (για το GUI, τη ΓραΌΌή εΜτολώΜ ή άλλο) χρειάζοΜται αποΌακρυσΌέΜη πρόσβαση για Μα λειτουργήσουΜ Όέσω Ύικτύου."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "ΌΌως, αΜ Ξέλετε Μα Μα χρησιΌοποιήσετε ΌοΜο το Web Interface, Όπορείτε Μα το απεΜεργοποιήσετε για Μα γλυτώσετε RAM."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "ΕΜεργοποίηση αποΌακρυσΌέΜης πρόσβασης"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Γλώσσα"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Ίάκελος ΌεταφορτώσεωΜ"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Μέγιστος αριΞΌός ταυτόχροΜωΜ λήψεωΜ"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Χρήση ΕπαΜασύΜΎεσης;"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "΀οποΞεσία αρχείου εΜτολώΜ επαΜασύΜΎεσης"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Εγκατάσταση ΠεριβάλλοΜτος Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "ΕΜεργοποίηση περιβάλλοΜτος Web;"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "ΔιεύΞυΜση που Ξα \"ακούει\". ΑΜ χρησιΌοποιήσετε 127.0.0.1 ή localhost, το περιβάλλοΜ Web Ξα είΜαι προσβάσιΌο ΌόΜο τοπικά."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "ΔιεύΞυΜση"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Θύρα"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "΀ο pyLoad προσφέρει αρκετoύς ΎιακοΌιστές υποστήριΟης. ΑκολουΞεί σύΜτοΌη επεΟήγηση."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "ΠροεπιλεγΌέΜος ΎιακοΌιστής, η καλύτερη επιλογή αΜ ΎεΜ γΜωρίζετε ποιοΜ Μα ΎιαλέΟετε."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Αυτός ο ΎιακοΌιστής υποστηρίζει SSL και είΜαι καλή εΜαλλακτική λύση σε σχέση Όε τοΜ προεπιλεγΌέΜο."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Μπορεί Μα χρησιΌοποιήΞεί από τοΜ apache και το lighttpd, απαιτείται παραΌετροποίηση, η οποία ΎεΜ είΜαι πολύ εύκολη."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Πολύ γρήγορη εΜαλλακτική λύση, γραΌΌέΜη σε C, απαιτεί libev και γΜώσεις linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Κατεβάστε το από εΎώ: https://github.com/jonashaag/bjoern, κάΜτε το compile,"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "και αΜτιγράψτε το bjoern.so στο pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Προσοχή: Σε σπάΜιες περιπτώσεις ο προεπιλεγΌέΜος ΎιακοΌιστής ΎεΜ Ύουλεύει. ΑΜ παρατηρήσετε προβλήΌατα Όε το Web Interface,"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "επιστρέψτε εΎώ και αλλάΟτε τοΜ Όε τοΜ threaded."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "ΔιακοΌιστής"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Εγκατάσταση SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Εκτελέστε αυτές τις εΜτολές από τοΜ φάκελο παραΌετροποίησης του pyLoad για Μα ΎηΌιουργήσετε πιστοποιητικά SSL:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "ΑΜ τελειώσατε και όλα πήγαΜ καλώς, τώρα Όπορείτε Μα εΜεργοποιήσετε το SSL."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "ΕΜεργοποίηση SSL;"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "ΕπιλέΟτε εΜέργεια"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - ΔηΌιουργία/ΕπεΟεργασία χρήστη"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Λίστα χρηστώΜ"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Διαγραφή χρήστη"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - ΀ερΌατισΌός"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Χρήστες"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "ΟρισΌός καιΜούριας ΎιαΎροΌής ρυΞΌίσεωΜ, ΎεΜ Ξα ΌεταφερΞούΜ οι τρέχουσες ρυΞΌίσεις!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "ΔιαΎροΌή αρχείου ρυΞΌίσεωΜ"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Η ΎιαΎροΌή ρυΞΌίσεωΜ ορίστηκε, η εγκατάσταση τώρα Ξα κλείσει, παρακαλώ κάΜτε επαΜεκκίΜηση για Μα συΜεχίστε."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Πατήστε Enter για έΟοΎο."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Ο ορισΌός της ΎιαΎροΌής ρυΞΌίσεωΜ απέτυχε: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: λείπει"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "ΚωΎικός χρήστη:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Ο κωΎικός είΜαι πολύ Όικρός. ΧρησιΌοποιήστε τουλάχιστοΜ 4 χαρακτήρες."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "ΚωΎικός (ΟαΜά):"
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Οι κωΎικοί ΎεΜ ταιριάζουΜ"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "Μαι"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "σωστό"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "όχι"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "λάΞος"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "ΛαΜΞασΌέΜη εισαγωγή"
+
diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoad.mo b/locale/en/LC_MESSAGES/pyLoad.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoadCli.mo b/locale/en/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/pyLoadGui.mo b/locale/en/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/en/LC_MESSAGES/setup.mo b/locale/en/LC_MESSAGES/setup.mo
deleted file mode 100755
index 1767783dd..000000000
--- a/locale/en/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/eo/LC_MESSAGES/django.po b/locale/eo/LC_MESSAGES/django.po
new file mode 100644
index 000000000..beb7646ec
--- /dev/null
+++ b/locale/eo/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Esperanto\n"
+"Language: eo_UY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/eo/LC_MESSAGES/pyLoad.po b/locale/eo/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..23f1b78f1
--- /dev/null
+++ b/locale/eo/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Esperanto\n"
+"Language: eo_UY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/eo/LC_MESSAGES/pyLoadCli.po b/locale/eo/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..10d313833
--- /dev/null
+++ b/locale/eo/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Esperanto\n"
+"Language: eo_UY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/eo/LC_MESSAGES/setup.po b/locale/eo/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..6eb3fbddd
--- /dev/null
+++ b/locale/eo/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Esperanto\n"
+"Language: eo_UY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/es/LC_MESSAGES/django.mo b/locale/es/LC_MESSAGES/django.mo
deleted file mode 100644
index 899597735..000000000
--- a/locale/es/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 000000000..dd1a7f293
--- /dev/null
+++ b/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Spanish\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Solicitud de nuevo Captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Por favor lee el texto en el captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad se reinició"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "apagado"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Éxito"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "encendido"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "¿Estás seguro de que quieres salir de pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Reiniciar enlace"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Borrar enlace"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Por favor, introduce un nombre de paquete."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Por favor, haz clic en la posición del captcha correcto."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Se produjo un error."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "La carpeta está vacía"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Fallidos"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "No hay captchas."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Las contraseñas no coinciden."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Ajustes guardados."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nueva carpeta"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "¿Estás seguro de que quieres reiniciar pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperando a %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Descargas activas"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Inicio"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "Cola"
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "Recolector"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Descargas"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Registros"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuración"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nombre"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Estado"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Información"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Tamaño"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Progreso"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Iniciar sesión"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Usuario"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Contraseña"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Tu nombre de usuario y contraseña no coinciden. Por favor inténtalo de nuevo."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Para reiniciar tus datos de inicio de sesión o añadir un usuario ejecuta:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Borrar terminados"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Reiniciar Fallidos"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Carpeta:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Contraseña:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Editar paquete"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Editar los detalles del paquete siguiente."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Nombre del paquete."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Carpeta"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nombre de la subcarpeta para estas descargas."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lista de contraseñas usadas para unrar."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Enviar"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Reiniciar"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Se ha cerrado sesión correctamente."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Ruta"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absoluta"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativa"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nombre"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "tamaño"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tipo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "última modificación"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "directorio padre"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "sin contenido"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "General"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Complementos"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Cuentas"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Elige una sección del menú"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "Premium"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Válido hasta"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Tráfico restante"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tiempo"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Máx. en Paralelo"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "¿Borrar?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "válido"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "inválido"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "sí"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "no"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Añadir"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Agregar cuenta"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Introduce los datos de tu cuenta para usar características premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Tu nombre de usuario."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "La contraseña para esta cuenta."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Tipo"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Elige el proveedor de alojamiento para tu cuenta."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Iniciar"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "anterior"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "siguiente"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fin"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Novedades"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Soporte"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistema"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr "Python:"
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "S.O.:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "versión de pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Carpeta de instalación:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Carpeta de configuración:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Carpeta de descarga:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Espacio Libre:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Idioma:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Puerto de interfaz web:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Puerto de interfaz remota:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Configuración"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Administrador de archivos"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Añadir paquete"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Pega tus enlaces o carga un archivo contenedor."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Nombre del nuevo paquete."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Enlaces"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Pega tus enlaces o cualquier texto aquí y pulsa el botón filtrar."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrar urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Contraseña para el archivo-RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Archivo"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Cargar contenedor."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Destino"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Leyendo captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "Captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "El captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Texto"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Introduzca el texto en el captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Cerrar"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interfaz web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "¡Actualización de pyLoad disponible!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins actualizados, ¡por favor reinicia!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Esperando captcha"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Salir"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrar"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Información"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "¡Por favor, inicie sesión!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Parar"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Descarga:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Reconectar:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Velocidad:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Activo:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Recargar"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "cargando"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Volver al principio"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Salir de pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Reiniciar pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Para añadir un usuario o cambiar contraseñas usa:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Importante: ¡El usuario Admin tiene siempre todos los permisos!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Cambiar Contraseña"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Admin"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Permisos"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "cambiar"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Introduce tu contraseña actual y la deseada."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Usuario"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Contraseña actual"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nueva contraseña"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "La nueva contraseña."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nueva contraseña (repetir)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Por favor, repite la nueva contraseña."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "No tienes permiso para acceder a esta página."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "No se encuentra el directorio de descarga."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "ilimitado"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "no disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Ejecuta pyload.py -s para acceder a la instalación."
+
diff --git a/locale/es/LC_MESSAGES/pyLoad.mo b/locale/es/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 90aa70f1d..000000000
--- a/locale/es/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/pyLoad.po b/locale/es/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..bd029162f
--- /dev/null
+++ b/locale/es/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Spanish\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Señal de salida recibida"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad ya está ejecutándose con pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Cambio de grupo fallido: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Cambio de usuario fallido: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "carpeta para los registros"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Iniciando"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Usando directorio de inicio: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto va a decodificar archivos contenedores"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "carpeta para archivos temporales"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "carpeta de descargas"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL para conexión segura"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Moviendo la configuración antigua de usuario a la BD"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Por favor, comprueba tus datos de inicio de sesión con ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Se han eliminado todos los enlaces"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Tiempo de descarga: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Espacio libre: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Activando Cuentas..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Activando Plugins..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad está funcionando"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "reiniciando pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad se cierra"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Instalar %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "no se pudo encontrar %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "no se pudo crear %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "apagando..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "ha ocurrido un error mientras se apagaba"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyload terminado desde el Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "El archivo de la base de datos fue eliminado por ser de una versión incompatible."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "El archivo de la base de datos NO pudo convertirse."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "La base de datos se convirtió de la v2 a la v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "La base de datos se convirtió de la v3 a la v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Convirtiendo la BD Django antigua"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "Terminado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "sin conexión"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "en línea"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "en cola"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "omitido"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "esperando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temp. sin conexión"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "comenzando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "fallido"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "abortado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "descifrando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personalizado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "descargando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "procesando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "desconocido"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paquete terminado: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Usando SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Error de backend remoto: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Iniciando %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Fallo al cargar servidor %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperando a %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certificados SSL no encontrados."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Lo sentimos, ya no admitimos iniciar %s directamente en pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Puedes utilizar el servidor con hilos que ofrece un buen rendimiento y ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "por supuesto puedes usar aún tu %s existente con el servidor fastcgi de pyLoad"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "las configuraciones de ejemplo se encuentran en el directorio pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "¡No puedes usar %(server)s, python-flup no está instalado!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Error importando servidor ligero: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Debes descargar y compilar bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Copia el módulo boern.so a la carpeta pyload/lib o usa la instalación setup.py"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Por supuesto debes estar familiarizado con linux y saber cómo compilar software"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Servidor en modo threaded, debido a problemas de rendimiento en Windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Este servidor no ofrece SSL, considera el usar el servidor con hilos en su lugar"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web incorporado: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web SSL con hilos : %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web con hilos: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Iniciando servidor fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Iniciando servidor web ligero (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "No tienes permiso para acceder a esta página."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "No se encuentra el directorio de descarga."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "ilimitado"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "no disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Ejecuta pyload.py -s para acceder a la instalación."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Descarga por trozos fallida, recurro a una única conexión | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Iniciando descarga: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Descarga finalizada: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Plugin %s no encuentra una función."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Descarga abortada: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Descarga reiniciada: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Descarga está fuera de servicio: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "La descarga está temporalmente fuera de servicio: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Descarga fallida: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "No pude conectar al servidor o conexión reiniciada, esperando 1 minuto y reintentando."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Descarga omitida: %(name)s debido a %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Descifrado comienza en: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Descifrado fallido: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Reintentando %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Obtención de información para %(name)s fallida | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Error ejecutando enlaces: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Falló la activación de %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Plugins activos: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Plugins desactivados: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Reconexión Fallida: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "¡Script de reconexión no encontrado!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Iniciando reconexión"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "¡Fallo al ejecutar script de reconexión!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Reconectado, nueva dirección IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "No hay suficiente espacio libre en el dispositivo"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "No pude iniciar sesión con cuenta %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Contraseña Incorrecta"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "La hora %s tiene un formato incorrecto, use: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "La cuenta %s no tiene tráfico suficiente, se comprobará otra vez en 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "La cuenta %s ha expirado, comprobando de nuevo en 1 hora"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Limite de descargas alcanzado"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s tiene un patrón inválido."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Error importando %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "No se cargó ningún proveedor"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Activa descarga directa en tu cuenta de Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList no puede ser limpiado."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Ajustes de cuenta eliminados, debido al nuevo formato de la configuración."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorización requerida (usuario:contraseña)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Por favor, introduce tu cuenta de %s o desactiva este plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Había Código HTML en el archivo Descargado (%s)... ¿error de redireccionamiento? La Descarga se reiniciará."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Archivo no disponible temporalmente"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: esperando entre descargas %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: esperando captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "El archivo descargado estaba vacío"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Clave de API inválida"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: No hay suficiente tráfico"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Tráfico excedido"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Tráfico compartido (descarga directa)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Ya estás descargando desde esta dirección IP, esperando 60 segundos"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Código de autenticación inválido, la descarga se reiniciará"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: No quedan espacios disponibles"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Necesitas una cuenta premium para este archivo"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Nombre de archivo inválido"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Error con descargas en paralelo, esperando 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "No has iniciado sesión."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Error de descifrado"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "No se proporcionó archivo de clave en la URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Código de error:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "El archivo no existe."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Los plugins se han actualizado, por favor reinicie pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins actualizados y cargados de nuevo"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "No hay disponibles actualizaciones de plugins"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "No hay actualizaciones de pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Nueva versión %s de pyLoad disponible ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Descarguela aquí: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "No se puede conectar con el servidor para actualizar"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nueva versión de %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Error mientras se actualizaba %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "No coincide la versión"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Descarga finalizada: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nueva Petición de Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Conteste con 'c %s texto al captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Por favor, añada primero su cuenta premium.to y reinicie pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Añadido %s desde HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s no instalado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "No se puede activar %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Activado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "No se ha activado ningún plugin de extracción"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Paquete %s encolado para su posterior extracción"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Comprobar paquete %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Extraer a %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "No se encontraron archivos para extraer"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "extrayendo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Protegido con contraseña"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Contraseña errónea"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Borrando archivos %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Extracción finalizada"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Error de Archivo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "El CRC no coincide"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Error Desconocido"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Error de configuración de usuario y grupo"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Puerto 9666 ya está en uso"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s créditos restantes"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "No se ha podido enviar la respuesta."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Su cuenta de CaptchaTrader no tiene créditos suficientes"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "No se ha encontrado lista de cifradores"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "La lista de cifradores está vacía"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Descarga finalizada: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nuevo CaptchaID desde carga: %s: %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Su Cuenta Captcha 9kw.eu no tiene suficientes créditos"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Código instalado para %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Código no ejecutable:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Error en %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Su cuenta de ExpertDecoders no suficientes créditos"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Por favor, añada primero su cuenta rehost.to y reinicie pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Por favor, agrega primero una cuenta válida de premiumize.me y reinicia pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d créditos restantes"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil y tesseract no están instalados y no hay cliente conectado para descifrar captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "No se obtuvieron resultados para el captcha en el tiempo asignado de ninguno de los plugins."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Configuración de usuario y grupo fallida: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "No hay cliente conectado para descifrar el captcha"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Paquete %(name)s agregado que contiene %(count)d enlaces"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Agregados %(count)d enlaces al paquete #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "No se detectó ningún motor de js, por favor instale Spidermonkey, ossp-js, pyv8 o rhino"
+
diff --git a/locale/es/LC_MESSAGES/pyLoadCli.mo b/locale/es/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index f969ad0d1..000000000
--- a/locale/es/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/pyLoadCli.po b/locale/es/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..527f03c05
--- /dev/null
+++ b/locale/es/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Spanish\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Interfaz de Línea de Comandos"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Descargas:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Velocidad: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Tamaño: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Finalizado el: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "esperando: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Estado:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "pausado"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "ejecutando"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Velocidad total"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Ficheros en cola"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Total"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Menú:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Añadir enlaces"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Administrar Cola"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Administrar Recolector"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Detener/Reanudar Servidor"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Parar servidor"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Salir"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Por favor usa esta sintaxis: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Comprobando %d enlace(s):"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "El archivo no existe."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad ha finalizado"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Muestra el estado del servidor"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Muestra las descargas en cola"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Muestra las descargas en recolector"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Agregar paquetes a la cola"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Agrega un paquete al recolector"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Borrar Archivos de la Cola/Recolector"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Borrar Paquetes de la Cola/Recolector"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Mover Paquetes de la Cola al Recolector o viceversa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Reiniciar archivos"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Reiniciar paquetes"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Comprobar el estado en-linea, trabaja con el contenedor local"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Comprueba el estado en-linea de un contenedor"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pausar el servidor"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continuar descargas"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Alternar detener/reanudar"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "parar servidor"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista de comandos:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "No se puede escribir el archivo de configuración del usuario"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Necesitas py-openssl para conectar a este Núcleo pyLoad."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Dirección: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Puerto: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Usuario: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Contraseña: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Los datos de inicio de sesión son incorrectos."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "No se puede establecer la conexión a %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Necesitas py-openssl para conectar a este núcleo pyLoad."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Modo interactivo ignorado ya que pasaste algunas órdenes."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Agregar Paquete:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Introduce un nombre para el paquete nuevo"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paquete: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analizar los enlaces que quieres añadir."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Teclea %s cuando acabes."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Enlaces añadidos: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " volver al menú principal"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Administrar Paquetes:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Administrar Enlaces:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "¿Qué quieres mover?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "¿Qué quieres borrar?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "¿Qué quieres reiniciar?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Elige lo que quieres hacer o introduce el número de paquete."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "borrar"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "mover"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "reiniciar"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - anterior"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - siguiente"
+
diff --git a/locale/es/LC_MESSAGES/pyLoadGui.mo b/locale/es/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index a4944783f..000000000
--- a/locale/es/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/setup.mo b/locale/es/LC_MESSAGES/setup.mo
deleted file mode 100644
index 87c4dd811..000000000
--- a/locale/es/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/es/LC_MESSAGES/setup.po b/locale/es/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..223755467
--- /dev/null
+++ b/locale/es/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Spanish\n"
+"Language: es_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "s"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Bienvenido al Asistente de Configuración de pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Se revisará tu sistema y se hará una configuración básica para ejecutar pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "El valor entre corchetes [] siempre es el valor por defecto,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "en caso que no quieras cambiarlo o no estés seguro de que opción elegir, solo presiona entrar."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "No lo olvides: siempre puedes volver a ejecutar este asistente con el parámetro --setup ó -s, cuando inicies pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Si tienes algún problema con el asistente presiona Ctrl-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "para abortar y no permitirle volver a iniciarse automáticamente con pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Cuando estés listo para la revisión del sistema, presiona entrar."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Necesitas pycurl, sqlite y python 2.5, 2.6 ó 2.7 para ejecutar pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Por favor corrige esto y vuelve a ejecutar pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "El asistente de la instalación se cerrará."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "La revisión del sistema ha finalizado, presiona entrar para ver el informe de estado."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Estado ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "descifrando contenedor"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "conexión SSL"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "descifrado automático de captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "GUI"
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interfaz web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "extensión Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Características disponibles:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Características no disponibles: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto no disponible"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Necesario si quieres descifrar archivos contenedores."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL no disponible"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Esto es necesario si quieres establecer una conexión segura con el núcleo o la interfaz web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "SSL no es útil si solamente quieres acceder a pyLoad localmente."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "reconocimiento de captcha no disponible"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Solo necesario para algunos servidores y como usuario gratuito."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI no disponible"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "El Interfaz de Usuario Gráfica."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "motor de JavaScript no encontrado"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Necesario para algunos enlaces Click'N'Load. Instala Spidermonkey, ossp-js, pyv8 o rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Puedes cancelar el asistente ahora y corregir algunas dependencias si quieres."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "¿Desea continuar con el asistente?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "¿Quiere cambiar la ruta de configuración? Actualmente es %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Si usas pyLoad en un servidor o la partición primaria reside en una memoria flash interna, puede ser buena idea cambiarla."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "¿Cambiar la ruta de configuración?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "¿Quiere configurar los datos de acceso y la configuración básica?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Esto se recomienda en la primera ejecución."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "¿Realizar la configuración básica?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "¿Quiere configurar SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "¿Configurar SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "¿Quiere configurar la interfaz web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "¿Configurar la interfaz web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Configuración finalizada con éxito."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Pulse entrar para salir y reiniciar pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Revisión del sistema ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Tu versión de python es demasiado nueva, por favor usa Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Tu versión de python es demasiado antigua, por favor usa al menos Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Versión de Python: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Tu versión %s jinja2 instalada parece demasiado antigua."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Puedes continuar de manera segura aunque la interfaz web no funciona,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "por favor actualícela o desinstálela, pyLoad incluye una biblioteca jinja2 suficiente."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "Motor JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Configuración básica ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Los siguientes datos de acceso son válidos para CLI, GUI e interfaz web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Usuario"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Los clientes externos (GUI, CLI u otros) necesitan acceso remoto para trabajar a través de la red."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "No obstante, si sólo quieres utilizar interfaz web puedes deshabilitarlo para ahorrar ram."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Activar acceso remoto"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Idioma"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Directorio de descargas"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Máximas descargas paralelas"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "¿Usar reconectar?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Ubicación del script de reconexión"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Configuración de la Interfaz Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "¿Activar interfaz web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Dirección de escucha, si usas 127.0.0.1 o localhost, la interfaz web solo podrá ser accesible localmente."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Dirección"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Puerto"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "lapyLoad ofrece varios backends de servidor después de una breve explicación."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Servidor por defecto, la mejor opción si no sabes cual elegir."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Este servidor ofrece SSL y es una buena alternativa al integrado."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Puede ser usado por apache, lighttpd, requiere que lo configures, lo cual no es una tarea fácil."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Una alternativa muy rápida escrita en C, requiere libev y conocimientos de linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Obtenlo de aquí: https://github.com/jonashaag/bjoern, compílalo"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "y copia bjoern.so a pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Atención: En algunos casos raros el servidor integrado no funciona, si notas problemas con la interfaz web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "vuelve aquí y cambia el servidor integrado por el servidor con hilos."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Servidor"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Configuración de SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Ejecuta estos comandos desde la carpeta de configuración pyLoad para crear los certificados ssl:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Si has terminado y todo acabo bien, podrás activar ssl ahora."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "¿Activar SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Elige una acción"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Crear/Editar usuario"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Listado de usuarios"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Eliminar usuario"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Salir"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Usuarios"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Configurando nuevo directorio de configuración, ¡la configuración actual no sera transferida!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Directorio de configuración"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Directorio de configuración cambiado, la instalación se cerrara, por favor reinicia para continuar."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Presiona Entrar para salir."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Ajuste del directorio de configuración fallido: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr "%s: OK"
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: perdido"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Contraseña: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "La contraseña es demasiado corta. Use al menos 4 símbolos."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Contraseña (de nuevo): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Las contraseñas no coinciden."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "sí"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "verdadero"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "v"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "no"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "falso"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Entrada no válida"
+
diff --git a/locale/fa/LC_MESSAGES/django.po b/locale/fa/LC_MESSAGES/django.po
new file mode 100644
index 000000000..7d167ef88
--- /dev/null
+++ b/locale/fa/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Persian\n"
+"Language: fa_IR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/fa/LC_MESSAGES/pyLoad.po b/locale/fa/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..41ff60bc7
--- /dev/null
+++ b/locale/fa/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Persian\n"
+"Language: fa_IR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/fa/LC_MESSAGES/pyLoadCli.po b/locale/fa/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..dd7471605
--- /dev/null
+++ b/locale/fa/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Persian\n"
+"Language: fa_IR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " سرعت: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " حجم: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ؎ناسه: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "وضعیت:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "در حال اجرا"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "سرعت کل"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "فایل‌های درون صف"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "مجموع"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "منو:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " اضافه کردن لینک"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " ؚستن سرور"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " خروج"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "فایل وجود ندارد."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "ؚستن سرور"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "لیست دستورات:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "فایل ٟیکرؚندی کارؚر نمی‌تواند نو؎ته ؎ود"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "آدرس: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "درگاه: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "نام کارؚری: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "رمز عؚور: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "اطلاعات ورود ا؎تؚاه است."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "ؚرای اتصال ØšÙ‡ هسته‌ی pyLoad ØšÙ‡ py-openssl نیاز دارید."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "لینک‌های اضافه ؎ده: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "حذف"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "انتقال"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "راه‌اندازی مجدد"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - قؚلی"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - ؚعدی"
+
diff --git a/locale/fa/LC_MESSAGES/setup.po b/locale/fa/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..e9ad35a8a
--- /dev/null
+++ b/locale/fa/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Persian\n"
+"Language: fa_IR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "رمز عؚور: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po
new file mode 100644
index 000000000..8dac03ec6
--- /dev/null
+++ b/locale/fi/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Finnish\n"
+"Language: fi_FI\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/fi/LC_MESSAGES/pyLoad.mo b/locale/fi/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/fi/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/pyLoad.po b/locale/fi/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..f50353be9
--- /dev/null
+++ b/locale/fi/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Finnish\n"
+"Language: fi_FI\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/fi/LC_MESSAGES/pyLoadCli.mo b/locale/fi/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/fi/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/pyLoadCli.po b/locale/fi/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..d98e4636c
--- /dev/null
+++ b/locale/fi/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Finnish\n"
+"Language: fi_FI\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "KomentorivikÀyttöliittymÀ"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s lataa:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "Nopeus:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "Koko:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "Valmistunut:"
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "Tunnus:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "odotetaan:"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Valikko:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "LisÀÀ linkkejÀ"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Jonon hallinta"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " KerÀÀjÀn hallinta"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Linkkien hallinta"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/fi/LC_MESSAGES/pyLoadGui.mo b/locale/fi/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/fi/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fi/LC_MESSAGES/setup.po b/locale/fi/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..f7fe897c7
--- /dev/null
+++ b/locale/fi/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Finnish\n"
+"Language: fi_FI\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo
deleted file mode 100644
index b7b2a3fb5..000000000
--- a/locale/fr/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 000000000..af432befa
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: French\n"
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Nouvelle demande de captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Merci de lire le texte du captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "PyLoad redémarré"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "Eteint"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "SuccÚs"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "Allumé"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Êtes-vous vraiment sûr de vouloir quitter pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Relancer le lien"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Effacer le lien"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Merci d'entrer un nom de package."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Veuillez cliquer sur le cÎté droit du captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Une erreur est survenue."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Le dossier est vide"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Échoué"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Aucun captcha à lire."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Les mots de passe ne correspondent pas."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "ParamÚtres enregistrés."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nouveau répertoire"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Êtes-vous sûr de vouloir redémarrer pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "attend %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Téléchargements actifs"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Page principale"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "File d'attente "
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "Paquets"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Téléchargements"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuration"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nom"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Status"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Taille"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Progression"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Identifiant"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Nom utilisateur"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Mot de passe"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Votre nom d'utilisateur et votre mot de passe ne correspondent pas. Veuillez réessayer."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Pour changer votre mot de passe ou ajouter un nouvel utilisateur, exécutez : "
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Supprimer les liens terminés"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Redémarrer les liens échoués"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Dossier :"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Mot de passe :"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Modifier le paquet"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Editer les détails du paquet ci-dessous."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Le nom du paquet."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Dossier"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nom du sous-dossier pour ces téléchargements."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Liste des mots de passes utilisés pour décompresser."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Valider"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Réinitialiser"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Vous êtes désormais déconnecté."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Chemin"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolu"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relatif"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nom"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "taille"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "derniÚre modification"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "dossier parent"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "pas de contenu"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Général"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Comptes"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Sélectionnez une section à partir du menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Valide jusqu'au"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Trafic restant"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Temps"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Nombre maximum en parallÚle"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Supprimer ? "
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "valide"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "invalide"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "oui"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "non"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Ajouter"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Ajouter un compte"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Entrez vos informations de compte pour utiliser les fonctionnalités premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Votre nom d'utilisateur."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Le mot de passe pour ce compte."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Choisissez l'hébergeur pour ce compte."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Démarrer"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "préc"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "suiv"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fin"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Nouvelles"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "SystÚme"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr "Python :"
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "OS :"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "Version de pyLoad :"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Dossier d'Installation :"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Dossier de configuration :"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Dossier de téléchargement :"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Espace libre :"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Langue :"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Port de l'interface web :"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Port de l'interface à distance :"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Configurer"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Gestionnaire de fichiers"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Ajouter un paquet"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Recopiez vos liens ou envoyez un conteneur."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Le nom du nouveau paquet."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Liens"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Collez vos liens ici ou n'importe quel texte, et cliquez sur le bouton « Filtrer les URL »."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrer les URL"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Mot de passe de l'archive RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Fichier"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Envoyer un conteneur."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Destination"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Lecture du catcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Le captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Texte"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Veuillez taper le texte du captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Fermer"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interface web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Mise à jour de pyLoad disponible !"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins mis à jour, redémarrez !"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha en attente"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Déconnexion"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administration"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Information"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Veuillez vous connecter !"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Arrêter"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Annuler"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Téléchargement :"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Reconnexion :"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Vitesse :"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Actif :"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Recharger la page"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "chargement"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Revenir en haut"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Quitter pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Redémarrer pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Pour ajouter un utilisateur ou modifier un mot de passe, utilisez :"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Important : L'utilisateur Admin a tous les droits !"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Modifier le mot de passe"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "modifier"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Entrez votre mot de passe actuel et celui souhaité."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Utilisateur"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Mot de passe actuel"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Le nouveau mot de passe."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nouveau mot de passe (répéter)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "S'il vous plaît répétez le nouveau mot de passe."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Vous n'avez pas la permission d'accéder à cette page."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Répertoire de téléchargement introuvable."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "Illimité"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Lancez pyload.py -s pour accéder au paramétrage."
+
diff --git a/locale/fr/LC_MESSAGES/pyLoad.mo b/locale/fr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 95fd73cae..000000000
--- a/locale/fr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/pyLoad.po b/locale/fr/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..fe56c2bfa
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: French\n"
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Signal d'arrêt reçu"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad est déjà lancé avec le pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Echec lors du changement de groupe: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Echec lors du changement d'utilisateur: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "dossier des logs"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Démarrage"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Utilisation du dossier principal : %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto pour décoder les fichiers conteneurs"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "dossier des fichiers temporaires"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "dossier des téléchargements"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL pour les connexions sécurisées"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Déplacement des anciennes configurations utilisateur vers la BDD"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Vérifiez vos identifiants avec ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Tous les liens ont été supprimés"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Temps de téléchargement : %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Espace libre : %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Activation des comptes ..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Activation des Extensions..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad est opérationnel"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "redémarrage de pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad se termine"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installation %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "impossible de trouver %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "impossible de créer %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "arrêt..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "erreur lors de l'arrêt"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad a été tué depuis le Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "La base de données des fichiers est incompatible et a été supprimé."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "La base de données NE peut PAS être convertie."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "La base de données a été convertie de la v2 vers la v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "La base de données a été convertie de la v3 vers la v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Conversion de l'ancienne BDD Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "fini"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "hors ligne"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "en ligne"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "en file d'attente"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "ignoré"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "en attente"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temp. hors ligne"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "démarrage"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "échoué"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "annulé"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "décryptage"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personnalisé"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "téléchargement en cours"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "traitement en cours"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "inconnu"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paquet terminé : %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Utilise SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Erreur du backend distant: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Démarre %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Échec du chargement du backend %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "attend %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certificats SSL non trouvés."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Désolé, nous avons arrêté le support du démarrage de %s directement dans pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Vous pouvez utiliser le serveur \"threaded\" qui offre de bonnes performances et le ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "bien sur vous pouvez toujours utiliser vos %s existants avec pyLoads fastcgi server"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "les exemples de configurations sont localisés dans le répertoire pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Ne peut utiliser %(server)s, python-flup n'est pas installé!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Erreur lors de l'importation du serveur léger : %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Vous devez télécharger et compiler bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Copiez le fichier boern.so dans le dossier pyload/lib, ou utilisez setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Évidemment vous devez être familier avec Linux et savoir comment compiler un logiciel"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Serveur placé en mode “threaded”, en raison de problÚmes de performances connus sous Windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Ce serveur ne permet pas la connexion SSL, vous devriez envisager d'utiliser le mode “threaded” à la place"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Démarrage serveur web interne: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Démarrage serveur web SSL : %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Démarrage serveur web : %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Démarrage serveur webfastcgi : %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Démarrage du serveur léger (bjoern) : %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Vous n'avez pas la permission d'accéder à cette page."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Répertoire de téléchargement introuvable."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "Illimité"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non disponible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Lancez pyload.py -s pour accéder au paramétrage."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Téléchargement par blocs échoué, repli vers une seule connexion | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Le téléchargement commence : %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Téléchargement terminé : %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Il manque une fonction au plugin %s."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Abandon du téléchargement : %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Téléchargement redémarré : %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Le téléchargement est hors-ligne : %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Le téléchargement est temporairement hors ligne: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Téléchargement échoué : %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Impossible de se connecter à l'hÎte ou connexion perdue, prochain essai dans une minute."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Téléchargement ignoré : %(name)s à cause de %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Démarrage du décryptage : %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Décryptage échoué : %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Nouvelle tentative en cours %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Échec de la collecte des infos pour %(name)s | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Erreur de l'exécution du hook : %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Impossible d'activer %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Plugins activés: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Plugins désactivés: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "La reconnexion a échoué : %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Script de reconnexion non trouvé !"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Lancement de la reconnexion"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Echec d'exécution du script de reconnexion !"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Reconnecté, nouvelle IP : %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Il n'y a pas assez de place sur le périphérique"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Impossible de s'identifier avec le compte %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Mauvais mot de passe"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Votre heure %s utilise un format incorrect, utilisez 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Le compte %s n'a pas assez de trafic, re-vérification dans 30 minutes"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Le compte %s a expiré, re-vérification dans 1H"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Limite de téléchargement atteinte"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s a un format invalide."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Erreur d'importation %(name)s : %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Aucun hébergeur chargé"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Activez le téléchargement direct dans votre compte Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "Le fichier links.txt ne peut être vidé."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Paramétrage du compte supprimé, du fait d'un nouveau format de configuration."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorisation requise (identifiant:mot de passe)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Veuillez entrer votre compte %s ou désactivez ce plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Il y avait du code HTML dans le fichier téléchargé (%s)... erreur de redirection ? Le téléchargement va redémarrer."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Fichier temporairement indisponible"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload : attente de %d s entre chaque téléchargements."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload : attendre le captcha pendant %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Le fichier téléchargé est vide"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "La clé de l'API est invalide"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s : il ne reste pas assez de trafic"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Le trafic a été dépassé"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare : partage du trafic (téléchargement direct)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Un téléchargement est déjà en cours depuis cette adresse, 60 secondes d'attente"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Code d'authentification invalide, le téléchargement va redémarrer"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom : aucun slot disponible"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Vous avez besoin d'un compte premium pour ce fichier"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Le nom du fichier est invalide"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Erreur de téléchargement parallÚle, attente pendant 60 secondes."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Vous n'êtes pas identifiés."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Le déchiffrement à échoué"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Aucune clé de fichier fournie dans l'URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Code d'erreur :"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Le fichier n'existe pas."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Les plugins ont été mis à jour, veuillez redémarrer pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins mis à jour et rechargés"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Aucune mise à jour pour les plugins"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Aucune mise à jour pour pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Nouvelle version %s disponible de pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Téléchargez-le ici : http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Incapable de se connecter au serveur pour les mises à jour"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nouvelle version de %(type)s | %(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Erreur lors de la mise à jour de %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Les versions discordent"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Téléchargement terminé : %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nouvelle demande de captcha : %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Répondre avec ' le texte c %s sur le captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Veuillez tout d'abord ajouter votre compte premium.to et redémarrez pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Ajout de %s à partir du HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s non installé"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Impossible d'activer %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Activé"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Pas de plugin d'extraction activé"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Le paquet %s est mis dans la file d'attente pour une extraction ultérieure"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Vérification du paquet %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Extraction vers %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Aucun fichier trouvé pour l'extraction"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "extraction en cours"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Mot de passe protégé"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Mot de passe incorrect"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Suppression de %s fichiers"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Extraction terminée"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Erreur d'archive"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC différents"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Erreur inconnue"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "La définition de l'utilisateur et du groupe a échoué"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load : port 9666 déjà en cours d'utilisation"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s crédits restants"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Impossible d'envoyer une réponse."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Votre compte CaptchaTrader n'a pas assez de crédits"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Liste des crypteurs non trouvée"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "La liste des crypteurs est vide"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Téléchargement fini : %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nouveau CaptchaID de l'upload: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Votre compte de captcha 9kw.eu n'a pas assez de crédits"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Scripts installés pour %s : "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script non exécutable :"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Erreur dans %(script)s : %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Votre compte ExpertDecoders n'a pas assez de crédits"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Veuillez d'abord ajouter votre compte rehost.to et redémarrez pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Veuillez d'abord ajouter un compte valide premiumize.me et redémarrez pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d crédits restants"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil et tesseract ne sont pas installés et il n'y a pas de client connecté pour le déchiffrement des captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Pas de résultats pour le captcha obtenu dans le temps impartis par aucun des plugins."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "La définition de l'utilisateur et du groupe a échoué :: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Pas de Client connecté pour le décryptage captcha"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Ajout package %(name)s contenant %(count)d liens"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Ajout de %(count)d liens au paquet #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Aucun moteur JS détecté, s'il vous plaît installez soit SpiderMonkey, ossp-js, pyv8 ou rhino"
+
diff --git a/locale/fr/LC_MESSAGES/pyLoadCli.mo b/locale/fr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 460641599..000000000
--- a/locale/fr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/pyLoadCli.po b/locale/fr/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..9fd31d4cd
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: French\n"
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Interface en ligne de commande"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s téléchargement(s) :"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Vitesse : "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Taille : "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Fini dans : "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID : "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "en attente : "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Statut:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "en pause"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "en cours"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Vitesse totale"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Fichiers en attente"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Menu :"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Ajouter des liens"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "Gérer la file d'attente"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "Gérer le collecteur"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "Mettre en pause/Restaurer le serveur"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Stopper le serveur"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Quitter"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Utilisez cette syntaxe : add <Nom paquet> <lien> <lien2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Vérification de %d liens :"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Le fichier n'existe pas."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad a quitté"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Affiche le statut du serveur"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "affiche les téléchargements en file d'attente"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "affiche les téléchargements dans le collecteur"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Ajouter le paquet à la file d'attente"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Ajouter le paquet au collecteur"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Supprimer les fichiers de la file d'attente/du collecteur"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Supprimer les paquets de la file d'attente/du collecteur"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Déplacer les paquets de la file d'attente au collecteur ou vice versa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Redémarrer les fichiers"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Redémarrer les paquets"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Vérifie le statut en ligne (fonctionne avec un conteneur local)"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Vérifie le statut en ligne d'un fichier conteneur"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Mettre le serveur en pause"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continuer les téléchargements"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Basculer pause/reprendre"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "stopper le serveur"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Liste des commandes :"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Impossible d'écrire le fichier de configuration"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Vous avez besoin de py-openssl pour vous connecter à ce Core pyLoad."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adresse: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port : "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Nom d'utilisateur: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Mot de passe : "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Les identifiants sont incorrects."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Impossible d'établir la connexion à %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Vous avez besoin de py-openssl pour vous connecter au moteur de Pyload."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Mode interactif ignoré jusqu'à ce que vous entriez des commandes."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Ajouter un paquet:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Entrez un nom pour le nouveau paquet"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paquet : %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analyse des liens que vous voulez ajouter."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Tapez %s une fois fini."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Liens ajoutés : "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " retour au menu principal"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Gérer les paquets:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Gérer les liens:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Que voulez-vous déplacer ?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Que voulez-vous supprimer ?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Que voulez-vous redémarrer ?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Choisissez ce que vous voulez faire ou entrez le numéro du paquet."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "supprimer"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "déplacer"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "redémarrer"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - précédent"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - suivant"
+
diff --git a/locale/fr/LC_MESSAGES/pyLoadGui.mo b/locale/fr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 001412ddf..000000000
--- a/locale/fr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/setup.mo b/locale/fr/LC_MESSAGES/setup.mo
deleted file mode 100644
index 89cb21969..000000000
--- a/locale/fr/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/fr/LC_MESSAGES/setup.po b/locale/fr/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..bdb5c1cd8
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: French\n"
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "o"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Bienvenue dans l'Assistant de Configuration de pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Votre systÚme va être vérifié et subir un réglage de base afin de lancer pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "La valeur entre crochets [] est toujours la valeur par défaut,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "dans le cas où vous ne voulez pas la changer ou vous ne savez pas quoi choisir, appuyez sur entrée."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "N'oubliez pas : vous pouvez toujours relancer cet assistant avec l'option --setup ou -s, lorsque vous démarrez pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Si vous avez un problÚme avec cet assistant appuyer sur CTRL-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "pour annuler et ne plus le laisser démarrer automatiquement avec pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Lorsque vous êtes prêt pour la vérification du systÚme, appuyez sur entrée."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Vous avez besoin de pycurl, sqlite et python 2.5, 2.6 ou 2.7 pour exécuter pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Merci d'effectuer les corrections nécessaires et de relancer pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Le configurateur va se fermer."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Vérification du systÚme fini, appuyer sur entrée pour voir votre rapport de status."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "décryptage des conteneurs"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "connexion ssl"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "décryptage automatique des catchas"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interface web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "extension Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Fonctions disponibles :"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Fonctions indisponibles : "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto n'est pas disponible"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Vous en avez besoin si vous souhaitez décrypter les fichiers conteneurs."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL n'est pas disponible"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Vous en avez besoin si vous souhaitez établir une connexion sécurisé avec le logiciel principal ou l'interface web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Si vous souhaitez accéder à pyLoad uniquement en local, ssl n'est pas utile."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "La Reconnaissance des catchas n'est pas disponible"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Ceci est nécessaire uniquement pour certains hÎtes en tant qu'utilisateur gratuit."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Interface graphique non disponible"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "L'Interface Graphique."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "pas de moteur JavaScript trouvé"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Vous en aurez besoin pour certains liens Click'N'Load. Installez SpiderMonkey, ossp-js, pyv8 ou Rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Si vous le souhaitez, vous pouvez maintenant sortir du configurateur et corriger certaines dépendances."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Poursuivre l'installation ?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Vous voulez changer le chemin de la configuration ? Le chemin actuel est %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Si vous utilisez pyLoad sur un serveur ou si la partition home se trouve sur un disque flash il est conseillé de le modifier."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Changer le chemin de la configuration?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Vous voulez configurer les données de connexion et les paramÚtres de base ?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Ceci est recommandé pour la premiÚre exécution."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Faire le réglage de base ?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Vous voulez configurer ssl?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Configurer ssl?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Vous voulez configurer l'interface Web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Configurer l'interface Web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Installation terminée avec succÚs."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Appuyez sur entrée pour quitter et redémarrer pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Vérification du SystÚme ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Votre version de python est trop récente, Merci d'utiliser Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Votre version de python est trop ancienne, merci d'utiliser au moins Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Version de Python : OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Vous avez installé jinja2 version %s, elle semble trop ancienne."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Vous pouvez continuer en toute sécurité mais si l'interface web ne fonctionne pas,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "mettez à jour ou désinstallez la, pyLoad inclus une librairie jinja2 suffisante."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "moteur JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Configuration de base ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Les données de connexion suivantes sont valables pour le CLI, GUI et l'interface Web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Nom utilisateur"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Les clients externes (GUI, CLI ou autre) nécessitent un accÚs distant pour fonctionner sur le réseau."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Cependant, si vous souhaitez uniquement utiliser l'interface web vous pouvez le désactiver pour économiser la RAM."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Activer l'accÚs distant"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Langue"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Dossier de téléchargement"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Téléchargements parallÚles max"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Utiliser la reconnexion ?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Emplacement du script de reconnexion"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Configuration de l'interface Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Activer l'interface Web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adresse d'écoute, si vous utilisez 127.0.0.1 ou localhost, l'interface Web sera accessible uniquement localement."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adresse"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad offre plusieurs serveur d'arriÚre plan, suivez maintenant une brÚve explication."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Serveur par défaut ; c'est le meilleur choix si vous ne savez pas lequel choisir."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Ce serveur permet l'utilisation de SSL ; c'est une bonne alternative à builtin."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Peut être utilisé par apache, lighttpd, mais vous oblige à les configurer, ce qui n'est pas trÚs facile."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Une alternative trÚs rapide écrite en C, nécessite libev et des connaissances en linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Télécharger le ici : https://github.com/jonashaag/bjoern, compilez-le"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "et copiez bjoern.so vers pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Attention : dans certain cas le serveur intégré ne marche pas, si vous avez des problÚmes avec l'interface web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "revenez ici et changez le serveur de “intégré” à “threaded”."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Serveur"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Configuration de SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Exécutez ces commande depuis le répertoire de configuration de pyLoad pour générer les certificats SSL :"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Si vous avez terminé et que tout s'est bien passé, vous pouvez activer le ssl maintenant."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Activer le SSL ?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Sélectionnez une action"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Créer/modifier l'utilisateur"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Lister les utilisateurs"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Supprimer un utilisateur"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Quitter"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Utilisateurs"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Définition d'un nouveau dossier de configuration, la configuration actuelle ne sera pas transférée !"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Dossier de configuration"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Dossier de configuration modifié, l'assistant va se fermer, veuillez le relancer ensuite."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Appuyez sur entrée pour quitter."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "La définition du dossier de configuration a échoué : %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr "%s : OK"
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s : absent"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Mot de passe : "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Le mot de passe est trop court. Utilisez au moins 4 caractÚres."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Mot de passe (encore) : "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Les mots de passe ne correspondent pas."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "oui"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "vrai"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "v"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "non"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "faux"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Entrée non valide"
+
diff --git a/locale/ga/LC_MESSAGES/django.po b/locale/ga/LC_MESSAGES/django.po
new file mode 100644
index 000000000..617ce8b23
--- /dev/null
+++ b/locale/ga/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Irish\n"
+"Language: ga_IE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/ga/LC_MESSAGES/pyLoad.po b/locale/ga/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..e3c89cc40
--- /dev/null
+++ b/locale/ga/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Irish\n"
+"Language: ga_IE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/ga/LC_MESSAGES/pyLoadCli.po b/locale/ga/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..67003e6d9
--- /dev/null
+++ b/locale/ga/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Irish\n"
+"Language: ga_IE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/ga/LC_MESSAGES/setup.po b/locale/ga/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..a03a53855
--- /dev/null
+++ b/locale/ga/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Irish\n"
+"Language: ga_IE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/gl/LC_MESSAGES/django.po b/locale/gl/LC_MESSAGES/django.po
new file mode 100644
index 000000000..44713bac7
--- /dev/null
+++ b/locale/gl/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Galician\n"
+"Language: gl_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Solicitude de Novo Captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Por favor lea o texto do captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad reiniciouse"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Éxito"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Está seguro de que quere saír de pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Reiniciar Ligazón"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Borrar Ligazón"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Por favor, introduza un nome ó paquete."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Por favor, prema á dereita do captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Produciuse un erro."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "O cartafol está baleiro"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Fallou"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Non hai Captchas para ler."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Os contrasinais non coinciden."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Parámetros gardados."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Novo cartafol"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Está seguro de que quere reiniciar pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperando %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Descargas Activas"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Inicio"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Descargas"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Rexistros"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuración"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Información"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Progreso"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "O seu nome de usuario e contrasinal non coinciden. Por favor, inténteo de novo."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Para reiniciar os seus datos de inicio de sesión ou engadir un usuario, execute:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Borrar Terminados"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Reiniciar Falidos"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Cartafol:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Contrasinal:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Editar Paquete"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Editar os detalles do seguinte paquete."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Nome do paquete."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nome do subcartafol para estas descargas."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lista de contrasinais usadas para descomprimir."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Reiniciar"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Pechouse a sesión correctamente."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Ruta"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absoluto"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nome"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "tamaño"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tipo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "última modificación"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "directorio pai"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "sen contido"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Engadidos"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Elixa unha selección do menú"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Válido ata"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Tráfico restante"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tempo"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Máx. en Paralelo"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Borrar?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "inválido"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "sí"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "non"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Engadir Conta"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Introduza os datos da súa conta para usar características premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "O seu nome de usuario."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "O contrasinal para esta conta."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Tipo"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Elixa un servidor para a súa conta."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Iniciar"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "anterior"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "seguinte"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fin"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Novidades"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Soporte"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistema"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "S.O.:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "versión de pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Cartafol de Instalación:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Cartafol de Configuración:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Cartafol de Descarga:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Espazo Libre:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Idioma:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Porto da Interface Web:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Porto da Interface Remota:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Instalar"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Administrador de ficheiros"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Engadir Paquete"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Pegue as súas ligazóns ou suba un contedor de ligazóns."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Nome do novo paquete."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Ligazóns"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Pegue as súas ligazóns ou calquera texto aquí e prema o botón de filtrar."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrar urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Contrasinal para o arquivo RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Ficheiro"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Cargar un contedor de ligazóns."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Lendo captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "O captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Texto"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Introduza o texto no captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interface web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Hai unha actualización de pyLoad dispoñible!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Engadidos actualizados, por favor reinicia!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Esperando captcha"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Saír"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrar"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Información"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Por favor, inicie sesión!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Deter"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Descarga:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Volverse a conectar:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Velocidade:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Activo:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Recargar páxina"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "cargando"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Volver ó principio"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Saír de pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Reiniciar pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Para engadir un usuario ou cambiar contrasinais empregue:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Importante: O usuario Administrador sempre ten todos os permisos!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Cambiar Contrasinal"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Administrador"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Permisos"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "cambiar"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Introduza o seu contrasinal actual e a desexado."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Usuario"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Contrasinal actual"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Novo contrasinal"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "O novo contrasinal."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Novo contrasinal (repetir)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Por favor, repita o seu novo contrasinal."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Non ten permisos para acceder a esta páxina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Non se encontra o directorio de descarga."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non dispoñible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Execute pyload.py -s para acceder á instalación."
+
diff --git a/locale/gl/LC_MESSAGES/pyLoad.po b/locale/gl/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..bca98364f
--- /dev/null
+++ b/locale/gl/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Galician\n"
+"Language: gl_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Sinal de saída recibida"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad xa está executándose con pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Cambio de grupo falido: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Cambio de usuario falido: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "cartafol para os rexistros"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Comezando"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Usando directorio de inicio: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto para descodificar contedores de ligazóns"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "cartafol para ficheiros temporais"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "cartafol para as descargas"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL para conexión segura"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Movendo a antiga configuración de usuario á BD"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Por favor, comprobe os seus datos de inicio de sesión con ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Todas as ligazóns foron eliminadas"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Tempo de descarga: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Espazo libre: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Activando Contas..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Activando Engadidos..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad está funcionando"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "reiniciando pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad pechouse"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Instalar %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "non se puido atopar %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "no se puido crear %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "apagando..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "produciuse un erro mentres se apagaba"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyload terminado dende o Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "O ficheiro da base de datos eliminouse por ser dunha versión incompatible."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "O ficheiro da base de datos NON se puido converter."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "A base de datos converteuse da v2 á v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "A base de datos converteuse da v3 á v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Convertendo a antiga BD Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "finalizado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "fora de liña"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "en liña"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "na cola"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "omitido"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "esperando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temporalmente fora de servicio"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "comezando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "falido"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "abortado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "descifrando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personalizado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "descargando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "procesando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "descoñecido"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paquete finalizado: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "Usando SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Erro remoto do motor: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Iniciando %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Fallou ó cargar o motor %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "esperando %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certificados SSL non encontrados."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Sentímolo, pero retirámolo soporte para iniciar %s directamente en pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Pode empregar o servidor con fíos que ofrece un bo rendemento e ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "por suposto pode seguir empregando o seu %s existente co servidor fastcgi de pyLoad"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "as configuracións de exemplo encóntranse no directorio pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Non pode usar %(server)s, python-flup non está instalado!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Erro importando servidor lixeiro: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Debe descargar e compilar bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Copie o módulo boern.so ó cartafol pyload/lib ou use a instalación setup.py"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Evidentemente, debe estar familiarizado con linux e saber como compilar software"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Servidor en modo threaded, debido a problemas de rendemento coñecidos en windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Este servidor non ofrece SSL, por favor considere usar o servidor con fíos no seu lugar"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web incorporado: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web SSL con fíos: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Iniciando servidor web con fíos: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Iniciando servidor fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Iniciando servidor web lixeiro (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Non ten permisos para acceder a esta páxina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Non se encontra o directorio de descarga."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non dispoñible"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Execute pyload.py -s para acceder á instalación."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Descarga por partes falida, conexión única de emerxencia| %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Iniciando descarga: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Descarga finalizada: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "O engadido %s non atopa ningunha función."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Descarga cancelada: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Descarga reiniciada: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "A descarga está fóra de servizo: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "A descarga está temporalmente fora de servizo: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Descarga falida: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "No se puido conectar ó servidor ou conexión reiniciada, esperando 1 minuto e tentando de novo."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Descarga omitida: %(name)s debido a %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Comeza o descifrado: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Descifrado falido: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Reintentando %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "A Obtención de información para %(name)s fallou | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Erro executando as ligazóns: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Fallou a activación de %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Engadidos activados: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Engadidos desactivados: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Reconexión Falida: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Non se atopou o script de reconexión!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Iniciando reconexión"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Fallo executando o script de reconexión!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Conectado de novo, nova dirección IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Non hai suficiente espazo libre no dispositivo"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Non se puido iniciar sesión coa conta %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Contrasinal erróneo"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "A hora %s ten un formato incorrecto, use: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "A conta %s non ten tráfico suficiente, comprobarase outra vez en 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "A conta %s expirou, compróbea de novo en 1h"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Limite de descargas alcanzado"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s ten un patrón inválido."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Erro importando %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Non se cargou ningún servidor"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Active a descarga directa na súa conta de Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "A lista de ligazóns non se puido limpar."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Configuración da conta borrada, debido ó formato da nova configuración."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorización requirida (usuario:contrasinal)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Arquivo non dispoñible temporalmente"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: esperando entre descargas %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: esperando captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "O arquivo descargado estaba baleiro"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Clave de API inválida"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Non queda suficiente tráfico"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Tráfico excedido"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Tráfico compartido (descarga directa)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Xa está descargando dende esta dirección ip, esperando 60 segundos"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Código de Autenticación Inválido, a descarga reiniciarase"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: No quedan espazos dispoñibles"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Precisa dunha conta premium para este arquivo"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Nome do arquivo inválido"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Erro con descargas en paralelo, esperando 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Non iniciou sesión."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "O ficheiro non existe."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Os engadidos foron actualizados, por favor reinicie pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Engadidos actualizados e cargados de novo"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Non hai actualizacións de engadidos dispoñibles"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "No hai actualizacións para pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Nova versión %s de pyLoad dispoñible ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Obtéñaa aquí: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Imposible conectar co servidor para actualizacións"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nova versión de %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Erro mentres se actualizaba %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Non coincide a versión"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Descarga finalizada: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nova Petición de Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Responda con 'c %s texto ó captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Engadido %s dende HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s non instalado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Non se puido activar %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Activado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Non se activaron os engadidos de extracción"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Paquete %s posto na cola para a súa extracción posterior"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Comprobar paquete %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Extraer a %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "extraendo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Protexido con contrasinal"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Contrasinal erróneo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Borrando ficheiros %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Extracción finalizada"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Erro de Arquivo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "O CRC non coincide"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Erro Descoñecido"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Configuración de Usuario e Grupo falida"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: O porto 9666 xa está en uso"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s créditos restantes"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "A súa conta de CaptchaTrader non ten créditos suficientes"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Descarga finalizada: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Scripts instalados para %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script non executable:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Erro en %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Por favor, engada primeiro a súa conta rehost.to e reinicie pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil e tesseract non están instalados e no hai cliente conectado para descifrar o captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Non se obtiveron resultados para o captcha no tempo asignado de ningún dos engadidos."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Configuración de Usuario e Grupo falida: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Non hai cliente conectado para descifrar o captcha"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Engadido o paquete %(name)s que contén %(count)d ligazóns"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Engadidos %(count)d ligazóns ó paquete #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Non se detectou ningún motor de js, por favor instale Spidermonkey, ossp-js, pyv8 ou rhino"
+
diff --git a/locale/gl/LC_MESSAGES/pyLoadCli.po b/locale/gl/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..deae3325f
--- /dev/null
+++ b/locale/gl/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Galician\n"
+"Language: gl_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Interface de Liña de Comandos"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Descargas:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Velocidade: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Tamaño: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Finalizado en: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "esperando: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Menú:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Engadir Ligazóns"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Administrar Cola"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Administrar Colector"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Retomar/Deter Servidor"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Parar Servidor"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Saír"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Por favor use esta sintaxe: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Comprobando %d ligazóns:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "O ficheiro non existe."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad terminou"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Amosa-lo estado do servidor"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Amosa-las descargas en cola"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Amosa-las descargas en colector"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Engadir paquetes á cola"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Engadir paquetes ó colector"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Borrar Ficheiros da Cola/Colector"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Borrar Paquetes da Cola/Colector"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Mover os Paquetes da Cola ó Colector ou viceversa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Reiniciar ficheiros"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Reiniciar paquetes"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Comprobar o estado da conexión, traballa cun contedor local"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Comprobar o estado en liña dun contedor"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pausa o servidor"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continuar as descargas"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Alternar deter/continuar"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "finalizar servidor"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista de comandos:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Resultou imposíbel escribir no ficheiro de configuración do usuario"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Necesita py-openssl para conectarse a este Núcleo de pyLoad."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Enderezo: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Porto: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Nome de usuario: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Contrasinal: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Os datos de inicio de sesión son incorrectos."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Non se puido establecer a conexión a %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Necesita py-openssl para conectarse a este núcleo pyLoad."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Modo interactivo ignorado, xa que pasou algúns comandos."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Engadir Paquete:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Introduza un nome para o novo paquete"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paquete: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Procesar as ligazóns que desexe engadir."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Teclee %s cando remate."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Ligazóns engadidos: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " volver ó menú principal"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Administrar Paquetes:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Administrar Ligazóns:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Que quere mover?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Que quere borrar?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Que quere reiniciar?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Elixa o que queira facer ou introduza o número do paquete."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "borrar"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "mover"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "reiniciar"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - anterior"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - seguinte"
+
diff --git a/locale/gl/LC_MESSAGES/setup.po b/locale/gl/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..86276ecf3
--- /dev/null
+++ b/locale/gl/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Galician\n"
+"Language: gl_ES\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "s"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Benvido ó Asistente de Configuración de pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Revisarase o seu sistema e farase unha configuración básica para executar pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "O valor entre corchetes [] sempre é o valor por defecto,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "en caso de que non queira cambialo ou non estea seguro de que opción elixir, só prema entrar."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Non o esqueza: Sempre pode volver a executar este asistente co parámetro --setup ou -s cando inicie pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Se ten algún problema co asistente presione Ctrl-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "para abortar e non permitirlle volver a iniciarse automaticamente con pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Cando estea listo para a comprobación do sistema, prema entrar."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Precisa pycurl, sqlite e python 2.5, 2.6 ou 2.7 para executar pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Por favor corrixa isto e volva a executar pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "O asistente da instalación pecharase."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "A comprobación do sistema concluíu, prema entrar para ver o informe de estado."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Estado ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "descifrando contedor"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "conexión ssl"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "descifrado automático de captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interface web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "extensión Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Características dispoñibles:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Características non dispoñibles: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto non dispoñible"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Precisa disto se quere descifrar arquivos de contedores."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL non dispoñible"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Isto é necesario se quere establecer unha conexión segura co núcleo ou a interface web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "SSL non é útil se tan só quere acceder a pyLoad localmente."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "recoñecemento de Captcha non dispoñible"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Só necesario para algúns servidores e como usuario gratuíto."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Gui non dispoñible"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "A Interface de Usuario Gráfica."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "motor de JavaScript non atopado"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Precisará disto para algunhas ligazóns Click'N'Load. Instale Spidermonkey, ossp-js, pyv8 ou rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Pode cancelar o asiste agora e corrixir algunhas dependencias se quere."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Desexa continuar co asistente?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Quere cambiar a ruta de configuración? Actualmente é %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Si usa pyLoad en un servidor ou a partición primaria reside nunha memoria flash interna, pode ser boa idea cambiala."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Cambia-la ruta de configuración?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Quere configurar os datos de acceso e a configuración básica?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Isto recoméndase na primeira execución."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Facer a configuración básica?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Quere configurar ssl?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Configurar ssl?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Quere configurar a interface web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Configurar a interface web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Configuración rematada correctamente."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Prema entrar para saír e reiniciar pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Comprobación do sistema ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "A súa versión de python é demasiado nova, por favor use Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "A súa versión de python é demasiado antiga, por favor use polo menos Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Versión de Python: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "A súa versión %s de jinja2 instalada parece demasiado antiga."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Pode continuar de maneira segura pero se a interface web non funcionara,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "por favor actualícea ou elimínea, pyLoad inclúe unha biblioteca jinja2 suficiente."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "Motor JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Configuración Básica ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Os seguintes datos de acceso son válidos para CLI, GUI e interface web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Os clientes externos (GUI, CLI ou outros) precisan acceso remoto para traballar a través da rede."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Non obstante, se só quere utilizar interface web pode desactivalo para aforrar ram."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Activar acceso remoto"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Cartafol de descargas"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Máximas descargas paralelas"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Usar reconectar?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Lugar do script de reconexión"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Configuración da Interface Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Activar a interface web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Dirección de escoita, se usa 127.0.0.1 ou localhost, a interface web só poderá ser accesible localmente."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad ofrece varias infraestruturas de servidores, a continuación unhas pequenas explicacións."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "O servidor por defecto, a mellor opción se non sabe cal elixir."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Este servidor ofrece SSL e é unha boa alternativa ó integrado."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Pode usalo apache, lighttpd, require que o configure, o cal non é unha tarefa fácil."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Unha alternativa moi rápida escrita en C, require libev e coñecementos de linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Obtéñao aquí: https://github.com/jonashaag/bjoern, e compíleo"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "e copie bjoern.so a pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Atención: Nalgúns casos raros o servidor integrado non funciona, se nota problemas coa interface web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "volva aquí e cambie o servidor integrado polo servidor con fíos."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Configuración de SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Execute estes comandos dende o cartafol de configuración de pyLoad para crear os certificados ssl:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Se xa terminou e todo rematou ben, poderá activar ssl agora."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Activar SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Elixa unha acción"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Crear/Editar usuario"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Listar usuarios"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Eliminar usuario"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Saír"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Usuarios"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Configurando o novo directorio de configuración, os parámetros actuais non se transferirán!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Ruta de configuración"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Ruta de configuración cambiada, a instalación pecharase, por favor reinicie para continuar."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Prema Entrar para saír."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Axuste da ruta de configuración falida: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: non atopado"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Contrasinal: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Contrasinal (de novo): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Os contrasinais non coinciden."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "sí"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "verdadeiro"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "v"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "non"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "falso"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Ingreso non válido"
+
diff --git a/locale/gui.pot b/locale/gui.pot
deleted file mode 100644
index dc74397a0..000000000
--- a/locale/gui.pot
+++ /dev/null
@@ -1,511 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR pyLoad Team
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
-"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\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=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: pyLoadGui.py:290
-msgid "paused"
-msgstr ""
-
-#: pyLoadGui.py:292
-msgid "running"
-msgstr ""
-
-#: pyLoadGui.py:332
-msgid "Unnamed"
-msgstr ""
-
-#: pyLoadGui.py:655
-#, python-format
-msgid "Finished downloading of '%s'"
-msgstr ""
-
-#: pyLoadGui.py:657
-#, python-format
-msgid "Failed downloading '%s'!"
-msgstr ""
-
-#: pyLoadGui.py:660
-#, python-format
-msgid "Added '%s' to queue"
-msgstr ""
-
-#: pyLoadGui.py:685
-msgid "Connection lost"
-msgstr ""
-
-#: pyLoadGui.py:685
-msgid "Lost connection to the core!"
-msgstr ""
-
-#: pyLoadGui.py:720
-msgid "Show"
-msgstr ""
-
-#: pyLoadGui.py:725 module/gui/MainWindow.py:133
-msgid "Exit"
-msgstr ""
-
-#: module/gui/Connector.py:76
-msgid "bad login credentials"
-msgstr ""
-
-#: module/gui/Connector.py:78
-msgid "no ssl support"
-msgstr ""
-
-#: module/gui/Connector.py:80
-msgid "can't connect to host"
-msgstr ""
-
-#: module/gui/Connector.py:94
-#, python-format
-msgid "server is version %(new)s client accepts version %(current)s"
-msgstr ""
-
-#: module/gui/Collector.py:47
-msgid "finished"
-msgstr ""
-
-#: module/gui/Collector.py:48
-msgid "offline"
-msgstr ""
-
-#: module/gui/Collector.py:49
-msgid "online"
-msgstr ""
-
-#: module/gui/Collector.py:50
-msgid "queued"
-msgstr ""
-
-#: module/gui/Collector.py:51
-msgid "skipped"
-msgstr ""
-
-#: module/gui/Collector.py:52
-msgid "waiting"
-msgstr ""
-
-#: module/gui/Collector.py:53
-msgid "temp. offline"
-msgstr ""
-
-#: module/gui/Collector.py:54
-msgid "starting"
-msgstr ""
-
-#: module/gui/Collector.py:55
-msgid "failed"
-msgstr ""
-
-#: module/gui/Collector.py:56
-msgid "aborted"
-msgstr ""
-
-#: module/gui/Collector.py:57
-msgid "decrypting"
-msgstr ""
-
-#: module/gui/Collector.py:58
-msgid "custom"
-msgstr ""
-
-#: module/gui/Collector.py:59
-msgid "downloading"
-msgstr ""
-
-#: module/gui/Collector.py:60
-msgid "processing"
-msgstr ""
-
-#: module/gui/Collector.py:284 module/gui/PackageDock.py:66
-#: module/gui/Queue.py:152
-msgid "Name"
-msgstr ""
-
-#: module/gui/Collector.py:286 module/gui/Queue.py:156
-msgid "Plugin"
-msgstr ""
-
-#: module/gui/Collector.py:288 module/gui/Queue.py:154
-msgid "Status"
-msgstr ""
-
-#: module/gui/Collector.py:290 module/gui/Queue.py:158
-msgid "Size"
-msgstr ""
-
-#: module/gui/Accounts.py:75
-msgid "not valid"
-msgstr ""
-
-#: module/gui/Accounts.py:77 module/gui/Accounts.py:207
-msgid "n/a"
-msgstr ""
-
-#: module/gui/Accounts.py:80
-msgid "%a, %d %b %Y %H:%M"
-msgstr ""
-
-#: module/gui/Accounts.py:83 module/gui/Accounts.py:205
-msgid "unlimited"
-msgstr ""
-
-#: module/gui/Accounts.py:138 module/gui/AccountEdit.py:38
-msgid "Type"
-msgstr ""
-
-#: module/gui/Accounts.py:140 module/gui/AccountEdit.py:39
-msgid "Login"
-msgstr ""
-
-#: module/gui/Accounts.py:142
-msgid "Valid until"
-msgstr ""
-
-#: module/gui/Accounts.py:144
-msgid "Traffic left"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:74
-msgid "General"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:75
-msgid "Plugins"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:89
-msgid "Reload"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:90 module/gui/ConnectionManager.py:206
-#: module/gui/AccountEdit.py:53
-msgid "Save"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:192
-msgid "Yes"
-msgstr ""
-
-#: module/gui/SettingsWidget.py:193
-msgid "No"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:42 module/gui/ConnectionManager.py:185
-msgid "pyLoad ConnectionManager"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:47
-msgid "New"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:48 module/gui/MainWindow.py:324
-msgid "Edit"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:49 module/gui/MainWindow.py:275
-#: module/gui/MainWindow.py:295 module/gui/MainWindow.py:323
-msgid "Remove"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:50
-msgid "Connect"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:56
-msgid "Connect:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:73
-msgid "Use internal Core:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:117
-#, python-format
-msgid "%s (Default)"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:190
-msgid "Name:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:191
-msgid "Host:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:192
-msgid "Local:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:193
-msgid "User:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:194
-msgid "Password:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:195
-msgid "Port:"
-msgstr ""
-
-#: module/gui/ConnectionManager.py:207 module/gui/CaptchaDock.py:65
-msgid "Cancel"
-msgstr ""
-
-#: module/gui/MainWindow.py:44
-msgid "pyLoad Client"
-msgstr ""
-
-#: module/gui/MainWindow.py:92
-msgid "Packages:"
-msgstr ""
-
-#: module/gui/MainWindow.py:96
-msgid "Files:"
-msgstr ""
-
-#: module/gui/MainWindow.py:100
-msgid "Status:"
-msgstr ""
-
-#: module/gui/MainWindow.py:104
-msgid "Space:"
-msgstr ""
-
-#: module/gui/MainWindow.py:108
-msgid "Speed:"
-msgstr ""
-
-#: module/gui/MainWindow.py:129
-msgid "File"
-msgstr ""
-
-#: module/gui/MainWindow.py:130
-msgid "Connections"
-msgstr ""
-
-#: module/gui/MainWindow.py:134
-msgid "Connection manager"
-msgstr ""
-
-#: module/gui/MainWindow.py:156
-msgid "Overview"
-msgstr ""
-
-#: module/gui/MainWindow.py:157
-msgid "Queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:158
-msgid "Collector"
-msgstr ""
-
-#: module/gui/MainWindow.py:159
-msgid "Accounts"
-msgstr ""
-
-#: module/gui/MainWindow.py:160
-msgid "Settings"
-msgstr ""
-
-#: module/gui/MainWindow.py:161
-msgid "Log"
-msgstr ""
-
-#: module/gui/MainWindow.py:190
-msgid "Hide Toolbar"
-msgstr ""
-
-#: module/gui/MainWindow.py:194
-msgid "Toggle Pause/Resume"
-msgstr ""
-
-#: module/gui/MainWindow.py:200
-msgid "Stop"
-msgstr ""
-
-#: module/gui/MainWindow.py:202 module/gui/MainWindow.py:302
-#: module/gui/MainWindow.py:322
-msgid "Add"
-msgstr ""
-
-#: module/gui/MainWindow.py:204
-msgid "Check Clipboard"
-msgstr ""
-
-#: module/gui/MainWindow.py:211 module/gui/MainWindow.py:308
-#: module/gui/Overview.py:101
-msgid "Package"
-msgstr ""
-
-#: module/gui/MainWindow.py:212 module/gui/MainWindow.py:309
-msgid "Container"
-msgstr ""
-
-#: module/gui/MainWindow.py:213
-msgid "Account"
-msgstr ""
-
-#: module/gui/MainWindow.py:214 module/gui/MainWindow.py:310
-msgid "Links"
-msgstr ""
-
-#: module/gui/MainWindow.py:238
-msgid "Push selected packages to queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:261
-msgid "New Account"
-msgstr ""
-
-#: module/gui/MainWindow.py:276 module/gui/MainWindow.py:298
-msgid "Restart"
-msgstr ""
-
-#: module/gui/MainWindow.py:277
-msgid "Pull out"
-msgstr ""
-
-#: module/gui/MainWindow.py:278
-msgid "Abort"
-msgstr ""
-
-#: module/gui/MainWindow.py:279 module/gui/MainWindow.py:297
-msgid "Edit Name"
-msgstr ""
-
-#: module/gui/MainWindow.py:296
-msgid "Push to queue"
-msgstr ""
-
-#: module/gui/MainWindow.py:299
-msgid "Refresh Status"
-msgstr ""
-
-#: module/gui/MainWindow.py:402
-#, python-format
-msgid "All Container Types (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:403
-#, python-format
-msgid "DLC (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:404
-#, python-format
-msgid "CCF (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:405
-#, python-format
-msgid "RSDF (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:406
-#, python-format
-msgid "Text Files (%s)"
-msgstr ""
-
-#: module/gui/MainWindow.py:408
-msgid "Open container"
-msgstr ""
-
-#: module/gui/PackageDock.py:25
-msgid "New Package"
-msgstr ""
-
-#: module/gui/PackageDock.py:68
-msgid "Password"
-msgstr ""
-
-#: module/gui/PackageDock.py:71
-msgid "Links in this Package"
-msgstr ""
-
-#: module/gui/PackageDock.py:77
-msgid "Create"
-msgstr ""
-
-#: module/gui/PackageDock.py:78
-msgid "Filter URLs"
-msgstr ""
-
-#: module/gui/CaptchaDock.py:29
-msgid "Captcha"
-msgstr ""
-
-#: module/gui/CaptchaDock.py:64
-msgid "OK"
-msgstr ""
-
-#: module/gui/AccountEdit.py:32
-msgid "Edit account"
-msgstr ""
-
-#: module/gui/AccountEdit.py:40
-msgid "New password"
-msgstr ""
-
-#: module/gui/AccountEdit.py:83
-msgid "Create account"
-msgstr ""
-
-#: module/gui/Queue.py:160
-msgid "ETA"
-msgstr ""
-
-#: module/gui/Queue.py:162
-msgid "Progress"
-msgstr ""
-
-#: module/gui/Queue.py:384
-#, python-format
-msgid "waiting %d seconds"
-msgstr ""
-
-#: module/gui/Overview.py:71 module/gui/Overview.py:152
-msgid "Downloading"
-msgstr ""
-
-#: module/gui/Overview.py:83
-msgid "Queued"
-msgstr ""
-
-#: module/gui/Overview.py:147
-msgid "ETA: "
-msgstr ""
-
-#: module/gui/Overview.py:149
-msgid "Parts: "
-msgstr ""
-
-#: module/gui/Overview.py:151
-msgid "Finished"
-msgstr ""
-
-#: module/gui/Overview.py:155
-#, python-format
-msgid "Speed: %s"
-msgstr ""
-
-#: module/gui/Overview.py:158 module/gui/Overview.py:160
-msgid "Size:"
-msgstr ""
diff --git a/locale/he/LC_MESSAGES/django.po b/locale/he/LC_MESSAGES/django.po
new file mode 100644
index 000000000..97c00c494
--- /dev/null
+++ b/locale/he/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hebrew\n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/he/LC_MESSAGES/pyLoad.po b/locale/he/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..4cbdb9e84
--- /dev/null
+++ b/locale/he/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hebrew\n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/he/LC_MESSAGES/pyLoadCli.po b/locale/he/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..31c156f35
--- /dev/null
+++ b/locale/he/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hebrew\n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/he/LC_MESSAGES/setup.po b/locale/he/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..5e8e45761
--- /dev/null
+++ b/locale/he/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hebrew\n"
+"Language: he_IL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/hi/LC_MESSAGES/django.po b/locale/hi/LC_MESSAGES/django.po
new file mode 100644
index 000000000..7cd5d416a
--- /dev/null
+++ b/locale/hi/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hindi\n"
+"Language: hi_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/hi/LC_MESSAGES/pyLoad.po b/locale/hi/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..ac081b482
--- /dev/null
+++ b/locale/hi/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hindi\n"
+"Language: hi_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/hi/LC_MESSAGES/pyLoadCli.po b/locale/hi/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..734b08099
--- /dev/null
+++ b/locale/hi/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hindi\n"
+"Language: hi_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s à€¡à€Ÿà€‰à€šà€²à¥‹à€¡:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " à€—à€€à€¿: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " à€†à€•à€Ÿà€°: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " à€†à€ˆà€¡à¥€: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "à€ªà¥à€°à€€à¥€à€•à¥à€·à€Ÿ à€®à¥‡à€‚ :"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "à€žà¥à€¥à€¿à€€à€¿:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "à€•à¥à€² à€—à€€à€¿"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "à€•à¥à€²"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "à€®à¥‡à€šà¥à€¯à¥‚ "
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "à€²à€¿à€‚à€•à¥à€ž à€œà¥‹à€¡à€Œà¥‡à€‚"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "à€¡à€Ÿà€‰à€šà€²à¥‹à€¡ à€œà€Ÿà€°à¥€ à€°à€–à¥‡à€‚ "
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "à€žà€°à¥à€µà€° à€¬à€‚à€Š à€•à€°à¥‡à€‚ "
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "à€•à€®à€Ÿà€‚à€¡ à€žà¥‚à€šà¥€ "
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "à€ªà¥‹à€°à¥à€Ÿ: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "à€‰à€ªà€¯à¥‹à€—à€•à€°à¥à€€à€Ÿ à€šà€Ÿà€®: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "à€ªà€Ÿà€žà€µà€°à¥à€¡: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "à€ªà¥ˆà€•à¥‡à€œ à€œà¥‹à€¡à€Œà¥‡à€‚:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "à€šà€ à€ªà¥ˆà€•à¥‡à€œ à€•à¥‹ à€šà€Ÿà€® à€Šà¥‡à€‚ "
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "à€ªà¥ˆà€•à¥‡à€œ: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "à€ªà¥‚à€°à€Ÿ à€¹à¥‹à€šà¥‡ à€ªà€° %s à€Ÿà€Ÿà€‡à€ª à€•à€°à¥‡à€‚ "
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "à€œà¥‹à¥œà¥‡ à€—à€ à€²à€¿à€‚à€•"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "à€®à¥à€–à¥à€¯à€Ÿ à€®à¥‡à€šà¥à€¯à¥‚ à€ªà€° à€µà€Ÿà€ªà€ž à€œà€Ÿà€à€‚ "
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "à€®à€¿à€Ÿà€Ÿà€¯à¥‡à€‚ "
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "à€ªà¥à€šà€ƒà€†à€°à€‚à€­ à€•à€°à¥‡à€‚ "
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr "-à€ªà€¿à€›à€²à€Ÿ "
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "-à€…à€—à€²à€Ÿ "
+
diff --git a/locale/hi/LC_MESSAGES/setup.po b/locale/hi/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..24da4ee92
--- /dev/null
+++ b/locale/hi/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hindi\n"
+"Language: hi_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "à€ªà€Ÿà€žà€µà€°à¥à€¡: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/hr/LC_MESSAGES/django.po b/locale/hr/LC_MESSAGES/django.po
new file mode 100644
index 000000000..29fbc3b16
--- /dev/null
+++ b/locale/hr/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Croatian\n"
+"Language: hr_HR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/hr/LC_MESSAGES/pyLoad.po b/locale/hr/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..e0fedc263
--- /dev/null
+++ b/locale/hr/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Croatian\n"
+"Language: hr_HR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/hr/LC_MESSAGES/pyLoadCli.po b/locale/hr/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..cf1966357
--- /dev/null
+++ b/locale/hr/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Croatian\n"
+"Language: hr_HR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/hr/LC_MESSAGES/setup.po b/locale/hr/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..cad508e12
--- /dev/null
+++ b/locale/hr/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Croatian\n"
+"Language: hr_HR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/hu/LC_MESSAGES/django.po b/locale/hu/LC_MESSAGES/django.po
new file mode 100644
index 000000000..3d97e2270
--- /dev/null
+++ b/locale/hu/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hungarian\n"
+"Language: hu_HU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Új Captcha kérés"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Olvasd el a captcha-n levő szöveget."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad újraindítva"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "ki"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Sikeres"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "be"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Biztos bezárod a pyLoad-ot?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Link újraindítás"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Link törlése"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Add meg a csomag nevét."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Kattints a megfelelő captcha helyre."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Hiba történt."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Üres könyvtár"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Sikertelen"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Nincs Captcha."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "A jelszavak nem egyeznek."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Beállítások elmentve."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Új könyvtár"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Biztos újraindítod a pyLoad-ot?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "várakozás %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktív letöltések"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Főoldal"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Letöltések"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Napló"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Beállítások"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Név"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Állapot"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Információ"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Méret"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Folyamat"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Bejelentkezés"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Felhasználónév"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Jelszó"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "A felhasználói név és a jelszó nem egyezik. Próbáld újra."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "A bejelentkezési adatok törléséhez vagy új felhasználó felvételéhez futtasd:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Sikeresek törlése"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Sikertelenek újrapróbálása"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Könyvtár:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Jelszó:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Csomag szerkesztése"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Csomag részletei."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Csomag neve."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "mappa"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Alkönyvtár a letöltéseknek."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Jelszavak a kitömörítéshez."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "KÌldés"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Alaphelyzet"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Sikeresen kijelentkeztél."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Elérési útvonal"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "abszolút"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relatív"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "név"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "méret"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "típus"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "utolsó módosítás"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "szÃŒlő könyvtár"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "nincs tartalom"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Általános"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "BeépÃŒlők"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Hozzáférések"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Válaszd ki a megfelelő szekciót a menÃŒből"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "BeépÃŒlő"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "Prémium"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Lejár"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Hátralévő adatforgalom"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Idő"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max. egyszerre futó"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Törlés?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "érvényes"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "érvénytelen"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "igen"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nem"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Hozzáad"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Fiók hozzáadása"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Add meg a prémium hozzáféréshez szÌkséges adatokat."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Felhasználói neved."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Jelszó ehhez a hozzáféréshez."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Típus"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Válaszd ki a hozzáférésedhez tartozó ellátót."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Indít"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "előző"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "következő"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Vége"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Hírek"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Segítségnyújtás"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Rendszer"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad verzió:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Telepítés helye:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Beállítások helye:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Letöltések helye:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Szabad terÃŒlet:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Nyelv:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Web felÃŒlet port:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Távoli elérés port:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Beállítás"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Állomány kezelő"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Csomag hozzáadása"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Illesztd be a linkeket, vagy tölts fel egy gyűjtőt."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Az új csomag neve."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Linkek"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Illesztd be a linkeket, vagy bármilyen szöveget és nyomj a linkek szűrésére."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Linkek kiszűrése"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Jelszó a RAR állományhoz"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Állomány"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Gyűjtő feltöltése."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Cél"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha olvasás"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "A captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Szöveg"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Add meg a captcha-n található szöveget."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Bezárás"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Webes felÃŒlet"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad frissítés elérhető!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "BeépÃŒlők frissítve, indítsd újra!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha várakozik"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Kijelentkezés"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Adminisztrálás"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Infó"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Kérlek jelentkezz be!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Megállít"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Mégse"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Letöltés:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Újracsatlakozás:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Sebesség:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktív:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Oldal frissítése"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "betöltés"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Vissza a tetejére"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "pyLoad bezárása"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "pyLoad újraindítása"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Új felhasználó hozzáadásához vagy jelszó változtatáshoz:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Fontos: Az Admin felhasználó mindig teljes jogkörrel rendelkezik!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Jelszó változtatás"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Engedélyek"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "változtat"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "A jelenlegi és a kívánt jelszó."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Felhasználó"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Jelenlegi jelszó"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Új jelszó"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Az új jelszó."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Új jelszó (ismét)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Ismételd meg az új jelszót."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Nincs jogosultságod a lap megtekintéséhez."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Letöltési könyvtár nem található."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "végtelen"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "nem elérhető"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "A beállításhoz futtasd: pyload.py -s."
+
diff --git a/locale/hu/LC_MESSAGES/pyLoad.po b/locale/hu/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..9f522930c
--- /dev/null
+++ b/locale/hu/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hungarian\n"
+"Language: hu_HU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Leállítási jel érkezett"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "a pyLoad már fut az alábbi pid számmal: %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Sikertelen csoport váltás: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Sikertelen felhasználó váltás: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "könyvtár a naplóknak"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Indítás"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Főkönyvtár használata: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto a konténer fájlok dekódolásához"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "könyvtár az átmeneti fájloknak"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "könyvtár a letöltéseknek"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL a biztonságos kapcsolathoz"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Ellenőrizd a belépési adataid a következő paranccsal: ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Minden link törölve"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Letöltési idő: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Szabad hely: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Hozzáférések aktiválása..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Bővítmények aktiválása..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad fut"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "pyLoad újraindítása"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad kilép"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Telepítés %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "leállítás..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "hiba leállítás közben"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad kilőve konzolról"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "befejezett"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "várólistán"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "átugorva"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "várakozás"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "átmenetileg offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "indítás"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "sikertelen"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "megszakított"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "dekódolás"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "egyéni"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "letöltés"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "feldolgozás alatt"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "ismeretlen"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Kész csomag: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "SSL ThriftBackend használata"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Távoli hiba: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Indítás: %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Backend betöltése sikertelen: %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "várakozás %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL tanúsítvány nem található."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Ez a szerver nem támogatja az SSL-t, kérlek használj helyette másikat"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Nincs jogosultságod a lap megtekintéséhez."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Letöltési könyvtár nem található."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "végtelen"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "nem elérhető"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "A beállításhoz futtasd: pyload.py -s."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Darabokban letöltés sikertelen, visszatérés egyszeri kapcsolatra | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Letöltés elindítva: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Letöltés befejezve: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "A %s bővítményből hiányzik egy funkció."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Letöltés megszakítva: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Letöltés újraindítva: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "A letöltés offline: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "A letöltés átmenetileg offline: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "A letöltés sikertelen: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Nem sikerÌlt csatlakozni a címhez, újrapróbálás 1 perc múlva."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Letöltés átugorva: %(name)s - %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Dekódolás elkezdve: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Dekódolás sikertelen: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Újra próbálkozás %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "%(name)s információinak lekérése sikertelen | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Sikertelen aktiválás: %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Újracsatlakozás sikertelen: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Újracsatlakozó script nem található!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Újracsatlakozás indítása"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Az újracsatlakozó scriptet nem sikerÌlt futtatni!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Újracsatlakozva, új IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Nincs elég hely az eszközön"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Nem sikerÃŒlt bejelentkezni: %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Hibás jelszó"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "%s hozzáférésen nincs elég adatforgalom, újra próbálás: 30 perc"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "%s hozzáférés lejárt, újra próbálás: 1 óra"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "A letöltési korlát elérve"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Hiba a(z) %(name)s betöltése közben: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Nincs szolgáltató betöltve"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Közvetlen letöltés aktiválása a Bitshare hozzáférésen"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Azonosítás szÌkséges (felhasználónév:jelszó)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Add meg a(z) %s hozzáférés adatait, vagy deaktiváld ezt a beépÃŒlőt"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "HTML kód van a letöltött fájlban (%s). Lehet, hogy átirányítási hiba, a letöltés újra lesz indítva."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Állomány átmenetileg nem elérhető"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: letöltések közti várakozás %d mp."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: captcha-ra várakozás %d mp."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "A letöltött fájl Ìres"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Érvénytelen API kulcs"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Nincs elég adatforgalom hátra"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Adatforgalom túllépve"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Adatforgalom megosztás (közvetlen letöltés)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Már van folyamatban letöltés erről az IP-ről, várakozás: 60mp"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Hibás Auth kód, a letöltés újraindítása"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Nincs ingyenes hely"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Ehhez a fájlhoz prémium hozzáférés szÌkséges"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Hibás fájlnév"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Többszálas letöltés hiba, várakozás: 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Nem vagy bejelentkezve."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Sikertelen dekódolás"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Nincs fájl kulcs az URL-ben"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Hibakód:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Bővítmények frissítve, indítsd újra a pyLoad-ot ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Bővítmények frissítve és betöltve"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Nincs bővítmény frissítés"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Nincs új pyLoad verzió"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Új pyLoad verzió elérhető: %s ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Letöltés: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Nem sikerÌlt a szerverhez csatlakozni a frissítésekért"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Új verzió: %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Hiba frissítés közben %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "verzió Ìtközés"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Letöltés befejezve: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Új captcha kérés: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Válaszolj ezzel: 'c %s szöveg a captcha-n'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Először adj meg rehost.to hozzáférést majd indítsd újra a pyLoad-ot"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "%s HotFolder-ből hozzáadva"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "Nincs %s telepítve"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Nem sikerÌlt aktiválni: %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Aktivált"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Nincs kicsomagoló bővítmény aktiválva"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "%s csomag a várólistán későbbi kitömörítésre"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Csomag ellenőrzése: %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Kicsomagolás: %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Nincs kitömöríthető fájl"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "kicsomagolás"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Jelszóval védett"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Hibás jelszó"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "%s fájl törlése"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Kicsomagolás kész"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Tömörített állomány hiba"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC hiba"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Ismeretlen hiba"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Felhasználó és csoport beállítás sikertelen"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: A 9666 port használatban van"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s kredit maradt"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Nem sikerÌlt választ kÌldeni."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "A CaptchaTrader hozzáférésen nincs elég credit"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Crypter lista nem található"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Crypter lista ÃŒres"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Letöltés befejezve: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Új CaptchaID a feltöltésből: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "A Captcha 9kw.eu hozzáférésen nincs elég credit"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Scriptek telepítve - %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script nem futtatható:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Hiba: %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Az ExpertDecoders hozzáférésen nincs elég credit"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Először adj meg rehost.to hozzáférést majd indítsd újra a pyLoad-ot."
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Először adj meg premiumize.me hozzáférést majd indítsd újra a pyLoad-ot."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d kredit maradt"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Captcha megfejtéshez nincs kliens csatlakozva és nincs telepítve Pil vagy tesseract"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Felhasználó és csoport beállítás sikertelen: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/hu/LC_MESSAGES/pyLoadCli.po b/locale/hu/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..288299d23
--- /dev/null
+++ b/locale/hu/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hungarian\n"
+"Language: hu_HU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Parancssoros felÃŒlet"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s letöltés:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Sebesség: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Méret: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Befejezve: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "várakozás: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Állapot:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "felfÃŒggesztve"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "fut"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "össz. sebesség"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Várólistás fájlok"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Összesen"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "MenÌ:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Link hozzáadás"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Várólista kezelése"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Gyűjtő kezelése"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Szerver szÌneteltetése/folytatása"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Szerver kilövése (Kill)"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Bezárás"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Használd a következő formát: add <Csomag neve> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "%d link ellenőrzése:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "A fájl nem létezik."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad leállítva"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Szerver állapota"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Várólistás fájlok"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Gyűjtőben levő letöltések"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Csomag várólistához adása"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Csomag gyűjtőhöz adása"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Fájl törlése várólista/gyűjtő"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Csomag törlése várólista/gyűjtő"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Csomag áthelyezése a várólista és a gyűjtő között"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Fájlok újraindítása"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Csomagok újraindítása"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Online státusz ellenőrzése, csak helyi konténer esetében"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "A konténer fájl online státuszának ellenőrzése"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Szerver szÌneteltetése"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "letöltések folytatása"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "SzÌneteltetés/folytatás"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "szerver kilövése (Kill)"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Parancsok listája:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Nem sikerÌlt írni a felhasználói beállítások fájlját"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Ehhez a pyLoad maghoz való csatlakozáshoz szÌkséged van py-openssl-re."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Cím: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Felhasználó: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Jelszó: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Hibás belépési adatok."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Nem sikerÌlt kapcsolódni ide: %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Ehhez a pyLoad maghoz való csatlakozáshoz szÌkséged van py-openssl-re."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Az interaktív mód néhány parancs után figyelmen kívÌl hagyva."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Csomag hozzáadása:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Add meg az új csomag nevét"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Csomag: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Hozzáadandó linkek."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Írd %s miután végeztél."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Link felvéve: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " vissza a főmenÃŒbe"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Csomagok kezelése:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Linkek kezelése:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Mit szeretnél áthelyezni?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Mit szeretnél törölni?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Mit szeretnél újraindítani?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Válaszd ki mit szeretnél csinálni, vagy adj meg egy csomag számot."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "törlés"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "áthelyezés"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "újraindítás"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - előző"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - következő"
+
diff --git a/locale/hu/LC_MESSAGES/setup.po b/locale/hu/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..bd43fefc0
--- /dev/null
+++ b/locale/hu/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Hungarian\n"
+"Language: hu_HU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "i"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Üdvözöllek a pyLoad beállítási segédben."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "EllenőrizzÃŒk a rendszert és létrehozunk egy alap beállítást a pyLoad számára."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "A [] közötti érték minden esetben az alapértelmezett érték,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "ha nem akarod megváltoztatni, vagy nem vagy biztos benne mit válassz, csak nyomj entert."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Ne feledd: a segédhez visszatérhetsz ha a pyload.py-t a --setup vagy -s paraméterekkel indítod."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Ha bármi probléma lépne fel a segéd futása közben nyomj STRG-C kombinációt"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "a megszakításhoz, és hogy ne induljon el újra automatikusan."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Ha készen állsz a rendszer ellenőrzésre, nyomj Entert."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "A pyLoad futtatásához szÌkséged van a pycurl, sqlite és python 2.5, 2.6 vagy 2.7 verzióra."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Ezek javítása után futtasd újra a pyLoad-ot."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "A telepítő bezárul."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Rendszer ellenőrzés kész, nyomj Entert az eredmény megjelenítéséhez."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Státusz ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "konténer dekódolás"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "ssl kapcsolat"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "automatikus captcha dekódolás"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Webes felÃŒlet"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "kiterjesztett Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Elérhető szolgáltatások:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Hiányzó szolgáltatások: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto nem elérhető"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Erre szÌkséged van a konténer fájlok dekódolásához."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL nem elérhető"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Erre szÌkséged van, ha biztonságos kapcsolatot szeretnél a mag vagy webes felÌlet eléréséhez."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Ha csak helyben akarod elérni a pyLoad-ot, az ssl nem szÌkséges."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "nincs elérhető Captcha felismerő"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Csak néhány szolgáltatóhoz kell és ingyenes felhasználóként."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI nem elérhető"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Grafikus felÃŒlet."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "nincs JavaScript motor"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Néhány Click'N'Load linknek szÌksége van erre. Telepítsd a Spidermonkey, ossp-js, pyv8 vagy rhino csomagokat."
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Most megszakíthatod a telepítést, és kijavíthatod az esetleges hibákat"
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Folytatod a beállítást?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Megváltoztatod a konfigurációs fájlok helyét? A jelenlegi: %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Megváltoztatod a konfigurációs fájlok helyét?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Be akarod állítani a bejelentkezési adatokat és az általános beállításokat?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Ajánlott az első futtatásnál."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Létre akarod hozni az általános beállításokat?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Be akarod állítani az ssl-t?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Beállítod az ssl-t?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Beállítod a webes kezelőfelÃŒletet?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Beállítod a webes kezelőfelÃŒletet?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "A beállítások sikeresen elvégezve."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Nyomj entert a kilépéshez majd indítsd újra a pyLoad-ot"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Rendszer ellenőrz ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "A python verziód túl új. Használd a 2.6/2.7-es verziót."
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "A python verziód túl régi. Használj legalább 2.5-ös verziót."
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python Verzió: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "A telepített jinja2 verzió %s túl régi."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Biztonságosan tovább haladhatsz, de ha a webes felÌlet nem működik,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "frissítsd vagy töröld, a pyLoad tartalmaz egy alkalmas jinja2 könyvtárat."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS motor"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Általános beállítások ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Ezek a belépési adatok a következőkhöz alkalmasak: CLI, GUI, web felÃŒlet."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Felhasználónév"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "KÃŒlső kliensek (GUI, CLI, ...) számára szÃŒkséges távoli elérés a hálózaton keresztÃŒli működéshez."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Ha csak a webes felÌletet használod, akkor kikapcsolhatod, hogy memóriát takaríts meg."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Távoli hozzáférés engedélyezése"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Nyelv"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Letöltési könyvtár"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Max. egyidejű letöltés"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Újracsatlakozás használata?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Újracsatlakozó script helye"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Webes felÌlet Beállítása ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Webes felÌlet engedélyezése?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Figyelt cím, ha csak helyben akarod elérni akkor használd az 127.0.0.1 vagy localhost nevet."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Cím"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "A pyLoad számos backend szerver lehetőséget nyújt, ezekről következik egy rövid ismertető."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Használható több web-szerverrel (apache, lighttpd) egyéni beállítást igényel, ami nem egyszerű."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Letölthető innen: https://github.com/jonashaag/bjoern, le kell fordítani"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Figyelem: Néhány kivételes esetben a beépített szerver nem működik megfelelően. Ha hibát észlel a webes felÃŒleten"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "térjen vissza ide, és válasszon másik szervert."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Szerver"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## SSL beállítás ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Futtasd ezeket a parancsokat a konfigurációs könyvtárban, az ssl tanúsítványok elkészítéséhez:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Ha kész vagy, és minden rendben, akkor aktiválhatod az ssl-t."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "SSL engedélyezése?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Válassz műveletet"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Felhasználó szerkesztés/felvétel"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Felhasználók listázása"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Felhasználó törlése"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Kilépés"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Felhasználók"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "A konfigurációs fájlok új helye, a jelenlegi konfiguráció nem kerÌl áthelyezésre."
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Konfiguráció helye"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "A konfigurációs fájlok helye megváltozott,"
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Nyomj Entert a kilépéshez."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "A beállítások eléri útja hibás: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: hiányzik"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Jelszó: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Rövid jelszó. Legalább 4 karaktert használj."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Jelszó (ismét): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "A jelszavak nem egyeznek."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "igen"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "igaz"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "i"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nem"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "hamis"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "h"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Hibás érték"
+
diff --git a/locale/id/LC_MESSAGES/django.po b/locale/id/LC_MESSAGES/django.po
new file mode 100644
index 000000000..97f0628e5
--- /dev/null
+++ b/locale/id/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Indonesian\n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/id/LC_MESSAGES/pyLoad.po b/locale/id/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..970c516c2
--- /dev/null
+++ b/locale/id/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,859 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Indonesian\n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, p \ No newline at end of file
diff --git a/locale/id/LC_MESSAGES/pyLoadCli.po b/locale/id/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..feb756424
--- /dev/null
+++ b/locale/id/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Indonesian\n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/id/LC_MESSAGES/setup.po b/locale/id/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..ca6ad636f
--- /dev/null
+++ b/locale/id/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Indonesian\n"
+"Language: id_ID\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/it/LC_MESSAGES/django.mo b/locale/it/LC_MESSAGES/django.mo
deleted file mode 100644
index 57858c8a6..000000000
--- a/locale/it/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/django.po b/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 000000000..58bcfe478
--- /dev/null
+++ b/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Italian\n"
+"Language: it_IT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Richiesto nuovo captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Leggi il testo nel captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyload riavviato"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "off"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Successo"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "on"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Sei sicuro di voler chiudere pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Riavvia il collegamento"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Cancella collegamento"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Immetti un nome per il pacchetto."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Clicca sulla posizione giusta del captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Si Ú verificato un errore."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "La cartella Ú vuota"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Falliti"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Nessun Captcha da leggere."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Le passwords non corrispondono."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Settaggi salvati."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nuova cartella"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Sei sicuro di voler riavviare pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "attendo %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Download attivi"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Home"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "Coda"
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "Collettore"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Download"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Log"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configurazione"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nome"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Stato"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Info"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Dimensione"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Avanzamento"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Login"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Nome utente"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Password"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Il tuo nome utente e la tua password non corrispondono. Riprova."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Per resettare i tuoi dati di accesso o aggiungere un utente esegui:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Elimina i completi"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Riavvia i falliti"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Cartella:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Password:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Modifica pacchetto"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Modifica i dettagli del pacchetto qui di seguito."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Il nome del pacchetto."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Cartella"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nome della sottocartella per questi download."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lista delle password usate per decomprimere."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Invia"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "reset"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Ti sei disconnesso con successo."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Percorso"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "assoluto"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nome"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "dimensioni"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tipo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "ultima modifica"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "cartella superiore"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "nessun contenuto"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Generale"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Account"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Scegli una sezione dal menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "Premium"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Valido fino al:"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Traffico rimanente:"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tempo"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max paralleli"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Elimina? "
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "valido"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "non valido"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "si"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "no"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Aggiungi"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Aggiungi Account"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Inserisci i dati di un account premium per usufruire delle sue funzionalità."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Il tuo nome utente."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "La password di questo account."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Tipo"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Scegli l'hoster per il tuo account."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Avvio"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "precedente"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "prossimo"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fine"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Novità"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Supporto"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistema"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr "Python:"
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "OS:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "Versione pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Cartella installazione:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Cartella configurazione:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Cartella download:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Spazio libero:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Lingua:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Porta Interfaccia Web:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Porta Interfaccia Remota:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Configurazione"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "File Manager"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Aggiungi pacchetto"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Incolla i tuoi link o carica un contenitore."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Il nome del nuovo pacchetto."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Collegamenti"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Incolla qui i tuoi link oppure qualsiasi testo e premi il pulsante filtra."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtra URL"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Password per l'archivio RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "File"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Carica un contenitore."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Destinazione"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha in lettura"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "Captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Il captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Testo"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Inserisci il testo per il captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Chiudi"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interfaccia web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Nessun aggiornamento disponibile!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugin aggiornati, per favore riavviare pyLoad."
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha in attesa"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Esci"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Amministra"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Info"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Effettua il login!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Arresto"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Annulla"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Download:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Riconnetti:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Velocità:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Attivo:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Ricarica pagina"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "carico"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Torna in cima"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Chiudi pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Riavvia pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Per aggiungere utenti o cambiare le password usa:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Importante: l'utente Admin ha sempre tutti i permessi!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Cambia Password"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Amministratore"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Permessi"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "cambia"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Inserisci la tua attuale e la nuova Password."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Utente"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Password attuale"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nuova password"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Inserisci la nuova password."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nuova password (ripetila)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Ripeti la nuova password."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Non hai i permessi per accedere a questa pagina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Cartella Download non trovata."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "illimitato"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non disponibile"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Esegui pyload.py -s per accedere al setup."
+
diff --git a/locale/it/LC_MESSAGES/pyLoad.mo b/locale/it/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 07b1c01c5..000000000
--- a/locale/it/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/pyLoad.po b/locale/it/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..683ffed12
--- /dev/null
+++ b/locale/it/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Italian\n"
+"Language: it_IT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Ricevuto segnale di uscita"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad già in esecuzione con pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Impossibile cambiare il gruppo: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Impossibile cambiare utente: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "cartella dei log"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Avvio"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Uso la cartella home: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto per decodificare la libreria di file"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "cartella per i file temporanei"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "cartella dei download"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL per la connessione sicura"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Sposto la vecchia configurazione utente al DB"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Per favore controlla i tuoi dati di accesso eseguendo ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Tutti i link rimossi"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Tempo di download: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Spazio libero: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Attivazione Account..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Attivo i Plugins..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad Ú attivo e funzionante"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "riavvio pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "esco da pyLoad"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installazione %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "%(desc)s: %(name)s non trovato"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "non posso creare %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "spegnimento..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "errore durante lo spegnimento"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad chiuso da Terminale"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Filedatabase Ú stato cancellato a causa dell'incompatibilità di versione."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Il file del database non poteva essere convertito."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Il database Ú stato convertito dalla v2 alla v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Il database Ú stato convertito dalla v3 alla v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Conversione del vecchio DB Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "finito"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "offiline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "online"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "in coda"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "saltato"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "in attesa"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temp. offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "avviando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "fallito"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "annullato"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "decifrando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personalizzato"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "scaricando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "elaborando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "sconosciuto"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Pacchetto completato: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "ThriftBackend SSL in uso"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Errore backend remoto: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Avvio %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Impossibile avviare backend %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "attendo %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certificati SSL non trovati."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Siamo spiacenti, abbiamo abbandonato il supporto per avviare %s direttamente all'interno di pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Puoi utilizzare il server threaded che offre buone prestazioni e l'SSL,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "naturalmente puoi continuare ad usare il tuo attuale %s con il server fastcgi di pyLoads"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "si possono trovare configurazioni di esempio nella directory pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Non Ú possibile usare %(server)s, python-flup inon Ú installato!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Errore di importazione del server lightweight: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "E' necessario scaricare e compilare bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Copia il file boern.so nella cartella pyload/lib oppure usa setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Naturalmente Ú necessario avere familiarità con linux e saper compilare da codice sorgente"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Il server Ú stato impostato su threaded, a causa di problemi di prestazioni noti sotto Windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Questo server non supporta SSL, si consiglia l'uso della versione threaded"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Avvio webserver integrato: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Avvio webserver SSL threaded: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Avvio Webserver threaded: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Avvio server fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Avvio il webserver lightweight (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Non hai i permessi per accedere a questa pagina."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Cartella Download non trovata."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "illimitato"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "non disponibile"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Esegui pyload.py -s per accedere al setup."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Download accellerato fallito, ripiego su singola connessione | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Avvio Download: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Download terminato: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Il plugin %s ha fallito una funzione."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Download annullato: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Download riavviato: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Il download Ú offline: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Il download Ú temporaneamente offline: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Download fallito: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Impossibile collegarsi all'host o connessione resettata, aspetto 1 minuto e riprovo."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Download saltato: %(name)s a causa di %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Decodifica avviata: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Decodifica fallita: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Riprovo %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Recupero informazioni per %(name)s fallito | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Errore durante l'esecuzione hooks: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Attivazione di %(name)s fallita"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Plugins attivati: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Plugins disattivati: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Riconnessione fallita: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Script per la riconnessione non trovato!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Nuovo tentativo di connessione"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Fallita l'esecuzione dello script di riconnessione!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Riconnesso, nuovo IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Spazio su disco insufficiente"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Impossibile effettuare il login con l'account %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Password errata"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Il tuo orario %s ha un formato sbagliato, usa: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "L'account %s non ha abbastanza traffico, controllo di nuovo tra 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "L'account %s Ú scaduto; prossimo controllo fra 1 ora"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Limite di download raggiunto"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s ha uno schema non valido."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Errore importando %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Nessun Hoster caricato"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Attiva il download diretto sul tuo account Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList non può essere cancellato."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Impostazioni account cancellato a causa del nuovo formato della configurazione."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Richiesta autorizzazione (nomeutente:password)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Per favore Inserisci il tuo account %s o disattiva questo plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "C'era del codice HTML nel file scaricato (%s)... errore di redirect? Il download verrà riavviato."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "File temporaneamente non disponibile"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: attendo %d s tra i download."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: attendo %d s per il captcha."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Il file scaricato era vuoto"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Chiave API invalida"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Traffico rimanente non sufficiente"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Traffico superato"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Traffic Share (download diretto)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Stai già scaricando da questo indirizzo IP, aspetta 60 secondi"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Auth Code non valido, il download sarà riavviato"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Nessuno slot gratuito libero"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Hai bisogno di un account Premium per questo file"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Nome di file riportato non valido"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Errore durante il download parallelo, attendo 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Non connesso."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Decifratura non riuscita"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Nessuna chiave fornita nell'URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Codice di errore:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Il file non esiste."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** I plugin sono stati aggiornati, riavvia pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins aggiornati e riavviati"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Nessun aggiornamento per i plugin disponibile"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Nessun aggiornamento per pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Nuova versione di pyLoad %s disponibile ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Scaricala da qui: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Impossibile collegarsi al server per gli aggiornamenti"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nuova versione di %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Errore durante l'aggiornamento di %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Versione non corrispondente"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Download finito: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nuova richiesta Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Rispondi usando 'c %s text on the captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Inserire prima il tuo account premium.to e riavviare pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Aggiunto %s da HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s non installato"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Impossibile attivare %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Attivato"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Nessun plugin per l'estrazione attivato"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Il pacchetto %s Ú stato messo in coda per l'estrazione successiva"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Verifico pacchetto %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Estraggo in %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Nessun file trovato da estrarre"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "estrazione"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Protetto da password"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Password errata"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Cancello %s file"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Estrazione completata"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Archivio danneggiato"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC non corrispondente"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Errore Sconosciuto"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Configurazione Utente e Gruppo fallita"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Porta 9666 già in uso"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s crediti rimasti"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Non posso inviare la risposta."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Il tuo account CaptchaTrader non ha crediti sufficienti"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Elenco Crypter non trovato"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Elenco Crypter vuoto"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Download finito: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nuovo CaptchaID dall'upload: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Il tuo account Captcha 9kw.eu non ha abbastanza crediti"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Script installati per %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script non eseguibile:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Errore in %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Il tuo account ExpertDecoders ha non abbastanza crediti"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Inserisci prima il tuo account rehost.to e poi riavvia pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Inserire un account premiumize.me valido e riavviare pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d crediti rimasti"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil e tesseract non sono installati e nessun Client Ú connesso per decifrare i captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Nessun risultato del captcha Ú stato trovato da alcun plugin nel tempo concesso."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Impostazione di Utente e Gruppo non riuscita: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Nessun client connesso per decifrare i captcha."
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Aggiunto pacchetto %(name)s contenente %(count)d collegamenti"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Aggiunti %(count)d link al pacchetto #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Nessun motore js rilevato, installare Spidermonkey, ossp-js, pyv8 o rhino"
+
diff --git a/locale/it/LC_MESSAGES/pyLoadCli.mo b/locale/it/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 158fd317a..000000000
--- a/locale/it/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/pyLoadCli.po b/locale/it/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..b9e963bc3
--- /dev/null
+++ b/locale/it/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Italian\n"
+"Language: it_IT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Interfaccia a riga di comando"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s File Scaricati:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Velocità: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Dimensione: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Terminato in: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "in attesa: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Stato:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "in pausa"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "in esecuzione"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Velocità totale"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "File in coda"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Totale"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Menu:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Aggiungi Link"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "Gestisci Coda"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "Gestisci Libreria"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Pausa/Riattiva Server"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Termina Server"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Esci"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Si prega di utilizzare questa sintassi: add <Nome pacchetto> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Controllo %d link:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Il file non esiste."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad Ú stato chiuso"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Mostra lo stato del server"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Mostra i download in coda"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Mostra i download nella libreria"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Aggiunge pacchetti alla coda"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Aggiungi pacchetto alla libreria"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Elimina file dalla Coda/Collezione"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Elimina pacchetti dalla Coda/Libreria"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Sposta Pacchetti dalla Coda alla Libreria o vice versa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Riavvia file"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Riavvia pacchetti"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Controlla stato online, funziona con contenitori locali"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Controlla stato online di un file contenitore"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Mette in pausa il server"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continua i download"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Commutatore pausa/riprendi"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr " termina server"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista dei comandi:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Impossibile scrivere il file di configurazione utente"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Hai bisogno di py-openssl per connetterti a questo pyLoad Core."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Indirizzo:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Porta: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Nome utente: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Password:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "I dati di login sono errati."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Impossibile stabilire una connessione a %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Hai bisogno di py-openssl per connetterti a questo pyLoad core."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Modalità interattiva ignorata dato che hai passato alcuni comandi."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Aggiungi Pacchetto:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Inserisci un nome per il nuovo pacchetto"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Pacchetto: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analizza i link che desideri aggiungere."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Digita %s quanto hai fatto."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Link aggiunti: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " torna al menu principale"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Gestisci Pacchetti:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Gestisci Link:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Che cosa vuoi spostare?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Che cosa vuoi cancellare?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Che cosa vuoi riavviare?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Scegli cosa vuoi fare oppure digita il numero del pacchetto."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "cancella"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "sposta"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "riavvia"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - precendente"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - sucessivo"
+
diff --git a/locale/it/LC_MESSAGES/pyLoadGui.mo b/locale/it/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index cd1cbdf73..000000000
--- a/locale/it/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/setup.mo b/locale/it/LC_MESSAGES/setup.mo
deleted file mode 100644
index e13b99bc9..000000000
--- a/locale/it/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/it/LC_MESSAGES/setup.po b/locale/it/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..9a7e5004d
--- /dev/null
+++ b/locale/it/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Italian\n"
+"Language: it_IT\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "s"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "n"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Benvenuto nell'Assistente di Configurazione di pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Il setup controllerà il tuo sistema e genererà una configurazione di base per avviare pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Il valore tra le parentesi quadre [] Ú sempre il valore di default,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "nel caso in cui non vuoi cambiare impostazione o non sei sicuro su cosa scegliere, premi semplicemente Invio."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Ricorda: Puoi sempre rieseguire questo assistente con i parametri --setup o -s, quando avvii pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Se hai qualche problema con questo assistente premi CRTL+C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "per annullare e non farlo partire più automaticamente con pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Quando sei pronto per la verifica del sistema premi Invio."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Hai bisogno di pycurl, sqlite e python 2.5, 2.6 o 2.7 per avviare pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Per favore corregilo e riavvia pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Il setup si chiuderà."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Verifica del sistema finita, premi Invio per vedere il report di stato."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Stato ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "decrittografia contenitore"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "connessione ssl"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "decrittografia automatica captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "GUI"
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interfaccia web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "Click'N'Load esteso"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Funzioni disponibili:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Funzioni mancanti: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto non disponibile"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Hai bisofno di questo se vuoi decifrare i contenitori di file."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL non disponibile"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Questo Ú necessario se vuoi stabilire una connessione sicura con il core o l'interfaccia web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Se vuoi accedere a pyLoad soltanto in locale, SSL non Ú necessario."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "Riconoscimento captcha non disponibile"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Necessario solo per alcuni hoster e come freeuser."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Gui non disponibile"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "L'interfaccia utente grafica (GUI)."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "nessun motore JavaScript trovato"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Avrai bisogno di questo per alcuni link Click'N'Load. Installa Spidermonkey, ossp-js, pyv8 o rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Se vuoi puoi annullare il setup ora e correggere alcune dipendenze."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Continuare con il setup?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Vuoi cambiare il percorso per la configurazione? Quello attuale Ú %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Se si utilizza pyLoad su un server o la partizione principale si trova su una memoria flash interna potrebbe essere una buona idea cambiarla."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Cambiare il percorso della configurazione?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Si desidera configurare i dati di accesso e le impostazioni di base?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "E' consigliato per il primo avvio."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Creare configurazione di base?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Vuoi configurare l'ssl?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Configurare ssl?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Vuoi configurare l'interfaccia web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Configurare l'interfaccia web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Configurazione terminata con successo."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Premi Invio per uscire e riavviare pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Verifica del Sistema ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "La versione di python Ú troppo recente, usare Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "La versione di python Ú troppo vecchia, usare almeno Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Versione di Python: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "La versione installata %s di jinja2 sembra essere troppo vecchia."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Si può tranquillamente continuare, ma se l'interfaccia web non funziona,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "si prega di aggiornarla o disinstallarla, pyLoad include una libreria jinja2 sufficiente."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "motore JS"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Configurazione Base ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "I seguenti dati di accesso sono validi per CLI, GUI e interfaccia web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Nome utente"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "I client esterni (GUI, CLI o altri) richiedono accesso remoto al sistema per funzionare nella rete."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Comunque, se hai in mente di utilizzare solo l'interfaccia web puoi disattivarlo per ridurre il consumo di memoria ram."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Attiva l'accesso remoto"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Lingua"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Cartella di download"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Numero massimo di download paralleli"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Usare la riconnessione?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Posizione dello script di riconnessione"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Configurazione dell'interfaccia web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Attivare l'interfaccia web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Indirizzo di ascolto, se usi 127.0.0.1 o localhost, l'interfaccia web sarà accessibile soltanto localmente."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Indirizzo"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Porta"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad offre diversi tipi di supporto a server, segue una breve spiegazione."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Server di default, la scelta migliore se non sai quale scegliere."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Questo server supporta SSL ed Ú una valida alternativa a quello integrato."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Può essere utilizzato con apache o lighttpd, ma ne richiede la configurazione, che non Ú una procedura semplice."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Alternativa molto veloce, scritta in C, richiede libev e conoscenza di linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Scaricalo da qui: https://github.com/jonashaag/bjoern, compilalo"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "e copia bjoern.so in pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Attenzione: In alcuni rari casi il server integrato non funziona, se noti problemi con l'interfaccia web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "torna qui e sostituisci il server integrato con quello threaded."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Server"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Configurazione SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Esegui questi comandi dalla cartella di configurazione di pyLoad per creare i certificati SSL:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Se hai finito e tutto Ú andato bene, puoi attivare SSL ora."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Attivare SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Seleziona azione"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Crea/Modifica utente"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Lista utenti"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Rimuovi utente"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Esci"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Utenti"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Imposta nuovo percorso per la configurazione. La configurazione attuale NON sarà trasferita!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Percorso di configurazione"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Percorso di configurazione modificato. il setup ora si chiuderà; riavvialo per andare avanti."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Premi Invio per uscire."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Impostazione del percorso di configurazione non riuscita: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr "%s: OK"
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: mancante"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Password:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Password troppo corta. Usa almeno 4 caratteri."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Password (di nuovo): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Le passwords non corrispondono."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "si"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "vero"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "v"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "no"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "falso"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "f"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Inserimento non valido"
+
diff --git a/locale/ja/LC_MESSAGES/django.po b/locale/ja/LC_MESSAGES/django.po
new file mode 100644
index 000000000..d65e93853
--- /dev/null
+++ b/locale/ja/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "新しい Captcha をリク゚スト"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Captcha のテキストを読んでください。"
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad が再起動したした"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "オフ"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "成功したした"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "オン"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "pyLoad を本圓に終了したすか?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "リンクを再起動"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "リンクを削陀"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "パッケヌゞ名を入力しおください。"
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "右の captcha の䜍眮をクリックしおください。"
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "゚ラヌが発生したした。"
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "フォルダが空です"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "読み取る Captcha がありたせん。"
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "パスワヌドが䞀臎したせん。"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "蚭定が保存されたした。"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "新芏フォルダ"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "pyLoad を本圓に再起動したすか?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "%s を埅機䞭"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "アクティブなダりンロヌド"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "ホヌム"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr "キュヌ"
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr "コレクタヌ"
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "ダりンロヌド"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "ログ"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "蚭定"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "名前"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "ステヌタス"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "情報"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "サむズ:"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "進捗"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "ログむン"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "ナヌザヌ名"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "パスワヌド"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "ナヌザ名かパスワヌドが䞀臎したせん。再詊行しおください。"
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "ログむン デヌタをリセットしたりナヌザを远加するには以䞋を実行しおください:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "削陀が完了したした"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "再起動に倱敗したした"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "フォルダ:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "パスワヌド:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "パッケヌゞを線集"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "䞋のパッケヌゞの詳现を線集したす。"
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "パッケヌゞの名前。"
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "フォルダヌ"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "これらのダりンロヌドのためのサブフォルダの名前です。"
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "unrar に䜿われたパスワヌドの䞀芧です。"
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "送信"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "リセット"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "正垞にログアりトしたした。"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "パス"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "絶察的"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "盞察的"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "名前"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "暙本数"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "皮類"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "最終曎新"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "芪ディレクトリ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "内容なし"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "暙準"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "プラグむン"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "アカりント"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "メニュヌからセクションを遞択"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "プラグむン"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "プレミアム"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "有効期限"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "残りトラフィック"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "時間"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "最倧の䞊列数"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "削陀したすか?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "有効です"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "無効"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "はい"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "いいえ"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "远加"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "プレミアム機胜を䜿うにはアカりント デヌタを入力しおください。"
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "あなたのナヌザ名です。"
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "このアカりントのパスワヌドです。"
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "あなたのアカりントのホスティング䌚瀟を遞択しおください。"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "開始"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "前ぞ"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "次ぞ"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "終了"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "ニュヌス"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "サポヌト"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad のバヌゞョン:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "むンストヌル フォルダ:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "蚭定フォルダ:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "ダりンロヌド フォルダ:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "空き領域:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "蚀語:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "りェブむンタフェヌスのポヌト:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "リモヌト むンタフェヌスのポヌト:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "ファむルマネヌゞャ"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "パッケヌゞを远加"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "リンクを貌り付けるかコンテナをアップロヌドしおください。"
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "新しいパッケヌゞの名前。"
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "リンク"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "ここにリンクや任意のテキストを貌り付けるかフィルタ ボタンを抌しおください。"
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "フィルタ URL"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "RAR アヌカむブのパスワヌド"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "ファむル"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "コンテナをアップロヌドしおください。"
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "保存先"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha を読み蟌み䞭"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha です。"
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "テキスト"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Captcha のテキストを入力しおください。"
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "閉じる"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "りェブむンタフェヌス"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad の曎新を利甚できたす!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "プラグむンが曎新されたした、再起動しおください!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha を埅機䞭"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "ログアりト"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "管理"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "情報"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "ログむンしおください!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "停止"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "ダりンロヌド:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "再接続:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "速床:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "アクティブ:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "ペヌゞを再読蟌"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "読み蟌み䞭"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "トップに戻る"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "pyLoad を終了"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "pyLoad を再起動"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "ナヌザを远加たたはパスワヌドを倉曎するには:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "重芁: 管理者ナヌザには垞にすべおの暩限がありたす!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "パスワヌドを倉曎"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "暩限"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "倉曎"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "珟圚のパスワヌドず新しいパスワヌドを入力しおください。"
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "ナヌザ"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "珟圚のパスワヌド"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "新しいパスワヌド"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "新しいパスワヌドです。"
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "新しいパスワヌド (再入力)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "新しいパスワヌドを繰り返しおください。"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "無制限"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "利甚䞍可"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/ja/LC_MESSAGES/pyLoad.po b/locale/ja/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..e742cdd43
--- /dev/null
+++ b/locale/ja/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "終了のシグナルを受信"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad はプロセス ID %s で既に実行䞭"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "グルヌプの倉曎に倱敗したした: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "ナヌザヌの倉曎に倱敗したした: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "ログのフォルダ"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "é–‹å§‹äž­"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "ホヌムディレクトリずしお %s を䜿甚䞭"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "䞀時ファむルのフォルダ"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "ダりンロヌド先のフォルダ"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "セキュアな接続のための OpenSSL"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "./pyload.py -u であなたのログむンデヌタをチェックしおください"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "すべおのリンクが削陀されたした"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "ダりンロヌド時間: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "空き領域: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "アカりントをアクティベヌト䞭..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "プラグむンをアクティベヌト䞭..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad が実行䞭"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "pyLoad を再起動䞭"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad が終了したす"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "%s をむンストヌル"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "シャットダりン䞭..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "シャットダりン䞭に゚ラヌ"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "完了"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "オフラむン"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "オンラむン"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "キュヌに入っおいたす。"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "スキップ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "埅機䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "䞀時的にオフラむン"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "é–‹å§‹äž­"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "倱敗したした"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "䞭止したした"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "埩号化"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "カスタム"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "ダりンロヌド䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "凊理䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "䞍明"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "パッケヌゞが完了したした: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "リモヌト バック゚ンド ゚ラヌ: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "開始 %(name)s%(addr)s%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr " バック゚ンド %(name)sの読み蟌みに倱敗した |%(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "%s を埅機䞭"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL 蚌明曞が芋぀かりたせん。"
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "このサヌバヌで SSL はありたせん、代わりにスレッドを䜿甚しおください"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "無制限"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "利甚䞍可"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "ダりンロヌド チャンクぞの移行に倱敗したした 1 ぀の接続 |。%s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "ダりンロヌド開始: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "ダりンロヌド完了: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "プラグむン %s には関数がありたせん。"
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "ダりンロヌドを䞭止: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "ダりンロヌドを再開: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "ダりンロヌドがオフラむン: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "ダりンロヌドが䞀時的にオフラむン: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "ダりンロヌドに倱敗したした: %(name)s|%(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "ホストたたは接続のリセットに接続できなかったため、1 分埌接続に再詊行しおください。"
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "ダりンロヌドをスキップしたした:%(plugin)sにより %(name)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "%(name)sの倱敗の情報取埗する |%(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "%(name)sをアクティブ に倱敗したした"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "再接続に倱敗したした: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "再接続スクリプトが芋぀かりたせん!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "再接続を開始䞭"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "倱敗した実行スクリプト再接続 "
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "再接続、新しい IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "デバむスに空き領域䞍足"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "アカりント%(user)s ではログむンできたせんでした |%(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "間違ったパスワヌド"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "アカりント %s が十分なトラフィックがない、30 分でもう䞀床チェック"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "アカりント %s は有効期限が切れお、1 h でもう䞀床チェック"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "ダりンロヌド制限に達したした"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "%(name)s%(msg)sのむンポヌト ゚ラヌ"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "ホスティング サヌビスが読み蟌たれおいたせん"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Bitshare アカりントで盎接のダりンロヌドを有効にしたす。"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr " (username:password)承認が必芁"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "%S アカりントを入力するか、このプラグむンを無効にしおください。"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "ファむルが䞀時的に利甚できたせん。"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: ダりンロヌド %d 秒間埅機しおいたす。"
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: キャプチャ %d 秒を埅っおいたす。"
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "ダりンロヌドしたファむルが空だった"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API キヌが無効です。"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s十分なトラフィックがない"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "トラフィックを超えおいたす"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "急流: トラフィックを共有 (ダりンロヌドによる盎販)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "既にこの ip アドレスからダりンロヌドする、60 秒を埅っおいたす。"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "無効な認蚌コヌド、ダりンロヌドが再起動"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom 空きスロット"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "このファむルのプレミアム アカりントを必芁がありたす。"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "無効なファむル名を報告"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "䞊列ダりンロヌド ゚ラヌ、60秒を埅っおいたす。"
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "ログむンしおいたせん。"
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "埩号化に倱敗したした"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "URL で提䟛されるファむル キヌがありたせん。"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "゚ラヌ コヌド:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "* * * プラグむンが曎新されお、pyLoad を再起動しおください * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "プラグむンの曎新し、再読み蟌み"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "プラグむンの曎新はありたせん。"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "PyLoad の曎新"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "* * * 新しい pyLoad バヌゞョン %s が利甚 * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "* * * それを埗るここで: http://pyload.org/download * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "曎新サヌバヌに接続するこずはできたせん。"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "新しいバヌゞョンの%(type)s|%(name)s%(version) .2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "%sを曎新するずきの゚ラヌ"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "バヌゞョンの䞍䞀臎"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "ダりンロヌド完了: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "新しいキャプチャ芁求: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "'C %s テキストのキャプチャを' ず答え"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "HotFolder から%s远加 "
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s がむンストヌルされたした"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "%S をアクティブにできたせんでした。"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "アクティブ化"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "アクティブ化のプラグむンがない"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "パッケヌゞ %s は、抜出埌のキュヌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "パッケヌゞ %s をチェック"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "%S を抜出したす。"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "ファむルを抜出するが芋぀かりたせん"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "展開䞭"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "パスワヌドで保護されおいたす"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "正しくないパスワヌド"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "%s 個のファむルを削陀䞭"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "展開が完了したした"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "アヌカむブ ゚ラヌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC が䞍䞀臎"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "䞍明な゚ラヌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "ナヌザヌ蚭定およびグルヌプに倱敗したした"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: 䜿甚䞭のポヌト 9666"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s のクレゞットを巊"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "応答を送信できたせんでした。"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "あなたの CaptchaTrader アカりントが十分なクレゞット"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Crypter リストが芋぀かりたせん"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Crypter リストは空です。"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "ダりンロヌド完了: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "アップロヌドから新しい CaptchaID: %s: %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "あなたのキャプチャ 9kw.eu アカりントが十分なクレゞット"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "%s のむンストヌル スクリプト"
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "スクリプトがない実行可胜ファむル:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "%(script)s の゚ラヌ%(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "あなたの ExpertDecoders アカりントが十分なクレゞット"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "たず、rehost.to アカりントを远加し、pyLoad を再起動しおください。"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d のクレゞットを残った"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil ずテッセラクト むンストヌルされおいないクラむアント接続 captcha 解読"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "ナヌザヌずグルヌプの蚭定に倱敗したした: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/ja/LC_MESSAGES/pyLoadCli.po b/locale/ja/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..cbbc8ec27
--- /dev/null
+++ b/locale/ja/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " コマンド ラむン むンタフェヌス"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s ダりンロヌド:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " 速床: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " サむズ: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " 完了: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "埅機䞭: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "ステヌタス:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "䞀時停止"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "実行しおいたす。"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "合蚈速床"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "キュヌ内のファむル"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "合蚈"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "メニュヌ:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " リンクを远加"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " キュヌを管理"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " コレクタヌを管理"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " サヌバヌを䞀時停止/再開"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " サヌバヌを終了させる"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " 終了"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "この構文を䜿甚しおください: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "%d 個のリンクをチェック䞭:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "ファむルが存圚したせん。"
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad が終了したした"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "印刷サヌバヌの状態"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "キュヌ内のダりンロヌドを印刷"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "コレクタヌ内のダりンロヌドを印刷"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "パッケヌゞをキュヌに远加"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "パッケヌゞをコレクタヌに远加"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "キュヌ/コレクタヌからファむルを削陀"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "キュヌ/コレクタヌからパッケヌゞを削陀"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "キュヌからコレクタヌ (たたはその逆) にパッケヌゞを移動"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "ファむルを再起動"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "パッケヌゞを再起動"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "オンラむンの状態やロヌカル コンテナの仕事をチェック"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "コンテナ ファむルのオンラむン状態をチェック"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "サヌバヌを䞀時停止"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "ダりンロヌドを継続"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "䞀時停止/再開を切替え"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "サヌバヌを終了させる"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "コマンドの䞀芧:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "ナヌザヌ蚭定ファむルに曞き蟌むこずができたせんでした"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "この pyLoad コアに接続するには py-openssl が必芁です。"
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "アドレス: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "ポヌト: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "ナヌザヌ名: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "パスワヌド: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "ログむン デヌタが間違っおいたす。"
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "%(addr)s:%(port)s ぞの接続を確立できたせんでした。"
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "この pyLoad コアに接続するには py-openssl が必芁です。"
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "察話モヌドはあなたが入力したいく぀かのコマンドを無芖したした。"
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "パッケヌゞを远加:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "新しいパッケヌゞの名前を入力"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "パッケヌゞ: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "远加したいリンクを解析したす"
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "終わったら %s を入力しおください。"
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "リンクが远加されたした: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " メむン メニュヌに戻る"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "パッケヌゞを管理:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "リンクを管理:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "䜕を移動したすか?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "䜕を削陀したすか?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "䜕を再起動したすか?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "䜕をするかを遞択するかパッケヌゞ番号を入力しおください。"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "削陀"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "移動"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "再起動"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - 前"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - 次"
+
diff --git a/locale/ja/LC_MESSAGES/setup.po b/locale/ja/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..9370418e4
--- /dev/null
+++ b/locale/ja/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Japanese\n"
+"Language: ja_JP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "pyLoad 蚭定アシスタントにようこそ。"
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "それはあなたのシステムをチェックし、基本的なセットアップ pyLoad を実行するために。"
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "角かっこ内の倀は垞に、既定倀は"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "それを倉曎したくない、䜕を遞択する確認が堎合ヒットだけを入力したす。"
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "忘れおはいけない pyload.py を起動するず垞にセットアップたたは-s パラメヌタヌでこのアシスタントを再実行するこずができたす。"
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "䞭止しないでください圌はもはや自動的に pyload.py を開始させおください。"
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "システム チェックの準備が敎ったら、ヒットを入力したす。"
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "pyLoad を実行するには pycurl ず sqlite、python 2.5 たたは 2.6、2.7 が必芁です。"
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "これを修正しお pyLoad を再実行しおください。"
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## 状態 ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "SSL 接続"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "自動 captcha 解読"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "りェブむンタフェヌス"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL を利甚できたせん"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI を利甚できたせん"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "グラフィカル ナヌザ むンタヌフェむスです。"
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "JavaScript ゚ンゞンが芋぀かりたせん"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "セットアップを続けたすか。"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "蚭定パスを倉曎したすか。珟圚は %s です。"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "蚭定パスの倉曎ですか"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "ログむン デヌタず基本蚭定を構成したすか。"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "最初の実行をお勧めしたすです。"
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "基本的なセットアップを行うか"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Ssl を構成したすか。"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Ssl を構成したすか。"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "りェブむンタ フェヌスを構成したすか。"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "りェブむンタ フェヌスを構成したすか。"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "セットアップは正垞に完了したした。"
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "PyLoad を再起動を終了を入力ヒット"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## システム チェック ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python のバヌゞョン: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS ゚ンゞン"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## 基本的なセットアップ ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "次の logindata は CLI ず GUI のりェブむンタ フェヌスに有効です。"
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "ナヌザヌ名"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "蚀語"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "ダりンロヌドフォルダ"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "最倧䞊列ダりンロヌド"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "再接続を䜿甚したす。"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "スクリプトの堎所を接続し盎したす"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## りェブむンタフェヌスのセットアップ ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "りェブむンタフェヌスをアクティベヌトしたすか?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "リスン アドレス、127.0.0.1 たたは localhost、りェブむンタ フェヌスを䜿甚する堎合は、ロヌカルでのみアクセス可胜。"
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "アドレス"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "ポヌト"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad 今、簡単な説明、次のいく぀かのサヌバヌ バック゚ンドを提䟛しおいたす。"
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Apache、lighttpd を䜿甚するこずができたす、あたりにも簡単な仕事ではない、それらを構成する必芁がありたす。"
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "ここからそれを埗る: https://github.com/jonashaag/bjoern、それをコンパむル"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "泚意: いく぀かのたれなケヌスで、組み蟌みのサヌバヌが動䜜しない、りェブむンタ フェヌスで問題が発生した堎合"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "ここに戻っおくるし、ここでスレッドに組み蟌みサヌバヌを倉曎したす。"
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "サヌバヌ"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## SSL セットアップ ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Ssl 蚌明曞を pyLoad config フォルダヌからこれらのコマンドを実行したす。"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "完了したら、すべおがうたく行った堎合 ssl を今すぐアクティブ化するこずができたす。"
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "SSL を有効にするか"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "動䜜を遞択"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - ナヌザを䜜成/線集"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - ナヌザ䞀芧を衚瀺"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - ナヌザを削陀"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - 終了"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "ナヌザヌ"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "蚭定パス"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "終了するには Enter を抌しおください。"
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "蚭定パスの蚭定に倱敗したした: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: ありたせん"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "パスワヌド: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "パスワヌドが短すぎたす。4 文字以䞊にしおください。"
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "パスワヌド (再入力): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "パスワヌドが䞀臎したせん。"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "はい"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "いいえ"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "無効な入力"
+
diff --git a/locale/ko/LC_MESSAGES/django.po b/locale/ko/LC_MESSAGES/django.po
new file mode 100644
index 000000000..8a8ec2874
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Korean\n"
+"Language: ko_KR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/ko/LC_MESSAGES/pyLoad.po b/locale/ko/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..33c6cc922
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Korean\n"
+"Language: ko_KR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/ko/LC_MESSAGES/pyLoadCli.po b/locale/ko/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..ed8539ae8
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Korean\n"
+"Language: ko_KR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/ko/LC_MESSAGES/setup.po b/locale/ko/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..e806aa68e
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Korean\n"
+"Language: ko_KR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/ms/LC_MESSAGES/django.po b/locale/ms/LC_MESSAGES/django.po
new file mode 100644
index 000000000..7ee9b2c75
--- /dev/null
+++ b/locale/ms/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Malay\n"
+"Language: ms_MY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Gagal"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Jenis"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistem"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Persediaan"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/ms/LC_MESSAGES/pyLoad.po b/locale/ms/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..e234643df
--- /dev/null
+++ b/locale/ms/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Malay\n"
+"Language: ms_MY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/ms/LC_MESSAGES/pyLoadCli.po b/locale/ms/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..4d9aef64d
--- /dev/null
+++ b/locale/ms/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Malay\n"
+"Language: ms_MY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Tambah pakej:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Masukkan nama untuk pakej baru"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Pakej: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Pautan ditambah: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " kembali ke menu utama"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Menguruskan pakej:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Menguruskan pautan:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Apakah yang anda ingin mengalih?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Apakah yang anda ingin hapuskan?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Apakah yang anda ingin memulakan semula?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "hapuskan"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "mengalih"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/ms/LC_MESSAGES/setup.po b/locale/ms/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..c84724694
--- /dev/null
+++ b/locale/ms/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Malay\n"
+"Language: ms_MY\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/nl/LC_MESSAGES/django.mo b/locale/nl/LC_MESSAGES/django.mo
deleted file mode 100644
index bea13751c..000000000
--- a/locale/nl/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/django.po b/locale/nl/LC_MESSAGES/django.po
new file mode 100644
index 000000000..fac53942b
--- /dev/null
+++ b/locale/nl/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Dutch\n"
+"Language: nl_NL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Nieuw Captcha verzoek"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Lees de tekst van de captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "PyLoad is herstart"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "uit"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Succes"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "aan"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Ben je zeker dat je pyLoad wilt afsluiten?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Herstart Link"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Verwijder Link"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Geef een naam voor het pakket op."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Gelieve op de correcte captcha positie te klikken."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Fout opgetreden."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Map is leeg"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Mislukt"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Geen captchas beschikbaar."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Wachtwoorden kwamen niet overeen."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Instellingen bewaard."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nieuwe map"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Ben je zeker dat je pyLoad wilt herstarten?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "wachtend %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Actieve downloads"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuratie"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Naam"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Status"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informatie"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Grootte"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Vooruitgang"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Inloggen"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Gebruikersnaam"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Wachtwoord"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Gebruikersnaam en wachtwoord komen niet overeen, probeer nogmaals."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Om logindata te resetten of gebruiker toe te voegen start:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Verwijder voltooide"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Herstart gefaalde"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Map:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Wachtwoord:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Pakket bewerken"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Bewerk de pakketdetails hieronder."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "De naam van het pakket."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Map"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Naam van de submap voor deze downloads."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lijst van wachtwoorden die voor het uitpakken gebruikt worden."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Verzenden"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Wissen"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Je bent succesvol uitgelogd."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Pad"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absoluut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relatief"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "naam"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "grootte"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "laatst gewijzigd"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "bovenliggende map"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "geen inhoud"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Algemeen"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Accounts"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Kies een categorie uit het menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Geldig tot"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Resterend volume"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tijd"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Verwijderen?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "geldig"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "ongeldig"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "ja"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nee"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Toevoegen"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Voeg Account toe"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Vul accountgegevens in om premium functies te gebruiken."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Gebruikersnaam."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Het wachtwoord voor deze account."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Kies de hoster voor je account."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "vorige"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "volgende"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Einde"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Nieuws"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Ondersteuning"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Systeem"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad versie:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Installatie Map:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Configuratie Map:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Download Map:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Beschikbare Ruimte:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Taal:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Webinterface Poort:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Remote Interface Poort:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Installatie"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Bestandsbeheer"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Pakket Toevoegen"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Plak je links of upload een dlc bestand."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "De naam van het nieuwe pakket."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Plak je links of tekst hier en druk op de filterknop."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Wachtwoorden voor RAR-archieven"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Bestand"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Upload een container."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Bestemming"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha's lezen"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "De captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Tekst"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Typ de tekst van de captcha in."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Sluiten"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad Update beschikbaar!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins updated, herstart pyLoad!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha's beschikbaar"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Afmelden"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administratie"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Informatie"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Gelieve aan te melden!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Snelheid:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Actief:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Herlaad pagina"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "laden"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Naar top"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Sluit pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Herstart pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Toevoegen van gebruiker of wijzigen wachtwoord gebruik:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Belangrijk: Administrator heeft altijd alle toegangsrechten!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Wachtwoord Wijzigen"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Toegangsrechten"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "wijzigen"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Vul je huidig en gewenste wachtwoord in."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Gebruiker"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Huidig wachtwoord"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nieuw wachtwoord"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Nieuw wachtwoord."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nieuw wachtwoord (herhalen)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Herhaal het nieuwe wachtwoord."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Je hebt geen rechten deze pagina te bezoeken."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Downloadmap niet gevonden."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "onbeperkt"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "niet beschikbaar"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Start pyload.py -s om setup te starten."
+
diff --git a/locale/nl/LC_MESSAGES/pyLoad.mo b/locale/nl/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 7c52dee71..000000000
--- a/locale/nl/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/pyLoad.po b/locale/nl/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..acd922208
--- /dev/null
+++ b/locale/nl/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Dutch\n"
+"Language: nl_NL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Quit signaal ontvangen"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad is al actief met pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Veranderen van groep mislukt: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Veranderen van gebruiker mislukt: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "map voor logs"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Startend"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Gebruik hoofdmap: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "Container bestanden decoderen met pycrypto"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "map voor tijdelijke bestanden"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "map voor downloads"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL voor beveiligde verbinding"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Oude configuratie naar DB verplaatsen"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Controleer je logingegevens met ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Alle links verwijderd"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Downloadtijd: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Beschikbare ruimte: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Accounts worden geactiveerd..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Plugins aan het activeren..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad is lopende"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "pyLoad wordt herstart"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad wordt afgesloten"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installeren %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "Niet te vinden %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "Niet aan te maken %(desc)s : %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "afsluiten..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "fout tijdens afsluiten"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad is afgesloten via Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Bestandsdatabase is verwijderd vanwege niet verenigbare versie."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Bestandsdatabase kan NIET geconverteerd worden."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Database is geconverteerd van v2 naar v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Database is geconverteerd van v3 naar v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Oude Django DB omzetten"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "compleet"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "in wachtrij"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "overgeslagen"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "wachtend"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "tijdelijk offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "startend"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "mislukt"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "afgebroken"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "decrypten"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "gepersonaliseerd"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "wordt gedownload"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "verwerken"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "onbekend"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Pakket compleet: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "SSL ThriftBackend gebruiken"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Remotebackendfout: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Opstarten %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Laden van backend %(name)s | %(error)s mislukt"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "wachtend %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL certificaten niet gevonden."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Sorry, we hebben ondersteuning voor starten van %s vanuit pyLoad verwijderd"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Je kan de threaded server gebruiken welke goede prestaties en ssl biedt,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "ook kan je de pyLoads fastcgi server gebruiken met je bestaande %s"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "voorbeeldconfiguratie is te vinden in pyload/webui/servers map"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Kan %(server)s niet gebruiken, python-flup is niet geinstalleerd!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Fout bij importeren lichtgewicht server: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Je moet bjoern downloaden en compileren, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Kopieer de boern.so naar pyload/lib map en gebruik setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Hiervoor is kennis van linux en compileren van software een vereiste"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Server op threated gezet, vanwege bekende prestatie problemen op windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Deze server heeft geen SSL, overweeg de threaded server te gebruiken"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Opstarten van builltin webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Opstarten van threaded SSL webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Opstarten van threaded webserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Opstarten van fastcgi server: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Opstarten van lichtgewicht webserver (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Je hebt geen rechten deze pagina te bezoeken."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Downloadmap niet gevonden."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "onbeperkt"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "niet beschikbaar"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Start pyload.py -s om setup te starten."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Downloaden in chunks is mislukt, val terug op één connectie | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Download gestart: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Download compleet: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Plugin %s mist een functie."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Download afgebroken: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Download herstart: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "%s offline"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Download is tijdelijk offline: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Download mislukt: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Kan geen verbinding maken met host, wacht 1 minuut en probeer opnieuw."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Download overgeslagen: %(name)s vanwege %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Decrypten gestart: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Decrypten mislukt: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Opnieuw proberen %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Informatie verzamelen voor %(name)s mislukt | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Uitvoeren van hooks mislukt: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Activeren mislukt van %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Geactiveerde plugins: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Niet geactiveerde plugins: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Opnieuw verbinden mislukt: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Opnieuw verbinden script niet gevonden!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Opnieuw aan het verbinden"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Uitvoeren van opnieuw verbinden script mislukt!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Opnieuw verbonden, nieuwe IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Onvoldoende schijfruimte vrij"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Kan niet inloggen onder gebruikersnaam %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Verkeerd wachtwoord opgegeven"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Tijd %s heeft verkeerde , gebruik: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Account %s heeft niet genoeg credits, wij proberen het opnieuw in 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Account %s is verlopen, opnieuw te controleren in 1h"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Downloadlimiet bereikt"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s heeft een ongeldig patroon."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Fout bij importeren %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Geen Hoster geladen"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Activeer Direct Downloaden in je Bitshare Account"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList kon niet gewist worden."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Accountgegevens verwijderd, vanwege nieuw configutatie formaat."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorisatie vereist (gebruikersnaam:wachtwoord)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Voer hier uw %s gegevens in of deactiveer deze plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Tijdens het proberen van het ophalen van het volgende bestand (%s) is er een html/pagina reactie teruggekomen, dit kan een doorsturing zijn naar een andere pagina. pyLoad zal nu opnieuw het bestand proberen binnen te halen."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Bestand tijdelijk niet beschikbaar"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: wachten tussen downloads %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: Wachten tot captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Downloadlink heeft geen bestand gekoppeld"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API sleutel ongeldig"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Overdracht limiet bijna bereikt , niet voldoende credits"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Dataverkeer overschreden"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Bandbreedte Delen (Direct Downloaden)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Er wordt al een bestand gedownload vanaf dit ip adres, na 60 seconden volgende poging"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Ongeldige Auth Code, download wordt automatisch herstart"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Geen beschikbaarde downloadslots"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "U heeft een premium account nodig voor deze bestand"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Bestandsnaam geeft ongeldigheidsmelding"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Gelijktijdige download mislukt, wacht 60 seconden voor volgende poging."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Niet ingelogd."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Ontcijferen van codering mislukt"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Geen bestandssleutel meegeleverd door URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Foutmeldingscode:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Bestand bestaat niet."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Plugins zijn bijgewerkt, pyLoad opnieuw starten aub***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins bijgewerkt en opnieuw geladen"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Geen plugin updates beschikbaar"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Geen Updates voor pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Nieuwe pyLoad Versie %s beschikbaar ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Download hier: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Niet in staat te verbinden met server voor updates"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nieuwe versie van %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Fout tijdens updaten van %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Versie mismatch"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Download compleet: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nieuw Captcha verzoek: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Beantwoord met 'c%s text on the captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Toegevoegde %s van HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "Niet %s geinstalleerd"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Kan %s niet activeren"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Geactiveerd"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Geen uitpak plug-ins geactiveerd"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Pakket %s in wachtrij voor later uitpakken"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Controleer pakket %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Uitpakken naar %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Geen bestanden gevonden om uit te pakken"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "uitpakken"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Beveiligd met een wachtwoord"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Verkeerd wachtwoord"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "%s bestanden verwijderen"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Uitpakken voltooid"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Archief fout"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC foutief"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Onbekende fout"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Instellen van gebruikers en de groep is mislukt"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Poort 9666 al in gebruik"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s credits over"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Kan het antwoord niet verzenden."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Uw CaptchaTrader Account heeft niet genoeg credits"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Crypter lijst niet gevonden"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Crypter lijst is leeg"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Download compleet: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nieuwe CaptchaID van upload: %s: %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Uw Captcha 9kw.eu Account heeft niet genoeg credits"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Geïnstalleerde scripts voor %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Script niet uitvoerbaar:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Fout in %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Uw ExpertDecoders Account heeft niet genoeg credits"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Gelieve eerst uw premium account toe te voegen en daarna pyLoad te herstarten"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "uw resterende aantal credits : %d"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Captcha ontcijfering niet mogelijk : A. Client is niet verbonden & B. Pil en Tesseract module niet gevonden"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Geen captcha resultaat ontvangen in aangegeven tijd bij een van de plugins."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Instellingen van gebruiker en groep kunnen niet geladen worden : %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Geen verbinding met client om captcha te decrypten"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Pakket %(name)s toevoegen met %(count)d links"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "%(count)d links toevoegen aan pakket #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Geen js engine gedetecteerd, installeer Spidermonkey, ossp-js, pyv8 of rhino"
+
diff --git a/locale/nl/LC_MESSAGES/pyLoadCli.mo b/locale/nl/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index c414cfea4..000000000
--- a/locale/nl/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/pyLoadCli.po b/locale/nl/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..4925904ae
--- /dev/null
+++ b/locale/nl/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Dutch\n"
+"Language: nl_NL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Opdrachtprompt"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Snelheid: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Grootte: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Compleet in: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "wachtend: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "gepauzeerd"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "lopend"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "totale Snelheid"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Bestanden in wachtrij"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Totaal"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Links toevoegen"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "Wachtrij beheren"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "Verzamelaar beheren"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " (De)Pauzeren Server"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Server afsluiten"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Afsluiten"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Gebruik deze opbouw: add <Package name> <link> <link2> enz"
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Controleren van %d links:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Bestand bestaat niet."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad was afgesloten"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Server status weergeven"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Downloads in wachtrij weergeven"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Downloads in verzamelaar weergeven"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Pakketten toevoegen aan wachtrij"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Pakket toevoegen aan verzamelaar"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Bestanden verwijderen uit Wachtrij/Verzamelaar"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Pakketten verwijderen uit Wachtrij/Verzamelaar"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Verplaats pakketten van Wachtrij naar Verzamelaar of omgekeerd"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Bestanden herstarten"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Pakketten herstarten"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Controleer online status, werkt met lokale container"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Controleert de online status van een container bestand"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Server pauzeren"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "downloads hervatten"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Pauzeren/Starten"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "server afsluiten"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lijst met commands:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Kan configuratiebestand voor gebruiker niet aanmaken"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Om verbinding te maken met deze pyLoad Core is py-openssl nodig."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adres: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Poort: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Gebruikersnaam: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Wachtwoord: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Login gegevens verkeerd."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Kan geen verbinding maken met %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Om verbinding te maken met pyLoad is py-openssl noodzakelijk."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Interactieve modus wordt genegeerd, omdat je commando's hebt ingevult."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Pakket toevoegen:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Naam invullen voor nieuw pakket"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Pakket: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Parse de links die je wilt toevoegen."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Type %s als het klaar is."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Toegevoegde links: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " terug naar hoofdmenu"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Pakketten beheren:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Links beheren:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Wat wil je verplaatsen?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Wat wil je verwijderen?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Wat wil je herstarten?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Kies wat je wilt doen of vul pakketnummer in."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "verwijderen"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "verplaatsen"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "herstarten"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - vorige"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - volgende"
+
diff --git a/locale/nl/LC_MESSAGES/pyLoadGui.mo b/locale/nl/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 8bbc9385f..000000000
--- a/locale/nl/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/setup.mo b/locale/nl/LC_MESSAGES/setup.mo
deleted file mode 100644
index 8d8fd0d4f..000000000
--- a/locale/nl/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/nl/LC_MESSAGES/setup.po b/locale/nl/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..23a10b62a
--- /dev/null
+++ b/locale/nl/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Dutch\n"
+"Language: nl_NL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "j"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Welkom bij de pyLoad configuratie assistent."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Deze zal uw systeem controleren en de basissetup uitvoeren."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "De waarden tussen de haakjes [] zijn de standaardwaarden,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "mocht het zo zijn dat u deze niet wil veranderen of indien u niet zeker bent, druk op enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Let op: u kunt altijd deze setup herstarten met de --setup of -s parameter als u pyload.py start."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Mocht u problemen hebben met deze assistent druk op STRG-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "om af te sluiten en de assistent niet meer automatisch te starten."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Als u klaar bent voor de systeemcontrole, druk op enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "U heeft pycurl, sqlite en python 2.5, 2.6 of 2.7 nodig om pyLoad te starten."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Corrigeer dit en herstart pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "De setup zal nu afbreken."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Systeemcontrole is uitgevoerd, druk op enter om het statusbericht te zien."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "ssl verbinding"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "automatische captcha ontcijfering"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "uitgebreide Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Functies beschikbaar:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Missende functies: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "geen py-crypto beschikbaar"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "U heeft dat nodig om container bestanden te openen."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "geen SSL beschikbaar"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Dit is nodig om een beveiligde verbinding in te stellen naar de core of de webinterface."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Als u pyLoad enkel lokaal gebruikt is SSL overbodig."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "geen captcha herkenning beschikbaar"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Wordt alleen gebruikt bij enkele hosters als freeuser."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI niet beschikbaar"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "De grafische gebruikersomgeving."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "geen JavaScript engine gevonden"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "U heeft dit nodig voor bepaalde Click'N'Load links. Instaleer Spidermoney, ossp-js, pyv8 of rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "U kunt nu setup afbreken en enkele afhankelijkheden installeren als u dat wilt."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Doorgaan met setup?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Wilt u de configuratiemap aanpassen? Huidige is %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Als u pyLoad op een server gebruikt of de home partitie bevindt zich op intern flash-geheugen, is het een goed idee om het te veranderen."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Verander configmap?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Wilt u login gegevens en basisinstellingen configureren?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Dit is aan te raden bij een eerste start."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Basissetup creëren?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Wilt u SSL configureren?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "SSL configureren?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Wilt u de webinterface configureren?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Webinterface configureren?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Setup is succesvol voltooid."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Druk op enter om af te sluiten en start pyLoad opnieuw"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Syteemcontrole ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Uw python versie is te nieuw, gebruik python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Uw python versie is te oud, gebruik minstens python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python versie: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Uw gestalleerde versie %s van jinja2 is te oud."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "U kan veilig verder, alleen de webinterface zal niet werken."
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "Upgrade of verwijder het, pyLoad komt zelf met een goede jinja2 versie."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Basisinstellingen ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "De volgende logingegevens zijn geldig voor CLI, GUI en webinterface."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Gebruikersnaam"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Externe cliënten (GUI, CLI of andere) hebben externe toegang nodig om te werken over het netwerk."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Als je enkel de webinterface wil gebruiken mag je het uitschakelen om RAM te besparen."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Externe toegang inschakelen"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Taal"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Downloadmap"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Maximale parallele downloads"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Gebruik reconnect?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Reconnect script pad"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Webinterface setup ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Activeer webinterface?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Listen adres, als u 127.0.0.1 of localhost gebruikt is de webinterface alleen lokaal beschikbaar."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adres"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Poort"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad beschikt over een aantal server backends, nu volgt een korte uitleg."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Standaard server, de beste keuze als je niet weet welke je moet kiezen."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Deze server beschikt over SSL en is een goed alternatief voor de interne server."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Kan gebruikt worden door apache, lighttpd, vereist configuratie wat niet makkelijk te doen is."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Super snel alternatief geschreven in C, vereist verstand van libev en linux."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Download het hier: https://github.com/jonashaag/bjoern, compile het"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "en kopieer bjoern.so naar modele/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Opgelet: In sommige gevallen werkt de builtin server niet, als je problemen hebt met de webinterface."
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "kom terug en verander de builtin server naar de threaded server."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Server"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## SSL setup ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Voer de volgende commando's uit vanuit pyLoad configuratiemap om ssl certificaten te maken:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Als de commando's succesvol uitgevoerd zijn kunt u SSL nu activeren."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "SSL activeren?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Aktie selecteren"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Aanmaken/wijzigen gebruiker"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Lijst met gebruikers"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Gebruiker verwijderen"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Afsluiten"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Gebruikers"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Nieuwe configuratiemap wordt ingesteld, uw huidige configuratie wordt niet overgenomen!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Configuratiepad"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Configuratiepad is veranderd en setup zal nu afsluiten, herstart setup om verder te gaan."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Druk op enter om af te sluiten."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Instellen van configuratiepad mislukt: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: mist"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Wachtwoord: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Wachtwoord te kort. Gebruik ten minste 4 symbolen."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Wachtwoord (nogmaals): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Wachtwoorden kwamen niet overeen."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "ja"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "waar"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "w"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nee"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "niet waar"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "nw"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Ongeldige invoer"
+
diff --git a/locale/no/LC_MESSAGES/django.po b/locale/no/LC_MESSAGES/django.po
new file mode 100644
index 000000000..37c9905c9
--- /dev/null
+++ b/locale/no/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Norwegian\n"
+"Language: no_NO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/no/LC_MESSAGES/pyLoad.po b/locale/no/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..917ddf78c
--- /dev/null
+++ b/locale/no/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Norwegian\n"
+"Language: no_NO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/no/LC_MESSAGES/pyLoadCli.po b/locale/no/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..9289aa524
--- /dev/null
+++ b/locale/no/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Norwegian\n"
+"Language: no_NO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/no/LC_MESSAGES/setup.po b/locale/no/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..634f65415
--- /dev/null
+++ b/locale/no/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Norwegian\n"
+"Language: no_NO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/pa/LC_MESSAGES/django.po b/locale/pa/LC_MESSAGES/django.po
new file mode 100644
index 000000000..4544bcc10
--- /dev/null
+++ b/locale/pa/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Punjabi\n"
+"Language: pa_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/pa/LC_MESSAGES/pyLoad.po b/locale/pa/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..89d3570f1
--- /dev/null
+++ b/locale/pa/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Punjabi\n"
+"Language: pa_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/pa/LC_MESSAGES/pyLoadCli.po b/locale/pa/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..6e1cd038f
--- /dev/null
+++ b/locale/pa/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Punjabi\n"
+"Language: pa_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/pa/LC_MESSAGES/setup.po b/locale/pa/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..5998f2af7
--- /dev/null
+++ b/locale/pa/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Punjabi\n"
+"Language: pa_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/pl/LC_MESSAGES/django.mo b/locale/pl/LC_MESSAGES/django.mo
deleted file mode 100644
index fae662552..000000000
--- a/locale/pl/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/django.po b/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 000000000..e44012bae
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Nowy obrazek (captcha)"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Proszę przeczytać tekst z obrazka."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad zrestartowano"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "Wył"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Powodzenie"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "Wł"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Na pewno chcesz zamknąć"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Zrestartuj link"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Usuń link"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Podaj nazwę paczki"
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Proszę kliknij na prawej stronie obrazka"
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "błąd"
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Folder jest pusty"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Niepowodzenie"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Brak obrazków (captcha) do odczytania."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Hasło nie pasuje"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Ustawienia zapisano"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nowy folder"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Czy chcesz zrestartować pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "czekaj %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Obecnie pobierane"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "Pobrane"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Logi"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Konfiguracja"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nazwa"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Status"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informacja"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Rozmiar"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Postęp"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Logowanie"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Nazwa uÅŒytkownika"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Hasło"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Nieprawidłowa nazwa uÅŒytkownika lub hasło. Proszę spróbować ponownie."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Aby zresetować dane logowania lub dodać uÅŒytkownika uruchom komendę:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Usuń zakończone"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Nieudany restart"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Katalog:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Hasło:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Edytuj paczkę"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Tutaj edytuj szczegóły paczki."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Nazwa paczki."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Folder"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nazwa podfolderu dla wskazanych pobrań."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lista haseł uÅŒywanych do rozpakowywania."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Wyślij"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Resetuj"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Wylogowano pomyślnie."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "ŚcieÅŒka"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "bezpośrednia"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "względna"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nazwa"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "rozmiar"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "rodzaj"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "ostatnio zmodyfikowany"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "katalog główny"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "brak treści"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Ogólne"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Wtyczki"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Konta"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Wybierz sekcję z menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "WaÅŒne do"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Pozostały transfer"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Czas"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Maksymalnie jednoczesnych pobrań"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Skasować?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "WaÅŒny"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "nie waÅŒny"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "tak"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nie"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Dodaj"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Dodaj konto"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Podaj swoje dane konta do korzystania z funkcji premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Nazwa uÅŒytkownika."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Hasło dla tego konta."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Typ"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Wybierz hosting dla Twojego konta."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Uruchom"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "poprzedni"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "następny"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Koniec"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Wiadomości"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Wsparcie"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "System operacyjny:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "Wersja pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Folder instalacji:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Folder konfiguracji:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Folder pobierania:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Wolne Miejsce:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Język:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Port interfejsu web:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Port interfejsu zdalnego:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Ustawienia"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "MenedŌer plików"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Dodaj paczkę"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Wklej linki lub wczytaj kontener."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Nazwa nowej paczki."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Linki"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Tutaj wklej linki lub dowolny tekst i wciśnij przycisk filtra."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtruj adresy url"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Hasło do archiwum RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Plik"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Wczytaj kontener."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Miejsce docelowe"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Odczyt captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "Captcha (obrazek)"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Tekst"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Wprowadź tekst z obrazka (captcha)."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Zamknij"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Interfejs Web"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Dostępna aktualizacja dla pyLoad'a!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Wtyczki zaktualizowane, zrestartuj program!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Oczekiwanie na captcha"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Wyloguj"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Zarządzaj"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Informacje"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Zaloguj się!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Zatrzymaj"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Pobrane:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Połącz ponownie:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Prędkość:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktywny:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "OdświeÅŒ stronę"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "wczytywanie"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Powrót na początek strony"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Zamknij pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Zrestartuj pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Aby dodać uÅŒytkownika lub zmienić hasło uÅŒyj:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Uwaga: Administrator ma zawsze wszystkie prawa!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Zmiana Hasła"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Administrator"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Uprawnienia"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "zmień"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Wprowadź swoje hasło"
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "UÅŒytkownik"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Aktualne hasło"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nowe hasło"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Nowe hasło."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nowe hasło (powtórz)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Proszę powtórzyć nowe hasło."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Nie posiadasz uprawnień dostępu do tej strony."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Nie znaleziono katalogu pobierania."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "Bez limitu"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "niedostępny"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "UÅŒyj polecenia pyload.py-s aby uzyskać dostęp do ustawień"
+
diff --git a/locale/pl/LC_MESSAGES/pyLoad.mo b/locale/pl/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 31ea1e162..000000000
--- a/locale/pl/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/pyLoad.po b/locale/pl/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..6247f50c8
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Otrzymano sygnał zakończenia"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad jest juŌ uruchomiony - proces %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Niepowodzenie przy zmianie grupy: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Niepowodzenie przy zmianie uÅŒytkownika: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "katalog na logi"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Rozpoczynam"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "UÅŒywam katalogu domowego: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto deszyfruje pliki kontenera"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "katalog dla plików tymczasowych"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "katalog na pobrane pliki"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL dla połączenia szyfrowanego"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Przenoszę starą konfigurację uÅŒytkownika do Bazy Danych"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Sprawdź dane logowania komendą ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Wszystkie linki zostały usunięte"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "Czas pobierania: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Wolne miejsce: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Aktywacja kont ..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Włączanie wtyczek ..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad jest uruchomiony i działa"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "restartuję pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad kończy działanie"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Zainstaluj %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "nie moÅŒna znaleźć %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "nie moÅŒna utworzyć %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "wyłączanie..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "błąd przy wyłączaniu"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "proces pyLoad wyłączono z poziomu terminala"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Baza plików została usunięta z powodu braku niekompatybilnej wersji."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Nie moÅŒna przekonwertować Bazy plików."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Przekonwertowano Bazę Danych z wersji v2 do v3. "
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Przekonwertowano Bazę Danych z wersji v3 do v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Konwertuję bazę Django do nowszej wersji"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "zakończono"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "zakolejkowane"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "pominięte"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "oczekuję"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "tymczasowo offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "rozpoczynam"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "niepowodzenie"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "anulowno"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "rozkodowuję"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "własny"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "pobieranie"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "przetwarzanie"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "nieznany"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paczka ukończona: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "UÅŒyj SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Błąd zdalnego zaplecza: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Uruchamiam %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Bład ładowania backendu %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "czekaj %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Nie znaleziono certyfikatów SSL."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Nie zalecamy uruchamiania %s bezpośrednio z pyLoad - zaprzestano wsparcia"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "MoÅŒesz wykorzystać wielowątkowy serwer oferujący dobrą wydajność i szyfrowanie ssl,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "oczywiście nadal moÅŒesz uÅŒywać %s z serwerem pyLoad fastcgi "
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "przykładowe konfiguracje znajdziesz w podkatalogu pyload/webui/servers "
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Nie moÅŒna uÅŒyć %(server)s - niezainstalowany python-flup "
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Błąd importu lightweight server: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "NaleÅŒy pobrać i skompilować bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Skopiuj plik boern.so do folderu pyload/lib lub uÅŒyj polecenia setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Oczywiście trzeba znać Linuksa i wiedzieć, jak się kompiluje programy"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Ze względu na znane problemy z wydajnością w systemie Windows ustaw serwer na threaded"
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Ten serwer nie wspiera SSL, naleÅŒy rozwaÅŒyć uÅŒycie serwera threaded"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Uruchamiam wbudowany webserwer: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Uruchamiam wielowątkowy webserwer z szyfrowaniem SSL: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Uruchamiam wielowątkowy serwer: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Uruchamiam serwer fastcgi: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Uruchamianie serwera lightweight (bjoern):% (host) s:% (port) d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Nie posiadasz uprawnień dostępu do tej strony."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Nie znaleziono katalogu pobierania."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "Bez limitu"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "niedostępny"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "UÅŒyj polecenia pyload.py-s aby uzyskać dostęp do ustawień"
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Pobranie fragmentów nie powiodło się, powrót do pojedynczego połączenia | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Pobieranie rozpocznie się: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Pobieranie zakończono: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Wtyczka %s nie zawiera funkcji."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Pobieranie przerwane: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Pobieram ponownie: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Pobieranie jest wyłączone: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Pobieranie jest tymczasowo niedostępne:% s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Pobieranie nie powiodło się: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Brak połączenia z hostem lub połączenie zostało zresetowane, zaczekaj 1 minutę i spróbuj ponownie."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Pominięto pobieranie: %(name)s z powodu %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Rozpoczęto rozszyfrowywanie: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "Rozszyfrowywanie nie powiodło się: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Ponawianie %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Pobieranie informacji o %(name)s nie powiodło się | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Błąd przy uruchomieniu: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Nie powiodła się aktywacja %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Aktywne wtyczki:% s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Nieaktywne wtyczki:% s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Nieudane ponowne łączenie: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Nie znaleziono skryptu ponownego łączenia!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Uruchamiam ponowne łączenie"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Niepowodzenie przy uruchamianiu skryptu ponownego łączenia!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Ponownie połączony, nowe IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Zbyt mało miejsca na dysku"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Nie moÅŒna zalogować się na koncie %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Hasło nieprawidłowe"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Format czasu %s jest nieprawidłowy, uÅŒyj: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Konto % s generuje zbyt mały ruch, sprawdź ponownie za 30min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Konto %s wygasło, sprawdź ponownie za 1godz"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Osiągnięto limit transferu"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s zawiera błędny wzorzec."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Błąd przy imporcie %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Nie załadowano Hostera"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Uaktywnij pobieranie bezpośrednie w ustawieniach swojego konta Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "Nie moÅŒna wyczyścić zawartości LinkList."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "Ustawienia konta usunięto po wprowadzeniu nowego formatu konfiguracji."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Wymagana autoryzacja (uÅŒytkownik:hasło)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Proszę wejść na konto %s lub wyłączyć wtyczkę"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "W pobranym pliku (%s) był kod HTML... przekierowanie błędu? Pobieranie zostanie uruchomione ponownie."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Plik czasowo niedostępny"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: Oczekiwanie pomiędzy pobraniami %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: czekam na captcha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Pobrany plik był pusty"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "niepoprawny klucz API"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Pozostało zbyt mało transferu"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Przekroczono transfer"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Współdzielenie ruchu (bezpośrednie pobieranie)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Trwa pobieranie spod tego adresu IP. Odczekaj 60 sekund"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Błędny kod autoryzacji. Pobieranie zostanie wznowione"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidshareCom: Brak wolnych slotów"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Ten plik wymaga konta premium"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Zgłoszono nieprawidłową nazwę pliku"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Błąd równoległego pobierania, odczekaj 60s."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Nie zalogowany."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Odszyfrowywanie nie powiodło się"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Nie umieszczono plik klucza w adresie URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Kod błędu:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Plik nie istnieje."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Wtyczki zostały zaktualizowane, proszę zrestartować pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Wtyczki zaktualizowane i przeładowane"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Brak dostępnych aktualizacji wtyczek"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Brak aktualizacji dla pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Dostępna nowa wersja %s ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Pobierz stąd: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Brak połączenia z serwerem aktualizacji"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nowa wersja %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Podczas aktualizacji wystąpił bląd %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Niezgodność wersji"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Pobieranie zakończono: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nowe Ōądanie captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "W odpowiedzi uÅŒyj 'c %s tekst z captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "Najpierw dodaj swoje konto premium.to i zrestartuj pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Dodano %s z HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "Nie zainstalowano %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Nie moÅŒna aktywować %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Aktywowany"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Brak aktywnych wtyczek do rozpakowywania plików"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Pakiet %s zakolejkowany do rozpakowania"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Sprawdzanie paczki %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Wypakowano %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Nie znaleziono plików do rozpakowania"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "wypakowuję"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Zabezpieczone hałsem"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Nieprawidłowe hasło"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Usuwanie %s plików"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Wypakowanie zakończone"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Błąd archiwum"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "Nieprawidłowa suma kontrolna CRC"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Wystąpił nieznany błąd"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Ustawienie uÅŒytkowników i grup nie powiodło się: %s"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Wtyczka Click'N'Load: Port 9666 jest zajęty"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "Pozostało %s punktów"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Nie moÅŒe wysłać odpowiedzi."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Posiadasz zbyt małą ilość punktów na koncie CaptchaTrader"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Nie odnaleziono listy Crypter"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Lista Crypter jest pusta"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Pobieranie zakończono: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Nowe CaptchaID z uploadu: %s: %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Twoje konto captcha 9kw.eu nie ma wystarczająco kredytów"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "Zainstalowane skrypty dla %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Niewykonywalny skrypt:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Błąd w %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Twoje konto ExpertDecoders nie ma wystarczająco kredytów"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Najpierw dodaj swoje konto rehost.to i zrestartuj pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "Dodaj najpierw waÅŒne konto premiumize.me i zrestartuj pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "Pozostało %s punktów"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Nie zainstalowano modułów pil i tesseract oraz brak połączenia z serwisem dekodującym captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Żaden z serwisów nie odkodowal captcha w dopuszczalnym czasie. "
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Ustawienia uÅŒytkowników i grup nie powiodło się: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Odkodowanie Captcha nieaktywne"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Dodano paczkę %(name)s zawierającą %(count)d linków"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Dodano %(count)d linków do paczki #%(package)d"
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Nie znaleziono silnika JavaScript, zainstaluj SpiderMonkey, ossp-js, pyv8 lub rhino"
+
diff --git a/locale/pl/LC_MESSAGES/pyLoadCli.mo b/locale/pl/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 565f26e3a..000000000
--- a/locale/pl/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/pyLoadCli.po b/locale/pl/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..44144e7dd
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "Interfejs Linii Komend"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Pobrań:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "Prędkość:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "Rozmiar:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "Zakończono w:"
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "Oczekiwanie:"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "Wstrzymane"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "Aktywne"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Prędkość pobierania"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Pliki w kolejce"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Ogółem"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "Dodaj linki"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Zarządzaj kolejką"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Zarządzanie poczekalnią linków"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "Zatrzymaj/Wznów serwer"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "Wyłącz serwer"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "Wyjście"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "UÅŒywamy składni: add <Package name> <link> <link2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Sprawdzanie linków %d:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Plik nie istnieje."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad został wyłączony"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Wyświetla status serwera"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Wyświetla pobierane pliki z kolejki"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Wyświetla pobierane pliki z poczekalni"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Dodaje pakiet do kolejki"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Dodaje paczkę do poczekalni"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Usuń pliki z Kolejki/Poczekalni"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Usuń Paczki z Kolejki/Poczekalni"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Przenoszenie paczek z kolejki do poczekalni i odwrotnie"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Zrestartuj pliki"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Zrestartuj paczki"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Sprawdź status online, współpracuje z lokalnym kontenerem"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Sprawdź stan online pliku kontenera"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Wstrzymaj serwer"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "kontynuuj pobieranie"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Wstrzymaj/Wznów"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "wyłącz serwer"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista poleceń:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Nie moÅŒna zapisać pliku konfiguracyjnego"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Do połączenia z pyLoad Core wymagana jest instalacja py-openssl."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adres:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "UÅŒytkownik:"
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Hasło:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Błędne dane logowania."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Nie moÅŒna połączyć z %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Do połączenia z pyLoad Core wymagana jest instalacja py-openssl."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Tryb interaktywny wyłączono po wprowadzonych komendach."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Dodaj pakiet:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Wprowadź nazwę dla nowego pakietu"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Pakiet: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analizuj linki które chcesz dodać."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Napisz %s gdy ukończone."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Dodanych linków:"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "powrót do głównego menu"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Zarządzanie pakietami:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Zarządzaj linkami:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Czy chcesz przenieść?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Czy na pewno chcesz usunąć?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Czy chcesz zrestartować?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Wybierz, co chcesz wykonać lub wprowadź numer paczki."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "skasuj"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "przenieś"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "Restart"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr "- poprzedni"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - następny"
+
diff --git a/locale/pl/LC_MESSAGES/pyLoadGui.mo b/locale/pl/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index bbfcb176e..000000000
--- a/locale/pl/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/setup.mo b/locale/pl/LC_MESSAGES/setup.mo
deleted file mode 100644
index c8303602c..000000000
--- a/locale/pl/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pl/LC_MESSAGES/setup.po b/locale/pl/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..a05658825
--- /dev/null
+++ b/locale/pl/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "t"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Witamy w asystencie konfiguracji pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Dokona sprawdzenia systemu i ustawi podstawowe parametry potrzebne do uruchomienia pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Wartość w nawiasach kwadratowych [] jest zawsze domyślną,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "jeśli nie chcesz zmieniać wartości lub nie masz pewności co wybrać, naciśnij enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Nie zapomnij: kiedy uruchamiasz pyload.py zawsze moÅŒesz ponownie wybrać asystenta dodając parametr --setup lub -s."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Jeśli masz jakiekolwiek problemy z asystentem wciśnij Ctlr-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "aby go zatrzymać i nie pozwolić mu automatycznie uruchomić się z pyload.py . "
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Naciśnij enter jak będziesz gotowy na sprawdzenie systemu."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Potrzebujesz pycurl, sqlite i pythona 2.5, 2.6 lub 2.7 aby uruchomić pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Proszę popraw to i ponownie uruchom pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Instalator teraz zakończy działanie."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Zakończono sprawdzanie systemu, naciśnij enter w celu obejrzenia raportu."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Stan ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "rozszyfrowywanie kontenerów"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "połączenie SSL"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "automatyczne rozpoznawanie captcha"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Interfejs Web"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "rozszerzone Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Dostępne funkcje:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Niedostępne funkcje:"
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto jest niedostępny"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Potrzebujesz go jeśli chcesz rozszyfrowywać pliki kontenerowe."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL niedostępny"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Jest to potrzebne jeśli chcesz nawiązywać szyfrowane połączenia z Core lub interfejsem Web."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Jeśli chcesz mieć dostęp tylko lokalny - SSL nie jest uÅŒyteczne."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "brak rozpoznawania captcha"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Potrzebne tylko do niektórych serwisów dla kont darmowych."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Interfejs graficzny niedostępny"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Graficzny Interfejs UÅŒytkownika."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "nie znaleziono silnika JavaScript"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Do dodania kilku linków jednocześnie będziesz potrzebował Click'N'Load. Zainstaluj SpiderMonkey, ossp-js, pyv8 lub rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "MoÅŒesz przerwać instalator i naprawić niektóre zaleÅŒności."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Kontynuować instalację?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Czy chcesz zmienić ścieÅŒkę do plików konfiguracji? Obecnie to %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Jeśli uÅŒywasz pyLoad'a na serwerze lub partycji home która znajduje się na pamięci flash - dobrym pomysłem moÅŒe być zmiana tego parametru."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Zmienić ścieÅŒkę dla plików konfiguracji?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Czy chcesz dokonać konfiguracji logowania i ustawień podstawowych?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Jest to wymagane przy pierwszym uruchomieniu."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Stworzyć podstawowe ustawienia?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Czy chcesz ustawić SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Ustawić SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Czy chcesz ustawić interfejs Web?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Ustawić interfejs Web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Instalacja zakończona pomyślnie."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Naciśnij enter aby wyjść i ponownie uruchom pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Sprawdzenie systemu ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Zainstalowana wersja Python'a jest zbyt nowa, Proszę uÅŒyć wersji 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Zainstalowana wersja Python'a jest zbyt stara, Proszę uÅŒyć przynajmniej wersji 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Wersja Python'a: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Zainstalowana jinja2 w wersji %s jest przestarzała."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "MoÅŒesz kontynuować pracę, ale interfejs Web nie będzie czynny,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "uaktualnij lub odinstaluj, pyLoad zawiera odpowiednią wersję biblioteki jinja2."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "silnik Java Skrypt"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Podstawowe Ustawienia ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Następujące parametry logowania są odpowiednie dla CLI, GUI i interfejsu Web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Nazwa uÅŒytkownika"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Klienty zewnętrzne (GUI, CLI lub inne) potrzebują zdalnego połączenia do działania przez sieć."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "JeÅŒeli chcesz uÅŒywać tylko interfejsu web moÅŒesz dezaktywować to aby zaoszczędzić pamięć."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Włącz zdalny dostęp"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Język"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Folder pobierania"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Maksymalna liczba jednoczesnych pobrań"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "UÅŒywać ponownego łączenia?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Lokalizacja skryptu do ponownego łączenia"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Ustawienia interfejsu Web ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Aktywować interfejs Web?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adres do nasłuchu, jeśli uÅŒyjesz 127.0.0.1 lub localhost, interfejs Web będzie dostępny jedynie lokalnie."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adres"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad oferuje kilka typów serwerów backends, a teraz po krótce wyjaśniam."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Serwer domyślny, najlepszy wybór, jeśli nie wiesz który wybrać."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Ten serwer wspiera SSL i jest dobrą alternatywą dla builtin."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "MoÅŒe być uÅŒywany przez apache, lighttpd, wymaga od Ciebie ich konfiguracji, która nie jest zbyt łatwym zadaniem."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Bardzo szybka alternatywa, napisany w C, wymaga libev i znajomości Linuxa"
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Pobierz go stąd: https://github.com/jonashaag/bjoern, i skompiluj go"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "i skopiuj plik bjoern.so do katalogu pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Uwaga: W pewnych, rzadkich przypadkach serwer builtin nie działa, jeśli występują problemy z interfejsem WWW"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "wróć tu i zmień serwer builtin na threaded"
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Serwer"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## Ustawienia SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "W celu wygenerowania certyfikatów ssl uruchom następujące komendy z katalogu pyload:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Jeśli wszystko pomyślnie się zakończyło, moÅŒesz aktywować SSL."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Aktywować SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Wybierz działanie"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1- Utwórz/Edutuj uŌytkownika"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2- PokaŌ uŌytkowników"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Usuń uÅŒytkownika"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4- Wyjście"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "UÅŒytkownicy"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Ustawiam nową ścieÅŒkę do plików konfiguracji, obecna konfiguracja nie zostanie tam przeniesiona!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "ŚcieÅŒka do plików konfiguracji"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "ŚcieÅŒka do plików konfiguracji została zmieniona, Instalator zostanie teraz zamknięty, uruchom go ponownie, aby przejść dalej."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Naciśnij Enter aby zakończyć."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Ustawienie ścieÅŒki do plików konfiguracji nie powiodło się: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: brakuje"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Hasło:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Hasło za krótkie. UÅŒyj przynajmniej 4 znaków."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Hasło (ponownie):"
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Hasło nie pasuje"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "tak"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "prawda"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nie"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "fałsz"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Błędne dane"
+
diff --git a/locale/pt/LC_MESSAGES/django.po b/locale/pt/LC_MESSAGES/django.po
new file mode 100644
index 000000000..07d1ea2f8
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Portuguese, Brazilian\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Pedir novo Captcha"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Por favor leia o texto do captcha."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad reiniciou"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "desligado"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Sucesso"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "ligado"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Tem a certeza que quer sair do pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Reiniciar Link"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Apagar Link"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Introduza o nome do pacote."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Por favor clique na posição correta do captcha."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Ocorreu um erro."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Pasta vazia"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Falhou"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Sem Captchas para ler."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "As senhas não correspondem."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Configurações guardadas."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Nova pasta"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Tem a certeza que deseja reiniciar o pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Downloads Activos"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Inicio"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Registo de ocorrências"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Configuração"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Nome"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Status"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Informação"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Tamanho"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Progresso"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Login"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Nome do usuário"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Senha"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "O utilizador e a senha não correspondem. Tente novamente."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Para restabelecer a sua informação de login ou adicionar um utilizador execute:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Apagar os terminados"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Reinício falhou"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Pasta:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Senha:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Editar Pacote"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Edite os detalhes do pacote abaixo."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "O nome do pacote."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Pasta"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Nome da sub-pasta para estes downloads."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lista das passwords usadas para extrair."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Enviar"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Restabelecer"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Terminou a sessão com sucesso."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Caminho"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absoluto"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "nome"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "tamanho"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tipo"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "última modificação"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "diretoria superior"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "sem conteúdo"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Geral"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Contas"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "Escolha uma das secções do menu"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Válida até"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Tráfego remanescente"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tempo"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Máximos Paralelos"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Apagar?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "válido"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "não válido"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "sim"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "não"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Adicionar"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Adicionar conta"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Introduza os dados da sua conta para utilizar as características premium."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "O seu nome de utilizador."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "A senha para esta conta."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "Tipo"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Escolha o host da sua conta."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Iniciar"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "anterior"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "próximo"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Fim"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Novidades"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Ajuda"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistema"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "SO:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "Versão do pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Pasta de instalação:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Pasta das configurações:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Pasta de Downloads:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Espaço Livre:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Idioma:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Porta do Webinterface:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Porta do interface remoto:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Configurações"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Adicionar pacote"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Introduza os seus links ou carregue um container."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Nome do novo pacote."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Introduza aqui os seus links ou texto e carregue no botão filtrar."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrar urls"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Senha para o arquivo RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Ficheiro"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Carregar um container."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Destino"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "A ler captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "O captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Texto"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Introduza o texto do captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Fechar"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "Atualização do pyLoad disponível!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins atualizados, por favor reinicie!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha a aguardar"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Sair"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrar"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Informações"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Inicie a Sessão!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Parar"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Restabelecer ligação:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Velocidade:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Activo:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Atualizar página"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "a carregar"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Voltar para o topo"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Sair do pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Reiniciar pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Para adicionar um utilizador ou alterar senhas utilize:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Importante: o Administrador tem sempre todas as permissões!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Alterar a senha"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "Administrador"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Permissões"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "alterar"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Introduza a sua senha actual e a desejada."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Utilizador"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Senha atual"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nova senha"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "A nova senha."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "A nova senha (repetir)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Por favor repita a nova senha."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "ilimitado"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/pt/LC_MESSAGES/pyLoad.po b/locale/pt/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..3dbe39ee1
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Portuguese, Brazilian\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "sinal de Sair recebido"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad já está rodando com pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Falha ao trocar grupo: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Falha ao trocar de usuario: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Iniciando"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Usando diretório: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Todos os links foram removidos"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Espaço livre: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Ativando contas..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad está rodando"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "reiniciando pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad fechou"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "desligando..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "erro durante desligamento"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "Terminado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "Offline"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "Online"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "em fila"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "ignorada"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "esperando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "Temp. off-line"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "iniciando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "falhou"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "abortada"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "descriptografando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "personalizado"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "Transferindo"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "processando"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "desconhecido"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Pacote finalizado: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Erro de back-end remoto: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Iniciando %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Falha ao carregar back-end %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "Certificado SSL não encontrado."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Este servidor não oferece SSL, por favor, considere usar HTTP{?} em vez disso"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "ilimitado"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "pedaços do Download falharam, retornar para conexão simples |%s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Inicio do Download: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Termino do Download: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Plugin %s está sem uma função."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Download abortado: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "Download reiniciado: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Download está offline: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Download esta temporariamente offline: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "Download falhou: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Impossível conectar com o host ou a conexão receptou, tentar novamente em 1 minuto."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Download pulado: %(name)s pois %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Busca de informações para %(name)s falhou|%(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Falha de ativação %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Reconectar Falhou: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Script de reconexão não encontrado!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Iniciando reconexão"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Falha ao executar script de reconexão!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Reconectado, novo IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Espaço insuficiente no dispositivo"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Não pode ligar com a conta %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Senha incorreta"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "A conta %s não tem tráfego suficiente, checando novamente em 30 min"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "A conta %s expirou, checando novamente em 1h"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "Limite de Download alcançado"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Erro ao importar %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Nenhum Hoster carregado"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Ativar Download direto em sua conta Bitshare"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Autorização necessaria (nome de usuario: senha)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "Por favor entre sua %s conta ou desative este plugin"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "Havia código HTML no arquivo baixado (%s)...erro de redirecionamento? O Download vai ser reiniciado."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Arquivo temporariamente indisponível"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: esperar entre os Downloads %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: esperando por capacha %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Arquivo baixado estava vazio"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "Chave de API inválida"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: Tráfego restante insuficiente"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Tráfego excedido"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Compartilhamento de tráfego (Download direto)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Ja está baixando deste endereço de ip, espere 60 segundos"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Código de autenticação invalido, Download vai ser reiniciado"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Sem slots vazios"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Você precisa de uma conta premium para este arquivo"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Nome do arquivo relatado como invalido"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Erro de Download paralelo, esperar 60 segundos."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "Não está logado."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "Descriptografia falhou"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Nenhum arquivo chave fornecido na URL"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Codigo do erro:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Plugins foram atualizados, por favor reinicie o pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Plugins atualizados e recarregados"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Nenhuma atualização de plugin disponível"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Nenhuma atualização para o pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "* * * Nova versão %s do pyLoad disponível * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "* * * faça o Download em : http://pyload.org/download * * *"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Impossível conectar com o servidor de atualizações"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Nova versão de %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Erro ao atualizar %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "Incompatibilidade de versão"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Download concluído: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Nova solicitação de Captcha: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Responder com 'c %s o texto no captcha'"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "Adicionado %s de HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "Sem %s instalado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Não foi possível activar o %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Ativado"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Não ha plugins de extração ativos"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Pacote %s enfileirados para extração posterior"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "Verificar pacote %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Extrair para %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Arquivos não encontrados para extrair"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "extraindo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Protegido por senha"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Senha incorreta"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "Excluindo %s arquivos"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Extração terminada"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Erro de arquivo"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "Incompatibilidade de CRC"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Erro desconhecido"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Configuração de usuário e grupo falhou"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'n'Load: porta 9666 já está em uso"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s créditos sobrando"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Não pode enviar resposta."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Sua conta CaptchaTrader não tem créditos suficientes"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Lista do Crypter não encontrada"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Lista do Crypter vazia"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Download terminado: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Novo CaptchaID do upload: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Sua conta 9kw.eu não tem créditos suficientes"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "scripts instalados para %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Scrip não executável:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Erro em %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "Sua conta ExpertDecoders não tem créditos suficientes"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "Por favo adicione sua conta rehost.to antes de reiniciar o pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d créditos sobrando"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil e tesseract não estão instalados e nenhum cliente está conectado para reconhecimento de captcha"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Configuração de usuário e grupo falhou: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/pt/LC_MESSAGES/pyLoadCli.po b/locale/pt/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..847c98c40
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Portuguese, Brazilian\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Interface de Linha de Comandos"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Velocidade: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Tamanho: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Terminado em: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "em espera: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Estado:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "pausados"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "em execução"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Velocidade total"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Arquivos na fila"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Adicionar Links"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Gerir Fila"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Gerir Coletor"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " Parar/Retomar servidor"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Terminar Servidor"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Sair"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "Por favor, use esta sintaxe: Adicionar <Nome do Pacote> <link> <link2>..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Checar %d links:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "O arquivo não existe."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad foi finalizado"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Estado do servidor de impressões"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Cópias de downloads na fila"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Cópias de downloads no coletor"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Adicionar pacotes para a fila"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Adicionar pacotes para o coletor"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Deletar arquivos da fila/coletor"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Deletar pacotes da fila/coletor"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Mover pacotes da fila para o coletor ou vice-versa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Reiniciar arquivos"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Reiniciar pacotes"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Verificar o estado online, trabalhar com recipiente local"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Verifica o estado online de um arquivo de contêiner"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Colocar servidor em Pausa"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "continuar downloads"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Alternar pausa/retomar"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "terminar servidor"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista de comandos:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Não foi possível escrever o arquivo de configuração do usuário"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Endereço: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Porta: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Nome do usuário: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Senha: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Dados de login estão errados."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Não conseguiu estabelecer conexão com %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "você precisa do py-openssl para se conectar com o pyLoad core."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "O modo interativo foi ignorado pois alguns comandos foram pulados por você."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Adicionar Pacote:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Insira um nome para o novo pacote"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Pacote: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Analise os links que você deseja adicionar."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Digite %s quando terminar."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Links adicionados: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " voltar ao menu principal"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Gerenciar pacotes:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Gerenciar Links:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "O que você deseja mover?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "O que você deseja excluir?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "O que você deseja reiniciar?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "deletar"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "mover"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "reiniciar"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - anterior"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - próximo"
+
diff --git a/locale/pt/LC_MESSAGES/setup.po b/locale/pt/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..989c21fab
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Portuguese, Brazilian\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "s"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Bem-vindo ao assistente de configuração do pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Ele irá verificar o seu sistema e fazer uma configuração básica para executar o pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "O valor entre colchetes [] é sempre o valor padrão,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "no caso de você não quer mudá-lo ou você não tiver certeza qual escolher, apenas tecle enter."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Não se esqueça: você sempre pode executar novamente o assistente com os parâmetros --setup ou -s, quando você iniciar o pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "para cancelar e não deixá-lo iniciar automaticamente com pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Quando estiver pronto para verificação do sistema, aperte enter."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Continuar com a configuração?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Você quer alterar o caminho de configuração? O atual é %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Alterar o caminho de configuração?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Você quer configurar dados de login e configurações básicas?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Isto é recomendável para a primeira execução."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Fazer a configuração básica?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Você quer configurar o ssl?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Configurar o ssl?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Você quer configurar a interface WEB?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Configurar a interface Web?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Configuração concluída com êxito."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Aperte enter para sair e reiniciar o pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "# # Instalação basica # #"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Os dados de login à seguir é válido para o CLI, GUI e interface Web."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Nome do usuário"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Língua"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "nº Downloads paralelos"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Usar Reconectar?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Local do script de reconexão"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "# # Instalação da interface WEB# #"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Ativar webinterface?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Endereço de escuta, se você usar 127.0.0.1 ou localhost, a interface Web será acessível apenas localmente."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Endereço"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Porta"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "O pyLoad oferece vários backends de servidor, a seguir uma breve explicação."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Pode ser usado pelo apache, lighttpd, requer que você os configure, o que não é tarefa muito fácil."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Pegue-a daqui: https://github.com/jonashaag/bjoern, é só compilar"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Atenção: Em alguns casos raros o servidor builtin não está funcionando, se você notar problemas com a interface Web"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "Volte aqui e mude o servidor builtin para o threaded ."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Servidor"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "# # Configuração SSL # #"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Execute esses comandos na pasta de configuração do pyLoad para fazer certificados ssl:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Se você está pronto e tudo correu bem, você pode ativar o ssl agora."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Ativar SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Selecione a ação"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Criar/editar usuário"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Lista de usuários"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Remover usuário"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Sair"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Usuários"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Pressione Enter para sair."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "A troca do caminho de configuração falhou: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Senha: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Senha muito curta. Use pelo menos 4 caracteres."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Senha (novamente): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "As senhas não correspondem."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "sim"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "verdadeiro"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "v"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "não"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "falso"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Entrada inválida"
+
diff --git a/locale/pt_BR/LC_MESSAGES/django.mo b/locale/pt_BR/LC_MESSAGES/django.mo
deleted file mode 100644
index 2fdf540b0..000000000
--- a/locale/pt_BR/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoad.mo b/locale/pt_BR/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2e8a1e5b3..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo b/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 2ca834196..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo b/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 28fe26252..000000000
--- a/locale/pt_BR/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/setup.mo b/locale/pt_BR/LC_MESSAGES/setup.mo
deleted file mode 100644
index c64d0f23c..000000000
--- a/locale/pt_BR/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/django.po b/locale/ro/LC_MESSAGES/django.po
new file mode 100644
index 000000000..5dfc685da
--- /dev/null
+++ b/locale/ro/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Romanian\n"
+"Language: ro_RO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/ro/LC_MESSAGES/pyLoad.mo b/locale/ro/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/ro/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/pyLoad.po b/locale/ro/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..c50b5c540
--- /dev/null
+++ b/locale/ro/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Romanian\n"
+"Language: ro_RO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/ro/LC_MESSAGES/pyLoadCli.mo b/locale/ro/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/ro/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/pyLoadCli.po b/locale/ro/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..17acda928
--- /dev/null
+++ b/locale/ro/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Romanian\n"
+"Language: ro_RO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/ro/LC_MESSAGES/pyLoadGui.mo b/locale/ro/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/ro/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ro/LC_MESSAGES/setup.po b/locale/ro/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..8520f88e1
--- /dev/null
+++ b/locale/ro/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Romanian\n"
+"Language: ro_RO\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/ru/LC_MESSAGES/django.mo b/locale/ru/LC_MESSAGES/django.mo
deleted file mode 100644
index 0a1394f21..000000000
--- a/locale/ru/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/django.po b/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 000000000..d6c3d748d
--- /dev/null
+++ b/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Russian\n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "ЗапрПс МПвПй картОМкО."
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "ПрПчОтайте текст Ма рОсуМке."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "ПрПграЌЌа pyLoad заМПвП загружеМа."
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "выкл."
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "УспешМП"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "вкл."
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Вы увереМы, чтП хПтОте закПМчОть pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "ПерезапустОть ссылку"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "УЎалОть ссылку."
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "ВвеЎОте ОЌя кПМтейМера."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "ППжалуйста клОкМОте Ма правОльМую пПзОцОю картОМкО."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "СвершОлась ПшОбка."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Папка пуста"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "СбПй"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Нет рОсуМка Ўля прПчтеМОя."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "ПарПлО Ме сПвпаЎают."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "CПхраМеМОя устаМПвПк завершОлась."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "НПвая папка"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Вы увереМы, чтП хПтОте перезагрузОть pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "ПжОЎаМОе %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "АктОвМые загрузкО"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "ДПЌПй"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "ЗакачкО"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Отчёты"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "НастрПйкО"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "НазваМОе"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Статус"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "ИМфПрЌацОя"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "РазЌер"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "ПрПгресс"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "ЛПгОМ"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "ППльзПватель"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "ПарПль"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "НеправОльМые ОЌя пПльзПвателя ОлО парПль! ППпрПбуйте еще раз."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Для сбрПса свПОх ЎаММых ОлО ЎПбавлеМОя МПвПгП пПльзПвателя запустОте: "
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "УЎалОть завершёММые"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "ПерезапустОть МеуЎавшОеся"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Папка:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "ПарПль:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "РеЎактОрПвать кПМтейМер"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "РеЎактОрПваМОе Ўеталей кПМтейМера."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "ИЌя кПМтейМера."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Папка"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "ИЌя пПЎпапкО Ўля этОх загрузПк."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "СпОсПк парПлей, ОспПльзуеЌых Ўля распакПвкО."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "ОтправОть"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "СбрПс"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Вы успешМП вышлО."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Путь"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "абсПлютМый"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "ПтМПсОтельМый"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "ОЌя"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "разЌер"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "тОп"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "пПслеЎМее ОзЌеМеМОе"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "рПЎОтельская папка"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "Птсутствует сПЎержОЌПе"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "ОсМПвМые"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "ПлагОМы"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "АккауМты"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "ВыберОте секцОю ЌеМю"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "ПрОлПжеМОе"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "ПреЌОуЌ аккауМт"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "ГПЎеМ ЎП"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "ОсталПсь трафОка"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "ВреЌя"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Макс. пПтПкПв"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "УЎалОть?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "ЎействОтелеМ"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "Ме гПЎеМ"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "Ўа"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "Мет"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "ДПбавОть"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "ДПбавлеМОе учетМПй запОсО"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "УкажОте преЌОуЌ аккауМт Ўля ОспПльзПваМОя всех егП вПзЌПжМПстей."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "ИЌя пПльзПвателя."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "ПарПль Ўля этПй учетМПй запОсО."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "ТОп"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "ВыберОте хПстОМг."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "НачалП"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "преЎ."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "слеЎ."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "КПМец"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "НПвПстО"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "ППЎЎержка"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "СОстеЌа"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "ОС:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "ВерсОя pyLoad:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Папка устаМПвкО:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Папка МастрПек:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "Папка скачаММПгП:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "СвПбПЎМП:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Язык:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "ППрт WEB ОМтерфейса:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "ППрт уЎалеММПгП управлеМОя:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "УстаМПвка"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Ѐайл ЌеМеЎжер"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "ДПбавОть кПМтейМер"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Вставьте ссылкО ОлО загрузОте кПМтейМер."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "ИЌя МПвПгП кПМтейМера."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "СсылкО"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Вставьте зЎесь ссылкО О МажЌОте \"ЀОльтр\""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "ЀОльтр"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "ПарПль Ўля архОва RAR"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Ѐайл"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "ЗагрузОть кПМтейМер."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "НазМачеМОе"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "ЧтеМОе картОМкО"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "КартОМка"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "КартОМка."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Текст"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "ВвеЎОте текст с картОМкО."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Закрыть"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Web-ОМтерфейс"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "ДПступМы ПбМПвлеМОя pyLoad!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "ПлагОМы ПбМПвлеМы, перезапустОте pyLoad!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "ОжОЎаеЌ рОсуМПк"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "ВыйтО"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "АЎЌОМОстрОрПваМОе"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "ИМфПрЌацОя"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "ППжалуйста, вПйЎОте сП свПОЌО учётМыЌО ЎаММыЌО!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "СтПп"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "ОтЌеМОть"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "Загрузка:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "ПерепПЎключеМОе:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "СкПрПсть:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "АктОвМые:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "ОбМПвОть страМОцу"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "загрузка"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Вверх"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "ЗавершОть pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "ПерезапустОть pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Для ЎПбавлеМОя пПльзПвателя ОлО ОзЌеМеМОя парПля:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "ВМОЌаМОе: аЎЌОМОстратПр уже ПблаЎает всеЌО праваЌО!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "ИзЌеМОть парПль"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "АЎЌОМ"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Права"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "ОзЌеМОть"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "ВвеЎОте текущОй О желаеЌый парПль."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "ППльзПватель"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "ТекущОй парПль"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "НПвый парПль"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "НПвый парПль."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "НПвый парПль (пПвтПр)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "ППжалуйста пПвтПрОте МПвый парПль."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "У вас Мет прав Ма прПсЌПтр страМОцы."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Папка Ўля закачек Ме МайЎеМа."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПграМОчеММый"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "Ме ЎПступМП"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Для ЎПступа к МастрПйкаЌ запустОте pyload.py -s."
+
diff --git a/locale/ru/LC_MESSAGES/pyLoad.mo b/locale/ru/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 156a1bc44..000000000
--- a/locale/ru/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/pyLoad.po b/locale/ru/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..b4fc6a768
--- /dev/null
+++ b/locale/ru/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Russian\n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "ППлучеМ сОгМал Ўля завершеМОя рабПты"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad уже запущеМ, pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Не уЎалПсь ОзЌеМОть группу: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Не уЎалПсь ОзЌеМОть пПльзПвателя: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "папка ПтчетПв"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Запускается"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "ОсМПвМая папка: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto Ўля ЎекПЎОрПваМОя файлПв кПМтейМерПв"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "папка вреЌеММых файлПв"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "папка Ўля закачек"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL Ўля безПпасМПгП сПеЎОМеМОя"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "ПереЌестОть прежМюю пПльзПвательскую кПМфОгурацОю в БД"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "ППжалуйста, прПверьте свПО учётМые ЎаММые запустОв ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Все ссылкО уЎалеМы"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "ВреЌя закачкО: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "СвПбПЎМПе ЌестП: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "АктОвацОя аккауМта..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "АктОвацОя плагОМа..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad загружеМ О рабПтает"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "перезапуск pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "завершеМОе рабПты pyLoad"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "УстаМПвка %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "Ме ЌПгу МайтО %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "Ме ЌПгу сПзЎать %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "завершеМОе рабПты..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "ПшОбка прО завершеМОО рабПты"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "pyLoad ПстаМПвлеМ с терЌОМала"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Базы ЎаММых была уЎалеМа пП прОчОМе МесПвЌестОЌПй версОО."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "База ЎаММых НЕ ЌПжет быть преПбразПваМа."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "База ЎаММых преПбразПваМа Оз v2 в v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "База ЎаММых преПбразПваМа Оз v2 в v3."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "ПреПбразПваМОе прежМей БД Django"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "закПМчеМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "Пф-лайМ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "ПМ-лайМ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "в ПчереЎО"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "прПпущеМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "ПжОЎаМОе"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "вреЌеММП МеЎПступМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "запускается"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "завершеМП с ПшОбкПй"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "прерваМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "раскПЎОрПваМОе"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "пПльзПвательскОй"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "загружается"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "Пбрабатывается"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "МеОзвестМП"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Пакет завершеМ: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "ИспПльзуется SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "ОшОбка уЎалеММПгП сервОса: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Запуск %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "ОшОбка прО загрузке %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "ПжОЎаМОе %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL сертОфОкаты Ме МайЎеМы."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "К сПжалеМОю, пПЎЎержка в pyLoad прекращеМа МачОМая с версОО %s"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Вы ЌПжете ОспПльзПвать threaded сервер Ўля пПвышеМОя прПОзвПЎОтельМПстО О ОспПльзПваМОя SSL,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "МП ЌПжМП О ОспПльзПвать прежМОй сервер %s с pyLoads fastcgi"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "ПрОЌеры кПМфОгурацОО web-серверПв ЌПжМП пПсЌПтреть в папке pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "ИспПльзПвать %(server)s МевПзЌПжМП, так как Ме устаМПвлеМ python-flup!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "ЭтПт сервер Ме преЎПставляет вПзЌПжМПсть ОспПльзПвать SSL, пПжалуйста рассЌПтрОте вПзЌПжМПсть ОспПльзПвать сервер threaded взаЌеМ выбраММПгП"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Запуск встрПеММПгП веб-сервера django: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Запуск веб-сервера lighttpd: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Запуск threaded веб-сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Запуск fastcgi-сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "У вас Мет прав Ма прПсЌПтр страМОцы."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "Папка Ўля закачек Ме МайЎеМа."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПграМОчеММый"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "Ме ЎПступМП"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Для ЎПступа к МастрПйкаЌ запустОте pyload.py -s."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Загрузка параллельМых пПтПкПв ЎаММых завершеМа с ПшОбкаЌО, вПзврат к ПЎМПпПтПчМПй загрузке | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "Загрузка Мачата: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "Загрузка завершеМа: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "В плагОМе %s Мет фуМкцОО."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "Загрузка прерваМа: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "ОшОбка загрузкО: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "Загрузка МеЎПступМа: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "Загрузка вреЌеММП МеЎПступМа: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "ОшОбка загрузкО: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "НевПзЌПжМП сПеЎОМОться с прПвайЎерПЌ ОлО срыв сПеЎОМеМОя, слеЎующая пПпытка через ПЎМу ЌОМуту."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "Загрузка прПпущеМа: %(name)s @ %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "ДекПЎОрПваМОе МачатП: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "ОшОбка ЎекПЎОрПваМОя: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "ППвтПрМая пПпытка %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "ППлучеМОе ОМфПрЌацОО пП %(name)s завершОлПсь ПшОбкПй | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "ОщОбка выпПлМеМОя захватПв: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "ОшОбка актОвацОО %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "ПерепПЎключеМОе Ме уЎалПсь: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "СкрОпт перепПЎключеМОя Ме МайЎеМ!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "НачОМаеЌ перепПЎключеМОе"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "ОшОбка прО выпПлМеМОО скрОпта перепПЎключеМОя"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "ПерепПЎключеМП, МПвый IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Не хватает Ќеста Ма устрПйстве."
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Не ЌПгу вПйтО с учетМПй запОсью %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "НеправОльМый парПль"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "ВреЌя %s ОЌеет МеправОльМый фПрЌат, ОспПльзуйте фПрЌат 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "НеЎПстатПчМП траффОка в аккауМте %s, слеЎующая прПверка через 30 ЌОМут"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Истек срПк ЎействОя аккауМта %s, слеЎующая прПверка через 1 час"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "ДПстОгМут лОЌОт загрузПк"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s ОЌеет Ме правОльМый шаблПМ."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "ОшОбка ОЌпПрта %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Не загружеМ МО ПЎОМ Оз файлПбЌеММОкПв"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "В свПей учётМПй запОсО включОте ПпцОю 'direct Download'"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "СпОсПк ссылПк Ме ЌПжет быть ПчОщеМ."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "УстаМПвкО учетМПй запОсО уЎалеМы в связО с МПвыЌ фПрЌатПЌ."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "НеПбхПЎОЌа автПрОзацОя (ОЌя пПльзПвателя:парПль)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "ППжалуйста, ввеЎОте ваш аккауМт Ўля %s ОлО ПтключОть этПт плагОМ"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "НайЎеМ HTML-кПЎ в файле загрузкО (%s)... ПшОбка переМаправлеМОя? Загрузка буЎет перезапущеМа."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Ѐайл вреЌеММП МеЎПступеМ"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: ПжОЎаМОе ЌежЎу загрузкаЌО %d сек."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: ПжОЎаМОе картОМкО %d сек."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "ЗагружеММый файл пуст"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "НеверМый ключ API"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: ОсталПсь МеЎПстатПчМП трафОка"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "ТрафОк закПМчОлся"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Traffic Share (пряЌая загрузка)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Уже ОЎет загрузка с этПгП IP, жЎеЌ 60 секуМЎ"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "НеправОльМый кПЎ ОЎеМтОфОкацОО, загрузка буЎет перезапущеМа"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: Нет свПбПЎМПгП слПта"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Для этПгП файла МужеМ преЌОуЌ аккауМт"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "ИЌя файла указаМП МеверМП"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "ОшОбка параллельМПй загрузкО, жЎеЌ 60 секуМЎ."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "ВхПЎ Ме прПОзвеЎеМ."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "СбПй расшОфрПвкО"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "Нет ключа файла в URL-аЎресе"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "КПЎ ПшОбкО:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Ѐайл Ме существует."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** ДПпПлМеМОя ПбМПвлеМы, перезапустОте pyLoad ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "ДПпПлМеМОя ПбМПвлеМы О перезагружеМы"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Нет ПбМПвлеМОй Ўля ЎПпПлМеМОй"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "Нет ПбМПвлеМОй pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** ДПступМа МПвая версОя pyLoad %s ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Скачайте зЎесь: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Не вПзЌПжМП сПеЎОМОться с серверПЌ ПбМПвлеМОй"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "НПвая версОя %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "ОшОбка прО ПбМПвлеМОО %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "КПМфлОкт версОО"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "Загрузка завершеМа: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "НПвый запрПс картОМкО: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "Ответ с текстПЌ Ма картОМке %s"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "ППжалуйста, сМачала ЎПбавьте учетМую запОсь premium.to О перезапустОте pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "ДПбавлеМ %s Оз HotFolder"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "%s Ме устаМПвлеМ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Не вПзЌПжМП актОвОрПвать %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "АктОвОрПваМ(a/П)"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Не актОвОрПваМП ЎПпПлМеМОй Ўля ОзвлечеМОя/распакПвкО"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Пакет %s пПставлеМ в ПчереЎь Ўля пПслеЎующегП ОзвлечеМОя/распакПвкО"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "ПрПверка пакета %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "ИзвлечеМОе/распакПвка в %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Не МайЎеМП файлПв Ўля ОзвлечеМОя"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "Озвлекается/распакПвывается"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "ЗащОщеМП парПлеЌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "НеправОльМый парПль"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "УЎалеМОе файлПв %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "ИзвлечеМОe/распакПвка завершеМП(a)"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "ОшОбка архОва"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "ОшОбка кПМтрПльМПй суЌЌы"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "НеОзвестМая ПшОбка"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "ОшОбка устаМПвкО пПльзПвателя О группы: %s"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: ППрт 9666 уже заМят"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "ОсталПсь %s креЎОтПв"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Не уЎалПсь ПтправОть Птвет."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "НеЎПстатПчМП среЎств Ма вашеЌ аккауМте CaptchaTrader"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "СпОсПк шОфрПваМОя Ме МайЎеМ"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "СпОсПк шОфрПваМОя пуст"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "Загрузка завершеМа: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "НПвая(ые) CaptchaID Оз загрузкО: %s: %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Ваша учетМая запОсь 9kw.eu ОЌеет Ме ЎПстатПчМП креЎОтПв"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "УстаМПвлеММые скрОпты Ўля %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Не вПзЌПжМП запустОть скрОпт:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "ОшОбка в %(script): %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "УчетМая запОсь ExpertDecoders Ме ОЌеет ЎПстатПчМП креЎОтПв"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "ППжалуйста сМачала ЎПбавьте ваш rehost.to аккауМт О перезапустОте pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "ППжалуйста сМачала ЎПбавьте ЎействОтельМую учетМую запОсь Ўля premiumize.me О перезапустОть pyLoad."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "ОсталПсь %s креЎОтПв"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil О tesseract Ме устаМПвлеМы. Не пПЎключеМ клОеМт Ўля расшОфрПвкО картОМПк"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Не пПлучеМ результат расшОфрПвкО картОМкО в течеМОе ПтвеЎёММПгП вреЌеМО МО ПЎМОЌ Оз ЎПпПлМеМОй"
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "ОшОбка устаМПвкО пПльзПвателя О группы: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Не пПЎключеМ клОеМт Ўля расшОфрПвкО картОМПк"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "ДПбавлеМ кПМтейМер %(name)s с %(count)d сылкаЌО"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "ДПбавлеМП %(count)d ссылПк в кПМтейМер #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Не ПбМаружеМа js платфПрЌа, пПжалуйста устаМПвОте лОбП Spidermonkey, ossp-js, pyv8 ОлО rhino"
+
diff --git a/locale/ru/LC_MESSAGES/pyLoadCli.mo b/locale/ru/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index adbb5e141..000000000
--- a/locale/ru/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/pyLoadCli.po b/locale/ru/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..20b49882a
--- /dev/null
+++ b/locale/ru/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Russian\n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "ИМтерфейс кПЌаМЎМПй стрПкО"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s загрузПк:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "СкПрПсть: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "РазЌер: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "ЗакПМчеМП в: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "ПжОЎаМОе: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "СПстПяМОе:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "прОПстаМПвлеМП"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "в прПцессе"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "Пбщая скПрПсть"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "ЀайлПв в ПчереЎО"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "ВсегП"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "МеМю:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "ДПбавОть ссылкО"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "УправлеМОе ПчереЎью"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "УправлеМОе кПллектПрПЌ ссылПк"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "ЗапустОть/ПрОПстаМПвОть сервер"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "ОстаМПвОть сервер"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "ВыхПЎ"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "ИспПльзуйте шаблПМ: add <ИЌя кПМтейМера> <ссылка1> <ссылка2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "ПрПверка %d ссылкО:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Ѐайл Ме МайЎеМ."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "РабПта pyLoad была прОМуЎОтельМП завершеМа"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "ППказать сПстПяМОе сервера"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "ППказать закачкО в ПчереЎО"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "ППказать закачкО в спОске кПллектПра"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "ДПбавОть кПМтейМер в ПчереЎь"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "ДПбавляет кПМтейМер в кПллектПр ссылПк"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "УЎалОть файлы Оз ПчереЎО/кПллектПра"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "УЎалОть кПМтейМер Оз ПчереЎО/кПллектПра"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "ПереЌестОть пакет(ы) Оз ПчереЎО в кПллектПр О МаПбПрПт"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "ПерезапустОть файлы"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "ПерезапустОть пакеты"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "ПрПверка статуса связО, Ўействует с ЌестМыЌО кПМтейМераЌО"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "ПрПверяет сПстПяМОе файла-кПМтейМера"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "ПрОПстаМПвОть сервер"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "прПЎПлжОть закачкО"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "ПереключОть пауза/прПЎПлжОть"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "ПстаМПвОть сервер"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "СпОсПк кПЌаМЎ: "
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "ЗапОсать файл кПМфОгурацОО пПльзПвателя Ме уЎалПсь"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Для сПеЎОМеМОя с яЎрПЌ pyLoad МеПбхПЎОЌ пакет py-openssl."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "АЎрес: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "ППрт: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "ИЌя пПльзПвателя: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "ПарПль: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "НеправОльМые учётМые ЎаММые."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "НевПзЌПжМП пПЎключОться к %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Для сПеЎОМеМОя с яЎрПЌ pyLoad МеПбхПЎОЌ пакет py-openssl."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "В связО с теЌ, чтП былО ввеЎеМы ряЎ кПЌаМЎ, ОМтерактОвМый режОЌ был прерваМ."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "ДПбавОть пакет:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "ВвеЎОте МазваМОе пакета"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "КПМтейМер: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "АМалОз ссылПк Ўля ЎПбавлеМОя."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "ВвеЎОте %s кПгЎа буЎете гПтПвы."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "ДПбавлеМы ссылкО: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "МазаЎ в главМПе ЌеМю"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "УправлеМОе пакетаЌО:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "УправлеМОе ссылкаЌО"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "ЧтП Вы хПтОте переЌестОть?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "ЧтП Вы хПтОте уЎалОть?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "ЧтП Вы хПтОте перезапустОть?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "ВыбОрайте, чтП Вы хПтОте сЎелать ОлО вМесОте МПЌер пакета."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "уЎалОть"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "переЌестОть"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "перезапустОть"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr "- преЎыЎущОй"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "- слеЎующОй"
+
diff --git a/locale/ru/LC_MESSAGES/pyLoadGui.mo b/locale/ru/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 11d4328c0..000000000
--- a/locale/ru/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/setup.mo b/locale/ru/LC_MESSAGES/setup.mo
deleted file mode 100644
index fb1201628..000000000
--- a/locale/ru/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/ru/LC_MESSAGES/setup.po b/locale/ru/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..b1d0093d4
--- /dev/null
+++ b/locale/ru/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Russian\n"
+"Language: ru_RU\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "Ўа"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "Мет"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "Вас прОветствует пПЌПщМОк пП МастрПйке pyLoad"
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "БуЎет прПвереМа сОстеЌа О вМесеМы первПМачальМые МастрПйкО Ўля запуска pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "В кваЎратМых скПбках [] указываются зМачеМОя пП уЌПлчаМОю,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "еслО Вы Ме хПтОте ЌеМять этО зМачеМОя ОлО Ме увереМы в свПёЌ выбПре, прПстП МажЌОте ENTER."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Не забуЎьте: Вы всегЎа ЌПжете сМПва запустОть пПЌПщМОка пП МастрПйкаЌ, Мабрав pyload.py с ключПЌ --setup ОлО -s."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "ПрО вПзМОкМПвеМОО прПблеЌ с этОЌ ассОстеМтПЌ МажЌОте STRG-C"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "Ўля прекращеМОя устаМПвкО. pyload.py бПльше Ме буЎет запускаться автПЌатОческО."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Для старта прПверкО сОстеЌы, МажЌОте ENTER."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Для рабПты pyLoad МеПбхПЎОЌы pycurl, sqlite О python 2.5, 2.6, 2.7."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "УстаМПвОте МеЎПстающОе пакеты О перезапустОте pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "ПрПграЌЌа устаМПвкО завершает рабПту."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "ПрПверка сОстеЌы завершеМа. ЧтПбы прПсЌПтреть Птчет, МажЌОте клавОшу ENTER."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Отчет ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "расшОфрПвка"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "SSL пПЎключеМОе"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "автПЌатОческая расшОфрПвка CAPTCHA"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "GUI "
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Web-ОМтерфейс"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "пПЎЎержка Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "ДПступМые фуМкцОО:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "НеЎПступМые фуМкцОО:"
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "МеЎПступеМ py-crypto"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "ОМ МужеМ Ўля раскПЎОрПваМОя файлПв."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "Мет SSL"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "ОМ МужеМ Ўля защОты сПеЎОМеМОя с яЎрПЌ ОлО web-ОМтерфейсПЌ."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "ПрО лПкальМых сПеЎОМеМОях SSL Ме МужеМ."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "РасшОфрПвка Captcha МеЎПступМа"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "НужМа Ма МекПтПрых файл-хПстОМгах."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Нет GUI"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "Нет пПЎЎержкО JavaScript"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "ЕтП пПМаЎПбОться Ўля МекПтПрых ссылПк Click'N'Load. ИМсталОрПвайте Spidermonkey, ossp-js, pyv8 ОлО rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "ПрО желаМОО сейчас ЌПжМП прервать устаМПвку О пПставОть МеПбхПЎОЌые завОсОЌые пакеты."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "ПрПЎПлжОть МастрПйку?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "НужМП лО ЌеМять путь к папке МастрПек? ТекущОй путь %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "ЕслО вы ОспПльзуете pyLoad Ма какПЌ-МОбуЎь сервере ОлО путь к кПМфОгурацОО указывает Ма flash-ЎОск, лучше пПЌеМяйте егП."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "ИзЌеМОть путь?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "ХПтОте ОзЌеМОть учётМые ЎаММые О ЎругОе базПвые МастрПйкО?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "НастПятельМП рекПЌеМЎуется прО первПЌ запуске."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "ИзЌеМОть ПсМПвМые МастрПйкО?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "ХПтОте МастрПОть SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "НастрПОть SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "ХПтОте МастрПОть WEB-ОМтерфейс?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "НастрПОть WEB-ОМтерфейс?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "УстаМПвка успешМП завершеМа."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "НажЌОте ENTER О запустОте pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## ПрПверка сОстеЌы ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "ВерсОя python слОшкПЌ МПвая, ОспПльзуйте Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "ВерсОя python слОшкПЌ старая, ОспПльзуйте Python версОО 2.5 О выше"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "ВерсОя Python: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "СлОшкПЌ старая версОя jinja2 %s."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "МПжМП спПкПйМП прПЎПлжОть МастрПйку, МП еслО web-ОМтерфейс Ме буЎет рабПтать,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "тП ПбМПвОте ОлО уЎалОте бОблОПтеку jinja2. ОМа уже есть в сПставе pyLoad."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## ОсМПвМые МастрПйкО ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "ЭтО учётМые ЎаММые пПЎхПЎят к CLI, GUI О WEB-ОМтерфейсу."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "ППльзПватель"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "ВМешМОЌ клОеМтаЌ (GUI, CLI О ЎругОе) пПМаЎПбОться ЎОстаМцОПММый ЎПступ Ўля рабПты через сеть."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "ЕслО Вы хПтОте пПльзПваться тПлькП сПеЎОМеМОеЌ через веб ОМтерфейс, ПтключОте егП, чтПбы уЌеМьшОть пПтреблеМОе ПператОвМПй паЌятО."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "ВключОть ЎОстаМцОПММый ЎПступ"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Язык"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "Папка закачек"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Макс.чОслП ПЎМПвреЌеММых закачек"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "ИспПльзПвать перепПЎключеМОе ОМтерМета?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Путь к скрОпту перепПЎключеМОя"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## УстаМПвкО WEB-ОМтерфейса ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "ВключОть WEB-ОМтерфейс?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "ip-аЎрес WEB-ОМтерфейса. ЕслО указать 127.0.0.1 ОлО localhost, тП WEB-ОМтерфейс буЎет ЎПступеМ тПлькП лПкальМП."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "АЎрес"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "ППрт"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "В pyLoad МескПлькП влПжеММых серверПв, МОже слеЎуют ПпОсаМОя в кПрПткПЌ вОЎе."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "СтаМЎартМая МастрПйка, МаОлучшОй выбПр еслО ваЌ Ме ясМП какПй сЎелать выбПр."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "ЕтПт сервер пПЎЎержОвает SSL О хПрПшая, гПЎМая альтерМатОва к builtin."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "МПжет ОспПльзПваться вЌесте с apache, lighttpd, требует МастрПйкО, чтП является Ме такОЌ прПстыЌ заЎаМОеЌ."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "ППлучОть егП зЎесь: https://github.com/jonashaag/bjoern О скПЌпОлОрПвать егП"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "ВМОЌаМОе: В МекПтПрых реЎкОх случаях встрПеММый сервер Ме рабПтает, еслО вы заЌетОлО прПблеЌы с веб-ОМтерфейсПЌ"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "верМуться сюЎа О ОзЌеМОть сервер builtin Ма сервер threaded."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Сервер"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## НастрПйкО SSL ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Для геМерацОО SSL сертОфОкатПв выпПлМОте слеЎующОе кПЌаМЎы, МахПЎясь в папке кПМфОгурацОО pyLoad:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "ЕслО всё прПшлП успешМП, сейчас ЌПжМП буЎет включОть пПЎЎержку SSL."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "ВключОть SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "ВыберОте ЎействОе"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - ДПбавОть/ИзЌеМОть пПльзПвателя"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - ППказать спОсПк пПльзПвателей"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - УЎалОть пПльзПвателя"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - ВыхПЎ"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "ППльзПвателО"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "УстаМПвлеМ МПвый путь к папке МастрПек. ТекущОе МастрПйкО Ме буЎут сПхраМеМы!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Путь к МастрПйкаЌ"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Путь к МастрПйкаЌ был ОзЌеМёМ. ПерезапустОте ассОстеМт Ўля прПЎПлжеМОя МастрПйкО."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "НажЌОте клавОшу Enter Ўля выхПЎа."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Не уЎалПсь устаМПвОть путь к МастрПйкаЌ: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: Птсутствует"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "ПарПль: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "СлОшкПЌ прПстПй парПль, МужМП ЌОМОЌуЌ 4 сОЌвПла."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "ПарПль (ППвтПрОть)"
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "ПарПлО Ме сПвпаЎают."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "Ўа"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "Да"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "Мет"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "ЛПжь"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "НекПрректМый ввПЎ"
+
diff --git a/locale/setup.pot b/locale/setup.pot
index cf4dd8cfc..ceba6a5d2 100644
--- a/locale/setup.pot
+++ b/locale/setup.pot
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: pyLoad 0.4.9\n"
+"Project-Id-Version: pyLoad 0.4.10\n"
"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
-"POT-Creation-Date: 2011-12-07 19:21+0100\n"
+"POT-Creation-Date: 2014-07-13 20:53+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"
@@ -17,479 +17,463 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: module/setup.py:51
+#: pyload/setup.py:33
msgid "y"
msgstr ""
-#: module/setup.py:53
+#: pyload/setup.py:35
msgid "n"
msgstr ""
-#: module/setup.py:72
-msgid "Welcome to the pyLoad Configuration Assistent."
+#: pyload/setup.py:53
+msgid "Welcome to the pyLoad Configuration Assistant."
msgstr ""
-#: module/setup.py:73
+#: pyload/setup.py:54
msgid ""
"It will check your system and make a basic setup in order to run pyLoad."
msgstr ""
-#: module/setup.py:75
+#: pyload/setup.py:56
msgid "The value in brackets [] always is the default value,"
msgstr ""
-#: module/setup.py:76
+#: pyload/setup.py:57
msgid ""
"in case you don't want to change it or you are unsure what to choose, just "
"hit enter."
msgstr ""
-#: module/setup.py:77
+#: pyload/setup.py:59
msgid ""
-"Don't forget: You can always rerun this assistent with --setup or -s "
-"parameter, when you start pyLoadCore."
+"Don't forget: You can always rerun this assistant with --setup or -s "
+"parameter, when you start pyload.py ."
msgstr ""
-#: module/setup.py:78
-msgid "If you have any problems with this assistent hit STRG-C,"
+#: pyload/setup.py:60
+msgid "If you have any problems with this assistant hit STRG-C,"
msgstr ""
-#: module/setup.py:79
-msgid "to abort and don't let him start with pyLoadCore automatically anymore."
+#: pyload/setup.py:61
+msgid "to abort and don't let him start with pyload.py automatically anymore."
msgstr ""
-#: module/setup.py:81
+#: pyload/setup.py:63
msgid "When you are ready for system check, hit enter."
msgstr ""
-#: module/setup.py:88
+#: pyload/setup.py:70
msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
msgstr ""
-#: module/setup.py:89
+#: pyload/setup.py:71
msgid "Please correct this and re-run pyLoad."
msgstr ""
-#: module/setup.py:90
+#: pyload/setup.py:72
msgid "Setup will now close."
msgstr ""
-#: module/setup.py:94
+#: pyload/setup.py:76
msgid "System check finished, hit enter to see your status report."
msgstr ""
-#: module/setup.py:96
+#: pyload/setup.py:78
msgid "## Status ##"
msgstr ""
-#: module/setup.py:101
+#: pyload/setup.py:83
msgid "container decrypting"
msgstr ""
-#: module/setup.py:102
+#: pyload/setup.py:85
msgid "ssl connection"
msgstr ""
-#: module/setup.py:103
+#: pyload/setup.py:87
msgid "automatic captcha decryption"
msgstr ""
-#: module/setup.py:104
-msgid "GUI"
-msgstr ""
-
-#: module/setup.py:105
+#: pyload/setup.py:89
msgid "Webinterface"
msgstr ""
-#: module/setup.py:106
+#: pyload/setup.py:91
msgid "extended Click'N'Load"
msgstr ""
-#: module/setup.py:113
+#: pyload/setup.py:98
msgid "Features available:"
msgstr ""
-#: module/setup.py:117
+#: pyload/setup.py:102
msgid "Featues missing: "
msgstr ""
-#: module/setup.py:121
+#: pyload/setup.py:106
msgid "no py-crypto available"
msgstr ""
-#: module/setup.py:122
+#: pyload/setup.py:107
msgid "You need this if you want to decrypt container files."
msgstr ""
-#: module/setup.py:126
+#: pyload/setup.py:111
msgid "no SSL available"
msgstr ""
-#: module/setup.py:127
+#: pyload/setup.py:112
msgid ""
"This is needed if you want to establish a secure connection to core or "
"webinterface."
msgstr ""
-#: module/setup.py:128
+#: pyload/setup.py:113
msgid "If you only want to access locally to pyLoad ssl is not usefull."
msgstr ""
-#: module/setup.py:132
+#: pyload/setup.py:117
msgid "no Captcha Recognition available"
msgstr ""
-#: module/setup.py:133
+#: pyload/setup.py:118
msgid "Only needed for some hosters and as freeuser."
msgstr ""
-#: module/setup.py:137
-msgid "Gui not available"
-msgstr ""
-
-#: module/setup.py:138
-msgid "The Graphical User Interface."
-msgstr ""
-
-#: module/setup.py:142
+#: pyload/setup.py:122
msgid "no JavaScript engine found"
msgstr ""
-#: module/setup.py:143
+#: pyload/setup.py:123
msgid ""
"You will need this for some Click'N'Load links. Install Spidermonkey, ossp-"
"js, pyv8 or rhino"
msgstr ""
-#: module/setup.py:145
+#: pyload/setup.py:125
msgid "You can abort the setup now and fix some dependicies if you want."
msgstr ""
-#: module/setup.py:147
+#: pyload/setup.py:127
msgid "Continue with setup?"
msgstr ""
-#: module/setup.py:153
+#: pyload/setup.py:133
#, python-format
msgid "Do you want to change the config path? Current is %s"
msgstr ""
-#: module/setup.py:154
+#: pyload/setup.py:135
msgid ""
"If you use pyLoad on a server or the home partition lives on an iternal "
"flash it may be a good idea to change it."
msgstr ""
-#: module/setup.py:155
+#: pyload/setup.py:136
msgid "Change config path?"
msgstr ""
-#: module/setup.py:162
+#: pyload/setup.py:142
msgid "Do you want to configure login data and basic settings?"
msgstr ""
-#: module/setup.py:163
+#: pyload/setup.py:143
msgid "This is recommend for first run."
msgstr ""
-#: module/setup.py:164
+#: pyload/setup.py:144
msgid "Make basic setup?"
msgstr ""
-#: module/setup.py:171
+#: pyload/setup.py:151
msgid "Do you want to configure ssl?"
msgstr ""
-#: module/setup.py:172
+#: pyload/setup.py:152
msgid "Configure ssl?"
msgstr ""
-#: module/setup.py:178
+#: pyload/setup.py:158
msgid "Do you want to configure webinterface?"
msgstr ""
-#: module/setup.py:179
+#: pyload/setup.py:159
msgid "Configure webinterface?"
msgstr ""
-#: module/setup.py:184
+#: pyload/setup.py:164
msgid "Setup finished successfully."
msgstr ""
-#: module/setup.py:185
+#: pyload/setup.py:165
msgid "Hit enter to exit and restart pyLoad"
msgstr ""
-#: module/setup.py:191
+#: pyload/setup.py:172
msgid "## System Check ##"
msgstr ""
-#: module/setup.py:196
+#: pyload/setup.py:175
msgid "Your python version is to new, Please use Python 2.6/2.7"
msgstr ""
-#: module/setup.py:199
+#: pyload/setup.py:178
msgid "Your python version is to old, Please use at least Python 2.5"
msgstr ""
-#: module/setup.py:202
+#: pyload/setup.py:181
msgid "Python Version: OK"
msgstr ""
-#: module/setup.py:249
+#: pyload/setup.py:224
#, python-format
msgid "Your installed jinja2 version %s seems too old."
msgstr ""
-#: module/setup.py:250
+#: pyload/setup.py:225
msgid "You can safely continue but if the webinterface is not working,"
msgstr ""
-#: module/setup.py:251
+#: pyload/setup.py:226
msgid ""
"please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
msgstr ""
-#: module/setup.py:268
+#: pyload/setup.py:243
msgid "JS engine"
msgstr ""
-#: module/setup.py:274
+#: pyload/setup.py:250
msgid "## Basic Setup ##"
msgstr ""
-#: module/setup.py:277
-msgid "The following logindata is valid for CLI, GUI and webinterface."
+#: pyload/setup.py:253
+msgid "The following logindata is valid for CLI and webinterface."
msgstr ""
-#: module/setup.py:282 module/setup.py:371 module/setup.py:387
+#: pyload/setup.py:259 pyload/setup.py:357 pyload/setup.py:373
msgid "Username"
msgstr ""
-#: module/setup.py:288
+#: pyload/setup.py:265
msgid ""
"External clients (GUI, CLI or other) need remote access to work over the "
"network."
msgstr ""
-#: module/setup.py:289
+#: pyload/setup.py:266
msgid ""
"However, if you only want to use the webinterface you may disable it to save "
"ram."
msgstr ""
-#: module/setup.py:290
+#: pyload/setup.py:267
msgid "Enable remote access"
msgstr ""
-#: module/setup.py:295
+#: pyload/setup.py:271
msgid "Language"
msgstr ""
-#: module/setup.py:298
+#: pyload/setup.py:273
msgid "Downloadfolder"
msgstr ""
-#: module/setup.py:299
+#: pyload/setup.py:274
msgid "Max parallel downloads"
msgstr ""
-#: module/setup.py:303
+#: pyload/setup.py:278
msgid "Use Reconnect?"
msgstr ""
-#: module/setup.py:306
+#: pyload/setup.py:281
msgid "Reconnect script location"
msgstr ""
-#: module/setup.py:311
+#: pyload/setup.py:286
msgid "## Webinterface Setup ##"
msgstr ""
-#: module/setup.py:314
+#: pyload/setup.py:289
msgid "Activate webinterface?"
msgstr ""
-#: module/setup.py:316
+#: pyload/setup.py:291
msgid ""
"Listen address, if you use 127.0.0.1 or localhost, the webinterface will "
"only accessible locally."
msgstr ""
-#: module/setup.py:317
+#: pyload/setup.py:292
msgid "Address"
msgstr ""
-#: module/setup.py:318
+#: pyload/setup.py:293
msgid "Port"
msgstr ""
-#: module/setup.py:320
+#: pyload/setup.py:295
msgid ""
"pyLoad offers several server backends, now following a short explanation."
msgstr ""
-#: module/setup.py:321
-msgid "Default server, best choice if you dont know which one to choose."
-msgstr ""
-
-#: module/setup.py:322
-msgid "This server offers SSL and is a good alternative to builtin."
+#: pyload/setup.py:296
+msgid "Default server; best choice if you plan to use pyLoad just for you."
msgstr ""
-#: module/setup.py:323
+#: pyload/setup.py:297
msgid ""
-"Can be used by apache, lighttpd, requires you to configure them, which is "
-"not too easy job."
+"Support SSL connection and can serve simultaneously more client flawlessly."
msgstr ""
-#: module/setup.py:324
-msgid "Very fast alternative written in C, requires libev and linux knowlegde."
-msgstr ""
-
-#: module/setup.py:325
-msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+#: pyload/setup.py:299
+msgid ""
+"Can be used by apache, lighttpd, etc.; needs to be properly configured "
+"before."
msgstr ""
-#: module/setup.py:326
-msgid "and copy bjoern.so to module/lib"
+#: pyload/setup.py:301
+msgid "Very fast alternative to builtin; requires libev and bjoern packages."
msgstr ""
-#: module/setup.py:329
+#: pyload/setup.py:305
msgid ""
"Attention: In some rare cases the builtin server is not working, if you "
"notice problems with the webinterface"
msgstr ""
-#: module/setup.py:330
+#: pyload/setup.py:306
msgid "come back here and change the builtin server to the threaded one here."
msgstr ""
-#: module/setup.py:333
+#: pyload/setup.py:315
msgid "Server"
msgstr ""
-#: module/setup.py:337
+#: pyload/setup.py:320
msgid "## SSL Setup ##"
msgstr ""
-#: module/setup.py:339
+#: pyload/setup.py:322
msgid ""
"Execute these commands from pyLoad config folder to make ssl certificates:"
msgstr ""
-#: module/setup.py:345
+#: pyload/setup.py:328
msgid "If you're done and everything went fine, you can activate ssl now."
msgstr ""
-#: module/setup.py:347
+#: pyload/setup.py:330
msgid "Activate SSL?"
msgstr ""
-#: module/setup.py:361
+#: pyload/setup.py:347
msgid "Select action"
msgstr ""
-#: module/setup.py:362
+#: pyload/setup.py:348
msgid "1 - Create/Edit user"
msgstr ""
-#: module/setup.py:363
+#: pyload/setup.py:349
msgid "2 - List users"
msgstr ""
-#: module/setup.py:364
+#: pyload/setup.py:350
msgid "3 - Remove user"
msgstr ""
-#: module/setup.py:365
+#: pyload/setup.py:351
msgid "4 - Quit"
msgstr ""
-#: module/setup.py:377
+#: pyload/setup.py:363
msgid "Users"
msgstr ""
-#: module/setup.py:406
+#: pyload/setup.py:391
msgid "Setting new configpath, current configuration will not be transfered!"
msgstr ""
-#: module/setup.py:407
+#: pyload/setup.py:392
msgid "Configpath"
msgstr ""
-#: module/setup.py:415
+#: pyload/setup.py:400
msgid "Configpath changed, setup will now close, please restart to go on."
msgstr ""
-#: module/setup.py:416
+#: pyload/setup.py:401
msgid "Press Enter to exit."
msgstr ""
-#: module/setup.py:420
+#: pyload/setup.py:405
#, python-format
msgid "Setting config path failed: %s"
msgstr ""
-#: module/setup.py:425
+#: pyload/setup.py:411
#, python-format
msgid "%s: OK"
msgstr ""
-#: module/setup.py:427
+#: pyload/setup.py:413
#, python-format
msgid "%s: missing"
msgstr ""
-#: module/setup.py:456
-msgid "[y]/n"
+#: pyload/setup.py:456
+msgid ""
+"Warning: Consider a password of 10 or more symbols if you expect to access "
+"from outside your local network (ex. internet)."
msgstr ""
-#: module/setup.py:458
-msgid "y/[n]"
+#: pyload/setup.py:457
+msgid "Password: "
msgstr ""
-#: module/setup.py:470
-msgid "Password: "
+#: pyload/setup.py:461
+#, python-format
+msgid "Password too short! Use at least %s symbols."
msgstr ""
-#: module/setup.py:475
-msgid "Password to short. Use at least 4 symbols."
+#: pyload/setup.py:464
+msgid "Password must be alphanumeric."
msgstr ""
-#: module/setup.py:481
+#: pyload/setup.py:467
msgid "Password (again): "
msgstr ""
-#: module/setup.py:488
+#: pyload/setup.py:473
msgid "Passwords did not match."
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "yes"
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "true"
msgstr ""
-#: module/setup.py:499
+#: pyload/setup.py:489
msgid "t"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "no"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "false"
msgstr ""
-#: module/setup.py:502
+#: pyload/setup.py:492
msgid "f"
msgstr ""
-#: module/setup.py:505 module/setup.py:516
+#: pyload/setup.py:495 pyload/setup.py:505
msgid "Invalid Input"
msgstr ""
diff --git a/locale/si/LC_MESSAGES/django.po b/locale/si/LC_MESSAGES/django.po
new file mode 100644
index 000000000..2fa2b0e15
--- /dev/null
+++ b/locale/si/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Sinhala\n"
+"Language: si_LK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "නඞ"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "ඎ්‍රධාන"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "ගඞනාන්තය"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "අවගංගු කරන්න"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/si/LC_MESSAGES/pyLoad.po b/locale/si/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..0aaf63cd8
--- /dev/null
+++ b/locale/si/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Sinhala\n"
+"Language: si_LK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/si/LC_MESSAGES/pyLoadCli.po b/locale/si/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..574f50aa9
--- /dev/null
+++ b/locale/si/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Sinhala\n"
+"Language: si_LK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/si/LC_MESSAGES/setup.po b/locale/si/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..3cbfa52f1
--- /dev/null
+++ b/locale/si/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Sinhala\n"
+"Language: si_LK\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/sq/LC_MESSAGES/django.po b/locale/sq/LC_MESSAGES/django.po
new file mode 100644
index 000000000..f8bfd5446
--- /dev/null
+++ b/locale/sq/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Albanian\n"
+"Language: sq_AL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/sq/LC_MESSAGES/pyLoad.po b/locale/sq/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..d9df0e4bf
--- /dev/null
+++ b/locale/sq/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Albanian\n"
+"Language: sq_AL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/sq/LC_MESSAGES/pyLoadCli.po b/locale/sq/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..80567cb30
--- /dev/null
+++ b/locale/sq/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Albanian\n"
+"Language: sq_AL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/sq/LC_MESSAGES/setup.po b/locale/sq/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..f1fb975f8
--- /dev/null
+++ b/locale/sq/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Albanian\n"
+"Language: sq_AL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/sr/LC_MESSAGES/django.mo b/locale/sr/LC_MESSAGES/django.mo
deleted file mode 100644
index 2f03721f1..000000000
--- a/locale/sr/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/django.po b/locale/sr/LC_MESSAGES/django.po
new file mode 100644
index 000000000..0083d9a21
--- /dev/null
+++ b/locale/sr/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Serbian (Cyrillic)\n"
+"Language: sr_SP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "НПвО захтев за прПверу"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "МПлОЌП Ўа прПчОтате текст са прПвере."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad рестартПваМ"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "ОскључеМП"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "УспешМП ПбављеМП"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "укључеМП"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Да лО сте сОгурМО Ўа желОте Ўа ОскључОте pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "РестартПватО везу"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "ОбрОсатО везу"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "УМесОте ОЌе пакета."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "КлОкМОте Ма ЎесМу страМу прПвере."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "ДесОла се грешка."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "ЀасцОкла је празМа"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "НеуспелП"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "НеЌа захтева за прПверу"
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "ЛПзОМке се Ме пПклапају"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "ППЎешавања сачуваМа."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "НПва фасцОкла"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Да лО сте сОгурМО Ўа желОте Ўа рестатујете pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "чекаЌ %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "АктОвМа преузОЌања"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Кућа"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "ПреузОЌања"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "ИзвештајО"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "ППЎешавање"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "ИЌе"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Статус"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "ИМфПрЌацОја"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "ВелОчОМа"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "НапреЎак"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "ПрОјава"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "КПрОсМОчкП ОЌе"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "ЛПзОМка"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "ИЌе О лПзОМка Ме ПЎгПварају. ППМПвОтО."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Да бО ресетПвалО пПЎатке прОјаве ОлО за ЎПЎавање кПрОсМОка:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "ОбрОшО завршеМП"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "РестартПвање МеуспешМП"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "ЀасцОкла:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "ЛПзОМка:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "УреЎО пакет"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "УреЎО Ўетаље пљкета."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "ИЌе пакета."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "ЀасцОкла"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "ИЌе пПЎфасцОкле за Пва преузОЌања."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "СпОсак лПзОМке за РАР."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Сачувај"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Ресет"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "УспешМП сте се ПЎјавОлО."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Путања"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "абсПлутМП"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "релатОвМП"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "ОЌе"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "велОчОМа"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "тОп"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "заЎња прПЌеМа"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "рПЎОтељска фасцОкла"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "МеЌа саЎржаја"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Опште"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "ДПЎатцО"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "НалПзО"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "ОЎабратО секцОју Оз ЌеМОја"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "ПрОкључак"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "ПреЌОјуЌ"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "ВажО ЎП"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "ПреПсталО прПтПк"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "ВреЌе"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Макс ОстПвреЌеМП"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "ОбрОшО?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "ОсправМП"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "МОје важМП"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "Ўа"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "Ме"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "ДПЎај"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "ДПЎај МалПг"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "УМПс пПЎатака за упПтребу преЌОјуЌ ЌПгућМПстО."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Ваше ОЌе:"
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "ЛПзОМка за Пвај МалПг"
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "ТОп"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "ОЎабратО хПст Вашег МалПга."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Стартуј"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "МазаЎ"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "Ўаље"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Крај"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "ВестО"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "ППЎршка"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "СОстеЌ"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "ОС:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "ВерзОја pyLoad-а:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "ЀасцОкла ОМсталацОје:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "ЀасцОкла пПЎешавања:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "ЀасцОкла преузОЌања:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "ПрПстПр:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "ЈезОк:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "ППрт вебОМтерфејса:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "ППрт уЎаљеМПг ОМтерфејса:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "ППЎешавање"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "МеМаџер ЎатПтеке"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "ДПЎај пакет"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "ПрОлепО лОМкПве ОлО пПслатО кПМтејМер."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "ИЌе МПвПг пакета."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Везе"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "ПрОлепОте везе ОлО текст О стОсМОте ЎугЌе ЀОлтер."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "ЀОлтрОрај УРЛ"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "ЛПзОМка за РАР архОве"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "ДатПтека"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "ППшаљО кПМтејМер."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "ОЎреЎОште"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "ЧОтање прПвере"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "ПрПвера"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "ПрПвера."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Текст"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "УМетО текст Оз прПвере."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "ЗатвПрО"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "ВебОМтерфејс"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "АжурОрања су ЎПступМа!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "ДПЎатцО су ажурОраМО, рестартПватО!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Чекање прПвере"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "ОЎјава"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "АЎЌОМОструј"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "ИМфП"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "ЛПгујте се!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "ЗауставО"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "ОткажО"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "ПреузЌО:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "ППМПвМа веза:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "БрзОМа:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "АктОвМП:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "ППМПвП учОтај страМу"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "учОтавање"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "НазаЎ Ма врх"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "ИскључОтО pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "РестартПватО pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "За ЎПЎавање/прПЌеМе лПзОМку упПтребОтО:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "ВажМП! АЎЌОМ кПрОсМОк увек ОЌа сва права!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "ПрПЌеМО лПзОМку"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "АЎЌОМОстратПр"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "ДПзвПле"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "прПЌеМО"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "УМетО треМутМу О жељеМу лПзОМку."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "КПрОсМОк"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "ТреМутМа лПзОМка"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "НПва лПзОМка"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "НПва лПзОМка:"
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "НПва лПзОМка (пПМПвО)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "ППМПвОтО МПву лПзОМку."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "НеЌате правП за прОступ ПвПј страМО."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "ЀасцОкла преузОЌања МОје МађеМа."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПграМОчеМП"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "МОје ЎПступМП"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "ППкреМутО pyload.py -s за прОступ пПЎешавању."
+
diff --git a/locale/sr/LC_MESSAGES/pyLoad.mo b/locale/sr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index e66d60ec6..000000000
--- a/locale/sr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/pyLoad.po b/locale/sr/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..a4d0514c0
--- /dev/null
+++ b/locale/sr/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Serbian (Cyrillic)\n"
+"Language: sr_SP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "ПрОЌљеМ је сОгМал за Озлазак"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad већ раЎО са брПјеЌ %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Не ЌПгу Ўа прПЌеМОЌ групу: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Не ЌПгу Ўа прПЌеМОЌ кПрОсМОка: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "фасцОкла за Озвештаје"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "ППкрећеЌ"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "ОсМПвМа фасцОкла: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pycrypto за ЎешОфрПвање кПМтејМера"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "фасцОкла за прОвреЌеМе ЎатПтеке"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "фасцОкла за преузОЌања"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL за сОгурМПсМе везе"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "ПреЌештање старОх пПЎешавања у базу"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "ПрПверОте ваше пПЎатке прОјављОвања са ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Све везе су уклПњеМе"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "ВреЌе преузОЌања: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "СлПбПЎМП прПстПра: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "АктОвОраЌ МалПге "
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "АктОвацОја ЎПЎатка..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad је спреЌаМ за кПрОшћење"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "пПМПвМП пПкретање pyLoad-а"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad се затвара"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "ИМсталацОја %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "Ме ЌПгу Ўа МађеЌ %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "Ме ЌПгу Ўа креОраЌ %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "ОскључујеЌ "
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "грешка прО ОскључОвању"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "убОтО pyLoad Оз терЌОМала"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "База је ОзбрОсаМП збПг МекПЌпатОбОлМе верзОје."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "База Ме ЌПже Ўа се кПМвертује"
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "База је кПМвертПваМа са v2 у v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "База је кПМвертПваМа са v3 у v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "КПМвертПвање старе Django базе"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "завршеМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "ваМ Ќреже"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "Ма ЌрежО"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "Ма чекању"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "прескПчеМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "чекаЌ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "прОвреЌеМП ваМ Ќреже"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "запПчОњеЌ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "МеуспелП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "прекОМутП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "ЎешОфрујеЌ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "прОлагПђеМП"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "преузОЌаЌ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "ПбрађујеЌ"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "МепПзМатП"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Пакет је завршеМ: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "УпПтреба SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Грешка уЎаљеМПг фОЎа: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "ППкрећеЌ %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Не ЌПгу Ўа учОтаЌ пПзаЎОМску кПЌпПМеМту %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "чекаЌ %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL сертОфОкатО МОсу прПМађеМО."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "ОЎпустОлО сЌП пПЎршку Ўа бО стартПвалО %s ЎОректМП са pyLoad"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "МПжете Ўа упПтребОте сервер са МавПјПЌ кПјО пружа ЎПбре перфПрЌаМсе О ссл,"
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "а ЌПжете О Ўа МаставОте са вашОЌ %s са pyLoads fastcgi серверПЌ"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "прОЌере пПЎешавања су у фасцОклО pyload/webui/servers"
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Не ЌПгу Ўа упПтребОЌ %(server)s, python-flup МОје ОМсталОраМ!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Грешка увПза lightweight сервера: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Требате Ўа преузЌете О кПЌпОлујете bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "КПпОрајте boern.so у фасцОклу pyload/lib ОлО упПтребОте setup.py install"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "НаравМП требате Ўа пПзМајете лОМукс О Ўа зМате какП Ўа кПМпОлујете прПграЌе"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Сервер пПстављеМ Ма МавПјеМ, збПг прПблеЌа Ма ВОМЎПз."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Овај сервер Ме пПЎржава SSL. УЌестП њега кПрОстОте угМежЎеМО."
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "СтартПвање уграђеМПг сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "СтартПвање МавПјеМПг ССЛ веб сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "СтартПвање МавПјеМПг веб сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "СтартПвање fastcgi сервера: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "СтартПвање вебсервера (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "НеЌате правП за прОступ ПвПј страМО."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "ЀасцОкла преузОЌања МОје МађеМа."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПграМОчеМП"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "МОје ЎПступМП"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "ППкреМутО pyload.py -s за прОступ пПЎешавању."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "ОЎлПЌцО преузОЌања МОсу успелО. Враћање Ма јеЎМу везу | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "ПреузОЌање је запПчетП: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "ПреузОЌање је завршеМП: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "ПрОкључку %s МеЎПстаје фуМкцОја."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "ПреузОЌање је прекОМутП: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "ПреузОЌање је пПМПвП пПкреМутП: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "ПреузОЌање је ваМ Ќреже: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "ПреузОЌање је треМутМП ваМ Ќреже: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "ПреузОЌање МОје успелП: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Не ЌПгу Ўа се пПвежеЌ са хПстПЌ ОлО је веза прекОМута. ППМПвМП пПвезОвање за 1 ЌОМут."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "ПреузОЌање је прескПчеМП: %(name)s збПг %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "ДешОфрПвање пПчелП: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "НеуспешМП ЎешОфрПвање: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "ППМављање %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Не ЌПгу Ўа преузЌеЌ пПЎатке за %(name)s | %(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr "Грешка пПкретања куке: %s"
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Не ЌПгу Ўа актОвОраЌ %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "АктОвМу ЎПЎатцО: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "НеактОвМО ЎПЎатцО: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "НеуспешМП пПМПвМП пПвезОвање: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "СкрОпт за пПМПвМП пПвезОвање МОје прПМађеМ."
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "ППкрећеЌ пПМПвМП пПвезОвање"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Не ЌПгу Ўа ОзвршОЌ скрОпт за пПМПвМП пПвезОвање."
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "ППМПвП је пПвезаМП; МПва IP аЎреса: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "НеЌа ЎПвПљМП прПстПра Ма уређају"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Не ЌПгу Ўа се прОјавОЌ са МалПгПЌ %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "НеОсправМа лПзОМка"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "ВреЌе %s ОЌа лПш фПрЌат, упПтребО: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "НалПг %s МеЌа ЎПвПљМП саПбраћаја. ПрПвераваЌ пПМПвП за 30 ЌОМута."
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "НалПг %s је ОстекаП. ПрПвераваЌ пПМПвП за сат вреЌеМа."
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "ДПстОгМутП је ПграМОчење преузОЌања"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s ОЌа МеважећО Пбразац."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Грешка увПза %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "НОјеЎаМ хПстер МОје учОтаМ"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "АктОвОрајте ЎОректМП преузОЌање у МалПгу Bitshare-а"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "LinkList Ме ЌПже Ўа буЎе ПбрОсаМ."
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "ППЎешавања МалПга ОзбрОсаМе збПг МПвОјег фПрЌата."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "ППтребМа је прПвера ОЎеМтОтета (кПрОсМОчкП ОЌе О лПзОМка)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "УМесОте МалПг Ма сервОсу %s ОлО ЎеактОвОрајте Пвај прОкључак."
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "ПрПМађеМ је HTML кÎЎ у преузетПј ЎатПтецО (%s). ПреузОЌање ће пПМПвП бОтО пПкреМутП."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "ДатПтека треМутМП МОје ЎПступМа"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: чекање ОзЌеђу преузОЌања %d с."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: чекање пПтврЎМПг кПЎа %d с."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "Преузета ЎатПтека је празМа"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API кључ је МеОсправаМ"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: МеЌате ЎПвПљМП саПбраћаја"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "ПрекПрачОлО сте саПбраћај"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Ўељење саПбраћаја (ЎОректМП преузОЌање)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Већ преузОЌате са Пве IP аЎресе. Сачекајте 60 секуМЎО."
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "НеОсправаМ кÎЎ за прПверу ОЎеМтОтета. ПреузОЌање ће пПМПвП бОтО пПкреМутП."
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "Rapidshare.com: МеЌа слПбПЎМОх слПтПва"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "ППтребаМ ваЌ је преЌОјуЌ МалПг Ўа бОсте преузелО Пву ЎатПтеку"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "ПрОјављеМП ОЌе ЎатПтеке МОје ОсправМП"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Грешка у ОстПвреЌеМПЌ преузОЌању. Сачекајте 60 секуМЎО."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "НОсте прОјављеМО."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "ДешОфрПвање МОје успелП"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "У URL аЎресО МОје МавеЎеМ кључ ЎатПтеке"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "КÎЎ грешке:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "ДатПтека Ме пПстПјО."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** ПрОкључцО су ажурОраМО. ППМПвП пПкреМОте pyLoad. ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "ПрОкључцО су ажурОраМО О пПМПвП учОтаМО"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "НеЌа ажурОрања прОкључака"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "НеЌа МПве верзОје pyLoad-а"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** ДПступМа је МПва верзОја, pyLoad %s ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** ПреузЌОте је ПЎавЎе: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "Не ЌПгу Ўа се пПвежеЌ са серверПЌ за ажурОрања"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "НПва верзОја %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "Грешка прО ажурОрању %s"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "ВерзОје се Ме пПклапају"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "ПреузОЌање је завршеМП: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "НПвО захтев пПтврЎМПг кПЎа: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "ОЎгПвПрОте са „c %s текст Ма пПтврЎМПЌ кПЎу“"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "ДПЎатП %s Оз HotFolder-а"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "НОје ОМсталОраМ %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "Не ЌПгу Ўа актОвОраЌ %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "АктОвОраМП"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "НОсу актОвОраМО прОкључцО за распакОвање"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "Пакет %s је стављеМ у реЎ за распакОвање"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "ПрПверО пакет %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Распакуј у %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "НеЌа ЎатПтека за распакОвање"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "распакујеЌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "ЗаштОћеМП лПзОМкПЌ"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "НеОсправМа лПзОМка"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "БрОшеЌ %s ЎатПтека"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "РаспакОвање је завршеМП"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Грешка у архОвО"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC се Ме пПклапа"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "НепПзМата грешка"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "ППстављање кПрОсМОка О групе МОје успелП"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: пПрт 9666 већ је у упПтребО"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "ПреПсталП је %s креЎОта"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "Не ЌПгу Ўа пПшаљеЌ ПЎгПвПр."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "У вашеЌ МалПгу Ма сервОсу CaptchaTrader МеЌа ЎПвПљМП креЎОта."
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "СпОсак шОфратПра МОје прПМађеМ"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "СпОсак шОфратПра је празаМ"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "ПреузОЌање је завршеМП: %(name)s @ %(plugin)s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "НПвО CaptchaID ПЎ ПтпреЌања: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "У вашеЌ МалПгу Ма сервОсу 9kw.eu МеЌа ЎПвПљМП креЎОта."
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "ИМсталОраМО скрОптО за %s: "
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "СкрОпта Ме ЌПже Ўа се ОзвршО:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Грешка у %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "У вашеЌ МалПгу Ма сервОсу ExpertDecoders МеЌа ЎПвПљМП креЎОта."
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "ПрвП ЎПЎајте МалПг Ма сервОсу rehost.to па пПМПвП пПкреМОте pyLoad."
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "ПреПсталП је %d креЎОта"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Pil О Tesseract МОсу ОМсталОраМО, МОтО је пПвезаМ клОјеМт раЎО ЎешОфрПвања пПтврЎМОх кПЎПва."
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "НО јеЎаМ резултат прПвере МОје прОЌљеМ у вреЌеМу ПЎ бОлП кПјег ЎПЎатка."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "ППстављање кПрОсМОка О групе МОје успелП: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "НеЌа прОкачеМПг клОјеМта за ЎешОфрПвање прПвере"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "ДПЎат пакет %(name)s са %(count)d везе(а)"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "ДПЎатП %(count)d везе у пакет #%(package)d"
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "НОсаЌ МашаП js ЌПтПр, ОМсталОратО Spidermonkey, ossp-js, pyv8 ОлО rhino"
+
diff --git a/locale/sr/LC_MESSAGES/pyLoadCli.mo b/locale/sr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 6d4d21e9d..000000000
--- a/locale/sr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/pyLoadCli.po b/locale/sr/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..21c5c2400
--- /dev/null
+++ b/locale/sr/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Serbian (Cyrillic)\n"
+"Language: sr_SP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " ИМтерфејс кПЌаМЎМе лОМОје"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s преузОЌања:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " БрзОМа: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " ВелОчОМа: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " ЗавршеМП: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr " ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "Ма чекању: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Статус:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "паузОраМП"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "пПкреМутП"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "УкупМа брзОМа"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "ДатПтеке у спОску"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "УкупМП"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "МеМО:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " ДПЎај везе"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Управљај спОскПЌ"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " Управљај сакупљачеЌ"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr " ПаузОрај/МаставО сервер"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " ОкПМчај сервер"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " ИзађО"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "КПрОстОте Пву сОМтаксу: add <ОЌе пакета> <веза> <Ўруга веза>
"
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "ПрПвера %d веза:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "ДатПтека Ме пПстПјО."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad је ПбустављеМ"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "КтаЌпа статус сервера"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "КтаЌпа преузОЌања у спОску"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "КтаЌпа преузОЌања у сакупљачу"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "ДПЎаје пакет у спОсак"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "ДПЎаје пакет у сакупљач"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "БрОше ЎатПтеке Оз спОска/сакупљача"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "БрОше пакете Оз спОска/сакупљача"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "ПреЌешта пакете Оз спОска у сакупљач О ПбрМутП"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "ППМПвП пПкреМО ЎатПтеке"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "ППМПвП пПкреМО пакете"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "ПрПверава ЎПступМПст (раЎО са лПкалМОЌ кПМтејМерПЌ)"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "ПрПверава ЎПступМПст ЎатПтеке кПМтејМера"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "ПаузОрај сервер"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "МаставО преузОЌања"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "ПаузОрај/МаставО"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "ПкПМчај сервер"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "СпОсак кПЌаМЎО:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Не ЌПгу Ўа пОшеЌ Ма ЎатПтеку са пПЎешавањОЌа"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Треба ваЌ py-openssl Ўа бО се прОвезалО Ма pyLoad језгрП."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "АЎреса: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "ППрт: "
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "КПрОсМОчкП ОЌе: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "ЛПзОМка: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "ППЎацО за прОјаву су пПгрешМО."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Не ЌПгу Ўа успПставОЌ везу са аЎресПЌ %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "ППтребаМ ваЌ је py-openssl Ўа бОсте се пПвезалО Ма сервер pyLoad-а."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "ЗаМеЌареМ је ОМтерактОвМО режОЌ јер сте заЎалО Меке кПЌаМЎе."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "ДПЎајте пакет:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "УМесОте ОЌе МПвПг пакета"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Пакет: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "РашчлаМОте везе кПје желОте Ўа ЎПЎате."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "УМесОте %s каЎа завршОте."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "ДПЎатП веза: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " МазаЎ Ма главМО ЌеМО"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Управљајте пакетОЌа:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Управљајте везаЌа:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Кта желОте Ўа преЌестОте?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Кта желОте Ўа ПбрОшете?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Кта желОте Ўа пПМПвП пПкреМете?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "ОЎабратО шта Ўа се ураЎО ОлО уМетО брПј пакета."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "ПбрОшО"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "преЌестО"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "пПМПвП пПкреМО"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " – претхПЎМП"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " – слеЎеће"
+
diff --git a/locale/sr/LC_MESSAGES/pyLoadGui.mo b/locale/sr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 5ece99c01..000000000
--- a/locale/sr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/setup.mo b/locale/sr/LC_MESSAGES/setup.mo
deleted file mode 100644
index 3ce6ecf6c..000000000
--- a/locale/sr/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sr/LC_MESSAGES/setup.po b/locale/sr/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..8363527b6
--- /dev/null
+++ b/locale/sr/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Serbian (Cyrillic)\n"
+"Language: sr_SP\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=(n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "ÐŽ"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "М"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "ДПбрП ЎПшлО кПЎ пПЌПћМОка за пПЎешавање pyLoad-а."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "ОМ ће прПверОтО сОстеЌ О МаЌестОтО ПсМПвМе пПставке такП Ўа ЌПжете Ўа пПкреМете pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "ВреЎМПст у угластОЌ заграЎаЌа је пПЎразуЌеваМа."
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "акП Ме желОте Ўа прПЌеМОте ОлО МезМате шта Ўа Озаберете, стОсМОте 'еМтер'."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "ЗМајте: ЌПжете пПМПвП Ўа пПкреМете Пвај асОстеМт са параЌетрПЌ --setup ОлО -s каЎа пПкреМет pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "АкП ОЌате прПблеЌа са асОстеМтПЌ, стОсМОте STRG-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "за пПМОштавање О МеЌПјте Ўа га пПкреМете аутПЌатскО са pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "КаЎа сте спреЌМО Ўа прПверОте сОстеЌ, стОсМОте 'еМтер'."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Треба ВаЌ pycurl, sqlite О python 2.5, 2.6 ОлО 2.7 за пПкретање pyLoad-а."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "ППправОте тП О пПМПвП пПкреМОте pyLoad."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Сетап ће се затвПрОтО."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "ПрПвера сОстеЌа је завршеМа, стОсМОте 'еМтер' Ўа бО вОЎелО резултат."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Статус ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "ЎешОфрПвање кПМтејМера"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "ссл веза"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "аутПЌатскП Малажење прПвере"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "ВебОМтерфејс"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "МапреЎаМ Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "ДПступМе фуМкцОје:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "ЀуМкцОје кПје МеЎПстају:"
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "py-crypto МОје ЎПступаМ"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "ТП ВаЌ треба акП желОте Ўа ЎешОфрујете кПМтејМере."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "ССЛ МОје ЎПступаМ"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "ППтребМП акП желОте Ўа ураЎОте сОгурМПсМу везу Ма језгрП ОлО вебОМтерфејс."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "АкП желОте Ўа прОступОте pyLoad саЌП лПкалМП, ССЛ МОје пПтребМП."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "МОје ЎПступМП Малажење прПвере"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "ППтребМП за Меке хПстере О каП бесплатаМ кПрОсМОк."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Gui МОје ЎПступМП"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "ГрафОчкО ОМтерфејс."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "МеЌа JavaScript ЌПтПр"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "ТП ВаЌ треба за Меке Click'N'Load везе. ИМсталОратО Spidermonkey, ossp-js, pyv8 ОлО rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "МПжете саЎа Ўа зауставОте О Ўа пПправОте Меке завОсМПстО акП желОте."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "НаставОтО са пПЎешавањеЌ?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "ЖелОте лО Ўа прПЌеМОте путању за пПЎешавање? ТреМутМа путања је %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "АкП кПрОстОте pyLoad Ма серверу ОлО Ма партОцОјО Ма ОМтерМПЌ флешу, ЌПже Ўа буЎе ЎПбрП Ўа прПЌеМОте."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "ПрПЌеМОтО путању за пПЎешавање?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "ЖелОте лО Ўа МаЌестОте пПЎатке за прОјаву О ПсМПвМе пПставке?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "ОвП је препПручеМП Ма првП пПкретање."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "УраЎО ПсМПсМП пПЎешавање?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "ЖелОте лО Ўа пПЎесОте SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "ППЎесОтО SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Да се кПМфОгурОше веб ОМтерфејс?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "ППЎесОтО веб ОМтерфејс?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "ППЎешавање је успешМП завршеМП."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "ПрОтОсМОте Enter Ўа Озађете О пПМПвП пПкреМете pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## ПрПвера сОстеЌа ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Ваша верзОја python је ЌМПгП МПва, упПтребОте python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Ваша верзОја python је престара, упПтребОте Ќакар python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "ВерзОја Python: У реЎу"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Ваша верзОја jinja2 %s Ўелу престара."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "МПжете Ўа МаставОте, алО акП веб ОМтерјест Ме раЎО,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "ажурОрајте ОлО ЎеОМсталОрајте га, pyLoad ОЌа ЎПвПљМП jinja2 бОблОПтеку."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS ЌПтПр"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## ОсМПвМП пПЎешавање ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "СлеЎећО пПЎацО за прОјаву важе за CLI, GUI О веб ОМтерфејс."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "КПрОсМОчкП ОЌе"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "ЈезОк"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "ЀасцОкла преузОЌања"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "МаксОЌалаМ брПј ОстПвреЌеМОх преузОЌања"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "КПрОстОтО пПМПвМП пПвезОвање?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "ЛПкацОја скрОпта за пПМПвМП пПвезОвање"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## ППЎешавање веб ОМтерфејса ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "АктОвОратО веб ОМтерфејс?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "АЎреса за прОјеЌ. АкП кПрОстОте 127.0.0.1 ОлО localhost, веб ОМтерфејс ће бОтО ЎПступаМ саЌП лПкалМП."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "АЎреса"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "ППрт"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad пружа МекПлОкП сОстеЌске пПЎршке сервера, евП краткП Пбјашњење."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "ППЎразуЌеваМ сервер, МајбПљО ОзбПр акП МезМате кПјО Ўа кПрОстОте."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Овај сервер пружа ССЛ О ЎПбра је алтерМатОва за уграђеМО."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "МПже Ўа га кПрОстО apache, lighttpd, пПтребМП је Ўа Ох пПЎесОте, штП МОје Мајлакше."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "ВрлП брза алтерМатОва пОсаМа у Њ, пПтребМП libev/лОМукс зМање."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "УзетО га ПвЎе: https://github.com/jonashaag/bjoern кПЌпОлујте га"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "О кПпОрајте bjoern.so у pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Пажња: у МекОЌ случајевОЌа уграђеМ сервер Ме раЎО, акП ОЌате прПблеЌа са веб ОМтерфејсПЌ"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "вратОте се ПвЎе О прПЌеМОте уграђеМ сервер са МавПјеМОЌ."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Сервер"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## ППЎешавање SSL-а ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "ППкреМОте Пве кПЌаМЎе Оз pyLoad фасцОкле пПЎешавања Ўа бО ураЎОлО ССЛ цертОфОкате:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "АкП сте завршОлО Ос све је у реЎу, ЌПжете саЎа Ўа актОвОрате ССЛ."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "АктОвОратО SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "ИзаберОте раЎњу"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 – креОрајте/уреЎОте кПрОсМОка"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 – ОзлОстајте кПрОсМОке"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 – уклПМОте кПрОсМОка"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 – ОзађОте"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "КПрОсМОцО"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "ППЎешавање МПве путање, актуелМа кПМфОгурацОја Меће бОтО преМета!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Путања кПМфОгурацОје"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Путања прПЌењеМа, сетај ће се затвПрОтО, пПМПвП пПкреМОте за Маставак."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "ПрОтОсМОте Enter Ўа Озађете."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Не ЌПгу Ўа пПставОЌ путању за пПЎешавање: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr "%s: У реЎу"
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: фалО"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "ЛПзОМка: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "ЛПзОМка је прекратка. УМесОте бар четОрО сОЌбПла."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "ППтврЎа лПзОМке: "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "ЛПзОМке се Ме пПклапају"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "Ўа"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "тачМП"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "т"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "Ме"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "МетачМП"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "М"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "НеОсправаМ уМПс"
+
diff --git a/locale/sv/LC_MESSAGES/django.mo b/locale/sv/LC_MESSAGES/django.mo
deleted file mode 100644
index c85b2c173..000000000
--- a/locale/sv/LC_MESSAGES/django.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/django.po b/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 000000000..8f3f05d72
--- /dev/null
+++ b/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Swedish\n"
+"Language: sv_SE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Ny captchabegÀran"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "Var god lÀs texten på captchan."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad startades om"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "Är det sÀkert att du vill avsluta pyLoad?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Starta om lÀnken"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Radera lÀnken"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "Var god skriv in ett paketnamn."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "Var god kiicka på den rÀtta captchapositionen."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Ett fel intrÀffade."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Mappen Àr tom"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Inga captchor att lÀsa."
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Lösenorden stÀmde inte överens."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "InstÀllningar sparade."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Ny mapp"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "Är det sÀkert att du vill starta om pyLoad?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "vÀntar %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktiva hÀmtningar"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Hem"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "HÀmtningar"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Loggar"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Konfiguration"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "Förlopp"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Ditt anvÀndarnamn och lösenord stÀmde inte. Försök igen."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "För att återstÀlla dina loginuppgifter eller lÀgga till en anvÀndare, kör:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Radera fÀrdiga"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Omstart misslyckades"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Mapp:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Lösenord:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Redigera paket"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Redigera paketdetaljerna hÀr."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Namnet på paketet."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Mappnamn för dessa nedladdningar."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Lösenordslista som anvÀnds vid uppackning."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "ÅterstÀll"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Du har loggats ut."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "SökvÀg"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "absolut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "relativ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "namn"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "storlek"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "typ"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "senast Àndrad"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "förÀldrakatalog"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "inget innehåll"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "TillÀggsprogram"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "VÀlj ett avsnitt från menyn"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Giltigt till"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Trafik kvar"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Tid"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Max samtidiga"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Ta bort?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "inte giltigt"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "ja"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "nej"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Skriv in dina kontouppgifter för att anvÀnda premiumfunktioner."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Ditt anvÀndarnamn."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Lösenord för detta konto."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Starta"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "föreg"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "nÀsta"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Slut"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Nyheter"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad-version:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Installationsmapp:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Konfigurationsmapp:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "HÀmtningsmapp:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Ledigt utrymme:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Språk:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Port för webbgrÀnssnitt:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Port för fjÀrrgrÀnssnitt:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Filhanterare"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "LÀgg till paket"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Klistra in lÀnkar eller ladda upp en container."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Namnet på det nya paketet."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "LÀnkar"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Klistra in dina lÀnkar eller valfri text och tryck filterknappen."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Filtrera lÀnkar"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "Lösenord för RAR-arkiv"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Fil"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Ladda upp en container."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "LÀser captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha"
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Skriv in texten på captchan."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "WebbgrÀnssnitt"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad uppdatering tillgÀnglig!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Plugins uppdaterade, starta om!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha vÀntar"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Logga ut"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Administrera"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "Logga in!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Stoppa"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "HÀmta:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Återanslut:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Hastighet:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktiv:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Uppdatera sidan"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "lÀser in"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Tillbaka upp"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "Avsluta pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "Starta om pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "För att lÀgga till anvÀndare eller byta lösenord anvÀnd:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Viktigt: AdminanvÀndare har alltid alla behörigheter!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Byt lösenord"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "Behörigheter"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "Àndra"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Ange ditt nuvarande och önskade lösenord."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "AnvÀndare"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Nuvarande lösenord"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Nytt lösenord"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Det nya lösenordet."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Nytt lösenord (upprepa)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "Upprepa det nya lösenordet."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Du har inte behörighet att komma åt denna sida."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "HÀmtningskatalogen hittades inte."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "inte tillgÀnglig"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Kör pyload.py -s för att komma åt konfigurationen."
+
diff --git a/locale/sv/LC_MESSAGES/pyLoad.mo b/locale/sv/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 29d93d585..000000000
--- a/locale/sv/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/pyLoad.po b/locale/sv/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..48292bdec
--- /dev/null
+++ b/locale/sv/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Swedish\n"
+"Language: sv_SE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Tog emot avslutningssignal"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad körs redan med pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Misslyckades att Àndra gruppen %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Kunde inte Àndra anvÀndaren: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "mapp för loggar"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Startar"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "AnvÀnder hemkatalog: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "pyCrypto avkodar containerfiler"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "mapp för temporÀra filer"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "mapp för hÀmtningar"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "OpenSSL för sÀker anslutning"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Flyttar gamla anvÀndarinstÀllningar till databasen"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Kontrollera dina login-uppgifter med './pyload.py -u'"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "Alla lÀnkar togs bort"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "HÀmtningstid: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Ledigt utrymme: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Aktiverar konton..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Aktiverar tillÀggsprogram..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad Àr igång"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "startar om pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyLoad avslutas"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "Installera %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "kunde inte hitta %(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "kunde inte skapa %(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "stÀnger ner..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "fel vid nedstÀngning"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr "dödade pyLoad från Terminal"
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr "Fildatabasen togs bort på grund av inkompatibel version."
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr "Fildatabasen kunde INTE konverteras."
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr "Databasen konverterades från v2 till v3."
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr "Databasen konverterades från v3 till v4."
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr "Konverterar gammal Django DB"
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "fÀrdig"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "frånkopplad"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "ansluten"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "kölagd"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "hoppades över"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "vÀntar"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "temp. frånkopplad"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "startar"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "misslyckades"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "avbruten"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "dekrypterar"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "anpassad"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "hÀmtar"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "behandlar"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "okÀnd"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paketet klart: %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr "AnvÀnder SSL ThriftBackend"
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Fel i fjÀrradministrationsmodulen: %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Startar %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "InlÀsningen av administrationsmodulen %(name)s misslyckades | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "vÀntar %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL-certifikat hittades inte."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Ledsen, start av %s direkt från pyLoad stöds inte lÀngre"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr "Du kan "
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr "naturligtvis kan du fortfarande anvÀda befintliga %s med pyLoads fastcgi server"
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr "exempel på konfigurationsfiler finns i mappen \"pyload/webui/servers\""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr "Kan inte anvÀnda %(server)s. python-flup Àr inte installerat!"
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr "Fel vid import av den lÀtta servern: %s"
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "Du måste ladda ner och kompilera bjoern, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr "Kopiera filen boern.so till mappen 'pyload/lib' eller anvÀnd 'setup.py install'"
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr "Naturligtvis behöver du vara bekant med Linux och veta hur man kompilerar programvara"
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr "Server instÀlld på trådad, pga kÀnda prestandaproblem i Windows."
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Den hÀr servern anvÀnder inte SSL, övervÀg att anvÀnda den trådade istÀllet "
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Startar inbyggd webbserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr "Startar trådad SSL-webbserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr "Startar trådad webbserver: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr "Startar fastcgi-server: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr "Startar den lÀtta webservern (bjoern): %(host)s:%(port)d"
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Du har inte behörighet att komma åt denna sida."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "HÀmtningskatalogen hittades inte."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "inte tillgÀnglig"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Kör pyload.py -s för att komma åt konfigurationen."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "HÀmtningen startar: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "HÀmtningen klar: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Plugin %s saknar en funktion."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "HÀmtningen avbröts: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "HÀmtning startades om: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "HÀmtningen Àr offline: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "HÀmtningen Àr tillfÀlligt offline: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "HÀmtning misslyckades: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "HÀmtning hoppades över: %(name)s pga %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr "Avkrypteringen startar: %s"
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr "HÀmtning misslyckades: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr "Försöker igen %s"
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Misslyckades aktivera %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Aktiverade plugins: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Avaktivera plugins: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Återanslutningen misslyckades: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Skript för att återansluta hittades inte!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Startar reconnect"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Exekveringen av återanslutningsskriptet misslyckades!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Återansluten, nytt IP-nummer: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "OtillrÀckligt utrymme på enheten"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr "Din tid %s har ett felaktigt format. AnvÀnd: 1:22-3:44"
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr "%s innehåller ett ogiltigt mönster."
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Fel vid import av %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr "lÀnklistan kunde inte raderas"
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr "KontoinstÀllningar raderade pga nytt format för konfigureringen."
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr "Filen finns inte."
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Aktiverat"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr "Ingen plugin returnerade captcharesultat i tid."
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr "Ingen klient för avkryptering av captcha ansluten"
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr "Lade till paketet %(name)s som innehåller %(count)d lÀnkar"
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr "Lade till %(count)d lÀnkar till paket #%(package)d "
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Ingen js motor hittades. Var vÀnlig installera Spidermonkey, ossp-js, pyv8 eller rhino"
+
diff --git a/locale/sv/LC_MESSAGES/pyLoadCli.mo b/locale/sv/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index f0c2c04bd..000000000
--- a/locale/sv/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/pyLoadCli.po b/locale/sv/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..18b3a4009
--- /dev/null
+++ b/locale/sv/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Swedish\n"
+"Language: sv_SE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " KommandoradsgrÀnssnitt"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s hÀmtningar:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Hastighet: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " Storlek: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " FÀrdig om: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "vÀntar: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Meny:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " LÀgg till lÀnkar"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " Hantera kö"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "Hantera lÀnksamlaren"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "Pausa/starta servern"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " Döda server"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " Avsluta"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "AnvÀnd denna syntax: add <paketnamn> <lÀnk> <lÀnk2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Kontrollerar %d lÀnkar:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Filen finns inte."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad avbröts"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Visar serverstatus"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Visar nedladdningar i kön"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Visar nedladdningar i lÀnksamlaren"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "LÀgger till paketet i kön"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "LÀgger till paketet i lÀnksamlaren"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Radera filer från kön/lÀnksamlaren"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Radera paket från kön/lÀnksamlaren"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Flytta paket från kön till lÀnksamlaren och vice versa"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Starta om filer"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Starta om paket"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Undersök onlinestatus, fungerar med lokal container"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Kontrollerar onlinestatusen för en containerfil"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Pausa servern"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "återuppta hÀmtningar"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Pausa/fortsÀtt"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "döda server"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Lista över kommandon:"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Kunde inte skriva anvÀndarens instÀllningsfil"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "Du behöver py-openssl för att ansluta till denna pyLoad Core."
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adress: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "AnvÀndarnamn: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Lösenord: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Inloggningsdata Àr felaktigt."
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "Kunde inte etablera anslutning till %(addr)s:%(port)s."
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "Du behöver py-openssl för att ansluta till denna pyLoad core."
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Interaktivt lÀge ignorerat då vissa kommandon skickades."
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "LÀgg till paket:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Ange ett namn för nya paketet"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paket: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Klistra in lÀnkarna som du vill lÀgga till."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Skriv %s nÀr du Àr fÀrdig."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "LÀnkar lades till: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr " tillbaka till huvudmenyn"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Hantera paket:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Hantera lÀnkar:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Vad vill du flytta?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Vad vill du ta bort?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Vad vill du starta om?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "VÀlj vad du vill göra eller skriv in ett paketnummer."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "ta bort"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "flytta"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "starta om"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - föregående"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr " - nÀsta"
+
diff --git a/locale/sv/LC_MESSAGES/pyLoadGui.mo b/locale/sv/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 4231a1bbf..000000000
--- a/locale/sv/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/setup.mo b/locale/sv/LC_MESSAGES/setup.mo
deleted file mode 100644
index 2cf2efd70..000000000
--- a/locale/sv/LC_MESSAGES/setup.mo
+++ /dev/null
Binary files differ
diff --git a/locale/sv/LC_MESSAGES/setup.po b/locale/sv/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..36013181c
--- /dev/null
+++ b/locale/sv/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Swedish\n"
+"Language: sv_SE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "j"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "VÀlkommen till konfigurationsguiden för pyLoad."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Den kommer att kontrollera ditt system och göra en grundkonfiguration för att kunna köra pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "VÀrden inom hakparanteser [] Àr alltid standardvÀrdet,"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "tryck bara Enter i de fall du inte vill Àndra det eller om du Àr osÀker vad du ska vÀlja."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Glöm inte: Du kan alltid köra denna guide igen med --setup eller parametern -s, nÀr du startar pyload.py ."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Om du får några problem med denna guide så tryck Ctrl-C,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "för att avbryta och låt bli att starta automatiskt med pyload.py ."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Tryck på Enter nÀr du Àr fÀrdig för att kontrollera systemet."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "Du behöver pycurl, sqlite och python 2.5, 2.6 eller 2.7 för att köra pyLoad."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "Korrigera detta och kör pyLoad igen."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Konfigurationen kommer nu att stÀngas."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Systemkontrollen Àr fÀrdig. Tryck på Enter för att se din statusrapport."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr "container-dekryptering"
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "ssl-anslutning"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr "automatisk captcha-dekryptering"
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "Grafiskt grÀnssnitt"
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "WebbgrÀnssnitt"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "utökad Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Funktioner tillgÀngliga:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Funktioner saknade: "
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr "ingen py-crypto tillgÀnglig"
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr "Du behöver denna om du vill dekryptera container-filer."
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "ingen SSL tillgÀnglig"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Denna krÀvs om du vill etablera en sÀker anslutning till core eller webbgrÀnssnittet."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "Om du endast vill ha lokalt åtkomst till pyLoad så Àr inte SSL anvÀndbart."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr "ingen Captcha Recognition tillgÀnglig"
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr "Behövs endast för vissa \"hosters\" och som \"freeuser\"."
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "GUI inte tillgÀngligt"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Grafiskt anvÀndargrÀnssnitt."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "ingen JavaScript-motor hittades"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Du behöver detta för några Click'N'Load-lÀnkar. Installera Spidermonkey, ossp-js, pyv8 eller rhino"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Du kan avbryta konfigurationen nu och rÀtta till beroenden om du vill."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "FortsÀtt med konfigurationen?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Vill du Àndra konfigurationssökvÀgen? Aktuell sökvÀg Àr %s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr "Om du anvÀnder pyLoad på en server eller hempartitionen finns på en intern Flash-disk så kan det vara en bra idé att Àndra den."
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Ändra konfigurationssökvÀg?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Vill du konfigurera inloggningsdata och grundinstÀllningar?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Detta rekommenderas för första gången."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Skapa grundkonfiguration?"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Vill du konfigurera SSL?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Konfigurera SSL?"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Vill du konfigurera webbgrÀnssnittet?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Konfigurera webbgrÀnssnittet?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Konfigurationen fÀrdigstÀlldes."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Tryck Enter för att avsluta och starta om pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Systemkontroll ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Din python-version Àr för ny. AnvÀnd Python 2.6/2.7"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Din python-version Àr för gammal. AnvÀnd minst Python 2.5"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python-version: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "Din installerade jinja2 version %s verkar vara för gammal."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr "Du kan med sÀkerhet fortsÀtta men om webbgrÀnssnittet inte fungerar,"
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr "uppgradera eller avinstallera det. pyLoad inkluderar ett lÀmpligt jinja2-bibliotek."
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS-motor"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Grundkonfiguration ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Följande inloggningsdata Àr giltigt för CLI, GUI och webbgrÀnssnitt."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr "Externa klienter (GUI, CLI osv) behöver fjÀrråtkomst för att fungera över nÀtverket."
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr "Men om du bara vill anvÀnda webbgrÀnssnittet kan du avaktivera den för att spara RAM."
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Aktivera fjÀrråtkomst"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "HÀmtningsmapp"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Max samtidiga hÀmtningar"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "AnvÀnd återanslutning (Reconnect)?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Plats för Reconnect-skript"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Konfiguration av webbgrÀnssnitt ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Aktivera webbgrÀnssnitt?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Lyssningsadress, om du anvÀnder 127.0.0.1 eller localhost, kommer webbgrÀnssnittet endast vara åtkomligt lokalt."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad erbjuder flera server backends. HÀr följer en kort förklaring."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr "Standardserver, bÀsta valet om du inte vet vilken du ska vÀlja."
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr "Denna server erbjuder SSL och Àr ett bra alternativ till den inbyggda."
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Kan anvÀndas med apache, lighttpd. KrÀver att du konfigurera dem, vilket inte Àr så svårt."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr "Mycket snabbt alternativ skrivet i C. KrÀver libev och Linux-kunskaper."
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "HÀmta det hÀr: https://github.com/jonashaag/bjoern, kompilera det"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr "och kopiera bjoern.so till pyload/lib"
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Viktigt: I vissa ovanliga fall kanske den inbyggda servern inte fungerar. Om du upplever problem med webbgrÀnssnittet"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "så kom tillbaka hit och Àndra den inbyggda servern till den trådade servern."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## SSL-konfiguration ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Kör dessa kommandon från konfigurationsmappen för pyLoad för att skapa SSL-certifikaten:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Du kan aktivera SSL nu om du Àr fÀrdig och allting gick bra."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "Aktivera SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "VÀlj åtgÀrd"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Skapa/Redigera anvÀndare"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Lista anvÀndare"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Ta bort anvÀndare"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Avsluta"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "AnvÀndare"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "StÀller in ny konfigurationssökvÀg. Aktuell konfiguration kommer inte att överföras!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "KonfigurationssökvÀg"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "KonfigurationssökvÀgen har Àndrats. Guiden kommer nu att stÀngas. Starta om för att fortsÀtta."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Tryck Enter för att avsluta."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "InstÀllning av konfigurationssökvÀg misslyckades: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: saknas"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Lösenord: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Lösenord (igen): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Lösenorden stÀmde inte överens."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "ja"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "sant"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "s"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "nej"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "falskt"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Ogiltig inmatning"
+
diff --git a/locale/te/LC_MESSAGES/django.po b/locale/te/LC_MESSAGES/django.po
new file mode 100644
index 000000000..e70361f00
--- /dev/null
+++ b/locale/te/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Telugu\n"
+"Language: te_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr ""
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr ""
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/te/LC_MESSAGES/pyLoad.po b/locale/te/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..fd50b53a0
--- /dev/null
+++ b/locale/te/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Telugu\n"
+"Language: te_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/te/LC_MESSAGES/pyLoadCli.po b/locale/te/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..88558b540
--- /dev/null
+++ b/locale/te/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Telugu\n"
+"Language: te_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr ""
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr ""
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr ""
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr ""
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr ""
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr ""
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr ""
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr ""
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr ""
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr ""
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr ""
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr ""
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/te/LC_MESSAGES/setup.po b/locale/te/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..0249422a8
--- /dev/null
+++ b/locale/te/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 06:29-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Telugu\n"
+"Language: te_IN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr ""
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr ""
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr ""
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr ""
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr ""
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 000000000..63e227a8d
--- /dev/null
+++ b/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "Yeni Captcha İsteği"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "LÃŒtfen captcha ÃŒstÃŒndeki metni okuyunuz."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad yeniden başlatıldı"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "Kapat"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "Başarılı"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "Aç"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "pyLoad'dan çıkmak istediğinize emin misiniz?"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "Bağlantıyı yeniden başlat"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "Bağlantıyı Sil"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "LÌtfen paket adını girin."
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "LÃŒtfen sağdaki captcha ya tıklatın."
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "Bir hata oluştu."
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "Klasör boş"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "Başarısız oldu"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "Okumak için Captcha bulunamadı"
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "Parolalar eşleşmedi."
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "Ayarlar kaydedildi."
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "Yeni klasör"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "pyLoad'u yeniden başlatmak istediğinize emin misiniz?"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "bekleyen %s"
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "Aktif İndirmeler"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "Ev"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "İndirmeler"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "Loglar "
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "Ayarlar"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "İsim"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Durum"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "Bilgi"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Boyut"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "İlerleme"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Oturum açma"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Kullanıcı Adı"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Şifre"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "Kullanıcı adınız ve parolanız uymuyor. LÌtfen tekrar deneyiniz."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "Hesabınızı sıfırlamak için veya yeni kullanıcı eklemek için çalıştırın:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "Bitenleri Temizle"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "Başarısızları Tekrar Başlat"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "Klasör:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "Şifre:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "Paketi DÃŒzenle"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "Aşağıdaki paket detaylarını dÃŒzenle."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "Paketin adı."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Klasör"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "Bu indirmeler için alt klasör adı."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "Unrar için parola listesi."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Gönder"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "Sıfırla"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "Başarılı şekilde çıkış yaptınız."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "Yol"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "tam"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "Bağıntılı"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "isim"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "boyut"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "tip"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "son değişimi"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "ana klasör"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "içerik yok"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Genel"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "Eklentiler"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Hesaplar"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "MenÌden bir bölÌm seçiniz"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Eklenti"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "Son geçerlilik tarihi"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "Kalan trafik"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "Zaman"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "Maks Paralel"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "Sil?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "geçerli"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "geçersiz"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "evet"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "hayır"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Ekle"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "Hesap Ekle"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "Premium özelliklerini kullanabilmek için hesap bilgilerini giriniz."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "Kullanıcı adınız."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "Bu hesap için parola."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "TÌrÌ"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "Hesabınız için host seçiniz."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "Başla"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "önceki"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "sonraki"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "Son"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "Haberler"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "Destek"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "Sistem"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad versiyonu:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "Kurulum KlasörÌ:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "Ayar KlasörÌ:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "İndirme KlasörÌ:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "Boş Alan:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "Dil:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Web ArayÃŒz Portu:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "Uzak ArayÃŒz Portu:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "Kurulum"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "Dosya Yöneticisi"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "Paket Ekle"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "Linklerinizi yapıştırın veya taşıyıcı dosyayı yÃŒkleyin."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "Yeni paketin adı."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "Linkler"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "Linklerinizi veya herhangi bir metni yapıştırıp filitrele butonuna basınız."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "Linkleri filitrele"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "RAR-Arşiv dosyaları için parola"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "Dosya"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "Taşıyıcı dosyayı yÃŒkleyin."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Hedef"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "Captcha okunuyor"
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "Captcha."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "Metin"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "Captcha ÃŒstÃŒndeki metni giriniz."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Kapat"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Web arayÌzÌ"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad GÃŒncelleme mevcut!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "Eklentiler gÃŒncellendi, lÃŒtfen yeniden başlatın !"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "Captcha bekliyor"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "Çıkış"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "Yönet"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "Bilgi"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "LÃŒtfen Giriş Yapın!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "Dur"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "İptal"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "İndirme:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "Yeniden bağlan:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "Hız:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "Aktif:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "Sayfayı yenile"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "yÃŒkleniyor"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "Sayfa başı"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "pyLoad'dan çık"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "pyLoad'ı yeniden başlat"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "Kullanıcı eklemek veya şifre değiştirmek:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "Önemli: Yönetici kullanıcısının her zaman tÃŒm izinleri vardır!"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "Parolayı Değiştir"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "İzinleri"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "değiştir"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "Şimdiki parolanızı ve değiştirmek istediğiniz parolayı giriniz."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "Kullanıcı"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "Parolanız"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "Yeni parola"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "Yeni parola."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "Yeni parola (tekrar)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "LÌtfen yeni parolanızı tekrar giriniz."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Bu sayfaya erişmek için izniniz yok."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "İndirme dizini bulunamadı."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "sınırsız"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "mevcut değil"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Ayarlara erişmek için pyload.py -s çalıştırın."
+
diff --git a/locale/tr/LC_MESSAGES/pyLoad.mo b/locale/tr/LC_MESSAGES/pyLoad.mo
deleted file mode 100644
index 2c20cfbd4..000000000
--- a/locale/tr/LC_MESSAGES/pyLoad.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/pyLoad.po b/locale/tr/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..ea9d3755c
--- /dev/null
+++ b/locale/tr/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "Çıkış sinyali alındı"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad zaten %s pid ile çalışıyor"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "Grup değiştirme başarısız: %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "Kullanıcı değiştirme başarısız: %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "gÌnlÌkler için klasör"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "Başlıyor"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "Ev dizinini kullan: %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "geçici dosyalar için klasör"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "indirmeler için klasör"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "GÃŒvenli bağlantı için OpenSSL"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "Eski kullanıcı ayarlarını veritabanına taşı"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "Giriş bilgilerinizi kontrol edin ./pyload.py -u"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "TÃŒm bağlantılar kaldırıldı"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "İndirme sÌresi: %s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "Boş alan: %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "Hesap etkinleştiriliyor ..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "Eklentiler etkinleştiriliyor ..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad başlatıldı ve çalışıyor"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "pyLoad yeniden başlatılıyor"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "pyload çıkış"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "kapatılıyor ..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "kapatma sırasında hata oluştu"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "bitti"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "Çevrimdışı"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "Çevrimiçi"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "sıraya alındı"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "atlandı"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "bekliyor"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "geçici çevrimdışı"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "başlatılıyor"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "başarısız oldu"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "iptal edildi"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "çözÌlÌyor"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "Özel"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "indiriliyor"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "işlem devam ediyor"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "bilinmeyen"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "Paket tamamlandı. %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "Uzak uç hatası: % s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "Başlıyor %(name)s: %(addr)s:%(port)s"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "Arka uç yÌklenirken hata %(name)s | %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr "bekleyen %s"
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL sertifikaları bulunamadı."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr "Maalesef pyLoad içinde doğrudan başlangıç​​ %s için destek dÌştÃŒ"
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr "bjoern'i indirmek ve derlemek gerekir, https://github.com/jonashaag/bjoern"
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "Bu sunucu hiçbir SSL sunmuyor, kullanarak yerine dişli dÌşÌnÃŒn lÃŒtfen"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr "Yerleşik web sunucusu başlatılıyor: %(host)s:%(port)d"
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr "Bu sayfaya erişmek için izniniz yok."
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr "İndirme dizini bulunamadı."
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "sınırsız"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr "mevcut değil"
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr "Ayarlara erişmek için pyload.py -s çalıştırın."
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "Karşıdan yÃŒkleme başarısız, tekli bağlantıya dönÃŒn | %s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "YÃŒkleme başlar: %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "YÃŒkleme bitti: %s"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "Eklenti %s işlevi eksik."
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "YÃŒkleme iptal edildi: %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "YÃŒkleme yeniden başlatıldı: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "YÃŒkleme çevrimdışı: %s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "YÃŒkleme geçici olarak çevrımdışı: %s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "YÃŒkleme başarısız: %(name)s | %(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "Sunucuya bağlanılamadı veya bağlantı sıfırlandı, 1 dakikalık erteleme bekleniyor."
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "YÌkleme atlandı: %(name)s bunun yÌzÌnden %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "Bilgisi %(name)s alınırken hata | %(err)s oluştu"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "Aktive etme başarısız %(name)s"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr "Eklentiler etkinleştiriliyor: %s"
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr "Eklentiler kapatılıyor: %s"
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "Tekrar bağlanma başarısız: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "Tekrar bağlanma komutu bulunamadı!"
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "Tekrar bağlanma başlatılıyor"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "Yeniden Bağlantı komut dosyası yÃŒrÃŒtmesi başarısız!"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "Yeniden Bağlandı, yeni IP: %s"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "Aygıtta yeterli alan yok"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "Hesap ile giriş yapılamadı %(user)s | %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "Hatalı Şifre"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "Hesap %s yeterli trafik yok, 30 dk içinde yeniden deneyin"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "Hesap %s sÌresi doldu, 1 saat içinde yeniden deneyin"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "İndirme sınırına erişildi"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "Alınırken hata %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "Hiçbir Sunucu yÌklenemedi"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "Bitshare hesabınızdaki direk yÃŒklemeyi aktifleştirin"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "Yetkilendirme gerekli (kullanıcı adı: şifre)"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "LÃŒtfen %s hesabınıza girin veya bu eklentiyi devre dışı bırakın"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "İndirilen dosyadaki (%s)... HTML kodunda yönlendirme hatası? Download yeniden başlatıldı."
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "Dosya geçici olarak kullanılamıyor"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr "Netload: YÌklemeler %d s arasında bekliyor."
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr "Netload: captcha için bekleniyor %d s."
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "İndirilen dosya boş"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API anahtarı geçersiz"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: yeterli trafik kalmadı"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "Trafik aşıldı"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr "Rapidshare: Trafik Paylaşılan (direk yÃŒkleme)"
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "Zaten bu IP adresinden yÌkleme yapılmakta, 60 saniye bekleniyor"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "Geçersiz kimlik doğrulama kodu, yÃŒkleme yeniden başlatılacak"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr "RapidShareCom: TÃŒm ÃŒcretsiz bağlantılar doldu"
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "Bu dosya için premium Ìyelik hesabı gerekir"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "Dosya adı geçersiz bildirdi"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "Paralel yÌkleme hatası, 60 saniye bekleniyor."
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "giriş yapılmadı."
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "ÇözÃŒmleme başarısız oldu"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "URL'de sağlanan hiçbir dosya anahtarı yok"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "Hata kodu:"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "*** Eklentiler gÃŒncellendi, pyLoad'ı yeniden başlatın ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "Eklentiler gÃŒncellendi ve yeniden yÃŒklendi"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "Eklentiler için gÌncelleme yok"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "pyLoad için gÌncelleme yok"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "*** Yeni pyLoad SÌrÌmÌ %s mevcuttur ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** Buradan indirin: http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "GÃŒncelleştirmeler için sunucuya bağlanmak mÃŒmkÃŒn değil"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "Yeni versiyonu %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "%s gÃŒncelleştirme hatası"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "SÃŒrÃŒm uyuşmazlığı"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "YÌkleme bitti: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "Yeni Captcha İsteği: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "captcha ÃŒstÃŒndeki 'c %s metnini cevapla"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "LÃŒtfen önce premium.to hesabınızı ekleyin ve pyLoad'u yeniden başlatın"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "HotFolder gelen ek %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "YÌklÌ %s yok"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "%s etkinleştirilemedi"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "Etkinleştirildi"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "Aktif Extract eklentisi yok"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "%s paketini daha sonra ayıklamak için sıraya alındı"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "%s paketi kontrol et"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "Arşiv ayıklanıyor şuraya %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "Arşivden ayıklanacak dosya bulunamadı"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "Ayıklanıyor"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "Şifre korumalı"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "Hatalı şifre"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "%s Dosya siliniyor"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "Ayıklama bitti"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "Arşiv hatası"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC Uyuşmazlığı"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "Bilinmeyen Hata"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "Kullanıcı ve Grup Ayarları başarısız oldu"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: Port 9666 zaten kullanılıyor"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "%s kredi kaldı"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "yanıt gönderilemedi."
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "Sizin CaptchaTrader hesabınızda yeterli kredi bulunmamakta"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "Şifreleme listesi bulunamadı"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Şifreleme listesi boş"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "YÌkleme bitti: %(name)s @ %(plugin)s "
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "Yeni CaptchaID yÌkleme tarafından: %s : %s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "Captcha 9kw.eu hesabı yeterli kredi yok"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "%s: için yÌklÌ komut dosyaları"
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "Komut dosya çalıştırılamaz:"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "Hata %(script)s: %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "ExpertDecoders hesabınızda yeterli kredi yok"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "LÃŒtfen önce rehost.to hesabınızı ekleyin ve pyLoad'u yeniden başlatın"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "LÃŒtfen önce bir geçerli premiumize.me hesabı ekleyin ve pyLoad yeniden başlatın."
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "%d kredi kaldı"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "Captcha şifresini çözmek için pil ve tesseract yÃŒklenmemiş ve hiçbir istemci bağlı değil"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "Kullanıcı ve grup kurma başarısız oldu: %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/tr/LC_MESSAGES/pyLoadCli.mo b/locale/tr/LC_MESSAGES/pyLoadCli.mo
deleted file mode 100644
index 30e94695b..000000000
--- a/locale/tr/LC_MESSAGES/pyLoadCli.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/pyLoadCli.po b/locale/tr/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..923de377c
--- /dev/null
+++ b/locale/tr/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " Komut satır arayÌzÌ"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s İndirmeler:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " Hız: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " BÃŒyÃŒklÃŒk: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " Bitiş zamanı: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "bekleniyor: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "Durum"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "Duraklatıldı"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "çalışıyor"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "toplam hız"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "Sıradaki Dosyalar"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "Toplam"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "MenÌ:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " Linkleri Ekle"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "Kuyruğu DÃŒzenle"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "KolektörÌ DÌzenle "
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "(Un)Sunucuyu Durdur"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "Sunucuyu Devredışı bırak"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "Çıkış"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "LÃŒtfen doğru eşleştirme metnini kullanın:Ekle <Paket adı> <link> <linkl2>"
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "%d Linkler Kontrol ediliyor"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Dosya bulunamadı"
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad durdu"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "Sunucu durumunu yazdır"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "Kuyruktakileri yazdır"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "Kolektördekileri yazdır"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "Kuyruğa paket ekle"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "Kolektöre paket ekle"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "Dosyaları Kuyruktan/Kolektör'den sil"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "Paketleri Kuyruktan/Kolektör'den sil"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "Paketleri Kuyruktan , Kolektöre taşı veya tam tersini yap"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Dosyaları yenile"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "Paketleri yenile"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "Yerel konteynerla çalıştıgını ve online olup olmadığını kontrol et"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "Konteyner dosyalarının çemrimiçi oldugunu kontrol et"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "Sunucuyu durdur"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "indirmelere devam et"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "Seçilenleri durdur/devam ettir"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "sunucuyu durdur yap"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "Komut listesi"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "Kullanıcı ayar dosyası yazılamadı"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "pyLoad çekirdeğine bağlanmak için py-opensll ye baglanmanız lazım"
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "Adres: "
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Port:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Kullanıcı adı: "
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Şifre: "
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "Giriş bilgileri yanlış"
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "%(addr)s:%(port)s. ile bağlantı kurulamadı"
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "pyLoad çekirdeğine bağlanmak için py-opensll ye baglanmanız lazım"
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "Bazı komutları onayladınızdan beri , Çevrimdışı modu engellendi"
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "Paket ekle:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "Yeni paketin adını girin"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Paket: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Eklemek isteniz linkleri çözÌmle"
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Tip %s tamamlandı"
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Eklenen linkler: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "MenÌye geri dön"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "Paketleri DÃŒzenle:"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Linkleri DÃŒzenle:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "Neleri taşımak istersiniz?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "Neleri silmek istersiniz?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "Neleri yenilemek istersiniz?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr "Ne yapmak istediniz seçin veya paket numarası girin."
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "sil"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "taşı"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "yeniden başlat "
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - önceki"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "-Sonraki"
+
diff --git a/locale/tr/LC_MESSAGES/pyLoadGui.mo b/locale/tr/LC_MESSAGES/pyLoadGui.mo
deleted file mode 100644
index 1ae24953f..000000000
--- a/locale/tr/LC_MESSAGES/pyLoadGui.mo
+++ /dev/null
Binary files differ
diff --git a/locale/tr/LC_MESSAGES/setup.po b/locale/tr/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..ddc5a5de8
--- /dev/null
+++ b/locale/tr/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Turkish\n"
+"Language: tr_TR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "e"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "h"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "PyLoad ayar sihirbazına hoşgeldiniz."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Sisteminizi kontrol edecek ve pyLoad'ın çalışması için bir temel kurulum yapacaktır."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "Parantez içindeki değerler [], her zaman varsayılan değerdir"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "değiştirmek istemediğinde yada neyi seçeceğinden emin değilsen, sadece enter'e bas."
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "Unutma: pyload.py -setup yada -s ekleyerek bu Asistanı her zaman yeniden başlatabilirsiniz."
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "Asistan ile herhangi bir sorun yaşarsanız STRG-C tuşuna basın,"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "iptal etmek ve pyload.py ile otomatik başlatmamak için."
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "Eğer sistem kontrolÃŒ için hazırsanız, enter'a basın."
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "pyLoad'ı çalıştırmak için pycurl, sqlite ve python 2.5, 2.6 veya 2.7 gerekir."
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "LÃŒtfen bunu dÃŒzeltin ve pyLoad'ı yeniden çalıştırın."
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "Kur şimdi kapanacak."
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "Sistem kontrolÃŒ tamamlandı, durum raporunu görmek için Enter tuşuna basın."
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "## Durum ##"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr "ssl bağlantısı"
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr "ArayÃŒz"
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Web arayÌzÌ"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr "genişletilmiş Click'N'Load"
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr "Mevcut Özellikler:"
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr "Eksik Özellikler:"
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr "SSL bulunmamaktadır"
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr "Sisteme yada Web arayÃŒzÃŒne gÃŒvenli bağlantı için bu gerekli."
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr "pyLoad'a yanlızca yerel erişmek istiyorsanız, ssl önerilmez."
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr "Kullanıcı ArayÃŒzÃŒ mevcut değil"
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr "Grafiksel Kullanıcı ArayÌzÌ."
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr "JavaScript motoru bulunamadı"
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr "Bazı Click'N'Load bağlantıları için gerekli. Spidermonkey, ossp-js, pyv8 yada rhino yÃŒkleyin"
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr "Şuan kurulum iptal edebilir ve gerekiyorsa bazı bağımlılık gerektiren dÃŒzeltmeleri yapabilirsiniz."
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "Kurulum ile devam edilsin mi?"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "Yapılandırma yolunu değiştirmek istiyor musunuz? Mevcut% s"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "Yapılandırma yolunu değiştirme?"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "Giriş verileri ve temel ayarları yapılandırmak istiyor musunuz?"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "Bu ilk çalışma için tavsiye edilir."
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "Temel kurulum yap"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "Ssl yapılandırmak istiyor musunuz?"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "Ssl yapılandırması"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "Web arayÌzÌnÌ yapılandırmak istiyor musunuz?"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "Web arayÌzÌ yapılandırması?"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "Kur başarıyla tamamlandı."
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "Çıkmak için Enter tuşuna basın ve pyLoad'ı yeniden başlatın"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr "## Sistem Kontrol ##"
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr "Python sÌrÌmÌnÌz çok yeni, Python 2.6/2.7 kullanın"
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr "Python sÌrÌmÌnÌz çok eski, en az Python 2.5 kullanın"
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr "Python Versiyon: OK"
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr "YÌklÌ olan Jinja2 versiyonu %s çok eski görÌnÌyor."
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr "JS motoru"
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "## Temel Kurulum ##"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "Aşağıdaki giriş verileri geçerlidir şunlar için CLI, GUI ve webarayÃŒzÃŒ."
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Kullanıcı Adı"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr "Uzaktan Erişim"
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "Dil"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr "İndirme klasörÌ"
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "Max indirme sayısı"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "Bağlantı sıfırlama?"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "Komut dosyası konumu yeniden bağlanın"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "## Web arayÌzÌ Kurulumu ##"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "Web arayÌzÌ etkin?"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "Adrese bak, eğer 127.0.0.1 veya localhost kullanıyorsanız, webinterface yerel olarak erişilebilir olacaktır."
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Adres"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Port"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad şimdi kısa bir açıklama sonrasında, birçok sunucu arka uçları sunmaktadır."
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "Tarafından kullanılabilmek için apache, lighttpd, sana onları konfigÃŒre etmek gerektirir; ki bu çok da kolay bir iş değildir."
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "Buradan edinin: https://github.com/jonashaag/bjoern, onu derleyin"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "Dikkat: Bazı farklı durumlarda yerleşik sunucu çalışmıyorsa, eğer webarayÃŒzÃŒyle ilgili problem farke edersen"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "Buraya gel ve burada yerleşik sunucu dişli değiştir."
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Sunucu"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "## SSL Kurulumu ##"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "Bu komutlar ssl sertifikaları yapmak için pyLoad config klasörÃŒnden çalıştırın:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "Eğer yaptıysan ve her şey iyi gittiyse, ssl'yi şimdi etkinleştirebilirsiniz."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "SSL Etkinleştir?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "Eylem seçin"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - Kullanıcı Oluştur / DÃŒzenle"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - Kullanıcıları listele"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - Kullanıcı kaldır"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - Çıkış"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "Kullanıcılar"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr "Yeni ayarlar yapılandırma yolu, eski ayarlar transfer edilmeyecek!"
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr "Yapılandırma yolu"
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr "Yapılandırma yolu değişti, kurulum şimdi kapanacak, devam etmek için yeniden başlatın."
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "Çıkmak için Enter tuşuna basın."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Yapılandırma ayar yolu başarısız oldu: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr "%s: eksik"
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Şifre: "
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "Şifre çok kısa. En az 4 sembol kullanın."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "Şifre (tekrar):"
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "Parolalar eşleşmedi."
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "evet"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "doğru"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr "d"
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "hayır"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "yanlış"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr "y"
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "Geçersiz giriş"
+
diff --git a/locale/uk/LC_MESSAGES/django.po b/locale/uk/LC_MESSAGES/django.po
new file mode 100644
index 000000000..0209d315c
--- /dev/null
+++ b/locale/uk/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "вОкл"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "вкл"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "НевЎалП"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "ІЌ’я"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Статус"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "РПзЌір"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "ЛПгіМ"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "ІЌ'я кПрОстувача"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "ПарПль"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Папка"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "ПіЎтверЎОтО"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "ЗагальМі"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "ОблікПві запОсО"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "ПлагіМ"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "ЎійсМОй"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "ДПЎатО"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "ТОп"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "СОстеЌа"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "ПараЌетрО"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "ПрОзМачеММя"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "ЗакрОтО"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "СкасуватО"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr "АЎЌіМ"
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПбЌежеМОй"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/uk/LC_MESSAGES/pyLoad.po b/locale/uk/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..20c9a1b17
--- /dev/null
+++ b/locale/uk/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "SSL-сертОфікатО Ме зМайЎеМП."
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "МеПбЌежеМОй"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/uk/LC_MESSAGES/pyLoadCli.po b/locale/uk/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..10ac61deb
--- /dev/null
+++ b/locale/uk/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr " ІМтерфейс кПЌаМЎМПгП ряЎка"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s заваМтажеМь:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr " КвОЎкість: "
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr " РПзЌір: "
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr " ЗакіМчОв у: "
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID: "
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "ПчікуваММя: "
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "СтаМ:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "прОзупОМеМП"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "загальМа швОЎкість"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "ЀайлО у черзі"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "ВсьПгП"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "МеМю:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr " ДПЎатО пПсОлаММя"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr " КеруваММя чергПю"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr " КеруватО кПлектПрПЌ"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr ""
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr " ВбОтО сервер"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr " ВОйтО"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "БуЎь ласка, вОкПрОстПвуйте такОй сОМтаксОс: ЎПЎатО < Ñ–ÐŒ'я Пакета > <link><link2>..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Перевірка %d пПсОлаММя:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Ѐайл Ме ісМує."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr ""
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "ПереЌістОтО пакуМкО з чергО у кПлектПр і МавпакО"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr ""
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr ""
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr ""
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr ""
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr ""
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr ""
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr ""
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr ""
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr ""
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "ДПЎатО пакет:"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "ВвеЎіть Ñ–ÐŒ'я Ўля МПвПгП пакета"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "ППсОлаММя ЎПЎаМП: "
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "КеруваММя пПсОлаММяЌО:"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "ЩП вО хПчете вОЎалОтО?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "вОЎалОтО"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "перезапустОтО"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " -пПпереЎМій"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr ""
+
diff --git a/locale/uk/LC_MESSAGES/setup.po b/locale/uk/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..a06a623f9
--- /dev/null
+++ b/locale/uk/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "ЛаскавП прПсОЌП ЎП асОстеМту устаМПвкО."
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "Ввашу сОстеЌу зараз буЎе перевіреМП і зрПблеМПбазПві устаМПвкО Ўля тПгП, щПб запустОтО pyLoad."
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "ЗМачеММя у кваЎратМОх Ўужках [] завжЎО є зМачеММя за заЌПвчаММяЌ"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "НатОсМіть enter щПб перезапустіть pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "ІЌ'я кПрОстувача"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "МПва/Language"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "АЎреса"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "ППрт"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Сервер"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "# # SSL МалаштуваММя # #"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "ВОкПМатО ці кПЌаМЎО з папкО pyLoad Ўля тПгП щПб зрПбОтО ssl сертОфікатО:"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "ЯкщП вО все зрПбОлО, і все прПйшлП ЎПбре, вО ЌПжете актОвуватО ssl зараз."
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "АктОвуватО SSL?"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "ВОбратО ÐŽÑ–ÑŽ"
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1 - СтвПреММя та реЎагуваММя кПрОстувач"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2 - СпОсПк кПрОстувачів"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3 - ВОЎалОтО кПрОстувача"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4 - ВОхіЎ"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "КПрОстувачі"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "НатОсМіть клавішу Enter, щПб закрОтО."
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "Не вЎалПся зЌіМОтО шлях ЎП МалаштуваМь: %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr ""
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "ПарПль заМаЎтП кПрПткОй. МіМіЌальМа ЎПвжОМа 4 сОЌвПла."
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "ПарПль (ще раз): "
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "Так"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "Ні"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "НевірМОй ввіЎ"
+
diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po
new file mode 100644
index 000000000..16cbce3da
--- /dev/null
+++ b/locale/vi/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Vietnamese\n"
+"Language: vi_VN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr ""
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr ""
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr ""
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "tắt"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr ""
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "trên"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr ""
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr ""
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr ""
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr ""
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr ""
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr ""
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr ""
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr ""
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr ""
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr ""
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr ""
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "Tên"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "Tình trạng"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "Kích thước"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "Đăng nhập"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "Tên người dùng"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "Mật khẩu"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "Thư mục"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "Gá»­i"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "Chung"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "Tài khoản"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "Plugin"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "hợp lệ"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "Thêm"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "Điểm đến"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "Đóng"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "Hủy bỏ"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "khÃŽng giới hạn"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/vi/LC_MESSAGES/pyLoad.po b/locale/vi/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..3f1e8e5c8
--- /dev/null
+++ b/locale/vi/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Vietnamese\n"
+"Language: vi_VN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr ""
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr ""
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr ""
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr ""
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr ""
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr ""
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr ""
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr ""
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr ""
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr ""
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr ""
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr ""
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr ""
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr ""
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr ""
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr ""
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr ""
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr ""
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr ""
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr ""
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr ""
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr ""
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr ""
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr ""
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr ""
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr ""
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr ""
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "khÃŽng giới hạn"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr ""
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr ""
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr ""
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr ""
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr ""
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr ""
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr ""
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr ""
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr ""
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr ""
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr ""
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr ""
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr ""
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr ""
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr ""
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr ""
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr ""
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr ""
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr ""
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr ""
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr ""
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr ""
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr ""
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr ""
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr ""
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr ""
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr ""
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr ""
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr ""
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr ""
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr ""
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr ""
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr ""
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/vi/LC_MESSAGES/pyLoadCli.po b/locale/vi/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..a35b37295
--- /dev/null
+++ b/locale/vi/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Vietnamese\n"
+"Language: vi_VN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "Giao diện Dòng lệnh"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "%s Tải xuống:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "Tốc độ:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "Kích thước:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "Kết thúc trong:"
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "ID:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "chờ:"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr ""
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr ""
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr ""
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr ""
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "Danh Mục:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "Thêm liên kết"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "(Un)Tạm dừng máy chủ"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "Dừng máy chủ"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "Thoát"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr ""
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "Đang kiểm tra %d liên kết:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "Tập tin khÃŽng tồn tại."
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr ""
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "in trạng thái máy chủ"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "in hàng đợi tải xuống"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr ""
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr ""
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr ""
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "Khởi động lại các tập tin."
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr ""
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr ""
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr ""
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "tạm dừng máy chủ"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "tiếp tục tải xuống"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "chuyển giữa tạm dừng/tiếp tục"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "dừng máy chủ"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr ""
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr ""
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr ""
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "địa chỉ:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "Cổng:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "Tên người dùng:"
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "Mật khẩu:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr ""
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr ""
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr ""
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr ""
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr ""
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "Gói: %s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "Phân tích liên kết bạn muốn thêm."
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "Nhập %s khi xong."
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "Liên kết đã thêm:"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "quay lại menu chính"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "Quản lÜ liên kết"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr "- quay lại"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "- kế tiếp"
+
diff --git a/locale/vi/LC_MESSAGES/setup.po b/locale/vi/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..c02da4262
--- /dev/null
+++ b/locale/vi/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:39-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Vietnamese\n"
+"Language: vi_VN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr ""
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr ""
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr ""
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr ""
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr ""
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr ""
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr ""
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr ""
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr ""
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr ""
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr ""
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr ""
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr ""
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr ""
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr ""
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr ""
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr ""
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr ""
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr ""
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr ""
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr ""
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr ""
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr ""
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr ""
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr ""
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr ""
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "Tên người dùng"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "NgÎn ngữ"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr ""
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr ""
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr ""
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr ""
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr ""
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr ""
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "Địa chỉ"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "Cổng"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr ""
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr ""
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr ""
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr ""
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr ""
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "Máy chủ"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr ""
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr ""
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr ""
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr ""
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr ""
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr ""
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr ""
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr ""
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr ""
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr ""
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr ""
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr ""
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "Mật khẩu:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr ""
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr ""
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr ""
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr ""
+
diff --git a/locale/zh/LC_MESSAGES/django.po b/locale/zh/LC_MESSAGES/django.po
new file mode 100644
index 000000000..192ea08f1
--- /dev/null
+++ b/locale/zh/LC_MESSAGES/django.po
@@ -0,0 +1,684 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Chinese Traditional\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/webui/translations.js:1
+msgid "New Captcha Request"
+msgstr "新验证请求"
+
+#: pyload/webui/translations.js:2 pyload/webui/themes/default/tml/captcha.html:7
+msgid "Please read the text on the captcha."
+msgstr "请读取验证码䞊的文本."
+
+#: pyload/webui/translations.js:3
+msgid "pyLoad restarted"
+msgstr "pyLoad已经重启"
+
+#: pyload/webui/translations.js:4
+#: pyload/webui/themes/default/tml/settings_item.html:16
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "off"
+msgstr "關"
+
+#: pyload/webui/translations.js:5
+msgid "Success"
+msgstr "成功"
+
+#: pyload/webui/translations.js:6
+#: pyload/webui/themes/default/tml/settings_item.html:14
+#: pyload/webui/themes/default/tml/base.html:123
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "on"
+msgstr "開"
+
+#: pyload/webui/translations.js:7
+msgid "You are really sure you want to quit pyLoad?"
+msgstr "确讀芁退出pyLoad吗"
+
+#: pyload/webui/translations.js:8
+msgid "Restart Link"
+msgstr "铟接重新匀始"
+
+#: pyload/webui/translations.js:9
+msgid "Delete Link"
+msgstr "删陀铟接"
+
+#: pyload/webui/translations.js:10
+msgid "Please Enter a packagename."
+msgstr "请蟓入䞋蜜包名字"
+
+#: pyload/webui/translations.js:11
+msgid "Please click on the right captcha position."
+msgstr "请点击正确的验证码䜍眮"
+
+#: pyload/webui/translations.js:12
+msgid "Error occured."
+msgstr "错误发生了"
+
+#: pyload/webui/translations.js:13
+#: pyload/webui/themes/default/tml/filemanager.html:65
+#: pyload/webui/themes/default/tml/folder.html:14
+msgid "Folder is empty"
+msgstr "文件倹䞺空"
+
+#: pyload/webui/translations.js:14
+msgid "Failed"
+msgstr "倱敗"
+
+#: pyload/webui/translations.js:15
+msgid "No Captchas to read."
+msgstr "没有获取验证码"
+
+#: pyload/webui/translations.js:16
+msgid "Passwords did not match."
+msgstr "密码䞍笊"
+
+#: pyload/webui/translations.js:17
+msgid "Settings saved."
+msgstr "讟眮已保存"
+
+#: pyload/webui/translations.js:18
+msgid "New folder"
+msgstr "新文件倹"
+
+#: pyload/webui/translations.js:19
+msgid "Are you sure you want to restart pyLoad?"
+msgstr "确定重启pyLoad吗"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:206
+msgid "Active Downloads"
+msgstr "掻劚䞋蜜"
+
+#: pyload/webui/themes/default/tml/home.html:211
+#: pyload/webui/themes/default/tml/base.html:84
+msgid "Home"
+msgstr "銖页"
+
+#: pyload/webui/themes/default/tml/home.html:214
+#: pyload/webui/themes/default/tml/queue.html:15
+#: pyload/webui/themes/default/tml/window.html:34
+#: pyload/webui/themes/default/tml/base.html:87
+msgid "Queue"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:217
+#: pyload/webui/themes/default/tml/queue.html:17
+#: pyload/webui/themes/default/tml/window.html:36
+#: pyload/webui/themes/default/tml/base.html:90
+msgid "Collector"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/home.html:220
+#: pyload/webui/themes/default/tml/downloads.html:6
+#: pyload/webui/themes/default/tml/base.html:93
+msgid "Downloads"
+msgstr "已䞋蜜"
+
+#: pyload/webui/themes/default/tml/home.html:226
+#: pyload/webui/themes/default/tml/logs.html:3
+#: pyload/webui/themes/default/tml/logs.html:4
+#: pyload/webui/themes/default/tml/base.html:99
+msgid "Logs"
+msgstr "日志"
+
+#: pyload/webui/themes/default/tml/home.html:229
+#: pyload/webui/themes/default/tml/settings.html:3
+#: pyload/webui/themes/default/tml/settings.html:4
+#: pyload/webui/themes/default/tml/base.html:102
+msgid "Config"
+msgstr "配眮"
+
+#: pyload/webui/themes/default/tml/home.html:237
+#: pyload/webui/themes/default/tml/queue.html:82
+#: pyload/webui/themes/default/tml/settings.html:91
+#: pyload/webui/themes/default/tml/window.html:7
+#: pyload/webui/themes/default/tml/admin.html:25
+msgid "Name"
+msgstr "名皱"
+
+#: pyload/webui/themes/default/tml/home.html:238
+#: pyload/webui/themes/default/tml/settings.html:93
+msgid "Status"
+msgstr "狀態"
+
+#: pyload/webui/themes/default/tml/home.html:239
+#: pyload/webui/themes/default/tml/info.html:14
+#: pyload/webui/themes/default/tml/info.html:15
+msgid "Information"
+msgstr "信息"
+
+#: pyload/webui/themes/default/tml/home.html:240
+msgid "Size"
+msgstr "倧小"
+
+#: pyload/webui/themes/default/tml/home.html:241
+msgid "Progress"
+msgstr "进床"
+
+#: pyload/webui/themes/default/tml/login.html:3
+#: pyload/webui/themes/default/tml/settings.html:178
+msgid "Login"
+msgstr "登入"
+
+#: pyload/webui/themes/default/tml/login.html:14
+msgid "Username"
+msgstr "䜿甚者名皱"
+
+#: pyload/webui/themes/default/tml/login.html:19
+#: pyload/webui/themes/default/tml/queue.html:92
+#: pyload/webui/themes/default/tml/settings.html:92
+#: pyload/webui/themes/default/tml/settings.html:183
+#: pyload/webui/themes/default/tml/window.html:21
+msgid "Password"
+msgstr "密碌"
+
+#: pyload/webui/themes/default/tml/login.html:29
+msgid "Your username and password didn't match. Please try again."
+msgstr "悚的甚户名和密码䞍匹配, 请再试䞀次."
+
+#: pyload/webui/themes/default/tml/login.html:30
+msgid "To reset your login data or add an user run:"
+msgstr "芁重眮悚的登圕数据或添加䞀䞪甚户, 请运行:"
+
+#: pyload/webui/themes/default/tml/queue.html:25
+msgid "Delete Finished"
+msgstr "删陀完成"
+
+#: pyload/webui/themes/default/tml/queue.html:26
+msgid "Restart Failed"
+msgstr "重启出错"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Folder:"
+msgstr "文件倹:"
+
+#: pyload/webui/themes/default/tml/queue.html:65
+msgid "Password:"
+msgstr "密码:"
+
+#: pyload/webui/themes/default/tml/queue.html:79
+msgid "Edit Package"
+msgstr "猖蟑䞋蜜"
+
+#: pyload/webui/themes/default/tml/queue.html:80
+msgid "Edit the package detais below."
+msgstr "修改文件包诊细泚释信息."
+
+#: pyload/webui/themes/default/tml/queue.html:83
+msgid "The name of the package."
+msgstr "文件名."
+
+#: pyload/webui/themes/default/tml/queue.html:87
+msgid "Folder"
+msgstr "目錄"
+
+#: pyload/webui/themes/default/tml/queue.html:88
+msgid "Name of subfolder for these downloads."
+msgstr "歀䞋蜜的子目圕名."
+
+#: pyload/webui/themes/default/tml/queue.html:93
+msgid "List of passwords used for unrar."
+msgstr "UNRAR解压猩密码列衚."
+
+#: pyload/webui/themes/default/tml/queue.html:97
+#: pyload/webui/themes/default/tml/settings.html:167
+#: pyload/webui/themes/default/tml/captcha.html:33
+#: pyload/webui/themes/default/tml/admin.html:61
+#: pyload/webui/themes/default/tml/admin.html:91
+msgid "Submit"
+msgstr "同意"
+
+#: pyload/webui/themes/default/tml/queue.html:98
+#: pyload/webui/themes/default/tml/settings.html:198
+#: pyload/webui/themes/default/tml/window.html:41
+#: pyload/webui/themes/default/tml/admin.html:92
+msgid "Reset"
+msgstr "重眮"
+
+#: pyload/webui/themes/default/tml/logout.html:8
+msgid "You were successfully logged out."
+msgstr "悚已成功泚销."
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "Path"
+msgstr "路埄"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "absolute"
+msgstr "绝对"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:39
+#: pyload/webui/themes/default/tml/pathchooser.html:41
+msgid "relative"
+msgstr "盞对"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:46
+msgid "name"
+msgstr "名称"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:47
+msgid "size"
+msgstr "倧小"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:48
+msgid "type"
+msgstr "类型"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:49
+msgid "last modified"
+msgstr "最后修改"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:54
+msgid "parent directory"
+msgstr "父目圕"
+
+#: pyload/webui/themes/default/tml/pathchooser.html:70
+msgid "no content"
+msgstr "没有内容"
+
+#: pyload/webui/themes/default/tml/settings.html:16
+msgid "General"
+msgstr "䞀般蚭定"
+
+#: pyload/webui/themes/default/tml/settings.html:17
+msgid "Plugins"
+msgstr "插件"
+
+#: pyload/webui/themes/default/tml/settings.html:18
+msgid "Accounts"
+msgstr "垳戶"
+
+#: pyload/webui/themes/default/tml/settings.html:45
+#: pyload/webui/themes/default/tml/settings.html:74
+msgid "Choose a section from the menu"
+msgstr "从菜单䞭选择䞀项目"
+
+#: pyload/webui/themes/default/tml/settings.html:90
+msgid "Plugin"
+msgstr "倖掛"
+
+#: pyload/webui/themes/default/tml/settings.html:94
+msgid "Premium"
+msgstr "高级垐号"
+
+#: pyload/webui/themes/default/tml/settings.html:95
+msgid "Valid until"
+msgstr "有效期至"
+
+#: pyload/webui/themes/default/tml/settings.html:96
+msgid "Traffic left"
+msgstr "剩䜙流量"
+
+#: pyload/webui/themes/default/tml/settings.html:97
+msgid "Time"
+msgstr "æ—¶é—Ž"
+
+#: pyload/webui/themes/default/tml/settings.html:98
+msgid "Max Parallel"
+msgstr "最倧连接数"
+
+#: pyload/webui/themes/default/tml/settings.html:99
+msgid "Delete?"
+msgstr "确讀删陀?"
+
+#: pyload/webui/themes/default/tml/settings.html:121
+msgid "valid"
+msgstr "有效的"
+
+#: pyload/webui/themes/default/tml/settings.html:124
+msgid "not valid"
+msgstr "无效"
+
+#: pyload/webui/themes/default/tml/settings.html:131
+msgid "yes"
+msgstr "是"
+
+#: pyload/webui/themes/default/tml/settings.html:134
+msgid "no"
+msgstr "吊"
+
+#: pyload/webui/themes/default/tml/settings.html:168
+#: pyload/webui/themes/default/tml/settings.html:197
+#: pyload/webui/themes/default/tml/base.html:117
+msgid "Add"
+msgstr "增加"
+
+#: pyload/webui/themes/default/tml/settings.html:176
+msgid "Add Account"
+msgstr "新增垳號"
+
+#: pyload/webui/themes/default/tml/settings.html:177
+msgid "Enter your account data to use premium features."
+msgstr "蟓入悚的垐户资料来䜿甚高级功胜."
+
+#: pyload/webui/themes/default/tml/settings.html:179
+#: pyload/webui/themes/default/tml/admin.html:71
+msgid "Your username."
+msgstr "悚的甚户名."
+
+#: pyload/webui/themes/default/tml/settings.html:184
+#: pyload/webui/themes/default/tml/admin.html:76
+msgid "The password for this account."
+msgstr "圓前密码仅䞺歀垐号所甚."
+
+#: pyload/webui/themes/default/tml/settings.html:188
+msgid "Type"
+msgstr "皮類"
+
+#: pyload/webui/themes/default/tml/settings.html:189
+msgid "Choose the hoster for your account."
+msgstr "请选择悚垐号对应的眑盘类型."
+
+#: pyload/webui/themes/default/tml/logs.html:12
+#: pyload/webui/themes/default/tml/base.html:114
+msgid "Start"
+msgstr "启劚"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "prev"
+msgstr "䞊䞀页"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "next"
+msgstr "䞋䞀页"
+
+#: pyload/webui/themes/default/tml/logs.html:12
+msgid "End"
+msgstr "完成"
+
+#: pyload/webui/themes/default/tml/info.html:18
+msgid "News"
+msgstr "新闻"
+
+#: pyload/webui/themes/default/tml/info.html:21
+msgid "Support"
+msgstr "技术支持"
+
+#: pyload/webui/themes/default/tml/info.html:37
+msgid "System"
+msgstr "系統"
+
+#: pyload/webui/themes/default/tml/info.html:40
+msgid "Python:"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/info.html:44
+msgid "OS:"
+msgstr "操䜜系统:"
+
+#: pyload/webui/themes/default/tml/info.html:48
+msgid "pyLoad version:"
+msgstr "pyLoad 版本:"
+
+#: pyload/webui/themes/default/tml/info.html:52
+msgid "Installation Folder:"
+msgstr "安装文件倹:"
+
+#: pyload/webui/themes/default/tml/info.html:56
+msgid "Config Folder:"
+msgstr "配眮文件倹:"
+
+#: pyload/webui/themes/default/tml/info.html:60
+msgid "Download Folder:"
+msgstr "䞋蜜文件倹:"
+
+#: pyload/webui/themes/default/tml/info.html:64
+msgid "Free Space:"
+msgstr "空闲空闎:"
+
+#: pyload/webui/themes/default/tml/info.html:68
+msgid "Language:"
+msgstr "语蚀:"
+
+#: pyload/webui/themes/default/tml/info.html:72
+msgid "Webinterface Port:"
+msgstr "Web界面端口:"
+
+#: pyload/webui/themes/default/tml/info.html:76
+msgid "Remote Interface Port:"
+msgstr "远皋接口端口:"
+
+#: pyload/webui/themes/default/tml/setup.html:3
+#: pyload/webui/themes/default/tml/setup.html:4
+msgid "Setup"
+msgstr "蚭定"
+
+#: pyload/webui/themes/default/tml/filemanager.html:19
+msgid "FileManager"
+msgstr "文件管理噚"
+
+#: pyload/webui/themes/default/tml/window.html:5
+#: pyload/webui/themes/default/tml/window.html:40
+msgid "Add Package"
+msgstr "添加䞋蜜"
+
+#: pyload/webui/themes/default/tml/window.html:6
+msgid "Paste your links or upload a container."
+msgstr "粘莎铟接或者䞊䌠䞀容噚."
+
+#: pyload/webui/themes/default/tml/window.html:8
+msgid "The name of the new package."
+msgstr "新建䞋蜜的名字."
+
+#: pyload/webui/themes/default/tml/window.html:12
+msgid "Links"
+msgstr "铟接"
+
+#: pyload/webui/themes/default/tml/window.html:13
+msgid "Paste your links here or any text and press the filter button."
+msgstr "圚这里粘莎悚的铟接或任䜕文本内容, 然后按\"过滀\"按钮."
+
+#: pyload/webui/themes/default/tml/window.html:14
+msgid "Filter urls"
+msgstr "过滀"
+
+#: pyload/webui/themes/default/tml/window.html:22
+msgid "Password for RAR-Archive"
+msgstr "RAR压猩包密码"
+
+#: pyload/webui/themes/default/tml/window.html:26
+msgid "File"
+msgstr "文件"
+
+#: pyload/webui/themes/default/tml/window.html:27
+msgid "Upload a container."
+msgstr "䞊䌠䞀容噚."
+
+#: pyload/webui/themes/default/tml/window.html:31
+msgid "Destination"
+msgstr "目的地"
+
+#: pyload/webui/themes/default/tml/captcha.html:6
+msgid "Captcha reading"
+msgstr "验证码读取䞭..."
+
+#: pyload/webui/themes/default/tml/captcha.html:13
+msgid "Captcha"
+msgstr "验证码"
+
+#: pyload/webui/themes/default/tml/captcha.html:14
+msgid "The captcha."
+msgstr "验证码."
+
+#: pyload/webui/themes/default/tml/captcha.html:20
+msgid "Text"
+msgstr "文本"
+
+#: pyload/webui/themes/default/tml/captcha.html:21
+msgid "Input the text on the captcha."
+msgstr "蟓入验证码䞊的文本."
+
+#: pyload/webui/themes/default/tml/captcha.html:34
+msgid "Close"
+msgstr "關閉"
+
+#: pyload/webui/themes/default/tml/base.html:20
+#: pyload/webui/themes/default/tml/base.html:139
+msgid "Webinterface"
+msgstr "Web界面"
+
+#: pyload/webui/themes/default/tml/base.html:39
+msgid "pyLoad Update available!"
+msgstr "pyLoad 曎新可甚!"
+
+#: pyload/webui/themes/default/tml/base.html:46
+msgid "Plugins updated, please restart!"
+msgstr "插件已曎新, 请重新启劚皋序!"
+
+#: pyload/webui/themes/default/tml/base.html:52
+msgid "Captcha waiting"
+msgstr "验证码等埅䞭"
+
+#: pyload/webui/themes/default/tml/base.html:57
+msgid "Logout"
+msgstr "泚销"
+
+#: pyload/webui/themes/default/tml/base.html:59
+#: pyload/webui/themes/default/tml/admin.html:8
+#: pyload/webui/themes/default/tml/admin.html:9
+msgid "Administrate"
+msgstr "管理"
+
+#: pyload/webui/themes/default/tml/base.html:61
+msgid "Info"
+msgstr "信息"
+
+#: pyload/webui/themes/default/tml/base.html:65
+msgid "Please Login!"
+msgstr "请登圕!"
+
+#: pyload/webui/themes/default/tml/base.html:115
+msgid "Stop"
+msgstr "停止"
+
+#: pyload/webui/themes/default/tml/base.html:116
+msgid "Cancel"
+msgstr "取消"
+
+#: pyload/webui/themes/default/tml/base.html:123
+msgid "Download:"
+msgstr "䞋蜜:"
+
+#: pyload/webui/themes/default/tml/base.html:124
+msgid "Reconnect:"
+msgstr "重新连接:"
+
+#: pyload/webui/themes/default/tml/base.html:125
+msgid "Speed:"
+msgstr "速床:"
+
+#: pyload/webui/themes/default/tml/base.html:126
+msgid "Active:"
+msgstr "掻劚:"
+
+#: pyload/webui/themes/default/tml/base.html:127
+msgid "Reload page"
+msgstr "刷新页面"
+
+#: pyload/webui/themes/default/tml/base.html:157
+msgid "loading"
+msgstr "加蜜䞭"
+
+#: pyload/webui/themes/default/tml/base.html:166
+msgid "Back to top"
+msgstr "返回顶郚"
+
+#: pyload/webui/themes/default/tml/admin.html:13
+msgid "Quit pyLoad"
+msgstr "退出pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:14
+msgid "Restart pyLoad"
+msgstr "重启pyLoad"
+
+#: pyload/webui/themes/default/tml/admin.html:18
+msgid "To add user or change passwords use:"
+msgstr "添加甚户或曎改密码请䜿甚:"
+
+#: pyload/webui/themes/default/tml/admin.html:19
+msgid "Important: Admin user have always all permissions!"
+msgstr "重芁提瀺管理员莊户具有所有操䜜权限"
+
+#: pyload/webui/themes/default/tml/admin.html:28
+#: pyload/webui/themes/default/tml/admin.html:67
+msgid "Change Password"
+msgstr "曎改密码"
+
+#: pyload/webui/themes/default/tml/admin.html:31
+msgid "Admin"
+msgstr ""
+
+#: pyload/webui/themes/default/tml/admin.html:34
+msgid "Permissions"
+msgstr "讞可"
+
+#: pyload/webui/themes/default/tml/admin.html:41
+msgid "change"
+msgstr "曎改"
+
+#: pyload/webui/themes/default/tml/admin.html:69
+msgid "Enter your current and desired Password."
+msgstr "请蟓入悚圓前以及需芁修改的密码."
+
+#: pyload/webui/themes/default/tml/admin.html:70
+msgid "User"
+msgstr "甚户"
+
+#: pyload/webui/themes/default/tml/admin.html:75
+msgid "Current password"
+msgstr "圓前密码"
+
+#: pyload/webui/themes/default/tml/admin.html:80
+msgid "New password"
+msgstr "新密码"
+
+#: pyload/webui/themes/default/tml/admin.html:81
+msgid "The new password."
+msgstr "新的密码."
+
+#: pyload/webui/themes/default/tml/admin.html:85
+msgid "New password (repeat)"
+msgstr "新密码 (重倍)"
+
+#: pyload/webui/themes/default/tml/admin.html:86
+msgid "Please repeat the new password."
+msgstr "请确讀悚的新密码."
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "無限制"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
diff --git a/locale/zh/LC_MESSAGES/pyLoad.po b/locale/zh/LC_MESSAGES/pyLoad.po
new file mode 100644
index 000000000..c8d759aab
--- /dev/null
+++ b/locale/zh/LC_MESSAGES/pyLoad.po
@@ -0,0 +1,865 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Chinese Traditional\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/Core.py:182
+msgid "Received Quit signal"
+msgstr "收到退出蚊號"
+
+#: pyload/Core.py:303
+#, python-format
+msgid "pyLoad already running with pid %s"
+msgstr "pyLoad已經圚執行pid %s"
+
+#: pyload/Core.py:317
+#, python-format
+msgid "Failed changing group: %s"
+msgstr "無法改變矀組 %s"
+
+#: pyload/Core.py:327
+#, python-format
+msgid "Failed changing user: %s"
+msgstr "無法改變䜿甚者 %s"
+
+#: pyload/Core.py:329
+msgid "folder for logs"
+msgstr "LOG文件倹"
+
+#: pyload/Core.py:340
+msgid "Starting"
+msgstr "開始"
+
+#: pyload/Core.py:341
+#, python-format
+msgid "Using home directory: %s"
+msgstr "䜿甚家目錄 %s"
+
+#: pyload/Core.py:350
+msgid "pycrypto to decode container files"
+msgstr "䜿甚pycrypto解密文件"
+
+#: pyload/Core.py:353
+msgid "folder for temporary files"
+msgstr "䞎时文件倹"
+
+#: pyload/Core.py:358
+msgid "folder for downloads"
+msgstr "䞋蜜文件倹"
+
+#: pyload/Core.py:361
+msgid "OpenSSL for secure connection"
+msgstr "䜿甚OpenSSL安党登陆"
+
+#: pyload/Core.py:365
+msgid "Moving old user config to DB"
+msgstr "将旧甚户讟定移入数据库"
+
+#: pyload/Core.py:368
+msgid "Please check your logindata with ./pyload.py -u"
+msgstr "请执行./pyload.py -u 来确讀䜠的logindata"
+
+#: pyload/Core.py:371
+msgid "All links removed"
+msgstr "刪陀所有連結"
+
+#: pyload/Core.py:402
+#, python-format
+msgid "Downloadtime: %s"
+msgstr "䞋蜜时闎%s"
+
+#: pyload/Core.py:412
+#, python-format
+msgid "Free space: %s"
+msgstr "剩逘空間 %s"
+
+#: pyload/Core.py:432
+msgid "Activating Accounts..."
+msgstr "啟動垳戶..."
+
+#: pyload/Core.py:438
+msgid "Activating Plugins..."
+msgstr "激掻插件䞭..."
+
+#: pyload/Core.py:441
+msgid "pyLoad is up and running"
+msgstr "pyLoad已是最新的狀態"
+
+#: pyload/Core.py:460
+msgid "restarting pyLoad"
+msgstr "重新啟動pyLoad"
+
+#: pyload/Core.py:464
+msgid "pyLoad quits"
+msgstr "離開pyLoad"
+
+#: pyload/Core.py:521
+#, python-format
+msgid "Install %s"
+msgstr "安装 %s"
+
+#: pyload/Core.py:557
+#, python-format
+msgid "could not find %(desc)s: %(name)s"
+msgstr "无法扟到%(desc)s: %(name)s"
+
+#: pyload/Core.py:559
+#, python-format
+msgid "could not create %(desc)s: %(name)s"
+msgstr "无法创建%(desc)s: %(name)s"
+
+#: pyload/Core.py:580
+msgid "shutting down..."
+msgstr "關閉..."
+
+#: pyload/Core.py:597
+msgid "error while shutting down"
+msgstr "關閉時出珟錯誀"
+
+#: pyload/Core.py:661
+msgid "killed pyLoad from Terminal"
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:174
+msgid "Filedatabase was deleted due to incompatible version."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:189
+msgid "Filedatabase could NOT be converted."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:198
+msgid "Database was converted from v2 to v3."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:206
+msgid "Database was converted from v3 to v4."
+msgstr ""
+
+#: pyload/database/DatabaseBackend.py:252
+msgid "Converting old Django DB"
+msgstr ""
+
+#: pyload/database/FileDatabase.py:45
+msgid "finished"
+msgstr "已完成"
+
+#: pyload/database/FileDatabase.py:45
+msgid "offline"
+msgstr "離線"
+
+#: pyload/database/FileDatabase.py:45
+msgid "online"
+msgstr "線䞊"
+
+#: pyload/database/FileDatabase.py:45
+msgid "queued"
+msgstr "䜇列"
+
+#: pyload/database/FileDatabase.py:45
+msgid "skipped"
+msgstr "跳過"
+
+#: pyload/database/FileDatabase.py:45
+msgid "waiting"
+msgstr "等埅䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "temp. offline"
+msgstr "暫時離線"
+
+#: pyload/database/FileDatabase.py:45
+msgid "starting"
+msgstr "開始"
+
+#: pyload/database/FileDatabase.py:45
+msgid "failed"
+msgstr "倱敗"
+
+#: pyload/database/FileDatabase.py:45
+msgid "aborted"
+msgstr "äž­æ­¢"
+
+#: pyload/database/FileDatabase.py:45
+msgid "decrypting"
+msgstr "解析"
+
+#: pyload/database/FileDatabase.py:45
+msgid "custom"
+msgstr "自蚂"
+
+#: pyload/database/FileDatabase.py:45
+msgid "downloading"
+msgstr "䞋茉䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "processing"
+msgstr "處理䞭"
+
+#: pyload/database/FileDatabase.py:45
+msgid "unknown"
+msgstr "未知"
+
+#: pyload/database/FileDatabase.py:531 pyload/plugins/hooks/IRCInterface.py:74
+#: pyload/plugins/hooks/XMPPInterface.py:83
+#, python-format
+msgid "Package finished: %s"
+msgstr "套件已完成 %s"
+
+#: pyload/remote/ThriftBackend.py:39
+msgid "Using SSL ThriftBackend"
+msgstr ""
+
+#: pyload/remote/RemoteManager.py:35
+#, python-format
+msgid "Remote backend error: %s"
+msgstr "遠端錯誀 %s"
+
+#: pyload/remote/RemoteManager.py:82
+#, python-format
+msgid "Starting %(name)s: %(addr)s:%(port)s"
+msgstr "%(name)s %(addr)s:%(port)s 啟動䞭"
+
+#: pyload/remote/RemoteManager.py:84
+#, python-format
+msgid "Failed loading backend %(name)s | %(error)s"
+msgstr "無法茉入 %(name)s %(error)s"
+
+#: pyload/webui/app/json.py:57
+#, python-format
+msgid "waiting %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:35
+msgid "SSL certificates not found."
+msgstr "沒有扟到SSL憑證"
+
+#: pyload/threads/ServerThread.py:39
+#, python-format
+msgid "Sorry, we dropped support for starting %s directly within pyLoad"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:40
+msgid "You can use the threaded server which offers good performance and ssl,"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:41
+#, python-format
+msgid "of course you can still use your existing %s with pyLoads fastcgi server"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:42
+msgid "sample configs are located in the pyload/webui/servers directory"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:49
+#, python-format
+msgid "Can't use %(server)s, python-flup is not installed!"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:56
+#, python-format
+msgid "Error importing lightweight server: %s"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:57
+msgid "You need to download and compile bjoern, https://github.com/jonashaag/bjoern"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:58
+msgid "Copy the boern.so to pyload/lib folder or use setup.py install"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:59
+msgid "Of course you need to be familiar with linux and know how to compile software"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:63
+msgid "Server set to threaded, due to known performance problems on windows."
+msgstr ""
+
+#: pyload/threads/ServerThread.py:80 pyload/threads/ServerThread.py:103
+msgid "This server offers no SSL, please consider using threaded instead"
+msgstr "目前的服務噚䞍支揎SSL請將䌺服噚暡匏改為Threaded"
+
+#: pyload/threads/ServerThread.py:82
+#, python-format
+msgid "Starting builtin webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:87
+#, python-format
+msgid "Starting threaded SSL webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:91
+#, python-format
+msgid "Starting threaded webserver: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:97
+#, python-format
+msgid "Starting fastcgi server: %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/threads/ServerThread.py:105
+#, python-format
+msgid "Starting lightweight webserver (bjoern): %(host)s:%(port)d"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:125
+msgid "You dont have permission to access this page."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:193
+msgid "Download directory not found."
+msgstr ""
+
+#: pyload/webui/app/pyload.py:260 pyload/webui/app/pyload.py:267
+msgid "unlimited"
+msgstr "無限制"
+
+#: pyload/webui/app/pyload.py:262 pyload/webui/app/pyload.py:269
+msgid "not available"
+msgstr ""
+
+#: pyload/webui/app/pyload.py:508
+msgid "Run pyload.py -s to access the setup."
+msgstr ""
+
+#: pyload/network/HTTPDownload.py:245
+#, python-format
+msgid "Download chunks failed, fallback to single connection | %s"
+msgstr "分區䞋茉倱敗採甚單䞀連接䞋茉%s"
+
+#: pyload/threads/PluginThread.py:183
+#, python-format
+msgid "Download starts: %s"
+msgstr "開始䞋茉 %s"
+
+#: pyload/threads/PluginThread.py:189
+#, python-format
+msgid "Download finished: %s"
+msgstr "%s 䞋茉完成"
+
+#: pyload/threads/PluginThread.py:194 pyload/threads/PluginThread.py:365
+#, python-format
+msgid "Plugin %s is missing a function."
+msgstr "%s 暡組猺少盞關功胜"
+
+#: pyload/threads/PluginThread.py:202 pyload/threads/PluginThread.py:265
+#: pyload/threads/PluginThread.py:382
+#, python-format
+msgid "Download aborted: %s"
+msgstr "取消䞋茉 %s"
+
+#: pyload/threads/PluginThread.py:222
+#, python-format
+msgid "Download restarted: %(name)s | %(msg)s"
+msgstr "重新䞋茉%(name)s %(msg)s"
+
+#: pyload/threads/PluginThread.py:231 pyload/threads/PluginThread.py:373
+#, python-format
+msgid "Download is offline: %s"
+msgstr "䞋茉任務斷線%s"
+
+#: pyload/threads/PluginThread.py:234
+#, python-format
+msgid "Download is temporary offline: %s"
+msgstr "䞋茉任務暫時斷線%s"
+
+#: pyload/threads/PluginThread.py:237 pyload/threads/PluginThread.py:304
+#, python-format
+msgid "Download failed: %(name)s | %(msg)s"
+msgstr "%(name)s 䞋茉倱敗%(msg)s"
+
+#: pyload/threads/PluginThread.py:254
+msgid "Couldn't connect to host or connection reset, waiting 1 minute and retry."
+msgstr "無法連線至host䞀分鐘埌重詊"
+
+#: pyload/threads/PluginThread.py:290
+#, python-format
+msgid "Download skipped: %(name)s due to %(plugin)s"
+msgstr "%(name)s 取消䞋茉由斌 %(plugin)s"
+
+#: pyload/threads/PluginThread.py:361
+#, python-format
+msgid "Decrypting starts: %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:376 pyload/threads/PluginThread.py:394
+#, python-format
+msgid "Decrypting failed: %(name)s | %(msg)s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:388
+#, python-format
+msgid "Retrying %s"
+msgstr ""
+
+#: pyload/threads/PluginThread.py:635
+#, python-format
+msgid "Info Fetching for %(name)s failed | %(err)s"
+msgstr "取埗 %(name)s的錯誀資蚊|%(err)s"
+
+#: pyload/HookManager.py:90 pyload/plugins/Hook.py:102
+#, python-format
+msgid "Error executing hooks: %s"
+msgstr ""
+
+#: pyload/HookManager.py:140
+#, python-format
+msgid "Failed activating %(name)s"
+msgstr "啟甚 %(name)s 時癌生錯誀"
+
+#: pyload/HookManager.py:144
+#, python-format
+msgid "Activated plugins: %s"
+msgstr ""
+
+#: pyload/HookManager.py:145
+#, python-format
+msgid "Deactivate plugins: %s"
+msgstr ""
+
+#: pyload/ThreadManager.py:137
+#, python-format
+msgid "Reconnect Failed: %s"
+msgstr "重新連接倱敗: %s"
+
+#: pyload/ThreadManager.py:176
+msgid "Reconnect script not found!"
+msgstr "重新連接腳本未扟到 "
+
+#: pyload/ThreadManager.py:182
+msgid "Starting reconnect"
+msgstr "開始重詊"
+
+#: pyload/ThreadManager.py:196
+msgid "Failed executing reconnect script!"
+msgstr "執行重詊腳本倱敗"
+
+#: pyload/ThreadManager.py:208
+#, python-format
+msgid "Reconnected, new IP: %s"
+msgstr "䜿甚新IP: %s 重詊"
+
+#: pyload/ThreadManager.py:288
+msgid "Not enough space left on device"
+msgstr "儲存空間䞍足"
+
+#: pyload/plugins/Account.py:85 pyload/plugins/Account.py:91
+#, python-format
+msgid "Could not login with account %(user)s | %(msg)s"
+msgstr "無法登入%(user)s %(msg)s"
+
+#: pyload/plugins/Account.py:86
+msgid "Wrong Password"
+msgstr "密碌錯誀"
+
+#: pyload/plugins/Account.py:240
+#, python-format
+msgid "Your Time %s has wrong format, use: 1:22-3:44"
+msgstr ""
+
+#: pyload/plugins/Account.py:266
+#, python-format
+msgid "Account %s has not enough traffic, checking again in 30min"
+msgstr "垳戶 %s 流量甚鑿30分鐘埌重新檢查"
+
+#: pyload/plugins/Account.py:273
+#, python-format
+msgid "Account %s is expired, checking again in 1h"
+msgstr "垳戶 %s 已過期䞀小時埌重詊"
+
+#: pyload/plugins/crypter/SerienjunkiesOrg.py:128
+msgid "Downloadlimit reached"
+msgstr "已達到䞋茉流量䞊限"
+
+#: pyload/plugins/PluginManager.py:153
+#, python-format
+msgid "%s has a invalid pattern."
+msgstr ""
+
+#: pyload/plugins/PluginManager.py:272
+#, python-format
+msgid "Error importing %(name)s: %(msg)s"
+msgstr "癌珟錯誀 %(name)s: %(msg)s"
+
+#: pyload/plugins/internal/MultiHoster.py:133
+msgid "No Hoster loaded"
+msgstr "扟䞍到任䜕Hoster"
+
+#: pyload/plugins/accounts/BitshareCom.py:37
+msgid "Activate direct Download in your Bitshare Account"
+msgstr "埞悚的Bitshare垳號䞭盎接䞋茉"
+
+#: pyload/plugins/container/LinkList.py:65
+msgid "LinkList could not be cleared."
+msgstr ""
+
+#: pyload/plugins/AccountManager.py:88
+msgid "Account settings deleted, due to new config format."
+msgstr ""
+
+#: pyload/plugins/hoster/BasePlugin.py:65
+msgid "Authorization required (username:password)"
+msgstr "需芁認證垳號密碌"
+
+#: pyload/plugins/hoster/SimplydebridCom.py:25
+#: pyload/plugins/hoster/RealdebridCom.py:41
+#: pyload/plugins/hoster/FreeWayMe.py:38 pyload/plugins/hoster/ZeveraCom.py:22
+#: pyload/plugins/hoster/UnrestrictLi.py:53
+#: pyload/plugins/hoster/Premium4Me.py:28 pyload/plugins/hoster/FastixRu.py:36
+#: pyload/plugins/hoster/AlldebridCom.py:38
+#: pyload/plugins/hoster/DebridItaliaCom.py:40
+#: pyload/plugins/hoster/RPNetBiz.py:26
+#: pyload/plugins/hoster/MultiDebridCom.py:41
+#: pyload/plugins/hoster/ReloadCc.py:24 pyload/plugins/hoster/RehostTo.py:26
+#: pyload/plugins/hoster/PremiumizeMe.py:22
+#: pyload/plugins/hooks/RPNetBiz.py:43
+#, python-format
+msgid "Please enter your %s account or deactivate this plugin"
+msgstr "請茞入䜠的垳戶或是停甚歀暡組"
+
+#: pyload/plugins/hoster/FilesMailRu.py:99
+#, python-format
+msgid "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted."
+msgstr "䞋茉檔案䞭包含HTML語法解析錯誀嗎準備重詊"
+
+#: pyload/plugins/hoster/NetloadIn.py:146
+#: pyload/plugins/hoster/NetloadIn.py:170
+msgid "File temporarily not available"
+msgstr "檔案暫時無法䞋茉"
+
+#: pyload/plugins/hoster/NetloadIn.py:183
+#, python-format
+msgid "Netload: waiting between downloads %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:214
+#, python-format
+msgid "Netload: waiting for captcha %d s."
+msgstr ""
+
+#: pyload/plugins/hoster/NetloadIn.py:252
+msgid "Downloaded File was empty"
+msgstr "䞋茉的檔案是空的"
+
+#: pyload/plugins/hoster/UploadedTo.py:129
+msgid "API key invalid"
+msgstr "API金鑰無效"
+
+#: pyload/plugins/hoster/UploadedTo.py:153
+#, python-format
+msgid "%s: Not enough traffic left"
+msgstr "%s: 流量已甚完"
+
+#: pyload/plugins/hoster/UploadedTo.py:156
+msgid "Traffic exceeded"
+msgstr "超出流量限制"
+
+#: pyload/plugins/hoster/RapidshareCom.py:100
+msgid "Rapidshare: Traffic Share (direct download)"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:127
+#: pyload/plugins/hoster/RapidshareCom.py:194
+msgid "Already downloading from this ip address, waiting 60 seconds"
+msgstr "æ­€IP䜍址已圚䞋茉及䞀項目請等埅六十秒埌重詊"
+
+#: pyload/plugins/hoster/RapidshareCom.py:131
+msgid "Invalid Auth Code, download will be restarted"
+msgstr "驗證倱敗任務即將重詊"
+
+#: pyload/plugins/hoster/RapidshareCom.py:199
+msgid "RapidShareCom: No free slots"
+msgstr ""
+
+#: pyload/plugins/hoster/RapidshareCom.py:202
+msgid "You need a premium account for this file"
+msgstr "歀檔案需芁高玚甚戶才胜䞋茉"
+
+#: pyload/plugins/hoster/RapidshareCom.py:204
+msgid "Filename reported invalid"
+msgstr "䞍正確的檔名"
+
+#: pyload/plugins/hoster/FileserveCom.py:99
+msgid "Parallel download error, now waiting 60s."
+msgstr "同時䞋茉錯誀60秒埌重詊"
+
+#: pyload/plugins/hoster/FileserveCom.py:215
+msgid "Not logged in."
+msgstr "未登入"
+
+#: pyload/plugins/hoster/MegaNz.py:56
+msgid "Decryption failed"
+msgstr "解析倱敗"
+
+#: pyload/plugins/hoster/MegaNz.py:106
+msgid "No file key provided in the URL"
+msgstr "URL未提䟛金鑰"
+
+#: pyload/plugins/hoster/MegaNz.py:118
+msgid "Error code:"
+msgstr "錯誀碌"
+
+#: pyload/plugins/Container.py:68
+msgid "File not exists."
+msgstr ""
+
+#: pyload/plugins/hooks/UpdateManager.py:74
+msgid "*** Plugins have been updated, please restart pyLoad ***"
+msgstr "倖掛已曎新請重新啟動pyLoad"
+
+#: pyload/plugins/hooks/UpdateManager.py:76
+msgid "Plugins updated and reloaded"
+msgstr "倖掛已曎新"
+
+#: pyload/plugins/hooks/UpdateManager.py:79
+msgid "No plugin updates available"
+msgstr "倖掛沒有可甚的曎新"
+
+#: pyload/plugins/hooks/UpdateManager.py:96
+msgid "No Updates for pyLoad"
+msgstr "pyLoad沒有可甚的曎新"
+
+#: pyload/plugins/hooks/UpdateManager.py:100
+#, python-format
+msgid "*** New pyLoad Version %s available ***"
+msgstr "pyLoad有新版本%s可䜿甚"
+
+#: pyload/plugins/hooks/UpdateManager.py:101
+msgid "*** Get it here: http://pyload.org/download ***"
+msgstr "*** 請埞歀䞋茉 http://pyload.org/download ***"
+
+#: pyload/plugins/hooks/UpdateManager.py:104
+msgid "Not able to connect server for updates"
+msgstr "無法連線至曎新䌺服噚"
+
+#: pyload/plugins/hooks/UpdateManager.py:148
+#, python-format
+msgid "New version of %(type)s|%(name)s : %(version).2f"
+msgstr "新版本 %(type)s|%(name)s : %(version).2f"
+
+#: pyload/plugins/hooks/UpdateManager.py:157
+#: pyload/plugins/hooks/UpdateManager.py:162
+#, python-format
+msgid "Error when updating %s"
+msgstr "曎新 %s 時癌生錯誀"
+
+#: pyload/plugins/hooks/UpdateManager.py:162
+msgid "Version mismatch"
+msgstr "版本䞍合"
+
+#: pyload/plugins/hooks/IRCInterface.py:82
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s "
+msgstr "%(name)s @ %(plugin)s 已完成"
+
+#: pyload/plugins/hooks/IRCInterface.py:95
+#, python-format
+msgid "New Captcha Request: %s"
+msgstr "驗證碌請求: %s"
+
+#: pyload/plugins/hooks/IRCInterface.py:96
+#, python-format
+msgid "Answer with 'c %s text on the captcha'"
+msgstr "請茞入驗證碌䞊的文字 %s"
+
+#: pyload/plugins/hooks/Premium4Me.py:29
+msgid "Please add your premium.to account first and restart pyLoad"
+msgstr "請先添加premium.to垳戶䞊重新啟動pyLoad"
+
+#: pyload/plugins/hooks/HotFolder.py:82
+#, python-format
+msgid "Added %s from HotFolder"
+msgstr "埞HotFolder增加 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:95
+#, python-format
+msgid "No %s installed"
+msgstr "未安裝 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:97
+#: pyload/plugins/hooks/ExtractArchive.py:102
+#, python-format
+msgid "Could not activate %s"
+msgstr "無法啟甚 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:107
+msgid "Activated"
+msgstr "已啟甚"
+
+#: pyload/plugins/hooks/ExtractArchive.py:109
+msgid "No Extract plugins activated"
+msgstr "解壓瞮倖掛未啟甚"
+
+#: pyload/plugins/hooks/ExtractArchive.py:121
+#, python-format
+msgid "Package %s queued for later extracting"
+msgstr "任務 %s 已排入解壓䜇列"
+
+#: pyload/plugins/hooks/ExtractArchive.py:144
+#, python-format
+msgid "Check package %s"
+msgstr "檢查䞋茉任務 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:185
+#, python-format
+msgid "Extract to %s"
+msgstr "解壓瞮至 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:200
+msgid "No files found to extract"
+msgstr "無法扟到壓瞮檔"
+
+#: pyload/plugins/hooks/ExtractArchive.py:207
+msgid "extracting"
+msgstr "解壓瞮䞭"
+
+#: pyload/plugins/hooks/ExtractArchive.py:218
+msgid "Password protected"
+msgstr "被加密的壓瞮檔"
+
+#: pyload/plugins/hooks/ExtractArchive.py:239
+msgid "Wrong password"
+msgstr "密碌錯誀"
+
+#: pyload/plugins/hooks/ExtractArchive.py:247
+#, python-format
+msgid "Deleting %s files"
+msgstr "刪陀檔案 %s"
+
+#: pyload/plugins/hooks/ExtractArchive.py:254
+msgid "Extracting finished"
+msgstr "解壓瞮完成"
+
+#: pyload/plugins/hooks/ExtractArchive.py:260
+msgid "Archive Error"
+msgstr "壓瞮檔損毀"
+
+#: pyload/plugins/hooks/ExtractArchive.py:262
+msgid "CRC Mismatch"
+msgstr "CRC䞍正確"
+
+#: pyload/plugins/hooks/ExtractArchive.py:266
+msgid "Unknown Error"
+msgstr "未知錯誀"
+
+#: pyload/plugins/hooks/ExtractArchive.py:318
+msgid "Setting User and Group failed"
+msgstr "蚭定䜿甚者及矀組倱敗"
+
+#: pyload/plugins/hooks/ClickAndLoad.py:75
+msgid "Click'N'Load: Port 9666 already in use"
+msgstr "Click'N'Load: 9666阜已被䜿甚"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:69
+#: pyload/plugins/hooks/Captcha9kw.py:59
+#: pyload/plugins/hooks/ExpertDecoders.py:50
+#, python-format
+msgid "%s credits left"
+msgstr "額床剩逘 %s"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:117
+msgid "Could not send response."
+msgstr "無法癌送回應。"
+
+#: pyload/plugins/hooks/CaptchaTrader.py:135
+msgid "Your CaptchaTrader Account has not enough credits"
+msgstr "CaptchaTrader垳戶額床䞍足"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:43
+msgid "Crypter list not found"
+msgstr "未扟到Crypteræž…å–®"
+
+#: pyload/plugins/hooks/LinkdecrypterCom.py:57
+msgid "Crypter list is empty"
+msgstr "Crypter枅單是空的"
+
+#: pyload/plugins/hooks/XMPPInterface.py:91
+#, python-format
+msgid "Download finished: %(name)s @ %(plugin)s"
+msgstr "%(name)s @ %(plugin)s 完成"
+
+#: pyload/plugins/hooks/Captcha9kw.py:93
+#, python-format
+msgid "New CaptchaID from upload: %s : %s"
+msgstr "䞊傳新的CaptchaID%s:%s"
+
+#: pyload/plugins/hooks/Captcha9kw.py:129
+msgid "Your Captcha 9kw.eu Account has not enough credits"
+msgstr "9kw.eu垳戶額床䞍足"
+
+#: pyload/plugins/hooks/ExternalScripts.py:54
+#, python-format
+msgid "Installed scripts for %s: "
+msgstr "%s 已安裝的腳本"
+
+#: pyload/plugins/hooks/ExternalScripts.py:69
+msgid "Script not executable:"
+msgstr "無法執行腳本"
+
+#: pyload/plugins/hooks/ExternalScripts.py:80
+#, python-format
+msgid "Error in %(script)s: %(error)s"
+msgstr "%(script)s %(error)s"
+
+#: pyload/plugins/hooks/ExpertDecoders.py:95
+msgid "Your ExpertDecoders Account has not enough credits"
+msgstr "ExpertDecoders垳戶額床䞍足"
+
+#: pyload/plugins/hooks/RehostTo.py:32
+msgid "Please add your rehost.to account first and restart pyLoad"
+msgstr "請先添加rehost.to垳戶䞊重新啟動pyLoad"
+
+#: pyload/plugins/hooks/PremiumizeMe.py:48
+msgid "Please add a valid premiumize.me account first and restart pyLoad."
+msgstr "請先蚭定premiumize.me垳戶䞊重新啟動pyLoad"
+
+#: pyload/plugins/hooks/CaptchaBrotherhood.py:69
+#, python-format
+msgid "%d credits left"
+msgstr "額床剩䞋 %d"
+
+#: pyload/plugins/Plugin.py:389
+msgid "Pil and tesseract not installed and no Client connected for captcha decrypting"
+msgstr "尚未安裝Pil及tesseract尚未連接至需芁解析的客戶端"
+
+#: pyload/plugins/Plugin.py:393
+msgid "No captcha result obtained in appropiate time by any of the plugins."
+msgstr ""
+
+#: pyload/plugins/Plugin.py:498 pyload/plugins/Plugin.py:532
+#, python-format
+msgid "Setting User and Group failed: %s"
+msgstr "蚭眮䜿甚者和組倱敗 %s"
+
+#: pyload/CaptchaManager.py:78
+msgid "No Client connected for captcha decrypting"
+msgstr ""
+
+#: pyload/Api.py:330
+#, python-format
+msgid "Added package %(name)s containing %(count)d links"
+msgstr ""
+
+#: pyload/Api.py:593
+#, python-format
+msgid "Added %(count)d links to package #%(package)d "
+msgstr ""
+
+#: pyload/common/JsEngine.py:156
+msgid "No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
diff --git a/locale/zh/LC_MESSAGES/pyLoadCli.po b/locale/zh/LC_MESSAGES/pyLoadCli.po
new file mode 100644
index 000000000..fd5ebd330
--- /dev/null
+++ b/locale/zh/LC_MESSAGES/pyLoadCli.po
@@ -0,0 +1,295 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Chinese Traditional\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/cli/Cli.py:75 pyload/cli/Cli.py:133
+msgid " Command Line Interface"
+msgstr "呜什列介面"
+
+#: pyload/cli/Cli.py:165
+#, python-format
+msgid "%s Downloads:"
+msgstr "已䞋茉 %s:"
+
+#: pyload/cli/Cli.py:177
+msgid " Speed: "
+msgstr "速床:"
+
+#: pyload/cli/Cli.py:177
+msgid " Size: "
+msgstr "倧小:"
+
+#: pyload/cli/Cli.py:178
+msgid " Finished in: "
+msgstr "距離完成:"
+
+#: pyload/cli/Cli.py:179
+msgid " ID: "
+msgstr "線號:"
+
+#: pyload/cli/Cli.py:184
+msgid "waiting: "
+msgstr "等埅䞭:"
+
+#: pyload/cli/Cli.py:191 pyload/cli/Cli.py:193
+msgid "Status:"
+msgstr "狀態:"
+
+#: pyload/cli/Cli.py:191
+msgid "paused"
+msgstr "已暫停"
+
+#: pyload/cli/Cli.py:193
+msgid "running"
+msgstr "執行䞭"
+
+#: pyload/cli/Cli.py:196
+msgid "total Speed"
+msgstr "瞜速床"
+
+#: pyload/cli/Cli.py:196
+msgid "Files in queue"
+msgstr "䜇列䞭檔案"
+
+#: pyload/cli/Cli.py:197
+msgid "Total"
+msgstr "瞜蚈"
+
+#: pyload/cli/Cli.py:203
+msgid "Menu:"
+msgstr "衚單:"
+
+#: pyload/cli/Cli.py:205
+msgid " Add Links"
+msgstr "加入連結"
+
+#: pyload/cli/Cli.py:206
+msgid " Manage Queue"
+msgstr "䜇列管理"
+
+#: pyload/cli/Cli.py:207
+msgid " Manage Collector"
+msgstr "收集噚管理"
+
+#: pyload/cli/Cli.py:208
+msgid " (Un)Pause Server"
+msgstr "暫停/啟動 䌺服噚"
+
+#: pyload/cli/Cli.py:209
+msgid " Kill Server"
+msgstr "終止䌺服噚"
+
+#: pyload/cli/Cli.py:210
+msgid " Quit"
+msgstr "離開"
+
+#: pyload/cli/Cli.py:289 pyload/cli/Cli.py:296
+msgid "Please use this syntax: add <Package name> <link> <link2> ..."
+msgstr "請䜿甚歀語法: add 任務名皱> <連結> <連結2> ..."
+
+#: pyload/cli/Cli.py:315
+#, python-format
+msgid "Checking %d links:"
+msgstr "檢查 %d 連結:"
+
+#: pyload/cli/Cli.py:324
+msgid "File does not exists."
+msgstr "檔案䞍存圚。"
+
+#: pyload/cli/Cli.py:385
+msgid "pyLoad was terminated"
+msgstr "pyLoad 已終止"
+
+#: pyload/cli/Cli.py:443
+msgid "Prints server status"
+msgstr "顯瀺䌺服噚狀態"
+
+#: pyload/cli/Cli.py:444
+msgid "Prints downloads in queue"
+msgstr "顯瀺䜇列䞭䞋茉項目"
+
+#: pyload/cli/Cli.py:445
+msgid "Prints downloads in collector"
+msgstr "顯瀺收集噚䞭䞋茉項目"
+
+#: pyload/cli/Cli.py:446
+msgid "Adds package to queue"
+msgstr "新增任務到䜇列"
+
+#: pyload/cli/Cli.py:447
+msgid "Adds package to collector"
+msgstr "新增任務到收集噚"
+
+#: pyload/cli/Cli.py:448
+msgid "Delete Files from Queue/Collector"
+msgstr "埞䜇列/收集噚䞭刪陀檔案"
+
+#: pyload/cli/Cli.py:449
+msgid "Delete Packages from Queue/Collector"
+msgstr "埞䜇列/收集噚䞭刪陀任務"
+
+#: pyload/cli/Cli.py:450
+msgid "Move Packages from Queue to Collector or vice versa"
+msgstr "圚䜇列或收集噚䞭移動任務"
+
+#: pyload/cli/Cli.py:451
+msgid "Restart files"
+msgstr "重新開始檔案"
+
+#: pyload/cli/Cli.py:452
+msgid "Restart packages"
+msgstr "重新開始任務"
+
+#: pyload/cli/Cli.py:453
+msgid "Check online status, works with local container"
+msgstr "查看線䞊狀態工䜜與本機容噚"
+
+#: pyload/cli/Cli.py:454
+msgid "Checks online status of a container file"
+msgstr "檢查容噚內檔案䞊線狀態"
+
+#: pyload/cli/Cli.py:455
+msgid "Pause the server"
+msgstr "暫停䌺服噚"
+
+#: pyload/cli/Cli.py:456
+msgid "continue downloads"
+msgstr "繌續䞋茉"
+
+#: pyload/cli/Cli.py:457
+msgid "Toggle pause/unpause"
+msgstr "暫停/繌續"
+
+#: pyload/cli/Cli.py:458
+msgid "kill server"
+msgstr "終止䌺服噚"
+
+#: pyload/cli/Cli.py:460
+msgid "List of commands:"
+msgstr "呜什枅單"
+
+#: pyload/cli/Cli.py:473
+msgid "Couldn't write user config file"
+msgstr "䞍胜寫入䜿甚者蚭定檔"
+
+#: pyload/cli/Cli.py:548
+msgid "You need py-openssl to connect to this pyLoad Core."
+msgstr "悚需芁安装py-openssl来连接这䞪pyLoad栞心。"
+
+#: pyload/cli/Cli.py:555
+msgid "Address: "
+msgstr "䜍址:"
+
+#: pyload/cli/Cli.py:556
+msgid "Port: "
+msgstr "連接埠:"
+
+#: pyload/cli/Cli.py:557
+msgid "Username: "
+msgstr "䜿甚者名皱:"
+
+#: pyload/cli/Cli.py:561
+msgid "Password: "
+msgstr "密碌:"
+
+#: pyload/cli/Cli.py:566 pyload/cli/Cli.py:575
+msgid "Login data is wrong."
+msgstr "登入資蚊錯誀。"
+
+#: pyload/cli/Cli.py:568 pyload/cli/Cli.py:577
+#, python-format
+msgid "Could not establish connection to %(addr)s:%(port)s."
+msgstr "無法建立 %(addr)s:%(port)s 連線。"
+
+#: pyload/cli/Cli.py:580
+msgid "You need py-openssl to connect to this pyLoad core."
+msgstr "悚需芁 py-openssl 䟆連接 pyLoad 栞心。"
+
+#: pyload/cli/Cli.py:582
+msgid "Interactive mode ignored since you passed some commands."
+msgstr "執行呜什䞭応略互動暡匏。"
+
+#: pyload/cli/AddPackage.py:48
+msgid "Add Package:"
+msgstr "新增䞋茉任務"
+
+#: pyload/cli/AddPackage.py:53
+msgid "Enter a name for the new package"
+msgstr "茞入新任務名皱"
+
+#: pyload/cli/AddPackage.py:57
+#, python-format
+msgid "Package: %s"
+msgstr "任務%s"
+
+#: pyload/cli/AddPackage.py:58
+msgid "Parse the links you want to add."
+msgstr "分析䜠想加入的連結。"
+
+#: pyload/cli/AddPackage.py:59
+#, python-format
+msgid "Type %s when done."
+msgstr "完成埌請茞入 %s"
+
+#: pyload/cli/AddPackage.py:60
+msgid "Links added: "
+msgstr "加入的連結:"
+
+#: pyload/cli/AddPackage.py:64 pyload/cli/ManageFiles.py:149
+msgid " back to main menu"
+msgstr "回到䞻遞單"
+
+#: pyload/cli/ManageFiles.py:97
+msgid "Manage Packages:"
+msgstr "管理任務"
+
+#: pyload/cli/ManageFiles.py:99
+msgid "Manage Links:"
+msgstr "管理連結"
+
+#: pyload/cli/ManageFiles.py:104
+msgid "What do you want to move?"
+msgstr "䜠想芁移動?"
+
+#: pyload/cli/ManageFiles.py:106
+msgid "What do you want to delete?"
+msgstr "䜠想芁刪陀?"
+
+#: pyload/cli/ManageFiles.py:108
+msgid "What do you want to restart?"
+msgstr "䜠想芁重啟?"
+
+#: pyload/cli/ManageFiles.py:113
+msgid "Choose what yout want to do or enter package number."
+msgstr ""
+
+#: pyload/cli/ManageFiles.py:115
+msgid "delete"
+msgstr "刪陀"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "move"
+msgstr "移動"
+
+#: pyload/cli/ManageFiles.py:115
+msgid "restart"
+msgstr "重啟"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - previous"
+msgstr " - 侊侀頁"
+
+#: pyload/cli/ManageFiles.py:148
+msgid " - next"
+msgstr "- 例侀頁"
+
diff --git a/locale/zh/LC_MESSAGES/setup.po b/locale/zh/LC_MESSAGES/setup.po
new file mode 100644
index 000000000..4496c4010
--- /dev/null
+++ b/locale/zh/LC_MESSAGES/setup.po
@@ -0,0 +1,459 @@
+msgid ""
+msgstr "Project-Id-Version: pyload\n"
+"Report-Msgid-Bugs-To: 'bugs@pyload.org'\n"
+"POT-Creation-Date: 2014-04-08 15:01+0200\n"
+"PO-Revision-Date: 2014-04-09 05:38-0400\n"
+"Last-Translator: pyloadTeam <team@pyload.org>\n"
+"Language-Team: Chinese Traditional\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: crowdin.net\n"
+
+#: pyload/setup.py:51
+msgid "y"
+msgstr "是"
+
+#: pyload/setup.py:53
+msgid "n"
+msgstr "吊"
+
+#: pyload/setup.py:72
+msgid "Welcome to the pyLoad Configuration Assistent."
+msgstr "歡迎䜿甚pyLoad蚭定助手"
+
+#: pyload/setup.py:73
+msgid "It will check your system and make a basic setup in order to run pyLoad."
+msgstr "它將會檢查悚的系統䞊完成基本蚭定"
+
+#: pyload/setup.py:75
+msgid "The value in brackets [] always is the default value,"
+msgstr "䞭括匧內的倌為預蚭倌"
+
+#: pyload/setup.py:76
+msgid "in case you don't want to change it or you are unsure what to choose, just hit enter."
+msgstr "劂果悚䞍知道該劂䜕蚭定或是䞍需改變請盎接按䞋Enter"
+
+#: pyload/setup.py:78
+msgid "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyload.py ."
+msgstr "小提醒 悚以埌可以透過pyLoadCore -s 或是 pyload.py --setup重新執行蚭定助手"
+
+#: pyload/setup.py:79
+msgid "If you have any problems with this assistent hit STRG-C,"
+msgstr "劂果䜠对本助理有任䜕疑问请摁STRG-C"
+
+#: pyload/setup.py:80
+msgid "to abort and don't let him start with pyload.py automatically anymore."
+msgstr "䞍再讓蚭定助手與pyLoad同時啟動"
+
+#: pyload/setup.py:82
+msgid "When you are ready for system check, hit enter."
+msgstr "按䞋確定埌開始檢查系統狀態"
+
+#: pyload/setup.py:89
+msgid "You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad."
+msgstr "䜠需芁pycurlsqlite以及python 2.52.6或是2.7来运行pyLoad。"
+
+#: pyload/setup.py:90
+msgid "Please correct this and re-run pyLoad."
+msgstr "请修改后重新运行pyLoad"
+
+#: pyload/setup.py:91
+msgid "Setup will now close."
+msgstr "安装向富关闭。"
+
+#: pyload/setup.py:95
+msgid "System check finished, hit enter to see your status report."
+msgstr "系统检查完毕恩䞋回蜊以查看系统报告。"
+
+#: pyload/setup.py:97
+msgid "## Status ##"
+msgstr "进皋"
+
+#: pyload/setup.py:101
+msgid "container decrypting"
+msgstr ""
+
+#: pyload/setup.py:102
+msgid "ssl connection"
+msgstr ""
+
+#: pyload/setup.py:103
+msgid "automatic captcha decryption"
+msgstr ""
+
+#: pyload/setup.py:104
+msgid "GUI"
+msgstr ""
+
+#: pyload/setup.py:105
+msgid "Webinterface"
+msgstr "Web界面"
+
+#: pyload/setup.py:106
+msgid "extended Click'N'Load"
+msgstr ""
+
+#: pyload/setup.py:113
+msgid "Features available:"
+msgstr ""
+
+#: pyload/setup.py:117
+msgid "Featues missing: "
+msgstr ""
+
+#: pyload/setup.py:121
+msgid "no py-crypto available"
+msgstr ""
+
+#: pyload/setup.py:122
+msgid "You need this if you want to decrypt container files."
+msgstr ""
+
+#: pyload/setup.py:126
+msgid "no SSL available"
+msgstr ""
+
+#: pyload/setup.py:127
+msgid "This is needed if you want to establish a secure connection to core or webinterface."
+msgstr ""
+
+#: pyload/setup.py:128
+msgid "If you only want to access locally to pyLoad ssl is not usefull."
+msgstr ""
+
+#: pyload/setup.py:132
+msgid "no Captcha Recognition available"
+msgstr ""
+
+#: pyload/setup.py:133
+msgid "Only needed for some hosters and as freeuser."
+msgstr ""
+
+#: pyload/setup.py:137
+msgid "Gui not available"
+msgstr ""
+
+#: pyload/setup.py:138
+msgid "The Graphical User Interface."
+msgstr ""
+
+#: pyload/setup.py:142
+msgid "no JavaScript engine found"
+msgstr ""
+
+#: pyload/setup.py:143
+msgid "You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino"
+msgstr ""
+
+#: pyload/setup.py:145
+msgid "You can abort the setup now and fix some dependicies if you want."
+msgstr ""
+
+#: pyload/setup.py:147
+msgid "Continue with setup?"
+msgstr "是吊繌續蚭定"
+
+#: pyload/setup.py:153
+#, python-format
+msgid "Do you want to change the config path? Current is %s"
+msgstr "目前存攟蚭定檔的䜍眮為 %s是吊芁修改"
+
+#: pyload/setup.py:155
+msgid "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it."
+msgstr ""
+
+#: pyload/setup.py:156
+msgid "Change config path?"
+msgstr "改變蚭定擋路埑"
+
+#: pyload/setup.py:162
+msgid "Do you want to configure login data and basic settings?"
+msgstr "是吊芁進行基本蚭定"
+
+#: pyload/setup.py:163
+msgid "This is recommend for first run."
+msgstr "第䞀次䜿甚pyLoad時建議䜿甚"
+
+#: pyload/setup.py:164
+msgid "Make basic setup?"
+msgstr "開始基瀎蚭定嗎"
+
+#: pyload/setup.py:171
+msgid "Do you want to configure ssl?"
+msgstr "開始蚭定SSL嗎"
+
+#: pyload/setup.py:172
+msgid "Configure ssl?"
+msgstr "蚭定SSL"
+
+#: pyload/setup.py:178
+msgid "Do you want to configure webinterface?"
+msgstr "開始蚭定網頁介面"
+
+#: pyload/setup.py:179
+msgid "Configure webinterface?"
+msgstr "蚭定網頁介面"
+
+#: pyload/setup.py:184
+msgid "Setup finished successfully."
+msgstr "蚭定完成"
+
+#: pyload/setup.py:185
+msgid "Hit enter to exit and restart pyLoad"
+msgstr "按䞋Enter離開蚭定介面䞊重新啟動pyLoad"
+
+#: pyload/setup.py:191
+msgid "## System Check ##"
+msgstr ""
+
+#: pyload/setup.py:194
+msgid "Your python version is to new, Please use Python 2.6/2.7"
+msgstr ""
+
+#: pyload/setup.py:197
+msgid "Your python version is to old, Please use at least Python 2.5"
+msgstr ""
+
+#: pyload/setup.py:200
+msgid "Python Version: OK"
+msgstr ""
+
+#: pyload/setup.py:247
+#, python-format
+msgid "Your installed jinja2 version %s seems too old."
+msgstr ""
+
+#: pyload/setup.py:248
+msgid "You can safely continue but if the webinterface is not working,"
+msgstr ""
+
+#: pyload/setup.py:249
+msgid "please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary."
+msgstr ""
+
+#: pyload/setup.py:264
+msgid "JS engine"
+msgstr ""
+
+#: pyload/setup.py:270
+msgid "## Basic Setup ##"
+msgstr "基本蚭定"
+
+#: pyload/setup.py:273
+msgid "The following logindata is valid for CLI, GUI and webinterface."
+msgstr "䞋列蚊息圚CLI、GUI及網頁介面皆是有效的"
+
+#: pyload/setup.py:279 pyload/setup.py:370 pyload/setup.py:386
+msgid "Username"
+msgstr "䜿甚者名皱"
+
+#: pyload/setup.py:285
+msgid "External clients (GUI, CLI or other) need remote access to work over the network."
+msgstr ""
+
+#: pyload/setup.py:286
+msgid "However, if you only want to use the webinterface you may disable it to save ram."
+msgstr ""
+
+#: pyload/setup.py:287
+msgid "Enable remote access"
+msgstr ""
+
+#: pyload/setup.py:291
+msgid "Language"
+msgstr "語蚀"
+
+#: pyload/setup.py:293
+msgid "Downloadfolder"
+msgstr ""
+
+#: pyload/setup.py:294
+msgid "Max parallel downloads"
+msgstr "同時最倧䞋茉敞量"
+
+#: pyload/setup.py:298
+msgid "Use Reconnect?"
+msgstr "開啟重詊機制"
+
+#: pyload/setup.py:301
+msgid "Reconnect script location"
+msgstr "重詊腳本路埑"
+
+#: pyload/setup.py:306
+msgid "## Webinterface Setup ##"
+msgstr "網頁介面蚭定"
+
+#: pyload/setup.py:309
+msgid "Activate webinterface?"
+msgstr "開啟網頁介面"
+
+#: pyload/setup.py:311
+msgid "Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally."
+msgstr "監聜䜍址䜿甚127.0.0.1或localhost可讓網頁介面只有圚本地端可䜿甚"
+
+#: pyload/setup.py:312
+msgid "Address"
+msgstr "IP䜍址"
+
+#: pyload/setup.py:313
+msgid "Port"
+msgstr "埠"
+
+#: pyload/setup.py:315
+msgid "pyLoad offers several server backends, now following a short explanation."
+msgstr "pyLoad提䟛了幟皮server暡匏以䞋䜜簡短說明"
+
+#: pyload/setup.py:316
+msgid "Default server, best choice if you dont know which one to choose."
+msgstr ""
+
+#: pyload/setup.py:317
+msgid "This server offers SSL and is a good alternative to builtin."
+msgstr ""
+
+#: pyload/setup.py:319
+msgid "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job."
+msgstr "再適當的蚭定埌可以被apache、lighttpd䜿甚專業暡匏"
+
+#: pyload/setup.py:320
+msgid "Very fast alternative written in C, requires libev and linux knowlegde."
+msgstr ""
+
+#: pyload/setup.py:321
+msgid "Get it from here: https://github.com/jonashaag/bjoern, compile it"
+msgstr "由歀䞋茉 https://github.com/jonashaag/bjoern䞊線譯"
+
+#: pyload/setup.py:322
+msgid "and copy bjoern.so to pyload/lib"
+msgstr ""
+
+#: pyload/setup.py:326
+msgid "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface"
+msgstr "譊告內建(buildin)䌺服噚圚某些情況會無法運䜜"
+
+#: pyload/setup.py:327
+msgid "come back here and change the builtin server to the threaded one here."
+msgstr "劂果䜠癌珟網頁介面有任䜕問題請重新執行蚭定助手䞊將䌺服噚暡匏蚭定為其逘暡匏"
+
+#: pyload/setup.py:329
+msgid "Server"
+msgstr "䌺服噚"
+
+#: pyload/setup.py:334
+msgid "## SSL Setup ##"
+msgstr "SSL蚭定"
+
+#: pyload/setup.py:336
+msgid "Execute these commands from pyLoad config folder to make ssl certificates:"
+msgstr "圚pyLoad蚭定檔目錄䞭執行䞋列指什以取埗SSL憑證"
+
+#: pyload/setup.py:342
+msgid "If you're done and everything went fine, you can activate ssl now."
+msgstr "劂果䞀切順利珟圚就可以開啟SSL"
+
+#: pyload/setup.py:344
+msgid "Activate SSL?"
+msgstr "啟甚SSL"
+
+#: pyload/setup.py:360
+msgid "Select action"
+msgstr "遞擇動䜜 "
+
+#: pyload/setup.py:361
+msgid "1 - Create/Edit user"
+msgstr "1. 建立或線茯䜿甚者"
+
+#: pyload/setup.py:362
+msgid "2 - List users"
+msgstr "2. 列出所有䜿甚者"
+
+#: pyload/setup.py:363
+msgid "3 - Remove user"
+msgstr "3. 刪陀䜿甚者"
+
+#: pyload/setup.py:364
+msgid "4 - Quit"
+msgstr "4. 離開"
+
+#: pyload/setup.py:376
+msgid "Users"
+msgstr "䜿甚者"
+
+#: pyload/setup.py:403
+msgid "Setting new configpath, current configuration will not be transfered!"
+msgstr ""
+
+#: pyload/setup.py:404
+msgid "Configpath"
+msgstr ""
+
+#: pyload/setup.py:412
+msgid "Configpath changed, setup will now close, please restart to go on."
+msgstr ""
+
+#: pyload/setup.py:413
+msgid "Press Enter to exit."
+msgstr "按䞋 Enter 離開。"
+
+#: pyload/setup.py:417
+#, python-format
+msgid "Setting config path failed: %s"
+msgstr "蚭定檔路埑有誀 %s"
+
+#: pyload/setup.py:422
+#, python-format
+msgid "%s: OK"
+msgstr ""
+
+#: pyload/setup.py:424
+#, python-format
+msgid "%s: missing"
+msgstr ""
+
+#: pyload/setup.py:464
+msgid "Password: "
+msgstr "密碌:"
+
+#: pyload/setup.py:468
+msgid "Password too short. Use at least 4 symbols."
+msgstr "密碌至少需芁四個字元"
+
+#: pyload/setup.py:471
+msgid "Password (again): "
+msgstr "再茞入䞀次密碌"
+
+#: pyload/setup.py:477
+msgid "Passwords did not match."
+msgstr "密码䞍笊"
+
+#: pyload/setup.py:493
+msgid "yes"
+msgstr "是"
+
+#: pyload/setup.py:493
+msgid "true"
+msgstr "是"
+
+#: pyload/setup.py:493
+msgid "t"
+msgstr ""
+
+#: pyload/setup.py:496
+msgid "no"
+msgstr "吊"
+
+#: pyload/setup.py:496
+msgid "false"
+msgstr "吊"
+
+#: pyload/setup.py:496
+msgid "f"
+msgstr ""
+
+#: pyload/setup.py:499 pyload/setup.py:509
+msgid "Invalid Input"
+msgstr "䞍合法的茞入"
+
diff --git a/module/Api.py b/module/Api.py
deleted file mode 100644
index f0bf5e264..000000000
--- a/module/Api.py
+++ /dev/null
@@ -1,1033 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from base64 import standard_b64encode
-from os.path import join
-from time import time
-import re
-
-from PyFile import PyFile
-from utils import freeSpace, compare_time
-from common.packagetools import parseNames
-from network.RequestFactory import getURL
-from remote import activated
-
-if activated:
- try:
- from remote.thriftbackend.thriftgen.pyload.ttypes import *
- from remote.thriftbackend.thriftgen.pyload.Pyload import Iface
- BaseObject = TBase
- except ImportError:
- print "Thrift not imported"
- from remote.socketbackend.ttypes import *
-else:
- from remote.socketbackend.ttypes import *
-
-# contains function names mapped to their permissions
-# unlisted functions are for admins only
-permMap = {}
-
-# decorator only called on init, never initialized, so has no effect on runtime
-def permission(bits):
- class _Dec(object):
- def __new__(cls, func, *args, **kwargs):
- permMap[func.__name__] = bits
- return func
-
- return _Dec
-
-
-urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE)
-
-class PERMS:
- ALL = 0 # requires no permission, but login
- ADD = 1 # can add packages
- DELETE = 2 # can delete packages
- STATUS = 4 # see and change server status
- LIST = 16 # see queue and collector
- MODIFY = 32 # moddify some attribute of downloads
- DOWNLOAD = 64 # can download from webinterface
- SETTINGS = 128 # can access settings
- ACCOUNTS = 256 # can access accounts
- LOGS = 512 # can see server logs
-
-class ROLE:
- ADMIN = 0 #admin has all permissions implicit
- USER = 1
-
-def has_permission(userperms, perms):
- # bytewise or perms before if needed
- return perms == (userperms & perms)
-
-
-class Api(Iface):
- """
- **pyLoads API**
-
- This is accessible either internal via core.api or via thrift backend.
-
- see Thrift specification file remote/thriftbackend/pyload.thrift\
- for information about data structures and what methods are usuable with rpc.
-
- Most methods requires specific permissions, please look at the source code if you need to know.\
- These can be configured via webinterface.
- Admin user have all permissions, and are the only ones who can access the methods with no specific permission.
- """
-
- EXTERNAL = Iface # let the json api know which methods are external
-
- def __init__(self, core):
- self.core = core
-
- def _convertPyFile(self, p):
- f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"],
- p["format_size"], p["status"], p["statusmsg"],
- p["package"], p["error"], p["order"])
- return f
-
- def _convertConfigFormat(self, c):
- sections = {}
- for sectionName, sub in c.iteritems():
- section = ConfigSection(sectionName, sub["desc"])
- items = []
- for key, data in sub.iteritems():
- if key in ("desc", "outline"):
- continue
- item = ConfigItem()
- item.name = key
- item.description = data["desc"]
- item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"]
- item.type = data["type"]
- items.append(item)
- section.items = items
- sections[sectionName] = section
- if "outline" in sub:
- section.outline = sub["outline"]
- return sections
-
- @permission(PERMS.SETTINGS)
- def getConfigValue(self, category, option, section="core"):
- """Retrieve config value.
-
- :param category: name of category, or plugin
- :param option: config option
- :param section: 'plugin' or 'core'
- :return: config value as string
- """
- if section == "core":
- value = self.core.config[category][option]
- else:
- value = self.core.config.getPlugin(category, option)
-
- return str(value) if not isinstance(value, basestring) else value
-
- @permission(PERMS.SETTINGS)
- def setConfigValue(self, category, option, value, section="core"):
- """Set new config value.
-
- :param category:
- :param option:
- :param value: new config value
- :param section: 'plugin' or 'core
- """
- self.core.hookManager.dispatchEvent("configChanged", category, option, value, section)
-
- if section == "core":
- self.core.config[category][option] = value
-
- if option in ("limit_speed", "max_speed"): #not so nice to update the limit
- self.core.requestFactory.updateBucket()
-
- elif section == "plugin":
- self.core.config.setPlugin(category, option, value)
-
- @permission(PERMS.SETTINGS)
- def getConfig(self):
- """Retrieves complete config of core.
-
- :return: list of `ConfigSection`
- """
- return self._convertConfigFormat(self.core.config.config)
-
- def getConfigDict(self):
- """Retrieves complete config in dict format, not for RPC.
-
- :return: dict
- """
- return self.core.config.config
-
- @permission(PERMS.SETTINGS)
- def getPluginConfig(self):
- """Retrieves complete config for all plugins.
-
- :return: list of `ConfigSection`
- """
- return self._convertConfigFormat(self.core.config.plugin)
-
- def getPluginConfigDict(self):
- """Plugin config as dict, not for RPC.
-
- :return: dict
- """
- return self.core.config.plugin
-
-
- @permission(PERMS.STATUS)
- def pauseServer(self):
- """Pause server: Tt wont start any new downloads, but nothing gets aborted."""
- self.core.threadManager.pause = True
-
- @permission(PERMS.STATUS)
- def unpauseServer(self):
- """Unpause server: New Downloads will be started."""
- self.core.threadManager.pause = False
-
- @permission(PERMS.STATUS)
- def togglePause(self):
- """Toggle pause state.
-
- :return: new pause state
- """
- self.core.threadManager.pause ^= True
- return self.core.threadManager.pause
-
- @permission(PERMS.STATUS)
- def toggleReconnect(self):
- """Toggle reconnect activation.
-
- :return: new reconnect state
- """
- self.core.config["reconnect"]["activated"] ^= True
- return self.core.config["reconnect"]["activated"]
-
- @permission(PERMS.LIST)
- def statusServer(self):
- """Some general information about the current status of pyLoad.
-
- :return: `ServerStatus`
- """
- serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()),
- self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
- not self.core.threadManager.pause and self.isTimeDownload(),
- self.core.config['reconnect']['activated'] and self.isTimeReconnect())
-
- for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]:
- serverStatus.speed += pyfile.getSpeed() #bytes/s
-
- return serverStatus
-
- @permission(PERMS.STATUS)
- def freeSpace(self):
- """Available free space at download directory in bytes"""
- return freeSpace(self.core.config["general"]["download_folder"])
-
- @permission(PERMS.ALL)
- def getServerVersion(self):
- """pyLoad Core version """
- return self.core.version
-
- def kill(self):
- """Clean way to quit pyLoad"""
- self.core.do_kill = True
-
- def restart(self):
- """Restart pyload core"""
- self.core.do_restart = True
-
- @permission(PERMS.LOGS)
- def getLog(self, offset=0):
- """Returns most recent log entries.
-
- :param offset: line offset
- :return: List of log entries
- """
- filename = join(self.core.config['log']['log_folder'], 'log.txt')
- try:
- fh = open(filename, "r")
- lines = fh.readlines()
- fh.close()
- if offset >= len(lines):
- return []
- return lines[offset:]
- except:
- return ['No log available']
-
- @permission(PERMS.STATUS)
- def isTimeDownload(self):
- """Checks if pyload will start new downloads according to time in config.
-
- :return: bool
- """
- start = self.core.config['downloadTime']['start'].split(":")
- end = self.core.config['downloadTime']['end'].split(":")
- return compare_time(start, end)
-
- @permission(PERMS.STATUS)
- def isTimeReconnect(self):
- """Checks if pyload will try to make a reconnect
-
- :return: bool
- """
- start = self.core.config['reconnect']['startTime'].split(":")
- end = self.core.config['reconnect']['endTime'].split(":")
- return compare_time(start, end) and self.core.config["reconnect"]["activated"]
-
- @permission(PERMS.LIST)
- def statusDownloads(self):
- """ Status off all currently running downloads.
-
- :return: list of `DownloadStatus`
- """
- data = []
- for pyfile in self.core.threadManager.getActiveFiles():
- if not isinstance(pyfile, PyFile):
- continue
-
- data.append(DownloadInfo(
- pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(),
- pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(),
- pyfile.status, pyfile.getStatusName(), pyfile.formatWait(),
- pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname))
-
- return data
-
- @permission(PERMS.ADD)
- def addPackage(self, name, links, dest=Destination.Queue):
- """Adds a package, with links to desired destination.
-
- :param name: name of the new package
- :param links: list of urls
- :param dest: `Destination`
- :return: package id of the new package
- """
- if self.core.config['general']['folder_per_package']:
- folder = name
- else:
- folder = ""
-
- folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_")
-
- pid = self.core.files.addPackage(name, folder, dest)
-
- self.core.files.addLinks(links, pid)
-
- self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)})
-
- self.core.files.save()
-
- return pid
-
- @permission(PERMS.ADD)
- def parseURLs(self, html=None, url=None):
- """Parses html content or any arbitaty text for links and returns result of `checkURLs`
-
- :param html: html source
- :return:
- """
- urls = []
-
- if html:
- urls += [x[0] for x in urlmatcher.findall(html)]
-
- if url:
- page = getURL(url)
- urls += [x[0] for x in urlmatcher.findall(page)]
-
- # remove duplicates
- return self.checkURLs(set(urls))
-
-
- @permission(PERMS.ADD)
- def checkURLs(self, urls):
- """ Gets urls and returns pluginname mapped to list of matches urls.
-
- :param urls:
- :return: {plugin: urls}
- """
- data = self.core.pluginManager.parseUrls(urls)
- plugins = {}
-
- for url, plugin in data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
- return plugins
-
- @permission(PERMS.ADD)
- def checkOnlineStatus(self, urls):
- """ initiates online status check
-
- :param urls:
- :return: initial set of data as `OnlineCheck` instance containing the result id
- """
- data = self.core.pluginManager.parseUrls(urls)
-
- rid = self.core.threadManager.createResultThread(data, False)
-
- tmp = [(url, (url, OnlineStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
- data = parseNames(tmp)
- result = {}
-
- for k, v in data.iteritems():
- for url, status in v:
- status.packagename = k
- result[url] = status
-
- return OnlineCheck(rid, result)
-
- @permission(PERMS.ADD)
- def checkOnlineStatusContainer(self, urls, container, data):
- """ checks online status of urls and a submited container file
-
- :param urls: list of urls
- :param container: container file name
- :param data: file content
- :return: online check
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
- th.write(str(data))
- th.close()
-
- return self.checkOnlineStatus(urls + [th.name])
-
- @permission(PERMS.ADD)
- def pollResults(self, rid):
- """ Polls the result available for ResultID
-
- :param rid: `ResultID`
- :return: `OnlineCheck`, if rid is -1 then no more data available
- """
- result = self.core.threadManager.getInfoResult(rid)
-
- if "ALL_INFO_FETCHED" in result:
- del result["ALL_INFO_FETCHED"]
- return OnlineCheck(-1, result)
- else:
- return OnlineCheck(rid, result)
-
-
- @permission(PERMS.ADD)
- def generatePackages(self, links):
- """ Parses links, generates packages names from urls
-
- :param links: list of urls
- :return: package names mapped to urls
- """
- result = parseNames((x, x) for x in links)
- return result
-
- @permission(PERMS.ADD)
- def generateAndAddPackages(self, links, dest=Destination.Queue):
- """Generates and add packages
-
- :param links: list of urls
- :param dest: `Destination`
- :return: list of package ids
- """
- return [self.addPackage(name, urls, dest) for name, urls
- in self.generatePackages(links).iteritems()]
-
- @permission(PERMS.ADD)
- def checkAndAddPackages(self, links, dest=Destination.Queue):
- """Checks online status, retrieves names, and will add packages.\
- Because of this packages are not added immediatly, only for internal use.
-
- :param links: list of urls
- :param dest: `Destination`
- :return: None
- """
- data = self.core.pluginManager.parseUrls(links)
- self.core.threadManager.createResultThread(data, True)
-
-
- @permission(PERMS.LIST)
- def getPackageData(self, pid):
- """Returns complete information about package, and included files.
-
- :param pid: package id
- :return: `PackageData` with .links attribute
- """
- data = self.core.files.getPackageData(int(pid))
-
- if not data:
- raise PackageDoesNotExists(pid)
-
- pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- links=[self._convertPyFile(x) for x in data["links"].itervalues()])
-
- return pdata
-
- @permission(PERMS.LIST)
- def getPackageInfo(self, pid):
- """Returns information about package, without detailed information about containing files
-
- :param pid: package id
- :return: `PackageData` with .fid attribute
- """
- data = self.core.files.getPackageData(int(pid))
-
- if not data:
- raise PackageDoesNotExists(pid)
-
- pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
- data["queue"], data["order"],
- fids=[int(x) for x in data["links"]])
-
- return pdata
-
- @permission(PERMS.LIST)
- def getFileData(self, fid):
- """Get complete information about a specific file.
-
- :param fid: file id
- :return: `FileData`
- """
- info = self.core.files.getFileData(int(fid))
- if not info:
- raise FileDoesNotExists(fid)
-
- fdata = self._convertPyFile(info.values()[0])
- return fdata
-
- @permission(PERMS.DELETE)
- def deleteFiles(self, fids):
- """Deletes several file entries from pyload.
-
- :param fids: list of file ids
- """
- for id in fids:
- self.core.files.deleteLink(int(id))
-
- self.core.files.save()
-
- @permission(PERMS.DELETE)
- def deletePackages(self, pids):
- """Deletes packages and containing links.
-
- :param pids: list of package ids
- """
- for id in pids:
- self.core.files.deletePackage(int(id))
-
- self.core.files.save()
-
- @permission(PERMS.LIST)
- def getQueue(self):
- """Returns info about queue and packages, **not** about files, see `getQueueData` \
- or `getPackageData` instead.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
- for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
-
- @permission(PERMS.LIST)
- def getQueueData(self):
- """Return complete data about everything in queue, this is very expensive use it sparely.\
- See `getQueue` for alternative.
-
- :return: list of `PackageData`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
- for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
-
- @permission(PERMS.LIST)
- def getCollector(self):
- """same as `getQueue` for collector.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- pack["linkstotal"])
- for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
-
- @permission(PERMS.LIST)
- def getCollectorData(self):
- """same as `getQueueData` for collector.
-
- :return: list of `PackageInfo`
- """
- return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
- pack["password"], pack["queue"], pack["order"],
- pack["linksdone"], pack["sizedone"], pack["sizetotal"],
- links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
- for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
-
-
- @permission(PERMS.ADD)
- def addFiles(self, pid, links):
- """Adds files to specific package.
-
- :param pid: package id
- :param links: list of urls
- """
- self.core.files.addLinks(links, int(pid))
-
- self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
- self.core.files.save()
-
- @permission(PERMS.MODIFY)
- def pushToQueue(self, pid):
- """Moves package from Collector to Queue.
-
- :param pid: package id
- """
- self.core.files.setPackageLocation(pid, Destination.Queue)
-
- @permission(PERMS.MODIFY)
- def pullFromQueue(self, pid):
- """Moves package from Queue to Collector.
-
- :param pid: package id
- """
- self.core.files.setPackageLocation(pid, Destination.Collector)
-
- @permission(PERMS.MODIFY)
- def restartPackage(self, pid):
- """Restarts a package, resets every containing files.
-
- :param pid: package id
- """
- self.core.files.restartPackage(int(pid))
-
- @permission(PERMS.MODIFY)
- def restartFile(self, fid):
- """Resets file status, so it will be downloaded again.
-
- :param fid: file id
- """
- self.core.files.restartFile(int(fid))
-
- @permission(PERMS.MODIFY)
- def recheckPackage(self, pid):
- """Proofes online status of all files in a package, also a default action when package is added.
-
- :param pid:
- :return:
- """
- self.core.files.reCheckPackage(int(pid))
-
- @permission(PERMS.MODIFY)
- def stopAllDownloads(self):
- """Aborts all running downloads."""
-
- pyfiles = self.core.files.cache.values()
- for pyfile in pyfiles:
- pyfile.abortDownload()
-
- @permission(PERMS.MODIFY)
- def stopDownloads(self, fids):
- """Aborts specific downloads.
-
- :param fids: list of file ids
- :return:
- """
- pyfiles = self.core.files.cache.values()
-
- for pyfile in pyfiles:
- if pyfile.id in fids:
- pyfile.abortDownload()
-
- @permission(PERMS.MODIFY)
- def setPackageName(self, pid, name):
- """Renames a package.
-
- :param pid: package id
- :param name: new package name
- """
- pack = self.core.files.getPackage(pid)
- pack.name = name
- pack.sync()
-
- @permission(PERMS.MODIFY)
- def movePackage(self, destination, pid):
- """Set a new package location.
-
- :param destination: `Destination`
- :param pid: package id
- """
- if destination not in (0, 1): return
- self.core.files.setPackageLocation(pid, destination)
-
- @permission(PERMS.MODIFY)
- def moveFiles(self, fids, pid):
- """Move multiple files to another package
-
- :param fids: list of file ids
- :param pid: destination package
- :return:
- """
- #TODO: implement
- pass
-
-
- @permission(PERMS.ADD)
- def uploadContainer(self, filename, data):
- """Uploads and adds a container file to pyLoad.
-
- :param filename: filename, extension is important so it can correctly decrypted
- :param data: file content
- """
- th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
- th.write(str(data))
- th.close()
-
- self.addPackage(th.name, [th.name], Destination.Queue)
-
- @permission(PERMS.MODIFY)
- def orderPackage(self, pid, position):
- """Gives a package a new position.
-
- :param pid: package id
- :param position:
- """
- self.core.files.reorderPackage(pid, position)
-
- @permission(PERMS.MODIFY)
- def orderFile(self, fid, position):
- """Gives a new position to a file within its package.
-
- :param fid: file id
- :param position:
- """
- self.core.files.reorderFile(fid, position)
-
- @permission(PERMS.MODIFY)
- def setPackageData(self, pid, data):
- """Allows to modify several package attributes.
-
- :param pid: package id
- :param data: dict that maps attribute to desired value
- """
- p = self.core.files.getPackage(pid)
- if not p: raise PackageDoesNotExists(pid)
-
- for key, value in data.iteritems():
- if key == "id": continue
- setattr(p, key, value)
-
- p.sync()
- self.core.files.save()
-
- @permission(PERMS.DELETE)
- def deleteFinished(self):
- """Deletes all finished files and completly finished packages.
-
- :return: list of deleted package ids
- """
- return self.core.files.deleteFinishedLinks()
-
- @permission(PERMS.MODIFY)
- def restartFailed(self):
- """Restarts all failed failes."""
- self.core.files.restartFailed()
-
- @permission(PERMS.LIST)
- def getPackageOrder(self, destination):
- """Returns information about package order.
-
- :param destination: `Destination`
- :return: dict mapping order to package id
- """
-
- packs = self.core.files.getInfoData(destination)
- order = {}
-
- for pid in packs:
- pack = self.core.files.getPackageData(int(pid))
- while pack["order"] in order.keys(): #just in case
- pack["order"] += 1
- order[pack["order"]] = pack["id"]
- return order
-
- @permission(PERMS.LIST)
- def getFileOrder(self, pid):
- """Information about file order within package.
-
- :param pid:
- :return: dict mapping order to file id
- """
- rawData = self.core.files.getPackageData(int(pid))
- order = {}
- for id, pyfile in rawData["links"].iteritems():
- while pyfile["order"] in order.keys(): #just in case
- pyfile["order"] += 1
- order[pyfile["order"]] = pyfile["id"]
- return order
-
-
- @permission(PERMS.STATUS)
- def isCaptchaWaiting(self):
- """Indicates wether a captcha task is available
-
- :return: bool
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTask()
- return not task is None
-
- @permission(PERMS.STATUS)
- def getCaptchaTask(self, exclusive=False):
- """Returns a captcha task
-
- :param exclusive: unused
- :return: `CaptchaTask`
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTask()
- if task:
- task.setWatingForUser(exclusive=exclusive)
- data, type, result = task.getCaptcha()
- t = CaptchaTask(int(task.id), standard_b64encode(data), type, result)
- return t
- else:
- return CaptchaTask(-1)
-
- @permission(PERMS.STATUS)
- def getCaptchaTaskStatus(self, tid):
- """Get information about captcha task
-
- :param tid: task id
- :return: string
- """
- self.core.lastClientConnected = time()
- t = self.core.captchaManager.getTaskByID(tid)
- return t.getStatus() if t else ""
-
- @permission(PERMS.STATUS)
- def setCaptchaResult(self, tid, result):
- """Set result for a captcha task
-
- :param tid: task id
- :param result: captcha result
- """
- self.core.lastClientConnected = time()
- task = self.core.captchaManager.getTaskByID(tid)
- if task:
- task.setResult(result)
- self.core.captchaManager.removeTask(task)
-
-
- @permission(PERMS.STATUS)
- def getEvents(self, uuid):
- """Lists occured events, may be affected to changes in future.
-
- :param uuid:
- :return: list of `Events`
- """
- events = self.core.pullManager.getEvents(uuid)
- newEvents = []
-
- def convDest(d):
- return Destination.Queue if d == "queue" else Destination.Collector
-
- for e in events:
- event = EventInfo()
- event.eventname = e[0]
- if e[0] in ("update", "remove", "insert"):
- event.id = e[3]
- event.type = ElementType.Package if e[2] == "pack" else ElementType.File
- event.destination = convDest(e[1])
- elif e[0] == "order":
- if e[1]:
- event.id = e[1]
- event.type = ElementType.Package if e[2] == "pack" else ElementType.File
- event.destination = convDest(e[3])
- elif e[0] == "reload":
- event.destination = convDest(e[1])
- newEvents.append(event)
- return newEvents
-
- @permission(PERMS.ACCOUNTS)
- def getAccounts(self, refresh):
- """Get information about all entered accounts.
-
- :param refresh: reload account info
- :return: list of `AccountInfo`
- """
- accs = self.core.accountManager.getAccountInfos(False, refresh)
- accounts = []
- for group in accs.values():
- accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"],
- acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"])
- for acc in group])
- return accounts
-
- @permission(PERMS.ALL)
- def getAccountTypes(self):
- """All available account types.
-
- :return: list
- """
- return self.core.accountManager.accounts.keys()
-
- @permission(PERMS.ACCOUNTS)
- def updateAccount(self, plugin, account, password=None, options={}):
- """Changes pw/options for specific account."""
- self.core.accountManager.updateAccount(plugin, account, password, options)
-
- @permission(PERMS.ACCOUNTS)
- def removeAccount(self, plugin, account):
- """Remove account from pyload.
-
- :param plugin: pluginname
- :param account: accountname
- """
- self.core.accountManager.removeAccount(plugin, account)
-
- @permission(PERMS.ALL)
- def login(self, username, password, remoteip=None):
- """Login into pyLoad, this **must** be called when using rpc before any methods can be used.
-
- :param username:
- :param password:
- :param remoteip: Omit this argument, its only used internal
- :return: bool indicating login was successful
- """
- return True if self.checkAuth(username, password, remoteip) else False
-
- def checkAuth(self, username, password, remoteip=None):
- """Check authentication and returns details
-
- :param username:
- :param password:
- :param remoteip:
- :return: dict with info, empty when login is incorrect
- """
- if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
- return "local"
- if self.core.startedInGui and remoteip == "127.0.0.1":
- return "local"
-
- return self.core.db.checkAuth(username, password)
-
- def isAuthorized(self, func, userdata):
- """checks if the user is authorized for specific method
-
- :param func: function name
- :param userdata: dictionary of user data
- :return: boolean
- """
- if userdata == "local" or userdata["role"] == ROLE.ADMIN:
- return True
- elif func in permMap and has_permission(userdata["permission"], permMap[func]):
- return True
- else:
- return False
-
-
- @permission(PERMS.ALL)
- def getUserData(self, username, password):
- """similar to `checkAuth` but returns UserData thrift type """
- user = self.checkAuth(username, password)
- if user:
- return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"])
- else:
- return UserData()
-
-
- def getAllUserData(self):
- """returns all known user and info"""
- res = {}
- for user, data in self.core.db.getAllUserData().iteritems():
- res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"])
-
- return res
-
- @permission(PERMS.STATUS)
- def getServices(self):
- """ A dict of available services, these can be defined by hook plugins.
-
- :return: dict with this style: {"plugin": {"method": "description"}}
- """
- data = {}
- for plugin, funcs in self.core.hookManager.methods.iteritems():
- data[plugin] = funcs
-
- return data
-
- @permission(PERMS.STATUS)
- def hasService(self, plugin, func):
- """Checks wether a service is available.
-
- :param plugin:
- :param func:
- :return: bool
- """
- cont = self.core.hookManager.methods
- return plugin in cont and func in cont[plugin]
-
- @permission(PERMS.STATUS)
- def call(self, info):
- """Calls a service (a method in hook plugin).
-
- :param info: `ServiceCall`
- :return: result
- :raises: ServiceDoesNotExists, when its not available
- :raises: ServiceException, when a exception was raised
- """
- plugin = info.plugin
- func = info.func
- args = info.arguments
- parse = info.parseArguments
-
- if not self.hasService(plugin, func):
- raise ServiceDoesNotExists(plugin, func)
-
- try:
- ret = self.core.hookManager.callRPC(plugin, func, args, parse)
- return str(ret)
- except Exception, e:
- raise ServiceException(e.message)
-
- @permission(PERMS.STATUS)
- def getAllInfo(self):
- """Returns all information stored by hook plugins. Values are always strings
-
- :return: {"plugin": {"name": value } }
- """
- return self.core.hookManager.getAllInfo()
-
- @permission(PERMS.STATUS)
- def getInfoByPlugin(self, plugin):
- """Returns information stored by a specific plugin.
-
- :param plugin: pluginname
- :return: dict of attr names mapped to value {"name": value}
- """
- return self.core.hookManager.getInfo(plugin)
-
- def changePassword(self, user, oldpw, newpw):
- """ changes password for specific user """
- return self.core.db.changePassword(user, oldpw, newpw)
-
- def setUserPermission(self, user, permission, role):
- self.core.db.setPermission(user, permission)
- self.core.db.setRole(user, role) \ No newline at end of file
diff --git a/module/CaptchaManager.py b/module/CaptchaManager.py
deleted file mode 100644
index 02cd10a11..000000000
--- a/module/CaptchaManager.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from time import time
-from traceback import print_exc
-from threading import Lock
-
-class CaptchaManager():
- def __init__(self, core):
- self.lock = Lock()
- self.core = core
- self.tasks = [] #task store, for outgoing tasks only
-
- self.ids = 0 #only for internal purpose
-
- def newTask(self, img, format, file, result_type):
- task = CaptchaTask(self.ids, img, format, file, result_type)
- self.ids += 1
- return task
-
- def removeTask(self, task):
- self.lock.acquire()
- if task in self.tasks:
- self.tasks.remove(task)
- self.lock.release()
-
- def getTask(self):
- self.lock.acquire()
- for task in self.tasks:
- if task.status in ("waiting", "shared-user"):
- self.lock.release()
- return task
- self.lock.release()
- return None
-
- def getTaskByID(self, tid):
- self.lock.acquire()
- for task in self.tasks:
- if task.id == str(tid): #task ids are strings
- self.lock.release()
- return task
- self.lock.release()
- return None
-
- def handleCaptcha(self, task):
- cli = self.core.isClientConnected()
-
- if cli: #client connected -> should solve the captcha
- task.setWaiting(50) #wait 50 sec for response
-
- for plugin in self.core.hookManager.activePlugins():
- try:
- plugin.newCaptchaTask(task)
- except:
- if self.core.debug:
- print_exc()
-
- if task.handler or cli: #the captcha was handled
- self.tasks.append(task)
- return True
-
- task.error = _("No Client connected for captcha decrypting")
-
- return False
-
-
-class CaptchaTask():
- def __init__(self, id, img, format, file, result_type='textual'):
- self.id = str(id)
- self.captchaImg = img
- self.captchaFormat = format
- self.captchaFile = file
- self.captchaResultType = result_type
- self.handler = [] #the hook plugins that will take care of the solution
- self.result = None
- self.waitUntil = None
- self.error = None #error message
-
- self.status = "init"
- self.data = {} #handler can store data here
-
- def getCaptcha(self):
- return self.captchaImg, self.captchaFormat, self.captchaResultType
-
- def setResult(self, text):
- if self.isTextual():
- self.result = text
- if self.isPositional():
- try:
- parts = text.split(',')
- self.result = (int(parts[0]), int(parts[1]))
- except:
- self.result = None
-
- def getResult(self):
- try:
- res = self.result.encode("utf8", "replace")
- except:
- res = self.result
-
- return res
-
- def getStatus(self):
- return self.status
-
- def setWaiting(self, sec):
- """ let the captcha wait secs for the solution """
- self.waitUntil = max(time() + sec, self.waitUntil)
- self.status = "waiting"
-
- def isWaiting(self):
- if self.result or self.error or time() > self.waitUntil:
- return False
-
- return True
-
- def isTextual(self):
- """ returns if text is written on the captcha """
- return self.captchaResultType == 'textual'
-
- def isPositional(self):
- """ returns if user have to click a specific region on the captcha """
- return self.captchaResultType == 'positional'
-
- def setWatingForUser(self, exclusive):
- if exclusive:
- self.status = "user"
- else:
- self.status = "shared-user"
-
- def timedOut(self):
- return time() > self.waitUntil
-
- def invalid(self):
- """ indicates the captcha was not correct """
- [x.captchaInvalid(self) for x in self.handler]
-
- def correct(self):
- [x.captchaCorrect(self) for x in self.handler]
-
- def __str__(self):
- return "<CaptchaTask '%s'>" % self.id
diff --git a/module/ConfigParser.py b/module/ConfigParser.py
deleted file mode 100644
index 78b612f13..000000000
--- a/module/ConfigParser.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-from time import sleep
-from os.path import exists, join
-from shutil import copy
-
-from traceback import print_exc
-from utils import chmod
-
-# ignore these plugin configs, mainly because plugins were wiped out
-IGNORE = (
- "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'),
- 'EasyShareCom', 'FlyshareCz'
- )
-
-CONF_VERSION = 1
-
-class ConfigParser:
- """
- holds and manage the configuration
-
- current dict layout:
-
- {
-
- section : {
- option : {
- value:
- type:
- desc:
- }
- desc:
-
- }
-
-
- """
-
-
- def __init__(self):
- """Constructor"""
- self.config = {} # the config values
- self.plugin = {} # the config for plugins
- self.oldRemoteData = {}
-
- self.pluginCB = None # callback when plugin config value is changed
-
- self.checkVersion()
-
- self.readConfig()
-
- self.deleteOldPlugins()
-
-
- def checkVersion(self, n=0):
- """determines if config need to be copied"""
- try:
- if not exists("pyload.conf"):
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
-
- if not exists("plugin.conf"):
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
-
- f = open("pyload.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- copy(join(pypath, "module", "config", "default.conf"), "pyload.conf")
- print "Old version of config was replaced"
-
- f = open("plugin.conf", "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":") + 1:].strip()
-
- if not v or int(v) < CONF_VERSION:
- f = open("plugin.conf", "wb")
- f.write("version: " + str(CONF_VERSION))
- f.close()
- print "Old version of plugin-config replaced"
- except:
- if n < 3:
- sleep(0.3)
- self.checkVersion(n + 1)
- else:
- raise
-
- def readConfig(self):
- """reads the config file"""
-
- self.config = self.parseConfig(join(pypath, "module", "config", "default.conf"))
- self.plugin = self.parseConfig("plugin.conf")
-
- try:
- homeconf = self.parseConfig("pyload.conf")
- if "username" in homeconf["remote"]:
- if "password" in homeconf["remote"]:
- self.oldRemoteData = {"username": homeconf["remote"]["username"]["value"],
- "password": homeconf["remote"]["username"]["value"]}
- del homeconf["remote"]["password"]
- del homeconf["remote"]["username"]
- self.updateValues(homeconf, self.config)
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
-
- def parseConfig(self, config):
- """parses a given configfile"""
-
- f = open(config)
-
- config = f.read()
-
- config = config.splitlines()[1:]
-
- conf = {}
-
- section, option, value, typ, desc = "", "", "", "", ""
-
- listmode = False
-
- for line in config:
- comment = line.rfind("#")
- if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace():
- line = line.rpartition("#") # removes comments
- if line[1]:
- line = line[0]
- else:
- line = line[2]
-
- line = line.strip()
-
- try:
- if line == "":
- continue
- elif line.endswith(":"):
- section, none, desc = line[:-1].partition('-')
- section = section.strip()
- desc = desc.replace('"', "").strip()
- conf[section] = {"desc": desc}
- else:
- if listmode:
- if line.endswith("]"):
- listmode = False
- line = line.replace("]", "")
-
- value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
-
- else:
- content, none, value = line.partition("=")
-
- content, none, desc = content.partition(":")
-
- desc = desc.replace('"', "").strip()
-
- typ, none, option = content.strip().rpartition(" ")
-
- value = value.strip()
-
- if value.startswith("["):
- if value.endswith("]"):
- listmode = False
- value = value[:-1]
- else:
- listmode = True
-
- value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
- else:
- value = self.cast(typ, value)
-
- if not listmode:
- conf[section][option] = {"desc": desc,
- "type": typ,
- "value": value}
-
- except Exception, e:
- print "Config Warning"
- print_exc()
-
- f.close()
- return conf
-
-
- def updateValues(self, config, dest):
- """sets the config values from a parsed config file to values in destination"""
-
- for section in config.iterkeys():
- if section in dest:
- for option in config[section].iterkeys():
- if option in ("desc", "outline"): continue
-
- if option in dest[section]:
- dest[section][option]["value"] = config[section][option]["value"]
-
- #else:
- # dest[section][option] = config[section][option]
-
-
- #else:
- # dest[section] = config[section]
-
- def saveConfig(self, config, filename):
- """saves config to filename"""
- with open(filename, "wb") as f:
- chmod(filename, 0600)
- f.write("version: %i \n" % CONF_VERSION)
- for section in config.iterkeys():
- f.write('\n%s - "%s":\n' % (section, config[section]["desc"]))
-
- for option, data in config[section].iteritems():
- if option in ("desc", "outline"): continue
-
- if isinstance(data["value"], list):
- value = "[ \n"
- for x in data["value"]:
- value += "\t\t" + str(x) + ",\n"
- value += "\t\t]\n"
- else:
- if type(data["value"]) in (str, unicode):
- value = data["value"] + "\n"
- else:
- value = str(data["value"]) + "\n"
- try:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value))
- except UnicodeEncodeError:
- f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value.encode("utf8")))
-
- def cast(self, typ, value):
- """cast value to given format"""
- if type(value) not in (str, unicode):
- return value
-
- elif typ == "int":
- return int(value)
- elif typ == "bool":
- return True if value.lower() in ("1", "true", "on", "an", "yes") else False
- elif typ == "time":
- if not value: value = "0:00"
- if not ":" in value: value += ":00"
- return value
- elif typ in ("str", "file", "folder"):
- try:
- return value.encode("utf8")
- except:
- return value
- else:
- return value
-
-
- def save(self):
- """saves the configs to disk"""
-
- self.saveConfig(self.config, "pyload.conf")
- self.saveConfig(self.plugin, "plugin.conf")
-
-
- def __getitem__(self, section):
- """provides dictonary like access: c['section']['option']"""
- return Section(self, section)
-
-
- def get(self, section, option):
- """get value"""
- val = self.config[section][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def set(self, section, option, value):
- """set value"""
-
- value = self.cast(self.config[section][option]["type"], value)
-
- self.config[section][option]["value"] = value
- self.save()
-
- def getPlugin(self, plugin, option):
- """gets a value for a plugin"""
- val = self.plugin[plugin][option]["value"]
- try:
- if type(val) in (str, unicode):
- return val.decode("utf8")
- else:
- return val
- except:
- return val
-
- def setPlugin(self, plugin, option, value):
- """sets a value for a plugin"""
-
- value = self.cast(self.plugin[plugin][option]["type"], value)
-
- if self.pluginCB: self.pluginCB(plugin, option, value)
-
- self.plugin[plugin][option]["value"] = value
- self.save()
-
- def getMetaData(self, section, option):
- """ get all config data for an option """
- return self.config[section][option]
-
- def addPluginConfig(self, name, config, outline=""):
- """adds config options with tuples (name, type, desc, default)"""
- if name not in self.plugin:
- conf = {"desc": name,
- "outline": outline}
- self.plugin[name] = conf
- else:
- conf = self.plugin[name]
- conf["outline"] = outline
-
- for item in config:
- if item[0] in conf:
- conf[item[0]]["type"] = item[1]
- conf[item[0]]["desc"] = item[2]
- else:
- conf[item[0]] = {
- "desc": item[2],
- "type": item[1],
- "value": self.cast(item[1], item[3])
- }
-
- values = [x[0] for x in config] + ["desc", "outline"]
- #delete old values
- for item in conf.keys():
- if item not in values:
- del conf[item]
-
- def deleteConfig(self, name):
- """Removes a plugin config"""
- if name in self.plugin:
- del self.plugin[name]
-
-
- def deleteOldPlugins(self):
- """ remove old plugins from config """
-
- for name in IGNORE:
- if name in self.plugin:
- del self.plugin[name]
-
-
-class Section:
- """provides dictionary like access for configparser"""
-
- def __init__(self, parser, section):
- """Constructor"""
- self.parser = parser
- self.section = section
-
- def __getitem__(self, item):
- """getitem"""
- return self.parser.get(self.section, item)
-
- def __setitem__(self, item, value):
- """setitem"""
- self.parser.set(self.section, item, value)
-
-
-if __name__ == "__main__":
- pypath = ""
-
- from time import time
-
- a = time()
-
- c = ConfigParser()
-
- b = time()
-
- print "sec", b - a
-
- print c.config
-
- c.saveConfig(c.config, "user.conf")
diff --git a/module/HookManager.py b/module/HookManager.py
deleted file mode 100644
index 16f692d76..000000000
--- a/module/HookManager.py
+++ /dev/null
@@ -1,315 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN, mkaay
- @interface-version: 0.1
-"""
-import __builtin__
-
-import traceback
-from thread import start_new_thread
-from threading import RLock
-
-from types import MethodType
-
-from module.PluginThread import HookThread
-from module.plugins.PluginManager import literal_eval
-from utils import lock
-
-class HookManager:
- """Manages hooks, delegates and handles Events.
-
- Every plugin can define events, \
- but some very usefull events are called by the Core.
- Contrary to overwriting hook methods you can use event listener,
- which provides additional entry point in the control flow.
- Only do very short tasks or use threads.
-
- **Known Events:**
- Most hook methods exists as events. These are the additional known events.
-
- ===================== ============== ==================================
- Name Arguments Description
- ===================== ============== ==================================
- downloadPreparing fid A download was just queued and will be prepared now.
- downloadStarts fid A plugin will immediately starts the download afterwards.
- linksAdded links, pid Someone just added links, you are able to modify the links.
- allDownloadsProcessed Every link was handled, pyload would idle afterwards.
- allDownloadsFinished Every download in queue is finished.
- unrarFinished folder, fname An Unrar job finished
- configChanged The config was changed via the api.
- pluginConfigChanged The plugin config changed, due to api or internal process.
- ===================== ============== ==================================
-
- | Notes:
- | allDownloadsProcessed is *always* called before allDownloadsFinished.
- | configChanged is *always* called before pluginConfigChanged.
-
-
- """
-
- def __init__(self, core):
- self.core = core
- self.config = self.core.config
-
- __builtin__.hookManager = self #needed to let hooks register themself
-
- self.log = self.core.log
- self.plugins = []
- self.pluginMap = {}
- self.methods = {} #dict of names and list of methods usable by rpc
-
- self.events = {} # contains events
-
- #registering callback for config event
- self.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring)
-
- self.addEvent("pluginConfigChanged", self.manageHooks)
-
- self.lock = RLock()
- self.createIndex()
-
- def try_catch(func):
- def new(*args):
- try:
- return func(*args)
- except Exception, e:
- args[0].log.error(_("Error executing hooks: %s") % str(e))
- if args[0].core.debug:
- traceback.print_exc()
-
- return new
-
-
- def addRPC(self, plugin, func, doc):
- plugin = plugin.rpartition(".")[2]
- doc = doc.strip() if doc else ""
-
- if plugin in self.methods:
- self.methods[plugin][func] = doc
- else:
- self.methods[plugin] = {func: doc}
-
- def callRPC(self, plugin, func, args, parse):
- if not args: args = tuple()
- if parse:
- args = tuple([literal_eval(x) for x in args])
-
- plugin = self.pluginMap[plugin]
- f = getattr(plugin, func)
- return f(*args)
-
-
- def createIndex(self):
- plugins = []
-
- active = []
- deactive = []
-
- for pluginname in self.core.pluginManager.hookPlugins:
- try:
- #hookClass = getattr(plugin, plugin.__name__)
-
- if self.core.config.getPlugin(pluginname, "activated"):
- pluginClass = self.core.pluginManager.loadClass("hooks", pluginname)
- if not pluginClass: continue
-
- plugin = pluginClass(self.core, self)
- plugins.append(plugin)
- self.pluginMap[pluginClass.__name__] = plugin
- if plugin.isActivated():
- active.append(pluginClass.__name__)
- else:
- deactive.append(pluginname)
-
-
- except:
- self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
- if self.core.debug:
- traceback.print_exc()
-
- self.log.info(_("Activated plugins: %s") % ", ".join(sorted(active)))
- self.log.info(_("Deactivate plugins: %s") % ", ".join(sorted(deactive)))
-
- self.plugins = plugins
-
- def manageHooks(self, plugin, name, value):
- if name == "activated" and value:
- self.activateHook(plugin)
- elif name == "activated" and not value:
- self.deactivateHook(plugin)
-
- def activateHook(self, plugin):
-
- #check if already loaded
- for inst in self.plugins:
- if inst.__name__ == plugin:
- return
-
- pluginClass = self.core.pluginManager.loadClass("hooks", plugin)
-
- if not pluginClass: return
-
- self.log.debug("Plugin loaded: %s" % plugin)
-
- plugin = pluginClass(self.core, self)
- self.plugins.append(plugin)
- self.pluginMap[pluginClass.__name__] = plugin
-
- # call core Ready
- start_new_thread(plugin.coreReady, tuple())
-
- def deactivateHook(self, plugin):
-
- hook = None
- for inst in self.plugins:
- if inst.__name__ == plugin:
- hook = inst
-
- if not hook: return
-
- self.log.debug("Plugin unloaded: %s" % plugin)
-
- hook.unload()
-
- #remove periodic call
- self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(hook.cb))
- self.plugins.remove(hook)
- del self.pluginMap[hook.__name__]
-
-
- @try_catch
- def coreReady(self):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.coreReady()
-
- self.dispatchEvent("coreReady")
-
- @try_catch
- def coreExiting(self):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.coreExiting()
-
- self.dispatchEvent("coreExiting")
-
- @lock
- def downloadPreparing(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.downloadPreparing(pyfile)
-
- self.dispatchEvent("downloadPreparing", pyfile)
-
- @lock
- def downloadFinished(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "downloadFinished" in plugin.__threaded__:
- self.startThread(plugin.downloadFinished, pyfile)
- else:
- plugin.downloadFinished(pyfile)
-
- self.dispatchEvent("downloadFinished", pyfile)
-
- @lock
- @try_catch
- def downloadFailed(self, pyfile):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "downloadFailed" in plugin.__threaded__:
- self.startThread(plugin.downloadFinished, pyfile)
- else:
- plugin.downloadFailed(pyfile)
-
- self.dispatchEvent("downloadFailed", pyfile)
-
- @lock
- def packageFinished(self, package):
- for plugin in self.plugins:
- if plugin.isActivated():
- if "packageFinished" in plugin.__threaded__:
- self.startThread(plugin.packageFinished, package)
- else:
- plugin.packageFinished(package)
-
- self.dispatchEvent("packageFinished", package)
-
- @lock
- def beforeReconnecting(self, ip):
- for plugin in self.plugins:
- plugin.beforeReconnecting(ip)
-
- self.dispatchEvent("beforeReconnecting", ip)
-
- @lock
- def afterReconnecting(self, ip):
- for plugin in self.plugins:
- if plugin.isActivated():
- plugin.afterReconnecting(ip)
-
- self.dispatchEvent("afterReconnecting", ip)
-
- def startThread(self, function, *args, **kwargs):
- t = HookThread(self.core.threadManager, function, args, kwargs)
-
- def activePlugins(self):
- """ returns all active plugins """
- return [x for x in self.plugins if x.isActivated()]
-
- def getAllInfo(self):
- """returns info stored by hook plugins"""
- info = {}
- for name, plugin in self.pluginMap.iteritems():
- if plugin.info:
- #copy and convert so str
- info[name] = dict([(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()])
- return info
-
-
- def getInfo(self, plugin):
- info = {}
- if plugin in self.pluginMap and self.pluginMap[plugin].info:
- info = dict([(x, str(y) if not isinstance(y, basestring) else y)
- for x, y in self.pluginMap[plugin].info.iteritems()])
-
- return info
-
- def addEvent(self, event, func):
- """Adds an event listener for event name"""
- if event in self.events:
- self.events[event].append(func)
- else:
- self.events[event] = [func]
-
- def removeEvent(self, event, func):
- """removes previously added event listener"""
- if event in self.events:
- self.events[event].remove(func)
-
- def dispatchEvent(self, event, *args):
- """dispatches event with args"""
- if event in self.events:
- for f in self.events[event]:
- try:
- f(*args)
- except Exception, e:
- self.log.warning("Error calling event handler %s: %s, %s, %s"
- % (event, f, args, str(e)))
- if self.core.debug:
- traceback.print_exc()
-
diff --git a/module/InitHomeDir.py b/module/InitHomeDir.py
deleted file mode 100644
index 156c9f932..000000000
--- a/module/InitHomeDir.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-
- This modules inits working directories and global variables, pydir and homedir
-"""
-
-from os import makedirs, path, chdir
-from os.path import join
-import sys
-from sys import argv, platform
-
-import __builtin__
-__builtin__.owd = path.abspath("") #original working directory
-__builtin__.pypath = path.abspath(path.join(__file__, "..", ".."))
-
-sys.path.append(join(pypath, "module", "lib"))
-
-homedir = ""
-
-if platform == 'nt':
- homedir = path.expanduser("~")
- if homedir == "~":
- import ctypes
-
- CSIDL_APPDATA = 26
- _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW
- _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND,
- ctypes.c_int,
- ctypes.wintypes.HANDLE,
- ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR]
-
- path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
- result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf)
- homedir = path_buf.value
-else:
- homedir = path.expanduser("~")
-
-__builtin__.homedir = homedir
-
-args = " ".join(argv[1:])
-
-# dirty method to set configdir from commandline arguments
-if "--configdir=" in args:
- pos = args.find("--configdir=")
- end = args.find("-", pos + 12)
-
- if end == -1:
- configdir = args[pos + 12:].strip()
- else:
- configdir = args[pos + 12:end].strip()
-elif path.exists(path.join(pypath, "module", "config", "configdir")):
- f = open(path.join(pypath, "module", "config", "configdir"), "rb")
- c = f.read().strip()
- f.close()
- configdir = path.join(pypath, c)
-else:
- if platform in ("posix", "linux2"):
- configdir = path.join(homedir, ".pyload")
- else:
- configdir = path.join(homedir, "pyload")
-
-if not path.exists(configdir):
- makedirs(configdir, 0700)
-
-__builtin__.configdir = configdir
-chdir(configdir)
-
-#print "Using %s as working directory." % configdir
diff --git a/module/PluginThread.py b/module/PluginThread.py
deleted file mode 100644
index 56c36c778..000000000
--- a/module/PluginThread.py
+++ /dev/null
@@ -1,677 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from Queue import Queue
-from threading import Thread
-from os import listdir, stat
-from os.path import join
-from time import sleep, time, strftime, gmtime
-from traceback import print_exc, format_exc
-from pprint import pformat
-from sys import exc_info, exc_clear
-from copy import copy
-from types import MethodType
-
-from pycurl import error
-
-from PyFile import PyFile
-from plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
-from common.packagetools import parseNames
-from utils import save_join
-from Api import OnlineStatus
-
-class PluginThread(Thread):
- """abstract base class for thread types"""
-
- #----------------------------------------------------------------------
- def __init__(self, manager):
- """Constructor"""
- Thread.__init__(self)
- self.setDaemon(True)
- self.m = manager #thread manager
-
-
- def writeDebugReport(self, pyfile):
- """ writes a
- :return:
- """
-
- dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S"))
- dump = self.getDebugDump(pyfile)
-
- try:
- import zipfile
-
- zip = zipfile.ZipFile(dump_name, "w")
-
- for f in listdir(join("tmp", pyfile.pluginname)):
- try:
- # avoid encoding errors
- zip.write(join("tmp", pyfile.pluginname, f), save_join(pyfile.pluginname, f))
- except:
- pass
-
- info = zipfile.ZipInfo(save_join(pyfile.pluginname, "debug_Report.txt"), gmtime())
- info.external_attr = 0644 << 16L # change permissions
-
- zip.writestr(info, dump)
- zip.close()
-
- if not stat(dump_name).st_size:
- raise Exception("Empty Zipfile")
-
- except Exception, e:
- self.m.log.debug("Error creating zip file: %s" % e)
-
- dump_name = dump_name.replace(".zip", ".txt")
- f = open(dump_name, "wb")
- f.write(dump)
- f.close()
-
- self.m.core.log.info("Debug Report written to %s" % dump_name)
-
- def getDebugDump(self, pyfile):
- dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % (
- self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version__, format_exc())
-
- tb = exc_info()[2]
- stack = []
- while tb:
- stack.append(tb.tb_frame)
- tb = tb.tb_next
-
- for frame in stack[1:]:
- dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name,
- frame.f_code.co_filename,
- frame.f_lineno)
-
- for key, value in frame.f_locals.items():
- dump += "\t%20s = " % key
- try:
- dump += pformat(value) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- del frame
-
- del stack #delete it just to be sure...
-
- dump += "\n\nPLUGIN OBJECT DUMP: \n\n"
-
- for name in dir(pyfile.plugin):
- attr = getattr(pyfile.plugin, name)
- if not name.endswith("__") and type(attr) != MethodType:
- dump += "\t%20s = " % name
- try:
- dump += pformat(attr) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- dump += "\nPYFILE OBJECT DUMP: \n\n"
-
- for name in dir(pyfile):
- attr = getattr(pyfile, name)
- if not name.endswith("__") and type(attr) != MethodType:
- dump += "\t%20s = " % name
- try:
- dump += pformat(attr) + "\n"
- except Exception, e:
- dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
-
- if pyfile.pluginname in self.m.core.config.plugin:
- dump += "\n\nCONFIG: \n\n"
- dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n"
-
- return dump
-
- def clean(self, pyfile):
- """ set thread unactive and release pyfile """
- self.active = False
- pyfile.release()
-
-
-class DownloadThread(PluginThread):
- """thread for downloading files from 'real' hoster plugins"""
-
- #----------------------------------------------------------------------
- def __init__(self, manager):
- """Constructor"""
- PluginThread.__init__(self, manager)
-
- self.queue = Queue() # job queue
- self.active = False
-
- self.start()
-
- #----------------------------------------------------------------------
- def run(self):
- """run method"""
- pyfile = None
-
- while True:
- del pyfile
- self.active = self.queue.get()
- pyfile = self.active
-
- if self.active == "quit":
- self.active = False
- self.m.threads.remove(self)
- return True
-
- try:
- if not pyfile.hasPlugin(): continue
- #this pyfile was deleted while queueing
-
- pyfile.plugin.checkForSameFiles(starting=True)
- self.m.log.info(_("Download starts: %s" % pyfile.name))
-
- # start download
- self.m.core.hookManager.downloadPreparing(pyfile)
- pyfile.plugin.preprocessing(self)
-
- self.m.log.info(_("Download finished: %s") % pyfile.name)
- self.m.core.hookManager.downloadFinished(pyfile)
- self.m.core.files.checkPackageFinished(pyfile)
-
- except NotImplementedError:
- self.m.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
- pyfile.setStatus("failed")
- pyfile.error = "Plugin does not work"
- self.clean(pyfile)
- continue
-
- except Abort:
- try:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- except:
- pass
-
- pyfile.setStatus("aborted")
-
- self.clean(pyfile)
- continue
-
- except Reconnect:
- self.queue.put(pyfile)
- #pyfile.req.clearCookies()
-
- while self.m.reconnecting.isSet():
- sleep(0.5)
-
- continue
-
- except Retry, e:
- reason = e.args[0]
- self.m.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason})
- self.queue.put(pyfile)
- continue
-
- except Fail, e:
- msg = e.args[0]
-
- if msg == "offline":
- pyfile.setStatus("offline")
- self.m.log.warning(_("Download is offline: %s") % pyfile.name)
- elif msg == "temp. offline":
- pyfile.setStatus("temp. offline")
- self.m.log.warning(_("Download is temporary offline: %s") % pyfile.name)
- else:
- pyfile.setStatus("failed")
- self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
- pyfile.error = msg
-
- self.m.core.hookManager.downloadFailed(pyfile)
- self.clean(pyfile)
- continue
-
- except error, e:
- if len(e.args) == 2:
- code, msg = e.args
- else:
- code = 0
- msg = e.args
-
- self.m.log.debug("pycurl exception %s: %s" % (code, msg))
-
- if code in (7, 18, 28, 52, 56):
- self.m.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry."))
- wait = time() + 60
-
- pyfile.waitUntil = wait
- pyfile.setStatus("waiting")
- while time() < wait:
- sleep(1)
- if pyfile.abort:
- break
-
- if pyfile.abort:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- pyfile.setStatus("aborted")
-
- self.clean(pyfile)
- else:
- self.queue.put(pyfile)
-
- continue
-
- else:
- pyfile.setStatus("failed")
- self.m.log.error("pycurl error %s: %s" % (code, msg))
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- self.m.core.hookManager.downloadFailed(pyfile)
-
- self.clean(pyfile)
- continue
-
- except SkipDownload, e:
- pyfile.setStatus("skipped")
-
- self.m.log.info(
- _("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message})
-
- self.clean(pyfile)
-
- self.m.core.files.checkPackageFinished(pyfile)
-
- self.active = False
- self.m.core.files.save()
-
- continue
-
-
- except Exception, e:
- pyfile.setStatus("failed")
- self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
- pyfile.error = str(e)
-
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- self.m.core.hookManager.downloadFailed(pyfile)
- self.clean(pyfile)
- continue
-
- finally:
- self.m.core.files.save()
- pyfile.checkIfProcessed()
- exc_clear()
-
-
- #pyfile.plugin.req.clean()
-
- self.active = False
- pyfile.finishIfDone()
- self.m.core.files.save()
-
-
- def put(self, job):
- """assing job to thread"""
- self.queue.put(job)
-
-
- def stop(self):
- """stops the thread"""
- self.put("quit")
-
-
-class DecrypterThread(PluginThread):
- """thread for decrypting"""
-
- def __init__(self, manager, pyfile):
- """constructor"""
- PluginThread.__init__(self, manager)
-
- self.active = pyfile
- manager.localThreads.append(self)
-
- pyfile.setStatus("decrypting")
-
- self.start()
-
- def getActiveFiles(self):
- return [self.active]
-
- def run(self):
- """run method"""
-
- pyfile = self.active
- retry = False
-
- try:
- self.m.log.info(_("Decrypting starts: %s") % self.active.name)
- self.active.plugin.preprocessing(self)
-
- except NotImplementedError:
- self.m.log.error(_("Plugin %s is missing a function.") % self.active.pluginname)
- return
-
- except Fail, e:
- msg = e.args[0]
-
- if msg == "offline":
- self.active.setStatus("offline")
- self.m.log.warning(_("Download is offline: %s") % self.active.name)
- else:
- self.active.setStatus("failed")
- self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": msg})
- self.active.error = msg
-
- return
-
- except Abort:
- self.m.log.info(_("Download aborted: %s") % pyfile.name)
- pyfile.setStatus("aborted")
-
- return
-
- except Retry:
- self.m.log.info(_("Retrying %s") % self.active.name)
- retry = True
- return self.run()
-
- except Exception, e:
- self.active.setStatus("failed")
- self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": str(e)})
- self.active.error = str(e)
-
- if self.m.core.debug:
- print_exc()
- self.writeDebugReport(pyfile)
-
- return
-
-
- finally:
- if not retry:
- self.active.release()
- self.active = False
- self.m.core.files.save()
- self.m.localThreads.remove(self)
- exc_clear()
-
-
- #self.m.core.hookManager.downloadFinished(pyfile)
-
-
- #self.m.localThreads.remove(self)
- #self.active.finishIfDone()
- if not retry:
- pyfile.delete()
-
-
-class HookThread(PluginThread):
- """thread for hooks"""
-
- #----------------------------------------------------------------------
- def __init__(self, m, function, args, kwargs):
- """Constructor"""
- PluginThread.__init__(self, m)
-
- self.f = function
- self.args = args
- self.kwargs = kwargs
-
- self.active = []
-
- m.localThreads.append(self)
-
- self.start()
-
- def getActiveFiles(self):
- return self.active
-
- def addActive(self, pyfile):
- """ Adds a pyfile to active list and thus will be displayed on overview"""
- if pyfile not in self.active:
- self.active.append(pyfile)
-
- def finishFile(self, pyfile):
- if pyfile in self.active:
- self.active.remove(pyfile)
-
- pyfile.finishIfDone()
-
- def run(self):
- try:
- try:
- self.kwargs["thread"] = self
- self.f(*self.args, **self.kwargs)
- except TypeError, e:
- #dirty method to filter out exceptions
- if "unexpected keyword argument 'thread'" not in e.args[0]:
- raise
-
- del self.kwargs["thread"]
- self.f(*self.args, **self.kwargs)
- finally:
- local = copy(self.active)
- for x in local:
- self.finishFile(x)
-
- self.m.localThreads.remove(self)
-
-
-class InfoThread(PluginThread):
- def __init__(self, manager, data, pid=-1, rid=-1, add=False):
- """Constructor"""
- PluginThread.__init__(self, manager)
-
- self.data = data
- self.pid = pid # package id
- # [ .. (name, plugin) .. ]
-
- self.rid = rid #result id
- self.add = add #add packages instead of return result
-
- self.cache = [] #accumulated data
-
- self.start()
-
- def run(self):
- """run method"""
-
- plugins = {}
- container = []
-
- for url, plugin in self.data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
-
- # filter out container plugins
- for name in self.m.core.pluginManager.containerPlugins:
- if name in plugins:
- container.extend([(name, url) for url in plugins[name]])
-
- del plugins[name]
-
- #directly write to database
- if self.pid > -1:
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
- self.m.core.files.save()
-
- elif self.add:
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True)
-
- else:
- #generate default result
- result = [(url, 0, 3, url) for url in urls]
-
- self.updateCache(pluginname, result)
-
- packs = parseNames([(name, url) for name, x, y, url in self.cache])
-
- self.m.log.debug("Fetched and generated %d packages" % len(packs))
-
- for k, v in packs:
- self.m.core.api.addPackage(k, v)
-
- #empty cache
- del self.cache[:]
-
- else: #post the results
-
-
- for name, url in container:
- #attach container content
- try:
- data = self.decryptContainer(name, url)
- except:
- print_exc()
- self.m.log.error("Could not decrypt container.")
- data = []
-
- for url, plugin in data:
- if plugin in plugins:
- plugins[plugin].append(url)
- else:
- plugins[plugin] = [url]
-
- self.m.infoResults[self.rid] = {}
-
- for pluginname, urls in plugins.iteritems():
- plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
- if hasattr(plugin, "getInfo"):
- self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True)
-
- #force to process cache
- if self.cache:
- self.updateResult(pluginname, [], True)
-
- else:
- #generate default result
- result = [(url, 0, 3, url) for url in urls]
-
- self.updateResult(pluginname, result, True)
-
- self.m.infoResults[self.rid]["ALL_INFO_FETCHED"] = {}
-
- self.m.timestamp = time() + 5 * 60
-
-
- def updateDB(self, plugin, result):
- self.m.core.files.updateFileInfo(result, self.pid)
-
- def updateResult(self, plugin, result, force=False):
- #parse package name and generate result
- #accumulate results
-
- self.cache.extend(result)
-
- if len(self.cache) >= 20 or force:
- #used for package generating
- tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size))))
- for name, size, status, url in self.cache]
-
- data = parseNames(tmp)
- result = {}
- for k, v in data.iteritems():
- for url, status in v:
- status.packagename = k
- result[url] = status
-
- self.m.setInfoResults(self.rid, result)
-
- self.cache = []
-
- def updateCache(self, plugin, result):
- self.cache.extend(result)
-
- def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None):
- try:
- result = [] #result loaded from cache
- process = [] #urls to process
- for url in urls:
- if url in self.m.infoCache:
- result.append(self.m.infoCache[url])
- else:
- process.append(url)
-
- if result:
- self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname))
- cb(pluginname, result)
-
- if process:
- self.m.log.debug("Run Info Fetching for %s" % pluginname)
- for result in plugin.getInfo(process):
- #result = [ .. (name, size, status, url) .. ]
- if not type(result) == list: result = [result]
-
- for res in result:
- self.m.infoCache[res[3]] = res
-
- cb(pluginname, result)
-
- self.m.log.debug("Finished Info Fetching for %s" % pluginname)
- except Exception, e:
- self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") %
- {"name": pluginname, "err": str(e)})
- if self.m.core.debug:
- print_exc()
-
- # generate default results
- if err:
- result = [(url, 0, 3, url) for url in urls]
- cb(pluginname, result)
-
-
- def decryptContainer(self, plugin, url):
- data = []
- # only works on container plugins
-
- self.m.log.debug("Pre decrypting %s with %s" % (url, plugin))
-
- # dummy pyfile
- pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1)
-
- pyfile.initPlugin()
-
- # little plugin lifecycle
- try:
- pyfile.plugin.setup()
- pyfile.plugin.loadToDisk()
- pyfile.plugin.decrypt(pyfile)
- pyfile.plugin.deleteTmp()
-
- for pack in pyfile.plugin.packages:
- pyfile.plugin.urls.extend(pack[1])
-
- data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls)
-
- self.m.log.debug("Got %d links." % len(data))
-
- except Exception, e:
- self.m.log.debug("Pre decrypting error: %s" % str(e))
- finally:
- pyfile.release()
-
- return data
diff --git a/module/PullEvents.py b/module/PullEvents.py
deleted file mode 100644
index 5ec76765e..000000000
--- a/module/PullEvents.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from time import time
-from module.utils import uniqify
-
-class PullManager():
- def __init__(self, core):
- self.core = core
- self.clients = []
-
- def newClient(self, uuid):
- self.clients.append(Client(uuid))
-
- def clean(self):
- for n, client in enumerate(self.clients):
- if client.lastActive + 30 < time():
- del self.clients[n]
-
- def getEvents(self, uuid):
- events = []
- validUuid = False
- for client in self.clients:
- if client.uuid == uuid:
- client.lastActive = time()
- validUuid = True
- while client.newEvents():
- events.append(client.popEvent().toList())
- break
- if not validUuid:
- self.newClient(uuid)
- events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()]
- return uniqify(events, repr)
-
- def addEvent(self, event):
- for client in self.clients:
- client.addEvent(event)
-
-class Client():
- def __init__(self, uuid):
- self.uuid = uuid
- self.lastActive = time()
- self.events = []
-
- def newEvents(self):
- return len(self.events) > 0
-
- def popEvent(self):
- if not len(self.events):
- return None
- return self.events.pop(0)
-
- def addEvent(self, event):
- self.events.append(event)
-
-class UpdateEvent():
- def __init__(self, itype, iid, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.destination = destination
-
- def toList(self):
- return ["update", self.destination, self.type, self.id]
-
-class RemoveEvent():
- def __init__(self, itype, iid, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.destination = destination
-
- def toList(self):
- return ["remove", self.destination, self.type, self.id]
-
-class InsertEvent():
- def __init__(self, itype, iid, after, destination):
- assert itype == "pack" or itype == "file"
- assert destination == "queue" or destination == "collector"
- self.type = itype
- self.id = iid
- self.after = after
- self.destination = destination
-
- def toList(self):
- return ["insert", self.destination, self.type, self.id, self.after]
-
-class ReloadAllEvent():
- def __init__(self, destination):
- assert destination == "queue" or destination == "collector"
- self.destination = destination
-
- def toList(self):
- return ["reload", self.destination]
-
-class AccountUpdateEvent():
- def toList(self):
- return ["account"]
-
-class ConfigUpdateEvent():
- def toList(self):
- return ["config"]
diff --git a/module/PyFile.py b/module/PyFile.py
deleted file mode 100644
index 3dede9360..000000000
--- a/module/PyFile.py
+++ /dev/null
@@ -1,285 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-from module.PullEvents import UpdateEvent
-from module.utils import formatSize, lock
-
-from time import sleep, time
-
-from threading import RLock
-
-statusMap = {
- "finished": 0,
- "offline": 1,
- "online": 2,
- "queued": 3,
- "skipped": 4,
- "waiting": 5,
- "temp. offline": 6,
- "starting": 7,
- "failed": 8,
- "aborted": 9,
- "decrypting": 10,
- "custom": 11,
- "downloading": 12,
- "processing": 13,
- "unknown": 14,
-}
-
-
-def setSize(self, value):
- self._size = int(value)
-
-class PyFile(object):
- """
- Represents a file object at runtime
- """
- __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "pluginname", "packageid",
- "error", "order", "lock", "plugin", "waitUntil", "active", "abort", "statusname",
- "reconnected", "progress", "maxprogress", "pluginmodule", "pluginclass")
-
- def __init__(self, manager, id, url, name, size, status, error, pluginname, package, order):
- self.m = manager
-
- self.id = int(id)
- self.url = url
- self.name = name
- self.size = size
- self.status = status
- self.pluginname = pluginname
- self.packageid = package #should not be used, use package() instead
- self.error = error
- self.order = order
- # database information ends here
-
- self.lock = RLock()
-
- self.plugin = None
- #self.download = None
-
- self.waitUntil = 0 # time() + time to wait
-
- # status attributes
- self.active = False #obsolete?
- self.abort = False
- self.reconnected = False
-
- self.statusname = None
-
- self.progress = 0
- self.maxprogress = 100
-
- self.m.cache[int(id)] = self
-
-
- # will convert all sizes to ints
- size = property(lambda self: self._size, setSize)
-
- def __repr__(self):
- return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname)
-
- @lock
- def initPlugin(self):
- """ inits plugin instance """
- if not self.plugin:
- self.pluginmodule = self.m.core.pluginManager.getPlugin(self.pluginname)
- self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.pluginname))
- self.plugin = self.pluginclass(self)
-
- @lock
- def hasPlugin(self):
- """Thread safe way to determine this file has initialized plugin attribute
-
- :return:
- """
- return hasattr(self, "plugin") and self.plugin
-
- def package(self):
- """ return package instance"""
- return self.m.getPackage(self.packageid)
-
- def setStatus(self, status):
- self.status = statusMap[status]
- self.sync() #@TODO needed aslong no better job approving exists
-
- def setCustomStatus(self, msg, status="processing"):
- self.statusname = msg
- self.setStatus(status)
-
- def getStatusName(self):
- if self.status not in (13, 14) or not self.statusname:
- return self.m.statusMsg[self.status]
- else:
- return self.statusname
-
- def hasStatus(self, status):
- return statusMap[status] == self.status
-
- def sync(self):
- """sync PyFile instance with database"""
- self.m.updateLink(self)
-
- @lock
- def release(self):
- """sync and remove from cache"""
- # file has valid package
- if self.packageid > 0:
- self.sync()
-
- if hasattr(self, "plugin") and self.plugin:
- self.plugin.clean()
- del self.plugin
-
- self.m.releaseLink(self.id)
-
- def delete(self):
- """delete pyfile from database"""
- self.m.deleteLink(self.id)
-
- def toDict(self):
- """return dict with all information for interface"""
- return self.toDbDict()
-
- def toDbDict(self):
- """return data as dict for databse
-
- format:
-
- {
- id: {'url': url, 'name': name ... }
- }
-
- """
- return {
- self.id: {
- 'id': self.id,
- 'url': self.url,
- 'name': self.name,
- 'plugin': self.pluginname,
- 'size': self.getSize(),
- 'format_size': self.formatSize(),
- 'status': self.status,
- 'statusmsg': self.getStatusName(),
- 'package': self.packageid,
- 'error': self.error,
- 'order': self.order
- }
- }
-
- def abortDownload(self):
- """abort pyfile if possible"""
- while self.id in self.m.core.threadManager.processingIds():
- self.abort = True
- if self.plugin and self.plugin.req:
- self.plugin.req.abortDownloads()
- sleep(0.1)
-
- self.abort = False
- if self.hasPlugin() and self.plugin.req:
- self.plugin.req.abortDownloads()
-
- self.release()
-
- def finishIfDone(self):
- """set status to finish and release file if every thread is finished with it"""
-
- if self.id in self.m.core.threadManager.processingIds():
- return False
-
- self.setStatus("finished")
- self.release()
- self.m.checkAllLinksFinished()
- return True
-
- def checkIfProcessed(self):
- self.m.checkAllLinksProcessed(self.id)
-
- def formatWait(self):
- """ formats and return wait time in humanreadable format """
- seconds = self.waitUntil - time()
-
- if seconds < 0: return "00:00:00"
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- def formatSize(self):
- """ formats size to readable format """
- return formatSize(self.getSize())
-
- def formatETA(self):
- """ formats eta to readable format """
- seconds = self.getETA()
-
- if seconds < 0: return "00:00:00"
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- def getSpeed(self):
- """ calculates speed """
- try:
- return self.plugin.req.speed
- except:
- return 0
-
- def getETA(self):
- """ gets established time of arrival"""
- try:
- return self.getBytesLeft() / self.getSpeed()
- except:
- return 0
-
- def getBytesLeft(self):
- """ gets bytes left """
- try:
- return self.plugin.req.size - self.plugin.req.arrived
- except:
- return 0
-
- def getPercent(self):
- """ get % of download """
- if self.status == 12:
- try:
- return self.plugin.req.percent
- except:
- return 0
- else:
- return self.progress
-
- def getSize(self):
- """ get size of download """
- try:
- if self.plugin.req.size:
- return self.plugin.req.size
- else:
- return self.size
- except:
- return self.size
-
- def notifyChange(self):
- e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue")
- self.m.core.pullManager.addEvent(e)
-
- def setProgress(self, value):
- if not value == self.progress:
- self.progress = value
- self.notifyChange()
diff --git a/module/PyPackage.py b/module/PyPackage.py
deleted file mode 100644
index f3be6c886..000000000
--- a/module/PyPackage.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-from module.PullEvents import UpdateEvent
-from module.utils import save_path
-
-class PyPackage():
- """
- Represents a package object at runtime
- """
- def __init__(self, manager, id, name, folder, site, password, queue, order):
- self.m = manager
- self.m.packageCache[int(id)] = self
-
- self.id = int(id)
- self.name = name
- self._folder = folder
- self.site = site
- self.password = password
- self.queue = queue
- self.order = order
- self.setFinished = False
-
- @property
- def folder(self):
- return save_path(self._folder)
-
- def toDict(self):
- """ Returns a dictionary representation of the data.
-
- :return: dict: {id: { attr: value }}
- """
- return {
- self.id: {
- 'id': self.id,
- 'name': self.name,
- 'folder': self.folder,
- 'site': self.site,
- 'password': self.password,
- 'queue': self.queue,
- 'order': self.order,
- 'links': {}
- }
- }
-
- def getChildren(self):
- """get information about contained links"""
- return self.m.getPackageData(self.id)["links"]
-
- def sync(self):
- """sync with db"""
- self.m.updatePackage(self)
-
- def release(self):
- """sync and delete from cache"""
- self.sync()
- self.m.releasePackage(self.id)
-
- def delete(self):
- self.m.deletePackage(self.id)
-
- def notifyChange(self):
- e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue")
- self.m.core.pullManager.addEvent(e)
diff --git a/module/Scheduler.py b/module/Scheduler.py
deleted file mode 100644
index 0bc396b69..000000000
--- a/module/Scheduler.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from time import time
-from heapq import heappop, heappush
-from thread import start_new_thread
-from threading import Lock
-
-class AlreadyCalled(Exception):
- pass
-
-
-class Deferred():
- def __init__(self):
- self.call = []
- self.result = ()
-
- def addCallback(self, f, *cargs, **ckwargs):
- self.call.append((f, cargs, ckwargs))
-
- def callback(self, *args, **kwargs):
- if self.result:
- raise AlreadyCalled
- self.result = (args, kwargs)
- for f, cargs, ckwargs in self.call:
- args += tuple(cargs)
- kwargs.update(ckwargs)
- f(*args ** kwargs)
-
-
-class Scheduler():
- def __init__(self, core):
- self.core = core
-
- self.queue = PriorityQueue()
-
- def addJob(self, t, call, args=[], kwargs={}, threaded=True):
- d = Deferred()
- t += time()
- j = Job(t, call, args, kwargs, d, threaded)
- self.queue.put((t, j))
- return d
-
-
- def removeJob(self, d):
- """
- :param d: defered object
- :return: if job was deleted
- """
- index = -1
-
- for i, j in enumerate(self.queue):
- if j[1].deferred == d:
- index = i
-
- if index >= 0:
- del self.queue[index]
- return True
-
- return False
-
- def work(self):
- while True:
- t, j = self.queue.get()
- if not j:
- break
- else:
- if t <= time():
- j.start()
- else:
- self.queue.put((t, j))
- break
-
-
-class Job():
- def __init__(self, time, call, args=[], kwargs={}, deferred=None, threaded=True):
- self.time = float(time)
- self.call = call
- self.args = args
- self.kwargs = kwargs
- self.deferred = deferred
- self.threaded = threaded
-
- def run(self):
- ret = self.call(*self.args, **self.kwargs)
- if self.deferred is None:
- return
- else:
- self.deferred.callback(ret)
-
- def start(self):
- if self.threaded:
- start_new_thread(self.run, ())
- else:
- self.run()
-
-
-class PriorityQueue():
- """ a non blocking priority queue """
-
- def __init__(self):
- self.queue = []
- self.lock = Lock()
-
- def __iter__(self):
- return iter(self.queue)
-
- def __delitem__(self, key):
- del self.queue[key]
-
- def put(self, element):
- self.lock.acquire()
- heappush(self.queue, element)
- self.lock.release()
-
- def get(self):
- """ return element or None """
- self.lock.acquire()
- try:
- el = heappop(self.queue)
- return el
- except IndexError:
- return None, None
- finally:
- self.lock.release() \ No newline at end of file
diff --git a/module/ThreadManager.py b/module/ThreadManager.py
deleted file mode 100644
index 8937f4a29..000000000
--- a/module/ThreadManager.py
+++ /dev/null
@@ -1,318 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from os.path import exists, join
-import re
-from subprocess import Popen
-from threading import Event, Lock
-from time import sleep, time
-from traceback import print_exc
-from random import choice
-
-import pycurl
-
-import PluginThread
-from module.PyFile import PyFile
-from module.network.RequestFactory import getURL
-from module.utils import freeSpace, lock
-
-
-class ThreadManager:
- """manages the download threads, assign jobs, reconnect etc"""
-
-
- def __init__(self, core):
- """Constructor"""
- self.core = core
- self.log = core.log
-
- self.threads = [] # thread list
- self.localThreads = [] #hook+decrypter threads
-
- self.pause = True
-
- self.reconnecting = Event()
- self.reconnecting.clear()
- self.downloaded = 0 #number of files downloaded since last cleanup
-
- self.lock = Lock()
-
- # some operations require to fetch url info from hoster, so we caching them so it wont be done twice
- # contains a timestamp and will be purged after timeout
- self.infoCache = {}
-
- # pool of ids for online check
- self.resultIDs = 0
-
- # threads which are fetching hoster results
- self.infoResults = {}
- #timeout for cache purge
- self.timestamp = 0
-
- pycurl.global_init(pycurl.GLOBAL_DEFAULT)
-
- for i in range(0, self.core.config.get("download", "max_downloads")):
- self.createThread()
-
-
- def createThread(self):
- """create a download thread"""
-
- thread = PluginThread.DownloadThread(self)
- self.threads.append(thread)
-
- def createInfoThread(self, data, pid):
- """
- start a thread whichs fetches online status and other infos
- data = [ .. () .. ]
- """
- self.timestamp = time() + 5 * 60
-
- PluginThread.InfoThread(self, data, pid)
-
- @lock
- def createResultThread(self, data, add=False):
- """ creates a thread to fetch online status, returns result id """
- self.timestamp = time() + 5 * 60
-
- rid = self.resultIDs
- self.resultIDs += 1
-
- PluginThread.InfoThread(self, data, rid=rid, add=add)
-
- return rid
-
-
- @lock
- def getInfoResult(self, rid):
- """returns result and clears it"""
- self.timestamp = time() + 5 * 60
-
- if rid in self.infoResults:
- data = self.infoResults[rid]
- self.infoResults[rid] = {}
- return data
- else:
- return {}
-
- @lock
- def setInfoResults(self, rid, result):
- self.infoResults[rid].update(result)
-
- def getActiveFiles(self):
- active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)]
-
- for t in self.localThreads:
- active.extend(t.getActiveFiles())
-
- return active
-
- def processingIds(self):
- """get a id list of all pyfiles processed"""
- return [x.id for x in self.getActiveFiles()]
-
-
- def work(self):
- """run all task which have to be done (this is for repetivive call by core)"""
- try:
- self.tryReconnect()
- except Exception, e:
- self.log.error(_("Reconnect Failed: %s") % str(e) )
- self.reconnecting.clear()
- if self.core.debug:
- print_exc()
- self.checkThreadCount()
-
- try:
- self.assignJob()
- except Exception, e:
- self.log.warning("Assign job error", e)
- if self.core.debug:
- print_exc()
-
- sleep(0.5)
- self.assignJob()
- #it may be failed non critical so we try it again
-
- if (self.infoCache or self.infoResults) and self.timestamp < time():
- self.infoCache.clear()
- self.infoResults.clear()
- self.log.debug("Cleared Result cache")
-
- #----------------------------------------------------------------------
- def tryReconnect(self):
- """checks if reconnect needed"""
-
- if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()):
- return False
-
- active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active]
-
- if not (0 < active.count(True) == len(active)):
- return False
-
- if not exists(self.core.config['reconnect']['method']):
- if exists(join(pypath, self.core.config['reconnect']['method'])):
- self.core.config['reconnect']['method'] = join(pypath, self.core.config['reconnect']['method'])
- else:
- self.core.config["reconnect"]["activated"] = False
- self.log.warning(_("Reconnect script not found!"))
- return
-
- self.reconnecting.set()
-
- #Do reconnect
- self.log.info(_("Starting reconnect"))
-
- while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0:
- sleep(0.25)
-
- ip = self.getIP()
-
- self.core.hookManager.beforeReconnecting(ip)
-
- self.log.debug("Old IP: %s" % ip)
-
- try:
- reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE)
- except:
- self.log.warning(_("Failed executing reconnect script!"))
- self.core.config["reconnect"]["activated"] = False
- self.reconnecting.clear()
- if self.core.debug:
- print_exc()
- return
-
- reconn.wait()
- sleep(1)
- ip = self.getIP()
- self.core.hookManager.afterReconnecting(ip)
-
- self.log.info(_("Reconnected, new IP: %s") % ip)
-
- self.reconnecting.clear()
-
- def getIP(self):
- """retrieve current ip"""
- services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"),
- ("http://checkip.dyndns.org/",".*Current IP Address: (\S+)</body>.*")]
-
- ip = ""
- for i in range(10):
- try:
- sv = choice(services)
- ip = getURL(sv[0])
- ip = re.match(sv[1], ip).group(1)
- break
- except:
- ip = ""
- sleep(1)
-
- return ip
-
- #----------------------------------------------------------------------
- def checkThreadCount(self):
- """checks if there are need for increasing or reducing thread count"""
-
- if len(self.threads) == self.core.config.get("download", "max_downloads"):
- return True
- elif len(self.threads) < self.core.config.get("download", "max_downloads"):
- self.createThread()
- else:
- free = [x for x in self.threads if not x.active]
- if free:
- free[0].put("quit")
-
-
- def cleanPycurl(self):
- """ make a global curl cleanup (currently ununused) """
- if self.processingIds():
- return False
- pycurl.global_cleanup()
- pycurl.global_init(pycurl.GLOBAL_DEFAULT)
- self.downloaded = 0
- self.log.debug("Cleaned up pycurl")
- return True
-
- #----------------------------------------------------------------------
- def assignJob(self):
- """assing a job to a thread if possible"""
-
- if self.pause or not self.core.api.isTimeDownload(): return
-
- #if self.downloaded > 20:
- # if not self.cleanPyCurl(): return
-
- free = [x for x in self.threads if not x.active]
-
- inuse = set([(x.active.pluginname,self.getLimit(x)) for x in self.threads if x.active and x.active.hasPlugin() and x.active.plugin.account])
- inuse = map(lambda x : (x[0], x[1], len([y for y in self.threads if y.active and y.active.pluginname == x[0]])) ,inuse)
- onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]]
-
- occ = [x.active.pluginname for x in self.threads if x.active and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit
-
- occ.sort()
- occ = tuple(set(occ))
- job = self.core.files.getJob(occ)
- if job:
- try:
- job.initPlugin()
- except Exception, e:
- self.log.critical(str(e))
- print_exc()
- job.setStatus("failed")
- job.error = str(e)
- job.release()
- return
-
- if job.plugin.__type__ == "hoster":
- spaceLeft = freeSpace(self.core.config["general"]["download_folder"]) / 1024 / 1024
- if spaceLeft < self.core.config["general"]["min_free_space"]:
- self.log.warning(_("Not enough space left on device"))
- self.pause = True
-
- if free and not self.pause:
- thread = free[0]
- #self.downloaded += 1
-
- thread.put(job)
- else:
- #put job back
- if occ not in self.core.files.jobCache:
- self.core.files.jobCache[occ] = []
- self.core.files.jobCache[occ].append(job.id)
-
- #check for decrypt jobs
- job = self.core.files.getDecryptJob()
- if job:
- job.initPlugin()
- thread = PluginThread.DecrypterThread(self, job)
-
-
- else:
- thread = PluginThread.DecrypterThread(self, job)
-
- def getLimit(self, thread):
- limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL",["0"])[0]
- return int(limit)
-
- def cleanup(self):
- """do global cleanup, should be called when finished with pycurl"""
- pycurl.global_cleanup()
diff --git a/module/cli/AddPackage.py b/module/cli/AddPackage.py
deleted file mode 100644
index a73401586..000000000
--- a/module/cli/AddPackage.py
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-
-from Handler import Handler
-from printer import *
-
-class AddPackage(Handler):
- """ let the user add packages """
-
- def init(self):
- self.name = ""
- self.urls = []
-
- def onEnter(self, inp):
- if inp == "0":
- self.cli.reset()
-
- if not self.name:
- self.name = inp
- self.setInput()
- elif inp == "END":
- #add package
- self.client.addPackage(self.name, self.urls, 1)
- self.cli.reset()
- else:
- if inp.strip():
- self.urls.append(inp)
- self.setInput()
-
- def renderBody(self, line):
- println(line, white(_("Add Package:")))
- println(line + 1, "")
- line += 2
-
- if not self.name:
- println(line, _("Enter a name for the new package"))
- println(line + 1, "")
- line += 2
- else:
- println(line, _("Package: %s") % self.name)
- println(line + 1, _("Parse the links you want to add."))
- println(line + 2, _("Type %s when done.") % mag("END"))
- println(line + 3, _("Links added: ") + mag(len(self.urls)))
- line += 4
-
- println(line, "")
- println(line + 1, mag("0.") + _(" back to main menu"))
-
- return line + 2 \ No newline at end of file
diff --git a/module/cli/Handler.py b/module/cli/Handler.py
deleted file mode 100644
index 476d09386..000000000
--- a/module/cli/Handler.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-class Handler:
- def __init__(self, cli):
- self.cli = cli
- self.init()
-
- client = property(lambda self: self.cli.client)
- input = property(lambda self: self.cli.input)
-
- def init(self):
- pass
-
- def onChar(self, char):
- pass
-
- def onBackSpace(self):
- pass
-
- def onEnter(self, inp):
- pass
-
- def setInput(self, inp=""):
- self.cli.setInput(inp)
-
- def backspace(self):
- self.cli.setInput(self.input[:-1])
-
- def renderBody(self, line):
- """ gets the line where to render output and should return the line number below its content """
- return line + 1 \ No newline at end of file
diff --git a/module/cli/ManageFiles.py b/module/cli/ManageFiles.py
deleted file mode 100644
index 4d0377d9d..000000000
--- a/module/cli/ManageFiles.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-
-from itertools import islice
-from time import time
-
-from Handler import Handler
-from printer import *
-
-from module.Api import Destination, PackageData
-
-class ManageFiles(Handler):
- """ possibility to manage queue/collector """
-
- def init(self):
- self.target = Destination.Queue
- self.pos = 0 #position in queue
- self.package = -1 #choosen package
- self.mode = "" # move/delete/restart
-
- self.cache = None
- self.links = None
- self.time = 0
-
- def onChar(self, char):
- if char in ("m", "d", "r"):
- self.mode = char
- self.setInput()
- elif char == "p":
- self.pos = max(0, self.pos - 5)
- self.backspace()
- elif char == "n":
- self.pos += 5
- self.backspace()
-
- def onBackSpace(self):
- if not self.input and self.mode:
- self.mode = ""
- if not self.input and self.package > -1:
- self.package = -1
-
- def onEnter(self, input):
- if input == "0":
- self.cli.reset()
- elif self.package < 0 and self.mode:
- #mode select
- packs = self.parseInput(input)
- if self.mode == "m":
- [self.client.movePackage((self.target + 1) % 2, x) for x in packs]
- elif self.mode == "d":
- self.client.deletePackages(packs)
- elif self.mode == "r":
- [self.client.restartPackage(x) for x in packs]
-
- elif self.mode:
- #edit links
- links = self.parseInput(input, False)
-
- if self.mode == "d":
- self.client.deleteFiles(links)
- elif self.mode == "r":
- map(self.client.restartFile, links)
-
- else:
- #look into package
- try:
- self.package = int(input)
- except:
- pass
-
- self.cache = None
- self.links = None
- self.pos = 0
- self.mode = ""
- self.setInput()
-
-
- def renderBody(self, line):
- if self.package < 0:
- println(line, white(_("Manage Packages:")))
- else:
- println(line, white((_("Manage Links:"))))
- line += 1
-
- if self.mode:
- if self.mode == "m":
- println(line, _("What do you want to move?"))
- elif self.mode == "d":
- println(line, _("What do you want to delete?"))
- elif self.mode == "r":
- println(line, _("What do you want to restart?"))
-
- println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1,2,3 or 1-3.")
- line += 2
- else:
- println(line, _("Choose what yout want to do or enter package number."))
- println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % (
- _("delete"), _("move"), _("restart")))
- line += 2
-
- if self.package < 0:
- #print package info
- pack = self.getPackages()
- i = 0
- for value in islice(pack, self.pos, self.pos + 5):
- try:
- println(line, mag(str(value.pid)) + ": " + value.name)
- line += 1
- i += 1
- except Exception, e:
- pass
- for x in range(5 - i):
- println(line, "")
- line += 1
- else:
- #print links info
- pack = self.getLinks()
- i = 0
- for value in islice(pack.links, self.pos, self.pos + 5):
- try:
- println(line, mag(value.fid) + ": %s | %s | %s" % (
- value.name, value.statusmsg, value.plugin))
- line += 1
- i += 1
- except Exception, e:
- pass
- for x in range(5 - i):
- println(line, "")
- line += 1
-
- println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next"))
- println(line + 1, mag("0.") + _(" back to main menu"))
-
- return line + 2
-
-
- def getPackages(self):
- if self.cache and self.time + 2 < time():
- return self.cache
-
- if self.target == Destination.Queue:
- data = self.client.getQueue()
- else:
- data = self.client.getCollector()
-
-
- self.cache = data
- self.time = time()
-
- return data
-
- def getLinks(self):
- if self.links and self.time + 1 < time():
- return self.links
-
- try:
- data = self.client.getPackageData(self.package)
- except:
- data = PackageData(links=[])
-
- self.links = data
- self.time = time()
-
- return data
-
- def parseInput(self, inp, package=True):
- inp = inp.strip()
- if "-" in inp:
- l, n, h = inp.partition("-")
- l = int(l)
- h = int(h)
- r = range(l, h + 1)
-
- ret = []
- if package:
- for p in self.cache:
- if p.pid in r:
- ret.append(p.pid)
- else:
- for l in self.links.links:
- if l.lid in r:
- ret.append(l.lid)
-
- return ret
-
- else:
- return [int(x) for x in inp.split(",")]
diff --git a/module/cli/__init__.py b/module/cli/__init__.py
deleted file mode 100644
index fa8a09291..000000000
--- a/module/cli/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from AddPackage import AddPackage
-from ManageFiles import ManageFiles \ No newline at end of file
diff --git a/module/cli/printer.py b/module/cli/printer.py
deleted file mode 100644
index c62c1800e..000000000
--- a/module/cli/printer.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-def blue(string):
- return "\033[1;34m" + unicode(string) + "\033[0m"
-
-def green(string):
- return "\033[1;32m" + unicode(string) + "\033[0m"
-
-def yellow(string):
- return "\033[1;33m" + unicode(string) + "\033[0m"
-
-def red(string):
- return "\033[1;31m" + unicode(string) + "\033[0m"
-
-def cyan(string):
- return "\033[1;36m" + unicode(string) + "\033[0m"
-
-def mag(string):
- return "\033[1;35m" + unicode(string) + "\033[0m"
-
-def white(string):
- return "\033[1;37m" + unicode(string) + "\033[0m"
-
-def println(line, content):
- print "\033[" + str(line) + ";0H\033[2K" + content \ No newline at end of file
diff --git a/module/common/APIExerciser.py b/module/common/APIExerciser.py
deleted file mode 100644
index 96f5ce9cf..000000000
--- a/module/common/APIExerciser.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import string
-from threading import Thread
-from random import choice, random, sample, randint
-from time import time, sleep
-from math import floor
-import gc
-
-from traceback import print_exc, format_exc
-
-from module.remote.thriftbackend.ThriftClient import ThriftClient, Destination
-
-def createURLs():
- """ create some urls, some may fail """
- urls = []
- for x in range(0, randint(20, 100)):
- name = "DEBUG_API"
- if randint(0, 5) == 5:
- name = "" #this link will fail
-
- urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20))))
-
- return urls
-
-AVOID = (0,3,8)
-
-idPool = 0
-sumCalled = 0
-
-
-def startApiExerciser(core, n):
- for i in range(n):
- APIExerciser(core).start()
-
-class APIExerciser(Thread):
-
-
- def __init__(self, core, thrift=False, user=None, pw=None):
- global idPool
-
- Thread.__init__(self)
- self.setDaemon(True)
- self.core = core
- self.count = 0 #number of methods
- self.time = time()
-
- if thrift:
- self.api = ThriftClient(user=user, password=pw)
- else:
- self.api = core.api
-
-
- self.id = idPool
-
- idPool += 1
-
- #self.start()
-
- def run(self):
-
- self.core.log.info("API Excerciser started %d" % self.id)
-
- out = open("error.log", "ab")
- #core errors are not logged of course
- out.write("\n" + "Starting\n")
- out.flush()
-
- while True:
- try:
- self.testAPI()
- except Exception:
- self.core.log.error("Excerciser %d throw an execption" % self.id)
- print_exc()
- out.write(format_exc() + 2 * "\n")
- out.flush()
-
- if not self.count % 100:
- self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count))
- if not self.count % 1000:
- out.flush()
-
- if not sumCalled % 1000: #not thread safe
- self.core.log.info("Exercisers tested %d api calls" % sumCalled)
- persec = sumCalled / (time() - self.time)
- self.core.log.info("Approx. %.2f calls per second." % persec)
- self.core.log.info("Approx. %.2f ms per call." % (1000 / persec))
- self.core.log.info("Collected garbage: %d" % gc.collect())
-
-
- #sleep(random() / 500)
-
- def testAPI(self):
- global sumCalled
-
- m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles",
- "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting",
- "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices" , "getAccounts", "getAllUserData"]
-
- method = choice(m)
- #print "Testing:", method
-
- if hasattr(self, method):
- res = getattr(self, method)()
- else:
- res = getattr(self.api, method)()
-
- self.count += 1
- sumCalled += 1
-
- #print res
-
- def addPackage(self):
- name = "".join(sample(string.ascii_letters, 10))
- urls = createURLs()
-
- self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]))
-
-
- def deleteFiles(self):
- info = self.api.getQueueData()
- if not info: return
-
- pack = choice(info)
- fids = pack.links
-
- if len(fids):
- fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))]
- self.api.deleteFiles(fids)
-
-
- def deletePackages(self):
- info = choice([self.api.getQueue(), self.api.getCollector()])
- if not info: return
-
- pids = [p.pid for p in info]
- if len(pids):
- pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1)))
- self.api.deletePackages(pids)
-
- def getFileData(self):
- info = self.api.getQueueData()
- if info:
- p = choice(info)
- if p.links:
- self.api.getFileData(choice(p.links).fid)
-
- def getPackageData(self):
- info = self.api.getQueue()
- if info:
- self.api.getPackageData(choice(info).pid)
-
- def getAccounts(self):
- self.api.getAccounts(False)
-
- def getCaptchaTask(self):
- self.api.getCaptchaTask(False) \ No newline at end of file
diff --git a/module/common/ImportDebugger.py b/module/common/ImportDebugger.py
deleted file mode 100644
index a997f7b0c..000000000
--- a/module/common/ImportDebugger.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-
-class ImportDebugger(object):
-
- def __init__(self):
- self.imported = {}
-
- def find_module(self, name, path=None):
-
- if name not in self.imported:
- self.imported[name] = 0
-
- self.imported[name] += 1
-
- print name, path
-
-sys.meta_path.append(ImportDebugger()) \ No newline at end of file
diff --git a/module/common/JsEngine.py b/module/common/JsEngine.py
deleted file mode 100644
index 576be2a1b..000000000
--- a/module/common/JsEngine.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from imp import find_module
-from os.path import join, exists
-from urllib import quote
-
-
-ENGINE = ""
-
-DEBUG = False
-JS = False
-PYV8 = False
-RHINO = False
-
-
-if not ENGINE:
- try:
- import subprocess
-
- subprocess.Popen(["js", "-v"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
- p = subprocess.Popen(["js", "-e", "print(23+19)"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- #integrity check
- if out.strip() == "42":
- ENGINE = "js"
- JS = True
- except:
- pass
-
-if not ENGINE or DEBUG:
- try:
- find_module("PyV8")
- ENGINE = "pyv8"
- PYV8 = True
- except:
- pass
-
-if not ENGINE or DEBUG:
- try:
- path = "" #path where to find rhino
-
- if exists("/usr/share/java/js.jar"):
- path = "/usr/share/java/js.jar"
- elif exists("js.jar"):
- path = "js.jar"
- elif exists(join(pypath, "js.jar")): #may raises an exception, but js.jar wasnt found anyway
- path = join(pypath, "js.jar")
-
- if not path:
- raise Exception
-
- import subprocess
-
- p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", "print(23+19)"],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- out, err = p.communicate()
- #integrity check
- if out.strip() == "42":
- ENGINE = "rhino"
- RHINO = True
- except:
- pass
-
-class JsEngine():
- def __init__(self):
- self.engine = ENGINE
- self.init = False
-
- def __nonzero__(self):
- return False if not ENGINE else True
-
- def eval(self, script):
- if not self.init:
- if ENGINE == "pyv8" or (DEBUG and PYV8):
- import PyV8
- global PyV8
-
- self.init = True
-
- if type(script) == unicode:
- script = script.encode("utf8")
-
- if not ENGINE:
- raise Exception("No JS Engine")
-
- if not DEBUG:
- if ENGINE == "pyv8":
- return self.eval_pyv8(script)
- elif ENGINE == "js":
- return self.eval_js(script)
- elif ENGINE == "rhino":
- return self.eval_rhino(script)
- else:
- results = []
- if PYV8:
- res = self.eval_pyv8(script)
- print "PyV8:", res
- results.append(res)
- if JS:
- res = self.eval_js(script)
- print "JS:", res
- results.append(res)
- if RHINO:
- res = self.eval_rhino(script)
- print "Rhino:", res
- results.append(res)
-
- warning = False
- for x in results:
- for y in results:
- if x != y:
- warning = True
-
- if warning: print "### WARNING ###: Different results"
-
- return results[0]
-
- def eval_pyv8(self, script):
- rt = PyV8.JSContext()
- rt.enter()
- return rt.eval(script)
-
- def eval_js(self, script):
- script = "print(eval(unescape('%s')))" % quote(script)
- p = subprocess.Popen(["js", "-e", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1)
- out, err = p.communicate()
- res = out.strip()
- return res
-
- def eval_rhino(self, script):
- script = "print(eval(unescape('%s')))" % quote(script)
- p = subprocess.Popen(["java", "-cp", path, "org.mozilla.javascript.tools.shell.Main", "-e", script],
- stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1)
- out, err = p.communicate()
- res = out.strip()
- return res.decode("utf8").encode("ISO-8859-1")
-
- def error(self):
- return _("No js engine detected, please install either Spidermonkey, ossp-js, pyv8 or rhino")
-
-if __name__ == "__main__":
- js = JsEngine()
-
- test = u'"Ì"+"À"'
- js.eval(test) \ No newline at end of file
diff --git a/module/common/__init__.py b/module/common/__init__.py
deleted file mode 100644
index de6d13128..000000000
--- a/module/common/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-__author__ = 'christian'
- \ No newline at end of file
diff --git a/module/common/json_layer.py b/module/common/json_layer.py
deleted file mode 100644
index 4d57a9f38..000000000
--- a/module/common/json_layer.py
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# abstraction layer for json operations
-
-try: # since python 2.6
- import json
- from json import loads as json_loads
- from json import dumps as json_dumps
-except ImportError: #use system simplejson if available
- import simplejson as json
- from simplejson import loads as json_loads
- from simplejson import dumps as json_dumps
diff --git a/module/common/packagetools.py b/module/common/packagetools.py
deleted file mode 100644
index 5bfbcba95..000000000
--- a/module/common/packagetools.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python
-
-# JDownloader/src/jd/controlling/LinkGrabberPackager.java
-
-import re
-from urlparse import urlparse
-
-def matchFirst(string, *args):
- """ matches against list of regexp and returns first match"""
- for patternlist in args:
- for pattern in patternlist:
- r = pattern.search(string)
- if r is not None:
- name = r.group(1)
- return name
-
- return string
-
-
-def parseNames(files):
- """ Generates packages names from name, data lists
-
- :param files: list of (name, data)
- :return: packagenames mapt to data lists (eg. urls)
- """
- packs = {}
-
- endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)"
-
- rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I),
- re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I),
- re.compile("(.*)\\.rar$", re.I),
- re.compile("(.*)\\.r\\d+$", re.I),
- re.compile("(.*)(\\.|_|-)\\d+$", re.I)]
-
- zipPats = [re.compile("(.*)\\.zip$", re.I),
- re.compile("(.*)\\.z\\d+$", re.I),
- re.compile("(?is).*\\.7z\\.[\\d]+$", re.I),
- re.compile("(.*)\\.a.$", re.I)]
-
- ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"),
- re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)]
-
- iszPats = [re.compile("(.*)\\.isz$", re.I),
- re.compile("(.*)\\.i\\d{2}$", re.I)]
-
- pat1 = re.compile("(\\.?CD\\d+)", re.I)
- pat2 = re.compile("(\\.?part\\d+)", re.I)
-
- pat3 = re.compile("(.+)[\\.\\-_]+$")
- pat4 = re.compile("(.+)\\.\\d+\\.xtm$")
-
- for file, url in files:
- patternMatch = False
-
- if file is None:
- continue
-
- # remove trailing /
- name = file.rstrip('/')
-
- # extract last path part .. if there is a path
- split = name.rsplit("/", 1)
- if len(split) > 1:
- name = split.pop(1)
-
- #check if a already existing package may be ok for this file
- # found = False
- # for pack in packs:
- # if pack in file:
- # packs[pack].append(url)
- # found = True
- # break
- #
- # if found: continue
-
- # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern
- before = name
- name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats)
- if before != name:
- patternMatch = True
-
- # xtremsplit pattern
- r = pat4.search(name)
- if r is not None:
- name = r.group(1)
-
- # remove part and cd pattern
- r = pat1.search(name)
- if r is not None:
- name = name.replace(r.group(0), "")
- patternMatch = True
-
- r = pat2.search(name)
- if r is not None:
- name = name.replace(r.group(0), "")
- patternMatch = True
-
- # additional checks if extension pattern matched
- if patternMatch:
- # remove extension
- index = name.rfind(".")
- if index <= 0:
- index = name.rfind("_")
- if index > 0:
- length = len(name) - index
- if length <= 4:
- name = name[:-length]
-
- # remove endings like . _ -
- r = pat3.search(name)
- if r is not None:
- name = r.group(1)
-
- # replace . and _ with space
- name = name.replace(".", " ")
- name = name.replace("_", " ")
-
- name = name.strip()
- else:
- name = ""
-
- # fallback: package by hoster
- if not name:
- name = urlparse(file).hostname
- if name: name = name.replace("www.", "")
-
- # fallback : default name
- if not name:
- name = "unknown"
-
- # build mapping
- if name in packs:
- packs[name].append(url)
- else:
- packs[name] = [url]
-
- return packs
-
-
-if __name__ == "__main__":
- from os.path import join
- from pprint import pprint
-
- f = open(join("..", "..", "testlinks2.txt"), "rb")
- urls = [(x.strip(), x.strip()) for x in f.readlines() if x.strip()]
- f.close()
-
- print "Having %d urls." % len(urls)
-
- packs = parseNames(urls)
-
- pprint(packs)
-
- print "Got %d urls." % sum([len(x) for x in packs.itervalues()])
diff --git a/module/common/pylgettext.py b/module/common/pylgettext.py
deleted file mode 100644
index fb36fecee..000000000
--- a/module/common/pylgettext.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from gettext import *
-
-_searchdirs = None
-
-origfind = find
-
-def setpaths(pathlist):
- global _searchdirs
- if isinstance(pathlist, list):
- _searchdirs = pathlist
- else:
- _searchdirs = list(pathlist)
-
-
-def addpath(path):
- global _searchdirs
- if _searchdirs is None:
- _searchdirs = list(path)
- else:
- if path not in _searchdirs:
- _searchdirs.append(path)
-
-
-def delpath(path):
- global _searchdirs
- if _searchdirs is not None:
- if path in _searchdirs:
- _searchdirs.remove(path)
-
-
-def clearpath():
- global _searchdirs
- if _searchdirs is not None:
- _searchdirs = None
-
-
-def find(domain, localedir=None, languages=None, all=False):
- if _searchdirs is None:
- return origfind(domain, localedir, languages, all)
- searches = [localedir] + _searchdirs
- results = list()
- for dir in searches:
- res = origfind(domain, dir, languages, all)
- if all is False:
- results.append(res)
- else:
- results.extend(res)
- if all is False:
- results = filter(lambda x: x is not None, results)
- if len(results) == 0:
- return None
- else:
- return results[0]
- else:
- return results
-
-#Is there a smarter/cleaner pythonic way for this?
-translation.func_globals['find'] = find
diff --git a/module/config/default.conf b/module/config/default.conf
deleted file mode 100644
index 2e9152ba2..000000000
--- a/module/config/default.conf
+++ /dev/null
@@ -1,65 +0,0 @@
-version: 1
-
-remote - "Remote":
- int port : "Port" = 7227
- ip listenaddr : "Adress" = 0.0.0.0
- bool nolocalauth : "No authentication on local connections" = True
- bool activated : "Activated" = True
-ssl - "SSL":
- bool activated : "Activated"= False
- file cert : "SSL Certificate" = ssl.crt
- file key : "SSL Key" = ssl.key
-webinterface - "Webinterface":
- bool activated : "Activated" = True
- builtin;threaded;fastcgi;lightweight server : "Server" = builtin
- bool https : "Use HTTPS" = False
- ip host : "IP" = 0.0.0.0
- int port : "Port" = 8001
- str template : "Template" = default
- str prefix: "Path Prefix" =
-log - "Log":
- bool file_log : "File Log" = True
- folder log_folder : "Folder" = Logs
- int log_count : "Count" = 5
- int log_size : "Size in kb" = 100
- bool log_rotate : "Log Rotate" = True
-general - "General":
- en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR language : "Language" = en
- folder download_folder : "Download Folder" = Downloads
- bool debug_mode : "Debug Mode" = False
- bool checksum : "Use Checksum" = False
- int min_free_space : "Min Free Space (MB)" = 200
- bool folder_per_package : "Create folder for each package" = True
- int renice : "CPU Priority" = 0
-download - "Download":
- int chunks : "Max connections for one download" = 3
- int max_downloads : "Max Parallel Downloads" = 3
- int max_speed : "Max Download Speed in kb/s" = -1
- bool limit_speed : "Limit Download Speed" = False
- str interface : "Download interface to bind (ip or Name)" = None
- bool ipv6 : "Allow IPv6" = False
- bool skip_existing : "Skip already existing files" = False
-permission - "Permissions":
- bool change_user : "Change user of running process" = False
- str user : "Username" = user
- str folder : "Folder Permission mode" = 0755
- bool change_file : "Change file mode of downloads" = False
- str file : "Filemode for Downloads" = 0644
- bool change_group : "Change group of running process" = False
- str group : "Groupname" = users
- bool change_dl : "Change Group and User of Downloads" = False
-reconnect - "Reconnect":
- bool activated : "Use Reconnect" = False
- str method : "Method" = None
- time startTime : "Start" = 0:00
- time endTime : "End" = 0:00
-downloadTime - "Download Time":
- time start : "Start" = 0:00
- time end : "End" = 0:00
-proxy - "Proxy":
- str address : "Address" = "localhost"
- int port : "Port" = 7070
- http;socks4;socks5 type : "Protocol" = http
- str username : "Username" = None
- password password : "Password" = None
- bool proxy : "Use Proxy" = False
diff --git a/module/config/gui_default.xml b/module/config/gui_default.xml
deleted file mode 100644
index 1faed776f..000000000
--- a/module/config/gui_default.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" ?>
-<root>
- <connections>
- <connection default="True" type="local" id="33965310e19b4a869112c43b39a16440">
- <name>Local</name>
- </connection>
- </connections>
- <mainWindow>
- <state></state>
- <geometry></geometry>
- </mainWindow>
- <language>en</language>
-</root>
diff --git a/module/database/DatabaseBackend.py b/module/database/DatabaseBackend.py
deleted file mode 100644
index 9530390c3..000000000
--- a/module/database/DatabaseBackend.py
+++ /dev/null
@@ -1,352 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-from threading import Thread
-from threading import Event
-from os import remove
-from os.path import exists
-from shutil import move
-
-from Queue import Queue
-from traceback import print_exc
-
-from module.utils import chmod
-
-try:
- from pysqlite2 import dbapi2 as sqlite3
-except:
- import sqlite3
-
-DB_VERSION = 4
-
-class style():
- db = None
-
- @classmethod
- def setDB(cls, db):
- cls.db = db
-
- @classmethod
- def inner(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return f(cls.db, *args, **kwargs)
- return x
-
- @classmethod
- def queue(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return cls.db.queue(f, *args, **kwargs)
- return x
-
- @classmethod
- def async(cls, f):
- @staticmethod
- def x(*args, **kwargs):
- if cls.db:
- return cls.db.async(f, *args, **kwargs)
- return x
-
-class DatabaseJob():
- def __init__(self, f, *args, **kwargs):
- self.done = Event()
-
- self.f = f
- self.args = args
- self.kwargs = kwargs
-
- self.result = None
- self.exception = False
-
-# import inspect
-# self.frame = inspect.currentframe()
-
- def __repr__(self):
- from os.path import basename
- frame = self.frame.f_back
- output = ""
- for i in range(5):
- output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name)
- frame = frame.f_back
- del frame
- del self.frame
-
- return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result)
-
- def processJob(self):
- try:
- self.result = self.f(*self.args, **self.kwargs)
- except Exception, e:
- print_exc()
- try:
- print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e
- except:
- pass
-
- self.exception = e
- finally:
- self.done.set()
-
- def wait(self):
- self.done.wait()
-
-class DatabaseBackend(Thread):
- subs = []
- def __init__(self, core):
- Thread.__init__(self)
- self.setDaemon(True)
- self.core = core
-
- self.jobs = Queue()
-
- self.setuplock = Event()
-
- style.setDB(self)
-
- def setup(self):
- self.start()
- self.setuplock.wait()
-
- def run(self):
- """main loop, which executes commands"""
- convert = self._checkVersion() #returns None or current version
-
- self.conn = sqlite3.connect("files.db")
- chmod("files.db", 0600)
-
- self.c = self.conn.cursor() #compatibility
-
- if convert is not None:
- self._convertDB(convert)
-
- self._createTables()
- self._migrateUser()
-
- self.conn.commit()
-
- self.setuplock.set()
-
- while True:
- j = self.jobs.get()
- if j == "quit":
- self.c.close()
- self.conn.close()
- break
- j.processJob()
-
- @style.queue
- def shutdown(self):
- self.conn.commit()
- self.jobs.put("quit")
-
- def _checkVersion(self):
- """ check db version and delete it if needed"""
- if not exists("files.version"):
- f = open("files.version", "wb")
- f.write(str(DB_VERSION))
- f.close()
- return
-
- f = open("files.version", "rb")
- v = int(f.read().strip())
- f.close()
- if v < DB_VERSION:
- if v < 2:
- try:
- self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version."))
- except:
- print "Filedatabase was deleted due to incompatible version."
- remove("files.version")
- move("files.db", "files.backup.db")
- f = open("files.version", "wb")
- f.write(str(DB_VERSION))
- f.close()
- return v
-
- def _convertDB(self, v):
- try:
- getattr(self, "_convertV%i" % v)()
- except:
- try:
- self.core.log.error(_("Filedatabase could NOT be converted."))
- except:
- print "Filedatabase could NOT be converted."
-
- #--convert scripts start
-
- def _convertV2(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
- try:
- self.manager.core.log.info(_("Database was converted from v2 to v3."))
- except:
- print "Database was converted from v2 to v3."
- self._convertV3()
-
- def _convertV3(self):
- self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
- try:
- self.manager.core.log.info(_("Database was converted from v3 to v4."))
- except:
- print "Database was converted from v3 to v4."
-
- #--convert scripts end
-
- def _createTables(self):
- """create tables for database"""
-
- self.c.execute('CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)')
- self.c.execute('CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))')
- self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)')
- self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
- self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
-
- self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \
- SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\
- FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\
- (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \
- FROM packages p JOIN links l ON p.id = l.package AND l.status in (0,4,13) GROUP BY p.id) s ON s.id = p.id \
- GROUP BY p.id')
-
- #try to lower ids
- self.c.execute('SELECT max(id) FROM LINKS')
- fid = self.c.fetchone()[0]
- if fid:
- fid = int(fid)
- else:
- fid = 0
- self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links"))
-
-
- self.c.execute('SELECT max(id) FROM packages')
- pid = self.c.fetchone()[0]
- if pid:
- pid = int(pid)
- else:
- pid = 0
- self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages"))
-
- self.c.execute('VACUUM')
-
-
- def _migrateUser(self):
- if exists("pyload.db"):
- try:
- self.core.log.info(_("Converting old Django DB"))
- except:
- print "Converting old Django DB"
- conn = sqlite3.connect('pyload.db')
- c = conn.cursor()
- c.execute("SELECT username, password, email from auth_user WHERE is_superuser")
- users = []
- for r in c:
- pw = r[1].split("$")
- users.append((r[0], pw[1] + pw[2], r[2]))
- c.close()
- conn.close()
-
- self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users)
- move("pyload.db", "pyload.old.db")
-
- def createCursor(self):
- return self.conn.cursor()
-
- @style.async
- def commit(self):
- self.conn.commit()
-
- @style.queue
- def syncSave(self):
- self.conn.commit()
-
- @style.async
- def rollback(self):
- self.conn.rollback()
-
- def async(self, f, *args, **kwargs):
- args = (self, ) + args
- job = DatabaseJob(f, *args, **kwargs)
- self.jobs.put(job)
-
- def queue(self, f, *args, **kwargs):
- args = (self, ) + args
- job = DatabaseJob(f, *args, **kwargs)
- self.jobs.put(job)
- job.wait()
- return job.result
-
- @classmethod
- def registerSub(cls, klass):
- cls.subs.append(klass)
-
- @classmethod
- def unregisterSub(cls, klass):
- cls.subs.remove(klass)
-
- def __getattr__(self, attr):
- for sub in DatabaseBackend.subs:
- if hasattr(sub, attr):
- return getattr(sub, attr)
-
-if __name__ == "__main__":
- db = DatabaseBackend()
- db.setup()
-
- class Test():
- @style.queue
- def insert(db):
- c = db.createCursor()
- for i in range(1000):
- c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar"))
- @style.async
- def insert2(db):
- c = db.createCursor()
- for i in range(1000*1000):
- c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", ("foo", i, "bar"))
-
- @style.queue
- def select(db):
- c = db.createCursor()
- for i in range(10):
- res = c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", ("foo", i))
- print res.fetchone()
-
- @style.queue
- def error(db):
- c = db.createCursor()
- print "a"
- c.execute("SELECT myerror FROM storage WHERE identifier=? AND key=?", ("foo", i))
- print "e"
-
- db.registerSub(Test)
- from time import time
- start = time()
- for i in range(100):
- db.insert()
- end = time()
- print end-start
-
- start = time()
- db.insert2()
- end = time()
- print end-start
-
- db.error()
-
diff --git a/module/database/FileDatabase.py b/module/database/FileDatabase.py
deleted file mode 100644
index 7e7efb028..000000000
--- a/module/database/FileDatabase.py
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/env python
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
- @author: mkaay
-"""
-
-
-from threading import RLock
-from time import time
-
-from module.utils import formatSize, lock
-from module.PullEvents import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
-from module.PyPackage import PyPackage
-from module.PyFile import PyFile
-from module.database import style, DatabaseBackend
-
-try:
- from pysqlite2 import dbapi2 as sqlite3
-except:
- import sqlite3
-
-
-class FileHandler:
- """Handles all request made to obtain information,
- modify status or other request for links or packages"""
-
- def __init__(self, core):
- """Constructor"""
- self.core = core
-
- # translations
- self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"), _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"), _("downloading"), _("processing"), _("unknown")]
-
- self.cache = {} #holds instances for files
- self.packageCache = {} # same for packages
- #@TODO: purge the cache
-
- self.jobCache = {}
-
- self.lock = RLock() #@TODO should be a Lock w/o R
- #self.lock._Verbose__verbose = True
-
- self.filecount = -1 # if an invalid value is set get current value from db
- self.queuecount = -1 #number of package to be loaded
- self.unchanged = False #determines if any changes was made since last call
-
- self.db = self.core.db
-
- def change(func):
- def new(*args):
- args[0].unchanged = False
- args[0].filecount = -1
- args[0].queuecount = -1
- args[0].jobCache = {}
- return func(*args)
- return new
-
- #----------------------------------------------------------------------
- def save(self):
- """saves all data to backend"""
- self.db.commit()
-
- #----------------------------------------------------------------------
- def syncSave(self):
- """saves all data to backend and waits until all data are written"""
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- pyfile.sync()
-
- pypacks = self.packageCache.values()
- for pypack in pypacks:
- pypack.sync()
-
- self.db.syncSave()
-
- @lock
- def getCompleteData(self, queue=1):
- """gets a complete data representation"""
-
- data = self.db.getAllLinks(queue)
- packs = self.db.getAllPackages(queue)
-
- data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()])
-
- for x in self.packageCache.itervalues():
- if x.queue != queue or x.id not in packs: continue
- packs[x.id].update(x.toDict()[x.id])
-
- for key, value in data.iteritems():
- if value["package"] in packs:
- packs[value["package"]]["links"][key] = value
-
- return packs
-
- @lock
- def getInfoData(self, queue=1):
- """gets a data representation without links"""
-
- packs = self.db.getAllPackages(queue)
- for x in self.packageCache.itervalues():
- if x.queue != queue or x.id not in packs: continue
- packs[x.id].update(x.toDict()[x.id])
-
- return packs
-
- @lock
- @change
- def addLinks(self, urls, package):
- """adds links"""
-
- self.core.hookManager.dispatchEvent("linksAdded", urls, package)
-
- data = self.core.pluginManager.parseUrls(urls)
-
- self.db.addLinks(data, package)
- self.core.threadManager.createInfoThread(data, package)
-
- #@TODO change from reloadAll event to package update event
- self.core.pullManager.addEvent(ReloadAllEvent("collector"))
-
- #----------------------------------------------------------------------
- @lock
- @change
- def addPackage(self, name, folder, queue=0):
- """adds a package, default to link collector"""
- lastID = self.db.addPackage(name, folder, queue)
- p = self.db.getPackage(lastID)
- e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue")
- self.core.pullManager.addEvent(e)
- return lastID
-
- #----------------------------------------------------------------------
- @lock
- @change
- def deletePackage(self, id):
- """delete package and all contained links"""
-
- p = self.getPackage(id)
- if not p:
- if id in self.packageCache: del self.packageCache[id]
- return
-
- oldorder = p.order
- queue = p.queue
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
-
- pyfiles = self.cache.values()
-
- for pyfile in pyfiles:
- if pyfile.packageid == id:
- pyfile.abortDownload()
- pyfile.release()
-
- self.db.deletePackage(p)
- self.core.pullManager.addEvent(e)
- self.core.hookManager.dispatchEvent("packageDeleted", id)
-
- if id in self.packageCache:
- del self.packageCache[id]
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue == queue and pack.order > oldorder:
- pack.order -= 1
- pack.notifyChange()
-
- #----------------------------------------------------------------------
- @lock
- @change
- def deleteLink(self, id):
- """deletes links"""
-
- f = self.getFile(id)
- if not f:
- return None
-
- pid = f.packageid
- e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue")
-
- oldorder = f.order
-
- if id in self.core.threadManager.processingIds():
- self.cache[id].abortDownload()
-
- if id in self.cache:
- del self.cache[id]
-
- self.db.deleteLink(f)
-
- self.core.pullManager.addEvent(e)
-
- p = self.getPackage(pid)
- if not len(p.getChildren()):
- p.delete()
-
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid == pid and pyfile.order > oldorder:
- pyfile.order -= 1
- pyfile.notifyChange()
-
- #----------------------------------------------------------------------
- def releaseLink(self, id):
- """removes pyfile from cache"""
- if id in self.cache:
- del self.cache[id]
-
- #----------------------------------------------------------------------
- def releasePackage(self, id):
- """removes package from cache"""
- if id in self.packageCache:
- del self.packageCache[id]
-
- #----------------------------------------------------------------------
- def updateLink(self, pyfile):
- """updates link"""
- self.db.updateLink(pyfile)
-
- e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue")
- self.core.pullManager.addEvent(e)
-
- #----------------------------------------------------------------------
- def updatePackage(self, pypack):
- """updates a package"""
- self.db.updatePackage(pypack)
-
- e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- #----------------------------------------------------------------------
- def getPackage(self, id):
- """return package instance"""
-
- if id in self.packageCache:
- return self.packageCache[id]
- else:
- return self.db.getPackage(id)
-
- #----------------------------------------------------------------------
- def getPackageData(self, id):
- """returns dict with package information"""
- pack = self.getPackage(id)
-
- if not pack:
- return None
-
- pack = pack.toDict()[id]
-
- data = self.db.getPackageData(id)
-
- tmplist = []
-
- cache = self.cache.values()
- for x in cache:
- if int(x.toDbDict()[x.id]["package"]) == int(id):
- tmplist.append((x.id, x.toDbDict()[x.id]))
- data.update(tmplist)
-
- pack["links"] = data
-
- return pack
-
- #----------------------------------------------------------------------
- def getFileData(self, id):
- """returns dict with file information"""
- if id in self.cache:
- return self.cache[id].toDbDict()
-
- return self.db.getLinkData(id)
-
- #----------------------------------------------------------------------
- def getFile(self, id):
- """returns pyfile instance"""
- if id in self.cache:
- return self.cache[id]
- else:
- return self.db.getFile(id)
-
- #----------------------------------------------------------------------
- @lock
- def getJob(self, occ):
- """get suitable job"""
-
- #@TODO clean mess
- #@TODO improve selection of valid jobs
-
- if occ in self.jobCache:
- if self.jobCache[occ]:
- id = self.jobCache[occ].pop()
- if id == "empty":
- pyfile = None
- self.jobCache[occ].append("empty")
- else:
- pyfile = self.getFile(id)
- else:
- jobs = self.db.getJob(occ)
- jobs.reverse()
- if not jobs:
- self.jobCache[occ].append("empty")
- pyfile = None
- else:
- self.jobCache[occ].extend(jobs)
- pyfile = self.getFile(self.jobCache[occ].pop())
-
- else:
- self.jobCache = {} #better not caching to much
- jobs = self.db.getJob(occ)
- jobs.reverse()
- self.jobCache[occ] = jobs
-
- if not jobs:
- self.jobCache[occ].append("empty")
- pyfile = None
- else:
- pyfile = self.getFile(self.jobCache[occ].pop())
-
- #@TODO: maybe the new job has to be approved...
-
-
- #pyfile = self.getFile(self.jobCache[occ].pop())
- return pyfile
-
- @lock
- def getDecryptJob(self):
- """return job for decrypting"""
- if "decrypt" in self.jobCache:
- return None
-
- plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys()
- plugins = str(tuple(plugins))
-
- jobs = self.db.getPluginJob(plugins)
- if jobs:
- return self.getFile(jobs[0])
- else:
- self.jobCache["decrypt"] = "empty"
- return None
-
- def getFileCount(self):
- """returns number of files"""
-
- if self.filecount == -1:
- self.filecount = self.db.filecount(1)
-
- return self.filecount
-
- def getQueueCount(self, force=False):
- """number of files that have to be processed"""
- if self.queuecount == -1 or force:
- self.queuecount = self.db.queuecount(1)
-
- return self.queuecount
-
- def checkAllLinksFinished(self):
- """checks if all files are finished and dispatch event"""
-
- if not self.getQueueCount(True):
- self.core.hookManager.dispatchEvent("allDownloadsFinished")
- self.core.log.debug("All downloads finished")
- return True
-
- return False
-
- def checkAllLinksProcessed(self, fid):
- """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting"""
-
- # reset count so statistic will update (this is called when dl was processed)
- self.resetCount()
-
- if not self.db.processcount(1, fid):
- self.core.hookManager.dispatchEvent("allDownloadsProcessed")
- self.core.log.debug("All downloads processed")
- return True
-
- return False
-
- def resetCount(self):
- self.queuecount = -1
-
- @lock
- @change
- def restartPackage(self, id):
- """restart package"""
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid == id:
- self.restartFile(pyfile.id)
-
- self.db.restartPackage(id)
-
- if id in self.packageCache:
- self.packageCache[id].setFinished = False
-
- e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def restartFile(self, id):
- """ restart file"""
- if id in self.cache:
- self.cache[id].status = 3
- self.cache[id].name = self.cache[id].url
- self.cache[id].error = ""
- self.cache[id].abortDownload()
-
-
- self.db.restartFile(id)
-
- e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def setPackageLocation(self, id, queue):
- """push package to queue"""
-
- p = self.db.getPackage(id)
- oldorder = p.order
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- self.db.clearPackageOrder(p)
-
- p = self.db.getPackage(id)
-
- p.queue = queue
- self.db.updatePackage(p)
-
- self.db.reorderPackage(p, -1, True)
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue != queue and pack.order > oldorder:
- pack.order -= 1
- pack.notifyChange()
-
- self.db.commit()
- self.releasePackage(id)
- p = self.getPackage(id)
-
- e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def reorderPackage(self, id, position):
- p = self.getPackage(id)
-
- e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
- self.db.reorderPackage(p, position)
-
- packs = self.packageCache.values()
- for pack in packs:
- if pack.queue != p.queue or pack.order < 0 or pack == p: continue
- if p.order > position:
- if pack.order >= position and pack.order < p.order:
- pack.order += 1
- pack.notifyChange()
- elif p.order < position:
- if pack.order <= position and pack.order > p.order:
- pack.order -= 1
- pack.notifyChange()
-
- p.order = position
- self.db.commit()
-
- e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @lock
- @change
- def reorderFile(self, id, position):
- f = self.getFileData(id)
- f = f[id]
-
- e = RemoveEvent("file", id, "collector" if not self.getPackage(f["package"]).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- self.db.reorderLink(f, position)
-
- pyfiles = self.cache.values()
- for pyfile in pyfiles:
- if pyfile.packageid != f["package"] or pyfile.order < 0: continue
- if f["order"] > position:
- if pyfile.order >= position and pyfile.order < f["order"]:
- pyfile.order += 1
- pyfile.notifyChange()
- elif f["order"] < position:
- if pyfile.order <= position and pyfile.order > f["order"]:
- pyfile.order -= 1
- pyfile.notifyChange()
-
- if id in self.cache:
- self.cache[id].order = position
-
- self.db.commit()
-
- e = InsertEvent("file", id, position, "collector" if not self.getPackage(f["package"]).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- @change
- def updateFileInfo(self, data, pid):
- """ updates file info (name, size, status, url)"""
- ids = self.db.updateLinkInfo(data)
- e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue")
- self.core.pullManager.addEvent(e)
-
- def checkPackageFinished(self, pyfile):
- """ checks if package is finished and calls hookmanager """
-
- ids = self.db.getUnfinished(pyfile.packageid)
- if not ids or (pyfile.id in ids and len(ids) == 1):
- if not pyfile.package().setFinished:
- self.core.log.info(_("Package finished: %s") % pyfile.package().name)
- self.core.hookManager.packageFinished(pyfile.package())
- pyfile.package().setFinished = True
-
-
- def reCheckPackage(self, pid):
- """ recheck links in package """
- data = self.db.getPackageData(pid)
-
- urls = []
-
- for pyfile in data.itervalues():
- if pyfile["status"] not in (0, 12, 13):
- urls.append((pyfile["url"], pyfile["plugin"]))
-
- self.core.threadManager.createInfoThread(urls, pid)
-
- @lock
- @change
- def deleteFinishedLinks(self):
- """ deletes finished links and packages, return deleted packages """
-
- old_packs = self.getInfoData(0)
- old_packs.update(self.getInfoData(1))
-
- self.db.deleteFinished()
-
- new_packs = self.db.getAllPackages(0)
- new_packs.update(self.db.getAllPackages(1))
- #get new packages only from db
-
- deleted = []
- for id in old_packs.iterkeys():
- if id not in new_packs:
- deleted.append(id)
- self.deletePackage(int(id))
-
- return deleted
-
- @lock
- @change
- def restartFailed(self):
- """ restart all failed links """
- self.db.restartFailed()
-
-class FileMethods():
- @style.queue
- def filecount(self, queue):
- """returns number of files in queue"""
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?", (queue, ))
- return self.c.fetchone()[0]
-
- @style.queue
- def queuecount(self, queue):
- """ number of files in queue not finished yet"""
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0,4)", (queue, ))
- return self.c.fetchone()[0]
-
- @style.queue
- def processcount(self, queue, fid):
- """ number of files which have to be proccessed """
- self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2,3,5,7,12) AND l.id != ?", (queue, str(fid)))
- return self.c.fetchone()[0]
-
- @style.inner
- def _nextPackageOrder(self, queue=0):
- self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,))
- max = self.c.fetchone()[0]
- if max is not None:
- return max + 1
- else:
- return 0
-
- @style.inner
- def _nextFileOrder(self, package):
- self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,))
- max = self.c.fetchone()[0]
- if max is not None:
- return max + 1
- else:
- return 0
-
- @style.queue
- def addLink(self, url, name, plugin, package):
- order = self._nextFileOrder(package)
- self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', (url, name, plugin, package, order))
- return self.c.lastrowid
-
- @style.queue
- def addLinks(self, links, package):
- """ links is a list of tupels (url,plugin)"""
- order = self._nextFileOrder(package)
- orders = [order + x for x in range(len(links))]
- links = [(x[0], x[0], x[1], package, o) for x, o in zip(links, orders)]
- self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links)
-
- @style.queue
- def addPackage(self, name, folder, queue):
- order = self._nextPackageOrder(queue)
- self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)', (name, folder, queue, order))
- return self.c.lastrowid
-
- @style.queue
- def deletePackage(self, p):
-
- self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),))
- self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),))
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?', (p.order, p.queue))
-
- @style.queue
- def deleteLink(self, f):
-
- self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),))
- self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?', (f.order, str(f.packageid)))
-
-
- @style.queue
- def getAllLinks(self, q):
- """return information about all links in queue q
-
- q0 queue
- q1 collector
-
- format:
-
- {
- id: {'name': name, ... 'package': id }, ...
- }
-
- """
- self.c.execute('SELECT l.id,l.url,l.name,l.size,l.status,l.error,l.plugin,l.package,l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder', (q,))
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
- @style.queue
- def getAllPackages(self, q):
- """return information about packages in queue q
- (only useful in get all data)
-
- q0 queue
- q1 collector
-
- format:
-
- {
- id: {'name': name ... 'links': {} }, ...
- }
- """
- self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \
- FROM packages p JOIN pstats s ON p.id = s.id \
- WHERE p.queue=? ORDER BY p.packageorder', str(q))
-
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'name': r[1],
- 'folder': r[2],
- 'site': r[3],
- 'password': r[4],
- 'queue': r[5],
- 'order': r[6],
- 'sizetotal': int(r[7]),
- 'sizedone': r[8] if r[8] else 0, #these can be None
- 'linksdone': r[9] if r[9] else 0,
- 'linkstotal': r[10],
- 'links': {}
- }
-
- return data
-
- @style.queue
- def getLinkData(self, id):
- """get link information as dict"""
- self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE id=?', (str(id), ))
- data = {}
- r = self.c.fetchone()
- if not r:
- return None
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
- @style.queue
- def getPackageData(self, id):
- """get data about links for a package"""
- self.c.execute('SELECT id,url,name,size,status,error,plugin,package,linkorder FROM links WHERE package=? ORDER BY linkorder', (str(id), ))
-
- data = {}
- for r in self.c:
- data[r[0]] = {
- 'id': r[0],
- 'url': r[1],
- 'name': r[2],
- 'size': r[3],
- 'format_size': formatSize(r[3]),
- 'status': r[4],
- 'statusmsg': self.manager.statusMsg[r[4]],
- 'error': r[5],
- 'plugin': r[6],
- 'package': r[7],
- 'order': r[8],
- }
-
- return data
-
-
- @style.async
- def updateLink(self, f):
- self.c.execute('UPDATE links SET url=?,name=?,size=?,status=?,error=?,package=? WHERE id=?', (f.url, f.name, f.size, f.status, f.error, str(f.packageid), str(f.id)))
-
- @style.queue
- def updatePackage(self, p):
- self.c.execute('UPDATE packages SET name=?,folder=?,site=?,password=?,queue=? WHERE id=?', (p.name, p.folder, p.site, p.password, p.queue, str(p.id)))
-
- @style.queue
- def updateLinkInfo(self, data):
- """ data is list of tupels (name, size, status, url) """
- self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1,2,3,14)', data)
- ids = []
- self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data]))
- for r in self.c:
- ids.append(int(r[0]))
- return ids
-
- @style.queue
- def reorderPackage(self, p, position, noMove=False):
- if position == -1:
- position = self._nextPackageOrder(p.queue)
- if not noMove:
- if p.order > position:
- self.c.execute('UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
- elif p.order < position:
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
-
- self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id)))
-
- @style.queue
- def reorderLink(self, f, position):
- """ reorder link with f as dict for pyfile """
- if f["order"] > position:
- self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?', (position, f["order"], f["package"]))
- elif f["order"] < position:
- self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?', (position, f["order"], f["package"]))
-
- self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f["id"]))
-
-
- @style.queue
- def clearPackageOrder(self, p):
- self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id)))
- self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?', (p.order, p.queue, str(p.id)))
-
- @style.async
- def restartFile(self, id):
- self.c.execute('UPDATE links SET status=3,error="" WHERE id=?', (str(id),))
-
- @style.async
- def restartPackage(self, id):
- self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),))
-
- @style.queue
- def getPackage(self, id):
- """return package instance from id"""
- self.c.execute("SELECT name,folder,site,password,queue,packageorder FROM packages WHERE id=?", (str(id), ))
- r = self.c.fetchone()
- if not r: return None
- return PyPackage(self.manager, id, * r)
-
- #----------------------------------------------------------------------
- @style.queue
- def getFile(self, id):
- """return link instance from id"""
- self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?", (str(id), ))
- r = self.c.fetchone()
- if not r: return None
- return PyFile(self.manager, id, * r)
-
-
- @style.queue
- def getJob(self, occ):
- """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
-
- #@TODO improve this hardcoded method
- pre = "('DLC', 'LinkList', 'SerienjunkiesOrg', 'CCF', 'RSDF')" #plugins which are processed in collector
-
- cmd = "("
- for i, item in enumerate(occ):
- if i: cmd += ", "
- cmd += "'%s'" % item
-
- cmd += ")"
-
- cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre)
-
- self.c.execute(cmd) # very bad!
-
- return [x[0] for x in self.c]
-
- @style.queue
- def getPluginJob(self, plugins):
- """returns pyfile ids with suited plugins"""
- cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2,3,14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins
-
- self.c.execute(cmd) # very bad!
-
- return [x[0] for x in self.c]
-
- @style.queue
- def getUnfinished(self, pid):
- """return list of max length 3 ids with pyfiles in package not finished or processed"""
-
- self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),))
- return [r[0] for r in self.c]
-
- @style.queue
- def deleteFinished(self):
- self.c.execute("DELETE FROM links WHERE status IN (0,4)")
- self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)")
-
- @style.queue
- def restartFailed(self):
- self.c.execute("UPDATE links SET status=3,error='' WHERE status IN (6, 8, 9)")
-
- @style.queue
- def findDuplicates(self, id, folder, filename):
- """ checks if filename exists with different id and same package """
- self.c.execute("SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?", (folder, id, filename))
- return self.c.fetchone()
-
- @style.queue
- def purgeLinks(self):
- self.c.execute("DELETE FROM links;")
- self.c.execute("DELETE FROM packages;")
-
-DatabaseBackend.registerSub(FileMethods)
-
-if __name__ == "__main__":
-
- pypath = "."
- _ = lambda x: x
-
- db = FileHandler(None)
-
- #p = PyFile(db, 5)
- #sleep(0.1)
-
- a = time()
-
- #print db.addPackage("package", "folder" , 1)
-
- pack = db.db.addPackage("package", "folder", 1)
-
- updates = []
-
-
- for x in range(0, 200):
- x = str(x)
- db.db.addLink("http://somehost.com/hoster/file/download?file_id=" + x, x, "BasePlugin", pack)
- updates.append(("new name" + x, 0, 3, "http://somehost.com/hoster/file/download?file_id=" + x))
-
-
- for x in range(0, 100):
- updates.append(("unimportant%s" % x, 0, 3, "a really long non existent url%s" % x))
-
- db.db.commit()
-
- b = time()
- print "adding 200 links, single sql execs, no commit", b-a
-
- print db.getCompleteData(1)
-
- c = time()
-
-
- db.db.updateLinkInfo(updates)
-
- d = time()
-
- print "updates", d-c
-
- print db.getCompleteData(1)
-
-
- e = time()
-
- print "complete data", e-d
diff --git a/module/database/StorageDatabase.py b/module/database/StorageDatabase.py
deleted file mode 100644
index 3ed29625f..000000000
--- a/module/database/StorageDatabase.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from module.database import style
-from module.database import DatabaseBackend
-
-class StorageMethods():
- @style.queue
- def setStorage(db, identifier, key, value):
- db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key))
- if db.c.fetchone() is not None:
- db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key))
- else:
- db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value))
-
- @style.queue
- def getStorage(db, identifier, key=None):
- if key is not None:
- db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key))
- row = db.c.fetchone()
- if row is not None:
- return row[0]
- else:
- db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier, ))
- d = {}
- for row in db.c:
- d[row[0]] = row[1]
- return d
-
- @style.queue
- def delStorage(db, identifier, key):
- db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key))
-
-DatabaseBackend.registerSub(StorageMethods)
diff --git a/module/database/UserDatabase.py b/module/database/UserDatabase.py
deleted file mode 100644
index 0c781057d..000000000
--- a/module/database/UserDatabase.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from hashlib import sha1
-import random
-
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
-
-class UserMethods():
- @style.queue
- def checkAuth(db, user, password):
- c = db.c
- c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user, ))
- r = c.fetchone()
- if not r:
- return {}
-
- salt = r[2][:5]
- pw = r[2][5:]
- h = sha1(salt + password)
- if h.hexdigest() == pw:
- return {"id": r[0], "name": r[1], "role": r[3],
- "permission": r[4], "template": r[5], "email": r[6]}
- else:
- return {}
-
- @style.queue
- def addUser(db, user, password):
- salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
- h = sha1(salt + password)
- password = salt + h.hexdigest()
-
- c = db.c
- c.execute('SELECT name FROM users WHERE name=?', (user, ))
- if c.fetchone() is not None:
- c.execute('UPDATE users SET password=? WHERE name=?', (password, user))
- else:
- c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
-
-
- @style.queue
- def changePassword(db, user, oldpw, newpw):
- db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user, ))
- r = db.c.fetchone()
- if not r:
- return False
-
- salt = r[2][:5]
- pw = r[2][5:]
- h = sha1(salt + oldpw)
- if h.hexdigest() == pw:
- salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
- h = sha1(salt + newpw)
- password = salt + h.hexdigest()
-
- db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
- return True
-
- return False
-
-
- @style.async
- def setPermission(db, user, perms):
- db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user))
-
- @style.async
- def setRole(db, user, role):
- db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
-
-
- @style.queue
- def listUsers(db):
- db.c.execute('SELECT name FROM users')
- users = []
- for row in db.c:
- users.append(row[0])
- return users
-
- @style.queue
- def getAllUserData(db):
- db.c.execute("SELECT name, permission, role, template, email FROM users")
- user = {}
- for r in db.c:
- user[r[0]] = {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]}
-
- return user
-
- @style.queue
- def removeUser(db, user):
- db.c.execute('DELETE FROM users WHERE name=?', (user, ))
-
-DatabaseBackend.registerSub(UserMethods)
diff --git a/module/database/__init__.py b/module/database/__init__.py
deleted file mode 100644
index 545789c0c..000000000
--- a/module/database/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from DatabaseBackend import DatabaseBackend
-from DatabaseBackend import style
-
-from FileDatabase import FileHandler
-from UserDatabase import UserMethods
-from StorageDatabase import StorageMethods \ No newline at end of file
diff --git a/module/debug.py b/module/debug.py
deleted file mode 100644
index 8d1ddd3d0..000000000
--- a/module/debug.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env python
-#coding:utf-8
-
-import re
-import InitHomeDir
-from os import listdir
-
-class Wrapper(object):
- pass
-
-def filter_info(line):
- if "object at 0x" in line:
- return False
- elif " at line " in line:
- return False
- elif " <DownloadThread(" in line:
- return False
- elif "<class '" in line:
- return False
- elif "PyFile " in line:
- return False
- elif " <module '" in line:
- return False
-
- else:
- return True
-
-def appendName(lines, name):
- test = re.compile("^[a-zA-z0-9]+ = ")
-
- for i, line in enumerate(lines):
- if test.match(line):
- lines[i] = name+"."+line
-
- return lines
-
-def initReport():
- reports = []
- for f in listdir("."):
- if f.startswith("debug_"):
- reports.append(f)
-
- for i, f in enumerate(reports):
- print "%s. %s" % (i,f)
-
- choice = raw_input("Choose Report: ")
-
- report = reports[int(choice)]
-
- f = open(report, "rb")
-
- content = f.readlines()
- content = [x.strip() for x in content if x.strip()]
-
- frame = Wrapper()
- plugin = Wrapper()
- pyfile = Wrapper()
-
- frame_c = []
- plugin_c = []
- pyfile_c = []
-
- dest = None
-
- for line in content:
- if line == "FRAMESTACK:":
- dest = frame_c
- continue
- elif line == "PLUGIN OBJECT DUMP:":
- dest = plugin_c
- continue
- elif line == "PYFILE OBJECT DUMP:":
- dest = pyfile_c
- continue
- elif line == "CONFIG:":
- dest = None
-
- if dest is not None:
- dest.append(line)
-
-
- frame_c = filter(filter_info, frame_c)
- plugin_c = filter(filter_info, plugin_c)
- pyfile_c = filter(filter_info, pyfile_c)
-
- frame_c = appendName(frame_c, "frame")
- plugin_c = appendName(plugin_c, "plugin")
- pyfile_c = appendName(pyfile_c, "pyfile")
-
- exec("\n".join(frame_c+plugin_c+pyfile_c) )
-
- return frame, plugin, pyfile
-
-if __name__=='__main__':
- print "No main method, use this module with your python shell" \ No newline at end of file
diff --git a/module/forwarder.py b/module/forwarder.py
deleted file mode 100644
index eacb33c2b..000000000
--- a/module/forwarder.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from sys import argv
-from sys import exit
-
-import socket
-import thread
-
-from traceback import print_exc
-
-class Forwarder():
-
- def __init__(self, extip,extport=9666):
- print "Start portforwarding to %s:%s" % (extip, extport)
- proxy(extip, extport, 9666)
-
-
-def proxy(*settings):
- while True:
- server(*settings)
-
-def server(*settings):
- try:
- dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- dock_socket.bind(("127.0.0.1", settings[2]))
- dock_socket.listen(5)
- while True:
- client_socket = dock_socket.accept()[0]
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.connect((settings[0], settings[1]))
- thread.start_new_thread(forward, (client_socket, server_socket))
- thread.start_new_thread(forward, (server_socket, client_socket))
- except Exception:
- print_exc()
-
-
-def forward(source, destination):
- string = ' '
- while string:
- string = source.recv(1024)
- if string:
- destination.sendall(string)
- else:
- #source.shutdown(socket.SHUT_RD)
- destination.shutdown(socket.SHUT_WR)
-
-if __name__ == "__main__":
- args = argv[1:]
- if not args:
- print "Usage: forwarder.py <remote ip> <remote port>"
- exit()
- if len(args) == 1:
- args.append(9666)
-
- f = Forwarder(args[0], int(args[1]))
- \ No newline at end of file
diff --git a/module/gui/AccountEdit.py b/module/gui/AccountEdit.py
deleted file mode 100644
index b22cfc49f..000000000
--- a/module/gui/AccountEdit.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-class AccountEdit(QWidget):
- """
- account editor widget
- """
-
- def __init__(self):
- QMainWindow.__init__(self)
-
- self.setWindowTitle(_("Edit account"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- self.setLayout(QGridLayout())
- l = self.layout()
-
- typeLabel = QLabel(_("Type"))
- loginLabel = QLabel(_("Login"))
- passwordLabel = QLabel(_("New password"))
- changePw = QCheckBox()
- changePw.setChecked(False)
- self.changePw = changePw
- password = QLineEdit()
- password.setEnabled(False)
- password.setEchoMode(QLineEdit.Password)
- self.password = password
- login = QLineEdit()
- self.login = login
- acctype = QComboBox()
- self.acctype = acctype
-
- save = QPushButton(_("Save"))
-
- self.connect(changePw, SIGNAL("toggled(bool)"), password, SLOT("setEnabled(bool)"))
-
- l.addWidget(save, 3, 0, 1, 3)
- l.addWidget(acctype, 0, 1, 1, 2)
- l.addWidget(login, 1, 1, 1, 2)
- l.addWidget(password, 2, 2)
- l.addWidget(changePw, 2, 1)
- l.addWidget(passwordLabel, 2, 0)
- l.addWidget(loginLabel, 1, 0)
- l.addWidget(typeLabel, 0, 0)
-
- self.connect(save, SIGNAL("clicked()"), self.slotSave)
-
- def slotSave(self):
- """
- save entered data
- """
- data = {"login": str(self.login.text()), "acctype": str(self.acctype.currentText()), "password": False}
- if self.changePw.isChecked():
- data["password"] = str(self.password.text())
- self.emit(SIGNAL("done"), data)
-
- @staticmethod
- def newAccount(types):
- """
- create empty editor instance
- """
- w = AccountEdit()
- w.setWindowTitle(_("Create account"))
-
- w.changePw.setChecked(True)
- w.password.setEnabled(True)
-
- w.acctype.addItems(types)
-
- return w
-
- @staticmethod
- def editAccount(types, base):
- """
- create editor instance with given data
- """
- w = AccountEdit()
-
- w.acctype.addItems(types)
- w.acctype.setCurrentIndex(types.index(base["type"]))
-
- w.login.setText(base["login"])
-
- return w
diff --git a/module/gui/Accounts.py b/module/gui/Accounts.py
deleted file mode 100644
index 8db04dfa9..000000000
--- a/module/gui/Accounts.py
+++ /dev/null
@@ -1,213 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from time import strftime, gmtime
-
-class AccountModel(QAbstractItemModel):
- """
- model for account view
- """
-
- def __init__(self, view, connector):
- QAbstractItemModel.__init__(self)
- self.connector = connector
- self.view = view
- self._data = []
- self.cols = 4
- self.mutex = QMutex()
-
- def reloadData(self, force=False):
- """
- reload account list
- """
- accounts = self.connector.proxy.getAccounts(False)
-
- if self._data == accounts:
- return
-
- if len(self._data) > 0:
- self.beginRemoveRows(QModelIndex(), 0, len(self._data)-1)
- self._data = []
- self.endRemoveRows()
-
- if len(accounts) > 0:
- self.beginInsertRows(QModelIndex(), 0, len(accounts)-1)
- self._data = accounts
- self.endInsertRows()
-
- def toData(self, index):
- """
- return index pointer
- """
- return index.internalPointer()
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(self.toData(index).type)
- elif index.column() == 1:
- return QVariant(self.toData(index).login)
- elif index.column() == 2:
- if not self.toData(index).valid:
- return QVariant(_("not valid"))
- if not self.toData(index).validuntil:
- return QVariant(_("n/a"))
- until = int(self.toData(index).validuntil)
- if until > 0:
- fmtime = strftime(_("%a, %d %b %Y %H:%M"), gmtime(until))
- return QVariant(fmtime)
- else:
- return QVariant(_("unlimited"))
- #elif role == Qt.EditRole:
- # if index.column() == 0:
- # return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def index(self, row, column, parent=QModelIndex()):
- """
- create index with data pointer
- """
- if parent == QModelIndex() and len(self._data) > row:
- pointer = self._data[row]
- index = self.createIndex(row, column, pointer)
- elif parent.isValid():
- pointer = parent.internalPointer().children[row]
- index = self.createIndex(row, column, pointer)
- else:
- index = QModelIndex()
- return index
-
- def parent(self, index):
- """
- no parents, everything on top level
- """
- return QModelIndex()
-
- def rowCount(self, parent=QModelIndex()):
- """
- account count
- """
- if parent == QModelIndex():
- return len(self._data)
- return 0
-
- def columnCount(self, parent=QModelIndex()):
- return self.cols
-
- def hasChildren(self, parent=QModelIndex()):
- """
- everything on top level
- """
- if parent == QModelIndex():
- return True
- else:
- return False
-
- def canFetchMore(self, parent):
- return False
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Type"))
- elif section == 1:
- return QVariant(_("Login"))
- elif section == 2:
- return QVariant(_("Valid until"))
- elif section == 3:
- return QVariant(_("Traffic left"))
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
-class AccountView(QTreeView):
- """
- view component for accounts
- """
-
- def __init__(self, connector):
- QTreeView.__init__(self)
- self.setModel(AccountModel(self, connector))
-
- self.setColumnWidth(0, 150)
- self.setColumnWidth(1, 150)
- self.setColumnWidth(2, 150)
- self.setColumnWidth(3, 150)
-
- self.setEditTriggers(QAbstractItemView.NoEditTriggers)
-
- self.delegate = AccountDelegate(self, self.model())
- self.setItemDelegateForColumn(3, self.delegate)
-
-class AccountDelegate(QItemDelegate):
- """
- used to display a progressbar for the traffic in the traffic cell
- """
-
- def __init__(self, parent, model):
- QItemDelegate.__init__(self, parent)
- self.model = model
-
- def paint(self, painter, option, index):
- """
- paint the progressbar
- """
- if not index.isValid():
- return
- if index.column() == 3:
- data = self.model.toData(index)
- opts = QStyleOptionProgressBarV2()
- opts.minimum = 0
- if data.trafficleft:
- if data.trafficleft == -1 or data.trafficleft is None:
- opts.maximum = opts.progress = 1
- else:
- opts.maximum = opts.progress = data.trafficleft
- if data.maxtraffic:
- opts.maximum = data.maxtraffic
-
- opts.rect = option.rect
- opts.rect.setRight(option.rect.right()-1)
- opts.rect.setHeight(option.rect.height()-1)
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- if data.trafficleft and data.trafficleft == -1:
- opts.text = QString(_("unlimited"))
- elif data.trafficleft is None:
- opts.text = QString(_("n/a"))
- else:
- opts.text = QString.number(round(float(opts.progress)/1024/1024, 2)) + " GB"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
- return
- QItemDelegate.paint(self, painter, option, index)
-
diff --git a/module/gui/CNLServer.py b/module/gui/CNLServer.py
deleted file mode 100644
index 5ecac8277..000000000
--- a/module/gui/CNLServer.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-import re
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-from threading import Thread
-from cgi import FieldStorage
-from os.path import abspath, dirname, join
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-try:
- from module.common import JsEngine
-except ImportError:
- import sys
- sys.path.append(join(abspath(dirname(__file__)), "..", ".."))
- from module.common import JsEngine
-
-js = JsEngine.JsEngine()
-core = None
-
-class CNLServer(Thread):
- def __init__(self):
- Thread.__init__(self)
- self.setDaemon(True)
-
- self.stop = False
- self.stopped = False
-
- def run(self):
- server_address = ('127.0.0.1', 9666)
- try:
- httpd = HTTPServer(server_address, CNLHandler)
- except:
- self.stopped = True
- return
-
- self.stopped = False
- while self.keep_running():
- httpd.handle_request()
- self.stopped = True
-
- def keep_running(self):
- return not self.stop
-
-
-class CNLHandler(BaseHTTPRequestHandler):
-
- #def log_message(self, *args):
- # pass
-
- def add_package(self, name, urls, queue=0):
- print "name", name
- print "urls", urls
- print "queue", queue
-
- def get_post(self, name, default=""):
- if name in self.post:
- return self.post[name]
- else:
- return default
-
- def start_response(self, string):
-
- self.send_response(200)
-
- self.send_header("Content-Length", len(string))
- self.send_header("Content-Language", "de")
- self.send_header("Vary", "Accept-Language, Cookie")
- self.send_header("Cache-Control", "no-cache, must-revalidate")
- self.send_header("Content-type", "text/html")
- self.end_headers()
-
- def do_GET(self):
- path = self.path.strip("/").lower()
- #self.wfile.write(path+"\n")
-
- self.map = [ (r"add$", self.add),
- (r"addcrypted$", self.addcrypted),
- (r"addcrypted2$", self.addcrypted2),
- (r"flashgot", self.flashgot),
- (r"crossdomain\.xml", self.crossdomain),
- (r"checkSupportForUrl", self.checksupport),
- (r"jdcheck.js", self.jdcheck),
- (r"", self.flash) ]
-
- func = None
- for r, f in self.map:
- if re.match(r"(flash(got)?/?)?"+r, path):
- func = f
- break
-
- if func:
- try:
- resp = func()
- if not resp: resp = "success"
- resp += "\r\n"
- self.start_response(resp)
- self.wfile.write(resp)
- except Exception,e :
- self.send_error(500, str(e))
- else:
- self.send_error(404, "Not Found")
-
- def do_POST(self):
- form = FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD':'POST',
- 'CONTENT_TYPE':self.headers['Content-Type'],
- })
-
- self.post = {}
- for name in form.keys():
- self.post[name] = form[name].value
-
- return self.do_GET()
-
- def flash(self):
- return "JDownloader"
-
- def add(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, 0)
-
- def addcrypted(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- dlc = self.get_post('crypted').replace(" ", "+")
-
- core.upload_container(package, dlc)
-
- def addcrypted2(self):
- package = self.get_post("source", "ClickAndLoad Package")
- crypted = self.get_post("crypted")
- jk = self.get_post("jk")
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- jk = "%s f()" % jk
- jk = js.eval(jk)
- Key = unhexlify(jk)
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- self.add_package(package, result, 0)
-
-
- def flashgot(self):
- autostart = int(self.get_post('autostart', 0))
- package = self.get_post('package', "FlashGot")
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, autostart)
-
- def crossdomain(self):
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
- def checksupport(self):
- pass
-
- def jdcheck(self):
- rep = "jdownloader=true;\n"
- rep += "var version='10629';\n"
- return rep
-
-
-if __name__ == "__main__":
- import xmlrpclib
- from module import InitHomeDir
- from module.ConfigParser import ConfigParser
-
- config = ConfigParser()
-
- ssl = ""
- if config.get("ssl", "activated"):
- ssl = "s"
-
- server_url = "http%s://%s:%s@%s:%s/" % (
- ssl,
- config.username,
- config.password,
- config.get("remote", "listenaddr"),
- config.get("remote", "port")
- )
-
- core = xmlrpclib.ServerProxy(server_url, allow_none=True)
-
- s = CNLServer()
- s.start()
- while not s.stopped:
- try:
- s.join(1)
- except KeyboardInterrupt:
- s.stop = True
- s.stopped = True
- print "quiting.."
diff --git a/module/gui/CaptchaDock.py b/module/gui/CaptchaDock.py
deleted file mode 100644
index b88cb53ca..000000000
--- a/module/gui/CaptchaDock.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class CaptchaDock(QDockWidget):
- """
- dock widget for captcha input
- """
-
- def __init__(self):
- QDockWidget.__init__(self, _("Captcha"))
- self.setObjectName("Captcha Dock")
- self.widget = CaptchaDockWidget(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.BottomDockWidgetArea)
- self.setFeatures(QDockWidget.NoDockWidgetFeatures)
- self.hide()
- self.processing = False
- self.currentID = None
- self.connect(self, SIGNAL("setTask"), self.setTask)
-
- def isFree(self):
- return not self.processing
-
- def setTask(self, tid, img, imgType):
- self.processing = True
- data = QByteArray(img)
- self.currentID = tid
- self.widget.emit(SIGNAL("setImage"), data)
- self.widget.input.setText("")
- self.show()
-
-class CaptchaDockWidget(QWidget):
- """
- widget for the input widgets
- """
-
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QHBoxLayout())
- layout = self.layout()
-
- imgLabel = QLabel()
- captchaInput = QLineEdit()
- okayButton = QPushButton(_("OK"))
- cancelButton = QPushButton(_("Cancel"))
-
- layout.addStretch()
- layout.addWidget(imgLabel)
- layout.addWidget(captchaInput)
- layout.addWidget(okayButton)
- layout.addWidget(cancelButton)
- layout.addStretch()
-
- self.input = captchaInput
-
- self.connect(okayButton, SIGNAL("clicked()"), self.slotSubmit)
- self.connect(captchaInput, SIGNAL("returnPressed()"), self.slotSubmit)
- self.connect(self, SIGNAL("setImage"), self.setImg)
- self.connect(self, SIGNAL("setPixmap(const QPixmap &)"), imgLabel, SLOT("setPixmap(const QPixmap &)"))
-
- def setImg(self, data):
- pixmap = QPixmap()
- pixmap.loadFromData(data)
- self.emit(SIGNAL("setPixmap(const QPixmap &)"), pixmap)
- self.input.setFocus(Qt.OtherFocusReason)
-
- def slotSubmit(self):
- text = self.input.text()
- tid = self.dock.currentID
- self.dock.currentID = None
- self.dock.emit(SIGNAL("done"), tid, str(text))
- self.dock.hide()
- self.dock.processing = False
-
diff --git a/module/gui/Collector.py b/module/gui/Collector.py
deleted file mode 100644
index 3ec4262f1..000000000
--- a/module/gui/Collector.py
+++ /dev/null
@@ -1,407 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from module.PyFile import statusMap
-from module.utils import formatSize
-
-from module.remote.thriftbackend.ThriftClient import Destination, FileDoesNotExists, ElementType
-
-statusMapReverse = dict((v,k) for k, v in statusMap.iteritems())
-
-translatedStatusMap = {} # -> CollectorModel.__init__
-
-class CollectorModel(QAbstractItemModel):
- """
- model for the collector view
- """
-
- def __init__(self, view, connector):
- QAbstractItemModel.__init__(self)
- self.connector = connector
- self.view = view
- self._data = []
- self.cols = 4
- self.interval = 1
- self.mutex = QMutex()
-
- global translatedStatusMap # workaround because i18n is not running at import time
- translatedStatusMap = {
- "finished": _("finished"),
- "offline": _("offline"),
- "online": _("online"),
- "queued": _("queued"),
- "skipped": _("skipped"),
- "waiting": _("waiting"),
- "temp. offline": _("temp. offline"),
- "starting": _("starting"),
- "failed": _("failed"),
- "aborted": _("aborted"),
- "decrypting": _("decrypting"),
- "custom": _("custom"),
- "downloading": _("downloading"),
- "processing": _("processing")
- }
-
- def translateStatus(self, string):
- """
- used to convert to locale specific status
- """
- return translatedStatusMap[string]
-
- def addEvent(self, event):
- """
- called from main loop, pass events to the correct methods
- """
- QMutexLocker(self.mutex)
- if event.eventname == "reload":
- self.fullReload()
- elif event.eventname == "remove":
- self.removeEvent(event)
- elif event.eventname == "insert":
- self.insertEvent(event)
- elif event.eventname == "update":
- self.updateEvent(event)
-
- def fullReload(self):
- """
- reload whole model, used at startup to load initial data
- """
- self._data = []
- order = self.connector.getPackageOrder(Destination.Collector)
- self.beginInsertRows(QModelIndex(), 0, len(order.values()))
- for position, pid in order.iteritems():
- pack = self.connector.getPackageData(pid)
- package = Package(pack)
- self._data.append(package)
- self._data = sorted(self._data, key=lambda p: p.data["order"])
- self.endInsertRows()
-
- def removeEvent(self, event):
- """
- remove an element from model
- """
- if event.type == ElementType.File:
- for p, package in enumerate(self._data):
- for k, child in enumerate(package.children):
- if child.id == event.id:
- self.beginRemoveRows(self.index(p, 0), k, k)
- del package.children[k]
- self.endRemoveRows()
- break
- else:
- for k, package in enumerate(self._data):
- if package.id == event.id:
- self.beginRemoveRows(QModelIndex(), k, k)
- del self._data[k]
- self.endRemoveRows()
- break
-
- def insertEvent(self, event):
- """
- inserts a new element in the model
- """
- if event.type == ElementType.File:
- try:
- info = self.connector.getFileData(event.id)
- except FileDoesNotExists:
- return
-
- for k, package in enumerate(self._data):
- if package.id == info.package:
- if package.getChild(info.fid):
- self.updateEvent(event)
- break
- self.beginInsertRows(self.index(k, 0), info.order, info.order)
- package.addChild(info)
- self.endInsertRows()
- break
- else:
- data = self.connector.getPackageData(event.id)
- package = Package(data)
- self.beginInsertRows(QModelIndex(), data.order, data.order)
- self._data.insert(data.order, package)
- self.endInsertRows()
-
- def updateEvent(self, event):
- """
- update an element in the model
- """
- if event.type == ElementType.File:
- try:
- info = self.connector.proxy.getFileData(event.id)
- except FileDoesNotExists:
- return
- for p, package in enumerate(self._data):
- if package.id == info.packageID:
- for k, child in enumerate(package.children):
- if child.id == event.id:
- child.update(info)
- if not info.status == 12:
- child.data["downloading"] = None
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
- break
- else:
- data = self.connector.getPackageData(event.id)
- if not data:
- return
- for p, package in enumerate(self._data):
- if package.id == event.id:
- package.update(data)
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(p, 0), self.index(p, self.cols))
- break
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- elif index.column() == 1:
- item = index.internalPointer()
- plugins = []
- if isinstance(item, Package):
- for child in item.children:
- if not child.data["plugin"] in plugins:
- plugins.append(child.data["plugin"])
- else:
- plugins.append(item.data["plugin"])
- return QVariant(", ".join(plugins))
- elif index.column() == 2:
- item = index.internalPointer()
- status = 0
- if isinstance(item, Package):
- for child in item.children:
- if child.data["status"] > status:
- status = child.data["status"]
- else:
- status = item.data["status"]
- return QVariant(self.translateStatus(statusMapReverse[status]))
- elif index.column() == 3:
- item = index.internalPointer()
- if isinstance(item, Link):
- return QVariant(formatSize(item.data["size"]))
- else:
- ms = 0
- for c in item.children:
- ms += c.data["size"]
- return QVariant(formatSize(ms))
- elif role == Qt.EditRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def index(self, row, column, parent=QModelIndex()):
- """
- creates a cell index with pointer to the data
- """
- if parent == QModelIndex() and len(self._data) > row:
- pointer = self._data[row]
- index = self.createIndex(row, column, pointer)
- elif parent.isValid():
- try:
- pointer = parent.internalPointer().children[row]
- except:
- return QModelIndex()
- index = self.createIndex(row, column, pointer)
- else:
- index = QModelIndex()
- return index
-
- def parent(self, index):
- """
- return index of parent element
- only valid for links
- """
- if index == QModelIndex():
- return QModelIndex()
- if index.isValid():
- link = index.internalPointer()
- if isinstance(link, Link):
- for k, pack in enumerate(self._data):
- if pack == link.package:
- return self.createIndex(k, 0, link.package)
- return QModelIndex()
-
- def rowCount(self, parent=QModelIndex()):
- """
- returns row count for the element
- """
- if parent == QModelIndex():
- #return package count
- return len(self._data)
- else:
- if parent.isValid():
- #index is valid
- pack = parent.internalPointer()
- if isinstance(pack, Package):
- #index points to a package
- #return len of children
- return len(pack.children)
- else:
- #index is invalid
- return False
- #files have no children
- return 0
-
- def columnCount(self, parent=QModelIndex()):
- return self.cols
-
- def hasChildren(self, parent=QModelIndex()):
- if not parent.isValid():
- return True
- return self.rowCount(parent) > 0
-
- def canFetchMore(self, parent):
- return False
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Name"))
- elif section == 1:
- return QVariant(_("Plugin"))
- elif section == 2:
- return QVariant(_("Status"))
- elif section == 3:
- return QVariant(_("Size"))
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- if index.column() == 0 and self.parent(index) == QModelIndex():
- return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
- def setData(self, index, value, role=Qt.EditRole):
- """
- called if package name editing is finished, sets new name
- """
- if index.column() == 0 and self.parent(index) == QModelIndex() and role == Qt.EditRole:
- self.connector.setPackageName(index.internalPointer().id, str(value.toString()))
- return True
-
-class Package(object):
- """
- package object in the model
- """
-
- def __init__(self, pack):
- self.id = pack.pid
- self.children = []
- for f in pack.links:
- self.addChild(f)
- self.data = {}
- self.update(pack)
-
- def update(self, pack):
- """
- update data dict from thift object
- """
- data = {
- "name": pack.name,
- "folder": pack.folder,
- "site": pack.site,
- "password": pack.password,
- "order": pack.order,
- }
- self.data.update(data)
-
- def addChild(self, f):
- """
- add child (Link) to package
- """
- self.children.insert(f.order, Link(f, self))
- self.children = sorted(self.children, key=lambda l: l.data["order"])
-
- def getChild(self, fid):
- """
- get child from package
- """
- for child in self.children:
- if child.id == int(fid):
- return child
- return None
-
- def getChildKey(self, fid):
- """
- get child index
- """
- for k, child in enumerate(self.children):
- if child.id == int(fid):
- return k
- return None
-
- def removeChild(self, fid):
- """
- remove child
- """
- for k, child in enumerate(self.children):
- if child.id == int(fid):
- del self.children[k]
-
-class Link(object):
- def __init__(self, f, pack):
- self.data = {"downloading": None}
- self.update(f)
- self.id = f.fid
- self.package = pack
-
- def update(self, f):
- """
- update data dict from thift object
- """
- data = {
- "url": f.url,
- "name": f.name,
- "plugin": f.plugin,
- "size": f.size,
- "format_size": f.format_size,
- "status": f.status,
- "statusmsg": f.statusmsg,
- "package": f.packageID,
- "error": f.error,
- "order": f.order,
- }
- self.data.update(data)
-
-class CollectorView(QTreeView):
- """
- view component for collector
- """
-
- def __init__(self, connector):
- QTreeView.__init__(self)
- self.setModel(CollectorModel(self, connector))
- self.setColumnWidth(0, 500)
- self.setColumnWidth(1, 100)
- self.setColumnWidth(2, 200)
- self.setColumnWidth(3, 100)
-
- self.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
-
diff --git a/module/gui/ConnectionManager.py b/module/gui/ConnectionManager.py
deleted file mode 100644
index def575abc..000000000
--- a/module/gui/ConnectionManager.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-from uuid import uuid4 as uuid
-
-class ConnectionManager(QWidget):
-
-
- warningShown = False
-
- def __init__(self):
- QWidget.__init__(self)
-
- if not self.warningShown:
- QMessageBox.warning(self, 'Warning',
- "We are sorry but the GUI is not stable yet. Please use the webinterface for much better experience. \n", QMessageBox.Ok)
- ConnectionManager.warningShown = True
-
- mainLayout = QHBoxLayout()
- buttonLayout = QVBoxLayout()
-
- self.setWindowTitle(_("pyLoad ConnectionManager"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- connList = QListWidget()
-
- new = QPushButton(_("New"))
- edit = QPushButton(_("Edit"))
- remove = QPushButton(_("Remove"))
- connect = QPushButton(_("Connect"))
-
- #box = QFrame()
- boxLayout = QVBoxLayout()
- #box.setLayout(boxLayout)
-
- boxLayout.addWidget(QLabel(_("Connect:")))
- boxLayout.addWidget(connList)
-
- line = QFrame()
- #line.setFixedWidth(100)
- line.setFrameShape(line.HLine)
- line.setFrameShadow(line.Sunken)
- line.setFixedHeight(10)
-
- boxLayout.addWidget(line)
-
- form = QFormLayout()
- form.setMargin(5)
- form.setSpacing(20)
-
- form.setAlignment(Qt.AlignRight)
- checkbox = QCheckBox()
- form.addRow(_("Use internal Core:"), checkbox)
-
- boxLayout.addLayout(form)
-
- mainLayout.addLayout(boxLayout)
- mainLayout.addLayout(buttonLayout)
-
- buttonLayout.addWidget(new)
- buttonLayout.addWidget(edit)
- buttonLayout.addWidget(remove)
- buttonLayout.addStretch()
- buttonLayout.addWidget(connect)
-
- self.setLayout(mainLayout)
-
- self.internal = checkbox
- self.new = new
- self.connectb = connect
- self.remove = remove
- self.editb = edit
- self.connList = connList
- self.edit = self.EditWindow()
- self.connectSignals()
-
- self.defaultStates = {}
-
- def connectSignals(self):
- self.connect(self, SIGNAL("setConnections"), self.setConnections)
- self.connect(self.new, SIGNAL("clicked()"), self.slotNew)
- self.connect(self.editb, SIGNAL("clicked()"), self.slotEdit)
- self.connect(self.remove, SIGNAL("clicked()"), self.slotRemove)
- self.connect(self.connectb, SIGNAL("clicked()"), self.slotConnect)
- self.connect(self.edit, SIGNAL("save"), self.slotSave)
- self.connect(self.connList, SIGNAL("itemDoubleClicked(QListWidgetItem *)"), self.slotItemDoubleClicked)
- self.connect(self.internal, SIGNAL("clicked()"), self.slotInternal)
-
- def setConnections(self, connections):
- self.connList.clear()
- for conn in connections:
- item = QListWidgetItem()
- item.setData(Qt.DisplayRole, QVariant(conn["name"]))
- item.setData(Qt.UserRole, QVariant(conn))
- self.connList.addItem(item)
- if conn["default"]:
- item.setData(Qt.DisplayRole, QVariant(_("%s (Default)") % conn["name"]))
- self.connList.setCurrentItem(item)
-
- def slotNew(self):
- data = {"id":uuid().hex, "type":"remote", "default":False, "name":"", "host":"", "port":"7228", "user":"admin", "password":""}
- self.edit.setData(data)
- self.edit.show()
-
- def slotEdit(self):
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.edit.setData(data)
- self.edit.show()
-
- def slotRemove(self):
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.emit(SIGNAL("removeConnection"), data)
-
- def slotConnect(self):
- if self.internal.checkState() == 2:
- data = {"type": "internal"}
- self.emit(SIGNAL("connect"), data)
- else:
- item = self.connList.currentItem()
- data = item.data(Qt.UserRole).toPyObject()
- data = self.cleanDict(data)
- self.emit(SIGNAL("connect"), data)
-
- def cleanDict(self, data):
- tmp = {}
- for k, d in data.items():
- tmp[str(k)] = d
- return tmp
-
- def slotSave(self, data):
- self.emit(SIGNAL("saveConnection"), data)
-
- def slotItemDoubleClicked(self, defaultItem):
- data = defaultItem.data(Qt.UserRole).toPyObject()
- self.setDefault(data, True)
- did = self.cleanDict(data)["id"]
- #allItems = self.connList.findItems("*", Qt.MatchWildcard)
- count = self.connList.count()
- for i in range(count):
- item = self.connList.item(i)
- data = item.data(Qt.UserRole).toPyObject()
- if self.cleanDict(data)["id"] == did:
- continue
- self.setDefault(data, False)
-
- def slotInternal(self):
- if self.internal.checkState() == 2:
- self.connList.clearSelection()
-
- def setDefault(self, data, state):
- data = self.cleanDict(data)
- self.edit.setData(data)
- data = self.edit.getData()
- data["default"] = state
- self.edit.emit(SIGNAL("save"), data)
-
- class EditWindow(QWidget):
- def __init__(self):
- QWidget.__init__(self)
-
- self.setWindowTitle(_("pyLoad ConnectionManager"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
-
- grid = QGridLayout()
-
- nameLabel = QLabel(_("Name:"))
- hostLabel = QLabel(_("Host:"))
- localLabel = QLabel(_("Local:"))
- userLabel = QLabel(_("User:"))
- pwLabel = QLabel(_("Password:"))
- portLabel = QLabel(_("Port:"))
-
- name = QLineEdit()
- host = QLineEdit()
- local = QCheckBox()
- user = QLineEdit()
- password = QLineEdit()
- password.setEchoMode(QLineEdit.Password)
- port = QSpinBox()
- port.setRange(1,10000)
-
- save = QPushButton(_("Save"))
- cancel = QPushButton(_("Cancel"))
-
- grid.addWidget(nameLabel, 0, 0)
- grid.addWidget(name, 0, 1)
- grid.addWidget(localLabel, 1, 0)
- grid.addWidget(local, 1, 1)
- grid.addWidget(hostLabel, 2, 0)
- grid.addWidget(host, 2, 1)
- grid.addWidget(portLabel, 3, 0)
- grid.addWidget(port, 3, 1)
- grid.addWidget(userLabel, 4, 0)
- grid.addWidget(user, 4, 1)
- grid.addWidget(pwLabel, 5, 0)
- grid.addWidget(password, 5, 1)
- grid.addWidget(cancel, 6, 0)
- grid.addWidget(save, 6, 1)
-
- self.setLayout(grid)
- self.controls = {"name": name,
- "host": host,
- "local": local,
- "user": user,
- "password": password,
- "port": port,
- "save": save,
- "cancel": cancel}
-
- self.connect(cancel, SIGNAL("clicked()"), self.hide)
- self.connect(save, SIGNAL("clicked()"), self.slotDone)
- self.connect(local, SIGNAL("stateChanged(int)"), self.slotLocalChanged)
-
- self.id = None
- self.default = None
-
- def setData(self, data):
- if not data: return
-
- self.id = data["id"]
- self.default = data["default"]
- self.controls["name"].setText(data["name"])
- if data["type"] == "local":
- data["local"] = True
- else:
- data["local"] = False
- self.controls["local"].setChecked(data["local"])
- if not data["local"]:
- self.controls["user"].setText(data["user"])
- self.controls["password"].setText(data["password"])
- self.controls["port"].setValue(int(data["port"]))
- self.controls["host"].setText(data["host"])
- self.controls["user"].setDisabled(False)
- self.controls["password"].setDisabled(False)
- self.controls["port"].setDisabled(False)
- self.controls["host"].setDisabled(False)
- else:
- self.controls["user"].setText("")
- self.controls["port"].setValue(1)
- self.controls["host"].setText("")
- self.controls["user"].setDisabled(True)
- self.controls["password"].setDisabled(True)
- self.controls["port"].setDisabled(True)
- self.controls["host"].setDisabled(True)
-
- def slotLocalChanged(self, val):
- if val == 2:
- self.controls["user"].setDisabled(True)
- self.controls["password"].setDisabled(True)
- self.controls["port"].setDisabled(True)
- self.controls["host"].setDisabled(True)
- elif val == 0:
- self.controls["user"].setDisabled(False)
- self.controls["password"].setDisabled(False)
- self.controls["port"].setDisabled(False)
- self.controls["host"].setDisabled(False)
-
- def getData(self):
- d = {}
- d["id"] = self.id
- d["default"] = self.default
- d["name"] = self.controls["name"].text()
- d["local"] = self.controls["local"].isChecked()
- d["user"] = self.controls["user"].text()
- d["password"] = self.controls["password"].text()
- d["host"] = self.controls["host"].text()
- d["port"] = self.controls["port"].value()
- if d["local"]:
- d["type"] = "local"
- else:
- d["type"] = "remote"
- return d
-
- def slotDone(self):
- data = self.getData()
- self.hide()
- self.emit(SIGNAL("save"), data)
-
diff --git a/module/gui/CoreConfigParser.py b/module/gui/CoreConfigParser.py
deleted file mode 100644
index 0d1d298c6..000000000
--- a/module/gui/CoreConfigParser.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-from os.path import exists
-from os.path import join
-
-
-CONF_VERSION = 1
-
-########################################################################
-class ConfigParser:
-
- #----------------------------------------------------------------------
- def __init__(self, configdir):
- """Constructor"""
- self.configdir = configdir
- self.config = {}
-
- if self.checkVersion():
- self.readConfig()
-
- #----------------------------------------------------------------------
- def checkVersion(self):
-
- if not exists(join(self.configdir, "pyload.conf")):
- return False
- f = open(join(self.configdir, "pyload.conf"), "rb")
- v = f.readline()
- f.close()
- v = v[v.find(":")+1:].strip()
-
- if int(v) < CONF_VERSION:
- return False
-
- return True
-
- #----------------------------------------------------------------------
- def readConfig(self):
- """reads the config file"""
-
- self.config = self.parseConfig(join(self.configdir, "pyload.conf"))
-
-
- #----------------------------------------------------------------------
- def parseConfig(self, config):
- """parses a given configfile"""
-
- f = open(config)
-
- config = f.read()
-
- config = config.split("\n")[1:]
-
- conf = {}
-
- section, option, value, typ, desc = "","","","",""
-
- listmode = False
-
- for line in config:
-
- line = line.rpartition("#") # removes comments
-
- if line[1]:
- line = line[0]
- else:
- line = line[2]
-
- line = line.strip()
-
- try:
-
- if line == "":
- continue
- elif line.endswith(":"):
- section, none, desc = line[:-1].partition('-')
- section = section.strip()
- desc = desc.replace('"', "").strip()
- conf[section] = { "desc" : desc }
- else:
- if listmode:
-
- if line.endswith("]"):
- listmode = False
- line = line.replace("]","")
-
- value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
-
- if not listmode:
- conf[section][option] = { "desc" : desc,
- "type" : typ,
- "value" : value}
-
-
- else:
- content, none, value = line.partition("=")
-
- content, none, desc = content.partition(":")
-
- desc = desc.replace('"', "").strip()
-
- typ, option = content.split()
-
- value = value.strip()
-
- if value.startswith("["):
- if value.endswith("]"):
- listmode = False
- value = value[:-1]
- else:
- listmode = True
-
- value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
- else:
- value = self.cast(typ, value)
-
- if not listmode:
- conf[section][option] = { "desc" : desc,
- "type" : typ,
- "value" : value}
-
- except:
- pass
-
-
- f.close()
- return conf
-
- #----------------------------------------------------------------------
- def cast(self, typ, value):
- """cast value to given format"""
- if type(value) not in (str, unicode):
- return value
-
- if typ == "int":
- return int(value)
- elif typ == "bool":
- return True if value.lower() in ("1","true", "on", "an","yes") else False
- else:
- return value
-
- #----------------------------------------------------------------------
- def get(self, section, option):
- """get value"""
- return self.config[section][option]["value"]
-
- #----------------------------------------------------------------------
- def __getitem__(self, section):
- """provides dictonary like access: c['section']['option']"""
- return Section(self, section)
-
-########################################################################
-class Section:
- """provides dictionary like access for configparser"""
-
- #----------------------------------------------------------------------
- def __init__(self, parser, section):
- """Constructor"""
- self.parser = parser
- self.section = section
-
- #----------------------------------------------------------------------
- def __getitem__(self, item):
- """getitem"""
- return self.parser.get(self.section, item)
diff --git a/module/gui/LinkDock.py b/module/gui/LinkDock.py
deleted file mode 100644
index ac2d4aae5..000000000
--- a/module/gui/LinkDock.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class NewLinkDock(QDockWidget):
- def __init__(self):
- QDockWidget.__init__(self, "New Links")
- self.setObjectName("New Links Dock")
- self.widget = NewLinkWindow(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea)
- self.hide()
-
- def slotDone(self):
- text = str(self.widget.box.toPlainText())
- lines = text.splitlines()
- self.emit(SIGNAL("done"), lines)
- self.widget.box.clear()
- self.hide()
-
-class NewLinkWindow(QWidget):
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QVBoxLayout())
- layout = self.layout()
-
- explanationLabel = QLabel("Select a package and then click Add button.")
- boxLabel = QLabel("Paste URLs here:")
- self.box = QTextEdit()
-
- save = QPushButton("Add")
-
- layout.addWidget(explanationLabel)
- layout.addWidget(boxLabel)
- layout.addWidget(self.box)
- layout.addWidget(save)
-
- self.connect(save, SIGNAL("clicked()"), self.dock.slotDone)
diff --git a/module/gui/MainWindow.py b/module/gui/MainWindow.py
deleted file mode 100644
index c71112e9b..000000000
--- a/module/gui/MainWindow.py
+++ /dev/null
@@ -1,697 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from os.path import join
-
-from module.gui.PackageDock import *
-from module.gui.LinkDock import *
-from module.gui.CaptchaDock import CaptchaDock
-from module.gui.SettingsWidget import SettingsWidget
-
-from module.gui.Collector import CollectorView, Package, Link
-from module.gui.Queue import QueueView
-from module.gui.Overview import OverviewView
-from module.gui.Accounts import AccountView
-from module.gui.AccountEdit import AccountEdit
-
-from module.remote.thriftbackend.ThriftClient import AccountInfo
-
-class MainWindow(QMainWindow):
- def __init__(self, connector):
- """
- set up main window
- """
- QMainWindow.__init__(self)
- #window stuff
- self.setWindowTitle(_("pyLoad Client"))
- self.setWindowIcon(QIcon(join(pypath, "icons","logo.png")))
- self.resize(1000,600)
-
- #layout version
- self.version = 3
-
- #init docks
- self.newPackDock = NewPackageDock()
- self.addDockWidget(Qt.RightDockWidgetArea, self.newPackDock)
- self.connect(self.newPackDock, SIGNAL("done"), self.slotAddPackage)
- self.captchaDock = CaptchaDock()
- self.addDockWidget(Qt.BottomDockWidgetArea, self.captchaDock)
- self.newLinkDock = NewLinkDock()
- self.addDockWidget(Qt.RightDockWidgetArea, self.newLinkDock)
- self.connect(self.newLinkDock, SIGNAL("done"), self.slotAddLinksToPackage)
-
- #central widget, layout
- self.masterlayout = QVBoxLayout()
- lw = QWidget()
- lw.setLayout(self.masterlayout)
- self.setCentralWidget(lw)
-
- #status
- self.statusw = QFrame()
- self.statusw.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
- self.statusw.setLineWidth(2)
- self.statusw.setLayout(QGridLayout())
- #palette = self.statusw.palette()
- #palette.setColor(QPalette.Window, QColor(255, 255, 255))
- #self.statusw.setPalette(palette)
- #self.statusw.setAutoFillBackground(True)
- l = self.statusw.layout()
-
- class BoldLabel(QLabel):
- def __init__(self, text):
- QLabel.__init__(self, text)
- f = self.font()
- f.setBold(True)
- self.setFont(f)
- self.setAlignment(Qt.AlignRight)
-
- class Seperator(QFrame):
- def __init__(self):
- QFrame.__init__(self)
- self.setFrameShape(QFrame.VLine)
- self.setFrameShadow(QFrame.Sunken)
-
- l.addWidget(BoldLabel(_("Packages:")), 0, 0)
- self.packageCount = QLabel("0")
- l.addWidget(self.packageCount, 0, 1)
-
- l.addWidget(BoldLabel(_("Files:")), 0, 2)
- self.fileCount = QLabel("0")
- l.addWidget(self.fileCount, 0, 3)
-
- l.addWidget(BoldLabel(_("Status:")), 0, 4)
- self.status = QLabel("running")
- l.addWidget(self.status, 0, 5)
-
- l.addWidget(BoldLabel(_("Space:")), 0, 6)
- self.space = QLabel("")
- l.addWidget(self.space, 0, 7)
-
- l.addWidget(BoldLabel(_("Speed:")), 0, 8)
- self.speed = QLabel("")
- l.addWidget(self.speed, 0, 9)
-
- #l.addWidget(BoldLabel(_("Max. downloads:")), 0, 9)
- #l.addWidget(BoldLabel(_("Max. chunks:")), 1, 9)
- #self.maxDownloads = QSpinBox()
- #self.maxDownloads.setEnabled(False)
- #self.maxChunks = QSpinBox()
- #self.maxChunks.setEnabled(False)
- #l.addWidget(self.maxDownloads, 0, 10)
- #l.addWidget(self.maxChunks, 1, 10)
-
- #set menubar and statusbar
- self.menubar = self.menuBar()
- #self.statusbar = self.statusBar()
- #self.connect(self.statusbar, SIGNAL("showMsg"), self.statusbar.showMessage)
- #self.serverStatus = QLabel(_("Status: Not Connected"))
- #self.statusbar.addPermanentWidget(self.serverStatus)
-
- #menu
- self.menus = {"file": self.menubar.addMenu(_("File")),
- "connections": self.menubar.addMenu(_("Connections"))}
-
- #menu actions
- self.mactions = {"exit": QAction(_("Exit"), self.menus["file"]),
- "manager": QAction(_("Connection manager"), self.menus["connections"])}
-
- #add menu actions
- self.menus["file"].addAction(self.mactions["exit"])
- self.menus["connections"].addAction(self.mactions["manager"])
-
- #toolbar
- self.actions = {}
- self.init_toolbar()
-
- #tabs
- self.tabw = QTabWidget()
- self.tabs = {"overview": {"w": QWidget()},
- "queue": {"w": QWidget()},
- "collector": {"w": QWidget()},
- "accounts": {"w": QWidget()},
- "settings": {}}
- #self.tabs["settings"]["s"] = QScrollArea()
- self.tabs["settings"]["w"] = SettingsWidget()
- #self.tabs["settings"]["s"].setWidgetResizable(True)
- #self.tabs["settings"]["s"].setWidget(self.tabs["settings"]["w"])
- self.tabs["log"] = {"w":QWidget()}
- self.tabw.addTab(self.tabs["overview"]["w"], _("Overview"))
- self.tabw.addTab(self.tabs["queue"]["w"], _("Queue"))
- self.tabw.addTab(self.tabs["collector"]["w"], _("Collector"))
- self.tabw.addTab(self.tabs["accounts"]["w"], _("Accounts"))
- self.tabw.addTab(self.tabs["settings"]["w"], _("Settings"))
- self.tabw.addTab(self.tabs["log"]["w"], _("Log"))
-
- #init tabs
- self.init_tabs(connector)
-
- #context menus
- self.init_context()
-
- #layout
- self.masterlayout.addWidget(self.tabw)
- self.masterlayout.addWidget(self.statusw)
-
- #signals..
- self.connect(self.mactions["manager"], SIGNAL("triggered()"), self.slotShowConnector)
-
- self.connect(self.tabs["queue"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotQueueContextMenu)
- self.connect(self.tabs["collector"]["package_view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotCollectorContextMenu)
- self.connect(self.tabs["accounts"]["view"], SIGNAL('customContextMenuRequested(const QPoint &)'), self.slotAccountContextMenu)
-
- self.connect(self.tabw, SIGNAL("currentChanged(int)"), self.slotTabChanged)
-
- self.lastAddedID = None
-
- self.connector = connector
-
- def init_toolbar(self):
- """
- create toolbar
- """
- self.toolbar = self.addToolBar(_("Hide Toolbar"))
- self.toolbar.setObjectName("Main Toolbar")
- self.toolbar.setIconSize(QSize(30,30))
- self.toolbar.setMovable(False)
- self.actions["toggle_status"] = self.toolbar.addAction(_("Toggle Pause/Resume"))
- pricon = QIcon()
- pricon.addFile(join(pypath, "icons","toolbar_start.png"), QSize(), QIcon.Normal, QIcon.Off)
- pricon.addFile(join(pypath, "icons","toolbar_pause.png"), QSize(), QIcon.Normal, QIcon.On)
- self.actions["toggle_status"].setIcon(pricon)
- self.actions["toggle_status"].setCheckable(True)
- self.actions["status_stop"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_stop.png")), _("Stop"))
- self.toolbar.addSeparator()
- self.actions["add"] = self.toolbar.addAction(QIcon(join(pypath, "icons","toolbar_add.png")), _("Add"))
- self.toolbar.addSeparator()
- self.actions["clipboard"] = self.toolbar.addAction(QIcon(join(pypath, "icons","clipboard.png")), _("Check Clipboard"))
- self.actions["clipboard"].setCheckable(True)
-
- self.connect(self.actions["toggle_status"], SIGNAL("toggled(bool)"), self.slotToggleStatus)
- self.connect(self.actions["clipboard"], SIGNAL("toggled(bool)"), self.slotToggleClipboard)
- self.connect(self.actions["status_stop"], SIGNAL("triggered()"), self.slotStatusStop)
- self.addMenu = QMenu()
- packageAction = self.addMenu.addAction(_("Package"))
- containerAction = self.addMenu.addAction(_("Container"))
- accountAction = self.addMenu.addAction(_("Account"))
- linksAction = self.addMenu.addAction(_("Links"))
- self.connect(self.actions["add"], SIGNAL("triggered()"), self.slotAdd)
- self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage)
- self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer)
- self.connect(accountAction, SIGNAL("triggered()"), self.slotNewAccount)
- self.connect(linksAction, SIGNAL("triggered()"), self.slotShowAddLinks)
-
- def init_tabs(self, connector):
- """
- create tabs
- """
- #overview
- self.tabs["overview"]["l"] = QGridLayout()
- self.tabs["overview"]["w"].setLayout(self.tabs["overview"]["l"])
- self.tabs["overview"]["view"] = OverviewView(connector)
- self.tabs["overview"]["l"].addWidget(self.tabs["overview"]["view"])
-
- #queue
- self.tabs["queue"]["l"] = QGridLayout()
- self.tabs["queue"]["w"].setLayout(self.tabs["queue"]["l"])
- self.tabs["queue"]["view"] = QueueView(connector)
- self.tabs["queue"]["l"].addWidget(self.tabs["queue"]["view"])
-
- #collector
- toQueue = QPushButton(_("Push selected packages to queue"))
- self.tabs["collector"]["l"] = QGridLayout()
- self.tabs["collector"]["w"].setLayout(self.tabs["collector"]["l"])
- self.tabs["collector"]["package_view"] = CollectorView(connector)
- self.tabs["collector"]["l"].addWidget(self.tabs["collector"]["package_view"], 0, 0)
- self.tabs["collector"]["l"].addWidget(toQueue, 1, 0)
- self.connect(toQueue, SIGNAL("clicked()"), self.slotPushPackageToQueue)
- self.tabs["collector"]["package_view"].setContextMenuPolicy(Qt.CustomContextMenu)
- self.tabs["queue"]["view"].setContextMenuPolicy(Qt.CustomContextMenu)
-
- #log
- self.tabs["log"]["l"] = QGridLayout()
- self.tabs["log"]["w"].setLayout(self.tabs["log"]["l"])
- self.tabs["log"]["text"] = QTextEdit()
- self.tabs["log"]["text"].logOffset = 0
- self.tabs["log"]["text"].setReadOnly(True)
- self.connect(self.tabs["log"]["text"], SIGNAL("append(QString)"), self.tabs["log"]["text"].append)
- self.tabs["log"]["l"].addWidget(self.tabs["log"]["text"])
-
- #accounts
- self.tabs["accounts"]["view"] = AccountView(connector)
- self.tabs["accounts"]["w"].setLayout(QVBoxLayout())
- self.tabs["accounts"]["w"].layout().addWidget(self.tabs["accounts"]["view"])
- newbutton = QPushButton(_("New Account"))
- self.tabs["accounts"]["w"].layout().addWidget(newbutton)
- self.connect(newbutton, SIGNAL("clicked()"), self.slotNewAccount)
- self.tabs["accounts"]["view"].setContextMenuPolicy(Qt.CustomContextMenu)
-
- def init_context(self):
- """
- create context menus
- """
- self.activeMenu = None
- #queue
- self.queueContext = QMenu()
- self.queueContext.buttons = {}
- self.queueContext.item = (None, None)
- self.queueContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.queueContext)
- self.queueContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.queueContext)
- self.queueContext.buttons["pull"] = QAction(QIcon(join(pypath, "icons","pull_small.png")), _("Pull out"), self.queueContext)
- self.queueContext.buttons["abort"] = QAction(QIcon(join(pypath, "icons","abort.png")), _("Abort"), self.queueContext)
- self.queueContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.queueContext)
- self.queueContext.addAction(self.queueContext.buttons["pull"])
- self.queueContext.addAction(self.queueContext.buttons["edit"])
- self.queueContext.addAction(self.queueContext.buttons["remove"])
- self.queueContext.addAction(self.queueContext.buttons["restart"])
- self.queueContext.addAction(self.queueContext.buttons["abort"])
- self.connect(self.queueContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload)
- self.connect(self.queueContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload)
- self.connect(self.queueContext.buttons["pull"], SIGNAL("triggered()"), self.slotPullOutPackage)
- self.connect(self.queueContext.buttons["abort"], SIGNAL("triggered()"), self.slotAbortDownload)
- self.connect(self.queueContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage)
-
- #collector
- self.collectorContext = QMenu()
- self.collectorContext.buttons = {}
- self.collectorContext.item = (None, None)
- self.collectorContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.collectorContext)
- self.collectorContext.buttons["push"] = QAction(QIcon(join(pypath, "icons","push_small.png")), _("Push to queue"), self.collectorContext)
- self.collectorContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit Name"), self.collectorContext)
- self.collectorContext.buttons["restart"] = QAction(QIcon(join(pypath, "icons","refresh_small.png")), _("Restart"), self.collectorContext)
- self.collectorContext.buttons["refresh"] = QAction(QIcon(join(pypath, "icons","refresh1_small.png")),_("Refresh Status"), self.collectorContext)
- self.collectorContext.addAction(self.collectorContext.buttons["push"])
- self.collectorContext.addSeparator()
- self.collectorContext.buttons["add"] = self.collectorContext.addMenu(QIcon(join(pypath, "icons","add_small.png")), _("Add"))
- self.collectorContext.addAction(self.collectorContext.buttons["edit"])
- self.collectorContext.addAction(self.collectorContext.buttons["remove"])
- self.collectorContext.addAction(self.collectorContext.buttons["restart"])
- self.collectorContext.addSeparator()
- self.collectorContext.addAction(self.collectorContext.buttons["refresh"])
- packageAction = self.collectorContext.buttons["add"].addAction(_("Package"))
- containerAction = self.collectorContext.buttons["add"].addAction(_("Container"))
- linkAction = self.collectorContext.buttons["add"].addAction(_("Links"))
- self.connect(self.collectorContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveDownload)
- self.connect(self.collectorContext.buttons["push"], SIGNAL("triggered()"), self.slotPushPackageToQueue)
- self.connect(self.collectorContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditPackage)
- self.connect(self.collectorContext.buttons["restart"], SIGNAL("triggered()"), self.slotRestartDownload)
- self.connect(self.collectorContext.buttons["refresh"], SIGNAL("triggered()"), self.slotRefreshPackage)
- self.connect(packageAction, SIGNAL("triggered()"), self.slotShowAddPackage)
- self.connect(containerAction, SIGNAL("triggered()"), self.slotShowAddContainer)
- self.connect(linkAction, SIGNAL("triggered()"), self.slotShowAddLinks)
-
- self.accountContext = QMenu()
- self.accountContext.buttons = {}
- self.accountContext.buttons["add"] = QAction(QIcon(join(pypath, "icons","add_small.png")), _("Add"), self.accountContext)
- self.accountContext.buttons["remove"] = QAction(QIcon(join(pypath, "icons","remove_small.png")), _("Remove"), self.accountContext)
- self.accountContext.buttons["edit"] = QAction(QIcon(join(pypath, "icons","edit_small.png")), _("Edit"), self.accountContext)
- self.accountContext.addAction(self.accountContext.buttons["add"])
- self.accountContext.addAction(self.accountContext.buttons["edit"])
- self.accountContext.addAction(self.accountContext.buttons["remove"])
- self.connect(self.accountContext.buttons["add"], SIGNAL("triggered()"), self.slotNewAccount)
- self.connect(self.accountContext.buttons["edit"], SIGNAL("triggered()"), self.slotEditAccount)
- self.connect(self.accountContext.buttons["remove"], SIGNAL("triggered()"), self.slotRemoveAccount)
-
- def slotToggleStatus(self, status):
- """
- pause/start toggle (toolbar)
- """
- self.emit(SIGNAL("setDownloadStatus"), status)
-
- def slotStatusStop(self):
- """
- stop button (toolbar)
- """
- self.emit(SIGNAL("stopAllDownloads"))
-
- def slotAdd(self):
- """
- add button (toolbar)
- show context menu (choice: links/package)
- """
- self.addMenu.exec_(QCursor.pos())
-
- def slotShowAddPackage(self):
- """
- action from add-menu
- show new-package dock
- """
- self.tabw.setCurrentIndex(1)
- self.newPackDock.show()
-
- def slotShowAddLinks(self):
- """
- action from add-menu
- show new-links dock
- """
- self.tabw.setCurrentIndex(1)
- self.newLinkDock.show()
-
- def slotShowConnector(self):
- """
- connectionmanager action triggered
- let main to the stuff
- """
- self.emit(SIGNAL("connector"))
-
- def slotAddPackage(self, name, links, password=None):
- """
- new package
- let main to the stuff
- """
- self.emit(SIGNAL("addPackage"), name, links, password)
-
- def slotAddLinksToPackage(self, links):
- """
- adds links to currently selected package
- only in collector
- """
- if self.tabw.currentIndex() != 1:
- return
-
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.connector.proxy.addFiles(item.id, links)
- break
-
- def slotShowAddContainer(self):
- """
- action from add-menu
- show file selector, emit upload
- """
- typeStr = ";;".join([
- _("All Container Types (%s)") % "*.dlc *.ccf *.rsdf *.txt",
- _("DLC (%s)") % "*.dlc",
- _("CCF (%s)") % "*.ccf",
- _("RSDF (%s)") % "*.rsdf",
- _("Text Files (%s)") % "*.txt"
- ])
- fileNames = QFileDialog.getOpenFileNames(self, _("Open container"), "", typeStr)
- for name in fileNames:
- self.emit(SIGNAL("addContainer"), str(name))
-
- def slotPushPackageToQueue(self):
- """
- push collector pack to queue
- get child ids
- let main to the rest
- """
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.emit(SIGNAL("pushPackageToQueue"), item.id)
- else:
- self.emit(SIGNAL("pushPackageToQueue"), item.package.id)
-
- def saveWindow(self):
- """
- get window state/geometry
- pass data to main
- """
- state_raw = self.saveState(self.version)
- geo_raw = self.saveGeometry()
-
- state = str(state_raw.toBase64())
- geo = str(geo_raw.toBase64())
-
- self.emit(SIGNAL("saveMainWindow"), state, geo)
-
- def closeEvent(self, event):
- """
- somebody wants to close me!
- let me first save my state
- """
- self.saveWindow()
- event.ignore()
- self.hide()
- self.emit(SIGNAL("hidden"))
-
- # quit when no tray is available
- if not QSystemTrayIcon.isSystemTrayAvailable():
- self.emit(SIGNAL("Quit"))
-
- def restoreWindow(self, state, geo):
- """
- restore window state/geometry
- """
- state = QByteArray(state)
- geo = QByteArray(geo)
-
- state_raw = QByteArray.fromBase64(state)
- geo_raw = QByteArray.fromBase64(geo)
-
- self.restoreState(state_raw, self.version)
- self.restoreGeometry(geo_raw)
-
- def slotQueueContextMenu(self, pos):
- """
- custom context menu in queue view requested
- """
- globalPos = self.tabs["queue"]["view"].mapToGlobal(pos)
- i = self.tabs["queue"]["view"].indexAt(pos)
- if not i:
- return
- item = i.internalPointer()
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.activeMenu = self.queueContext
- showAbort = False
- if isinstance(item, Link) and item.data["downloading"]:
- showAbort = True
- elif isinstance(item, Package):
- for child in item.children:
- if child.data["downloading"]:
- showAbort = True
- break
- if showAbort:
- self.queueContext.buttons["abort"].setEnabled(True)
- else:
- self.queueContext.buttons["abort"].setEnabled(False)
- if isinstance(item, Package):
- self.queueContext.index = i
- #self.queueContext.buttons["remove"].setEnabled(True)
- #self.queueContext.buttons["restart"].setEnabled(True)
- self.queueContext.buttons["pull"].setEnabled(True)
- self.queueContext.buttons["edit"].setEnabled(True)
- elif isinstance(item, Link):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(True)
- else:
- self.queueContext.index = None
- #self.queueContext.buttons["remove"].setEnabled(False)
- #self.queueContext.buttons["restart"].setEnabled(False)
- self.queueContext.buttons["pull"].setEnabled(False)
- self.queueContext.buttons["edit"].setEnabled(False)
- self.queueContext.exec_(menuPos)
-
- def slotCollectorContextMenu(self, pos):
- """
- custom context menu in package collector view requested
- """
- globalPos = self.tabs["collector"]["package_view"].mapToGlobal(pos)
- i = self.tabs["collector"]["package_view"].indexAt(pos)
- if not i:
- return
- item = i.internalPointer()
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.activeMenu = self.collectorContext
- if isinstance(item, Package):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(True)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(True)
- self.collectorContext.buttons["restart"].setEnabled(True)
- elif isinstance(item, Link):
- self.collectorContext.index = i
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(True)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(True)
- else:
- self.collectorContext.index = None
- self.collectorContext.buttons["edit"].setEnabled(False)
- self.collectorContext.buttons["remove"].setEnabled(False)
- self.collectorContext.buttons["push"].setEnabled(False)
- self.collectorContext.buttons["restart"].setEnabled(False)
- self.collectorContext.exec_(menuPos)
-
- def slotLinkCollectorContextMenu(self, pos):
- """
- custom context menu in link collector view requested
- """
- pass
-
- def slotRestartDownload(self):
- """
- restart download action is triggered
- """
- smodel = self.tabs["queue"]["view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("restartDownload"), item.id, isinstance(item, Package))
-
- def slotRemoveDownload(self):
- """
- remove download action is triggered
- """
- if self.activeMenu == self.queueContext:
- view = self.tabs["queue"]["view"]
- else:
- view = self.tabs["collector"]["package_view"]
- smodel = view.selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("removeDownload"), item.id, isinstance(item, Package))
-
- def slotToggleClipboard(self, status):
- """
- check clipboard (toolbar)
- """
- self.emit(SIGNAL("setClipboardStatus"), status)
-
- def slotEditPackage(self):
- # in Queue, only edit name
- if self.activeMenu == self.queueContext:
- view = self.tabs["queue"]["view"]
- else:
- view = self.tabs["collector"]["package_view"]
- view.edit(self.activeMenu.index)
-
- def slotEditCommit(self, editor):
- self.emit(SIGNAL("changePackageName"), self.activeMenu.index.internalPointer().id, editor.text())
-
- def slotPullOutPackage(self):
- """
- pull package out of the queue
- """
- smodel = self.tabs["queue"]["view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- if isinstance(item, Package):
- self.emit(SIGNAL("pullOutPackage"), item.id)
- else:
- self.emit(SIGNAL("pullOutPackage"), item.package.id)
-
- def slotAbortDownload(self):
- view = self.tabs["queue"]["view"]
- smodel = view.selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- self.emit(SIGNAL("abortDownload"), item.id, isinstance(item, Package))
-
- # TODO disabled because changing desktop on linux, main window disappears
- #def changeEvent(self, e):
- # if e.type() == QEvent.WindowStateChange and self.isMinimized():
- # e.ignore()
- # self.hide()
- # self.emit(SIGNAL("hidden"))
- # else:
- # super(MainWindow, self).changeEvent(e)
-
- def slotTabChanged(self, index):
- if index == 2:
- self.emit(SIGNAL("reloadAccounts"))
- elif index == 3:
- self.tabs["settings"]["w"].loadConfig()
-
- def slotRefreshPackage(self):
- smodel = self.tabs["collector"]["package_view"].selectionModel()
- for index in smodel.selectedRows(0):
- item = index.internalPointer()
- pid = item.id
- if isinstance(item, Link):
- pid = item.package.id
- self.emit(SIGNAL("refreshStatus"), pid)
-
- def slotNewAccount(self):
- types = self.connector.proxy.getAccountTypes()
- self.accountEdit = AccountEdit.newAccount(types)
-
- #TODO make more easy n1, n2, n3
- def save(data):
- if data["password"]:
- self.accountEdit.close()
- n1 = data["acctype"]
- n2 = data["login"]
- n3 = data["password"]
- self.connector.updateAccount(n1, n2, n3, None)
-
- self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save)
- self.accountEdit.show()
-
- def slotEditAccount(self):
- types = self.connector.getAccountTypes()
-
- data = self.tabs["accounts"]["view"].selectedIndexes()
- if len(data) < 1:
- return
-
- data = data[0].internalPointer()
-
- self.accountEdit = AccountEdit.editAccount(types, data)
-
- #TODO make more easy n1, n2, n3
- #TODO reload accounts tab after insert of edit account
- #TODO if account does not exist give error
- def save(data):
- self.accountEdit.close()
- n1 = data["acctype"]
- n2 = data["login"]
- if data["password"]:
- n3 = data["password"]
- self.connector.updateAccount(n1, n2, n3, None)
-
- self.accountEdit.connect(self.accountEdit, SIGNAL("done"), save)
- self.accountEdit.show()
-
- def slotRemoveAccount(self):
- data = self.tabs["accounts"]["view"].selectedIndexes()
- if len(data) < 1:
- return
-
- data = data[0].internalPointer()
-
- self.connector.removeAccount(data.type, data.login)
-
- def slotAccountContextMenu(self, pos):
- globalPos = self.tabs["accounts"]["view"].mapToGlobal(pos)
- i = self.tabs["accounts"]["view"].indexAt(pos)
- if not i:
- return
-
- data = i.internalPointer()
-
- if data is None:
- self.accountContext.buttons["edit"].setEnabled(False)
- self.accountContext.buttons["remove"].setEnabled(False)
- else:
- self.accountContext.buttons["edit"].setEnabled(True)
- self.accountContext.buttons["remove"].setEnabled(True)
-
- menuPos = QCursor.pos()
- menuPos.setX(menuPos.x()+2)
- self.accountContext.exec_(menuPos)
diff --git a/module/gui/Overview.py b/module/gui/Overview.py
deleted file mode 100644
index 183383b5e..000000000
--- a/module/gui/Overview.py
+++ /dev/null
@@ -1,197 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from module.utils import formatSpeed, formatSize
-
-class OverviewModel(QAbstractListModel):
- PackageName = 10
- Progress = 11
- PartsFinished = 12
- Parts = 13
- ETA = 14
- Speed = 15
- CurrentSize = 16
- MaxSize = 17
- Status = 18
-
- def __init__(self, view, connector):
- QAbstractListModel.__init__(self)
-
- self.packages = []
-
- def queueChanged(self): #dirty..
- self.beginResetModel()
-
- self.packages = []
-
- def partsFinished(p):
- f = 0
- for c in p.children:
- if c.data["status"] == 0:
- f += 1
- return f
-
- def maxSize(p):
- ms = 0
- cs = 0
- for c in p.children:
- try:
- s = c.data["downloading"]["size"]
- except:
- s = c.data["size"]
- if c.data["downloading"]:
- cs += s - c.data["downloading"]["bleft"]
- elif self.queue.getProgress(c, False) == 100:
- cs += s
- ms += s
- return ms, cs
-
- def getProgress(p):
- for c in p.children:
- if c.data["status"] == 13:
- pass # TODO return _("Unpacking"), int(c.data["progress"])
- return _("Downloading"), self.queue.getProgress(p)
-
- d = self.queue._data
- for p in d:
- status, progress = getProgress(p)
- maxsize, currentsize = maxSize(p)
- speed = self.queue.getSpeed(p)
- if speed:
- eta = (maxsize - (maxsize * (progress/100.0)))/speed
- else:
- eta = 0
- if not speed and not progress:
- status = _("Queued")
- info = {
- OverviewModel.PackageName: p.data["name"],
- OverviewModel.Progress: progress,
- OverviewModel.PartsFinished: partsFinished(p),
- OverviewModel.Parts: len(p.children),
- OverviewModel.ETA: int(eta),
- OverviewModel.Speed: speed,
- OverviewModel.CurrentSize: currentsize,
- OverviewModel.MaxSize: maxsize,
- OverviewModel.Status: status,
- }
-
- self.packages.append(info)
-
- self.endResetModel()
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- return QVariant(_("Package"))
-
- def rowCount(self, parent=QModelIndex()):
- return len(self.packages)
-
- def data(self, index, role=Qt.DisplayRole):
- if role in [OverviewModel.PackageName, OverviewModel.Progress, OverviewModel.PartsFinished, OverviewModel.Parts, OverviewModel.ETA, OverviewModel.Speed, OverviewModel.CurrentSize, OverviewModel.MaxSize, OverviewModel.Status]:
- return QVariant(self.packages[index.row()][role])
- return QVariant()
-
-class OverviewView(QListView):
- def __init__(self, connector):
- QListView.__init__(self)
- self.setModel(OverviewModel(self, connector))
-
- self.setAlternatingRowColors(True)
- self.delegate = OverviewDelegate(self)
- self.setItemDelegate(self.delegate)
-
-class OverviewDelegate(QItemDelegate):
- def __init__(self, parent):
- QItemDelegate.__init__(self, parent)
- self.parent = parent
- self.model = parent.model()
-
- def paint(self, painter, option, index):
- option.rect.setHeight(59+16)
- option.rect.setWidth(self.parent.width()-20)
-
- #if option.state & QStyle.State_Selected:
- # painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
-
- packagename = index.data(OverviewModel.PackageName).toString()
- partsf = index.data(OverviewModel.PartsFinished).toString()
- parts = index.data(OverviewModel.Parts).toString()
- eta = int(index.data(OverviewModel.ETA).toString())
- speed = index.data(OverviewModel.Speed).toString() or 0
- progress = int(index.data(OverviewModel.Progress).toString())
- currentSize = int(index.data(OverviewModel.CurrentSize).toString())
- maxSize = int(index.data(OverviewModel.MaxSize).toString())
- status = index.data(OverviewModel.Status).toString()
-
- def formatEta(seconds): #TODO add to utils
- if seconds <= 0: return ""
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return _("ETA: ") + "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
- statusline = QString(_("Parts: ") + "%s/%s" % (partsf, parts))
- if partsf == parts:
- speedline = _("Finished")
- elif not status == _("Downloading"):
- speedline = QString(status)
- else:
- speedline = QString(formatEta(eta) + " " + _("Speed: %s") % formatSpeed(speed))
-
- if progress in (0,100):
- sizeline = QString(_("Size:") + "%s" % formatSize(maxSize))
- else:
- sizeline = QString(_("Size:") + "%s / %s" % (formatSize(currentSize), formatSize(maxSize)))
-
- f = painter.font()
- f.setPointSize(12)
- f.setBold(True)
- painter.setFont(f)
-
- r = option.rect.adjusted(4, 4, -4, -4)
- painter.drawText(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename)
- newr = painter.boundingRect(r.left(), r.top(), r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, packagename)
-
- f.setPointSize(10)
- f.setBold(False)
- painter.setFont(f)
-
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline)
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignHCenter, sizeline)
- painter.drawText(r.left(), newr.bottom()+5, r.width(), r.height(), Qt.AlignTop | Qt.AlignRight, speedline)
- newr = painter.boundingRect(r.left(), newr.bottom()+2, r.width(), r.height(), Qt.AlignTop | Qt.AlignLeft, statusline)
- newr.setTop(newr.bottom()+8)
- newr.setBottom(newr.top()+20)
- newr.setRight(self.parent.width()-25)
-
- f.setPointSize(10)
- painter.setFont(f)
-
- opts = QStyleOptionProgressBarV2()
- opts.maximum = 100
- opts.minimum = 0
- opts.progress = progress
- opts.rect = newr
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- opts.text = QString.number(opts.progress) + "%"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
-
- def sizeHint(self, option, index):
- return QSize(self.parent.width()-22, 59+16)
diff --git a/module/gui/PackageDock.py b/module/gui/PackageDock.py
deleted file mode 100644
index 73db8f177..000000000
--- a/module/gui/PackageDock.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-import re
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-class NewPackageDock(QDockWidget):
- def __init__(self):
- QDockWidget.__init__(self, _("New Package"))
- self.setObjectName("New Package Dock")
- self.widget = NewPackageWindow(self)
- self.setWidget(self.widget)
- self.setAllowedAreas(Qt.RightDockWidgetArea|Qt.LeftDockWidgetArea)
- self.hide()
-
- def slotDone(self):
- text = str(self.widget.box.toPlainText())
- pw = str(self.widget.passwordInput.text())
- if not pw:
- pw = None
- lines = []
- for line in text.splitlines():
- line = line.strip()
- if not line:
- continue
- lines.append(line)
- self.emit(SIGNAL("done"), str(self.widget.nameInput.text()), lines, pw)
- self.widget.nameInput.setText("")
- self.widget.passwordInput.setText("")
- self.widget.box.clear()
- self.hide()
-
- def parseUri(self):
-
- text=str(self.widget.box.toPlainText())
- self.widget.box.setText("")
- result = re.findall(r"(?:ht|f)tps?:\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}", text)
- for url in result:
- if "\n" or "\t" or "\r" or "\"" or "<" or "'" in url:
- url = url[:-1]
- self.widget.box.append("%s " % url)
-
-class NewPackageWindow(QWidget):
- def __init__(self, dock):
- QWidget.__init__(self)
- self.dock = dock
- self.setLayout(QGridLayout())
- layout = self.layout()
-
- nameLabel = QLabel(_("Name"))
- nameInput = QLineEdit()
- passwordLabel = QLabel(_("Password"))
- passwordInput = QLineEdit()
-
- linksLabel = QLabel(_("Links in this Package"))
-
- self.box = QTextEdit()
- self.nameInput = nameInput
- self.passwordInput = passwordInput
-
- save = QPushButton(_("Create"))
- parseUri = QPushButton(_("Filter URLs"))
-
- layout.addWidget(nameLabel, 0, 0)
- layout.addWidget(nameInput, 0, 1)
- layout.addWidget(passwordLabel, 1, 0)
- layout.addWidget(passwordInput, 1, 1)
- layout.addWidget(linksLabel, 2, 0, 1, 2)
- layout.addWidget(self.box, 3, 0, 1, 2)
- layout.addWidget(parseUri, 4, 0, 1, 2)
- layout.addWidget(save, 5, 0, 1, 2)
-
- self.connect(save, SIGNAL("clicked()"), self.dock.slotDone)
- self.connect(parseUri, SIGNAL("clicked()"), self.dock.parseUri) \ No newline at end of file
diff --git a/module/gui/Queue.py b/module/gui/Queue.py
deleted file mode 100644
index 0a0cbb810..000000000
--- a/module/gui/Queue.py
+++ /dev/null
@@ -1,390 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from time import time
-
-from module.remote.thriftbackend.ThriftClient import Destination
-from module.gui.Collector import CollectorModel, Package, Link, CollectorView, statusMapReverse
-from module.utils import formatSize, formatSpeed
-
-class QueueModel(CollectorModel):
- """
- model for the queue view, inherits from CollectorModel
- """
-
- def __init__(self, view, connector):
- CollectorModel.__init__(self, view, connector)
- self.cols = 6
- self.wait_dict = {}
-
- self.updater = self.QueueUpdater(self.interval)
- self.connect(self.updater, SIGNAL("update()"), self.update)
-
- class QueueUpdater(QObject):
- """
- timer which emits signal for a download status reload
- @TODO: make intervall configurable
- """
-
- def __init__(self, interval):
- QObject.__init__(self)
-
- self.interval = interval
- self.timer = QTimer()
- self.timer.connect(self.timer, SIGNAL("timeout()"), self, SIGNAL("update()"))
-
- def start(self):
- self.timer.start(1000)
-
- def stop(self):
- self.timer.stop()
-
- def start(self):
- self.updater.start()
-
- def stop(self):
- self.updater.stop()
-
- def fullReload(self):
- """
- reimplements CollectorModel.fullReload, because we want the Queue data
- """
- self._data = []
- order = self.connector.getPackageOrder(Destination.Queue)
- self.beginInsertRows(QModelIndex(), 0, len(order.values()))
- for position, pid in order.iteritems():
- pack = self.connector.getPackageData(pid)
- package = Package(pack)
- self._data.append(package)
- self._data = sorted(self._data, key=lambda p: p.data["order"])
- self.endInsertRows()
- self.updateCount()
-
- def insertEvent(self, event):
- """
- wrap CollectorModel.insertEvent to update the element count
- """
- CollectorModel.insertEvent(self, event)
- self.updateCount()
-
- def removeEvent(self, event):
- """
- wrap CollectorModel.removeEvent to update the element count
- """
- CollectorModel.removeEvent(self, event)
- self.updateCount()
-
- def updateEvent(self, event):
- """
- wrap CollectorModel.updateEvent to update the element count
- """
- CollectorModel.updateEvent(self, event)
- self.updateCount()
-
- def updateCount(self):
- """
- calculate package- and filecount for statusbar,
- ugly?: Overview connects to this signal for updating
- """
- packageCount = len(self._data)
- fileCount = 0
- for p in self._data:
- fileCount += len(p.children)
- self.mutex.unlock()
- self.emit(SIGNAL("updateCount"), packageCount, fileCount)
- self.mutex.lock()
-
- def update(self):
- """
- update slot for download status updating
- """
- locker = QMutexLocker(self.mutex)
- downloading = self.connector.statusDownloads()
- if not downloading:
- return
- for p, pack in enumerate(self._data):
- for d in downloading:
- child = pack.getChild(d.fid)
- if child:
- dd = {
- "name": d.name,
- "speed": d.speed,
- "eta": d.eta,
- "format_eta": d.format_eta,
- "bleft": d.bleft,
- "size": d.size,
- "format_size": d.format_size,
- "percent": d.percent,
- "status": d.status,
- "statusmsg": d.statusmsg,
- "format_wait": d.format_wait,
- "wait_until": d.wait_until
- }
- child.data["downloading"] = dd
- k = pack.getChildKey(d.fid)
- self.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), self.index(k, 0, self.index(p, 0)), self.index(k, self.cols, self.index(p, self.cols)))
- self.updateCount()
-
- def headerData(self, section, orientation, role=Qt.DisplayRole):
- """
- returns column heading
- """
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- if section == 0:
- return QVariant(_("Name"))
- elif section == 2:
- return QVariant(_("Status"))
- elif section == 1:
- return QVariant(_("Plugin"))
- elif section == 3:
- return QVariant(_("Size"))
- elif section == 4:
- return QVariant(_("ETA"))
- elif section == 5:
- return QVariant(_("Progress"))
- return QVariant()
-
- def getWaitingProgress(self, item):
- """
- returns time to wait, caches startingtime to provide progress
- """
- locker = QMutexLocker(self.mutex)
- if isinstance(item, Link):
- if item.data["status"] == 5 and item.data["downloading"]:
- until = float(item.data["downloading"]["wait_until"])
- try:
- since, until_old = self.wait_dict[item.id]
- if not until == until_old:
- raise Exception
- except:
- since = time()
- self.wait_dict[item.id] = since, until
- since = float(since)
- max_wait = float(until-since)
- rest = int(until-time())
- if rest < 0:
- return 0, None
- res = 100/max_wait
- perc = rest*res
- return perc, rest
- return None
-
- def getProgress(self, item, locked=True):
- """
- return download progress, locks by default
- since it's used in already locked calls,
- it provides an option to not lock
- """
- if locked:
- locker = QMutexLocker(self.mutex)
- if isinstance(item, Link):
- try:
- if item.data["status"] == 0:
- return 100
- return int(item.data["downloading"]["percent"])
- except:
- return 0
- elif isinstance(item, Package):
- count = len(item.children)
- perc_sum = 0
- for child in item.children:
- try:
- if child.data["status"] == 0: #completed
- perc_sum += 100
- perc_sum += int(child.data["downloading"]["percent"])
- except:
- pass
- if count == 0:
- return 0
- return perc_sum/count
- return 0
-
- def getSpeed(self, item):
- """
- calculate download speed
- """
- if isinstance(item, Link):
- if item.data["downloading"]:
- return int(item.data["downloading"]["speed"])
- elif isinstance(item, Package):
- count = len(item.children)
- speed_sum = 0
- all_waiting = True
- running = False
- for child in item.children:
- val = 0
- if child.data["downloading"]:
- if not child.data["statusmsg"] == "waiting":
- all_waiting = False
- val = int(child.data["downloading"]["speed"])
- running = True
- speed_sum += val
- if count == 0 or not running or all_waiting:
- return None
- return speed_sum
- return None
-
- def data(self, index, role=Qt.DisplayRole):
- """
- return cell data
- """
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- elif index.column() == 1:
- item = index.internalPointer()
- plugins = []
- if isinstance(item, Package):
- for child in item.children:
- if not child.data["plugin"] in plugins:
- plugins.append(child.data["plugin"])
- else:
- plugins.append(item.data["plugin"])
- return QVariant(", ".join(plugins))
- elif index.column() == 2:
- item = index.internalPointer()
- status = 0
- speed = self.getSpeed(item)
- if isinstance(item, Package):
- for child in item.children:
- if child.data["status"] > status:
- status = child.data["status"]
- else:
- status = item.data["status"]
-
- if speed is None or status == 7 or status == 10 or status == 5:
- return QVariant(self.translateStatus(statusMapReverse[status]))
- else:
- return QVariant("%s (%s)" % (self.translateStatus(statusMapReverse[status]), formatSpeed(speed)))
- elif index.column() == 3:
- item = index.internalPointer()
- if isinstance(item, Link):
- if item.data["status"] == 0: #TODO needs change??
- #self.getProgress(item, False) == 100:
- return QVariant(formatSize(item.data["size"]))
- elif self.getProgress(item, False) == 0:
- try:
- return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"])))
- except:
- return QVariant("0 B / %s" % formatSize(item.data["size"]))
- else:
- try:
- return QVariant("%s / %s" % (formatSize(item.data["size"]-item.data["downloading"]["bleft"]), formatSize(item.data["size"])))
- except:
- return QVariant("? / %s" % formatSize(item.data["size"]))
- else:
- ms = 0
- cs = 0
- for c in item.children:
- try:
- s = c.data["downloading"]["size"]
- except:
- s = c.data["size"]
- if c.data["downloading"]:
- cs += s - c.data["downloading"]["bleft"]
- elif self.getProgress(c, False) == 100:
- cs += s
- ms += s
- if cs == 0 or cs == ms:
- return QVariant(formatSize(ms))
- else:
- return QVariant("%s / %s" % (formatSize(cs), formatSize(ms)))
- elif index.column() == 4:
- item = index.internalPointer()
- if isinstance(item, Link):
- if item.data["downloading"]:
- return QVariant(item.data["downloading"]["format_eta"])
- elif role == Qt.EditRole:
- if index.column() == 0:
- return QVariant(index.internalPointer().data["name"])
- return QVariant()
-
- def flags(self, index):
- """
- cell flags
- """
- if index.column() == 0 and self.parent(index) == QModelIndex():
- return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
- return Qt.ItemIsSelectable | Qt.ItemIsEnabled
-
-class QueueView(CollectorView):
- """
- view component for queue
- """
-
- def __init__(self, connector):
- CollectorView.__init__(self, connector)
- self.setModel(QueueModel(self, connector))
-
- self.setColumnWidth(0, 300)
- self.setColumnWidth(1, 100)
- self.setColumnWidth(2, 140)
- self.setColumnWidth(3, 180)
- self.setColumnWidth(4, 70)
-
- self.setEditTriggers(QAbstractItemView.NoEditTriggers)
-
- self.delegate = QueueProgressBarDelegate(self, self.model())
- self.setItemDelegateForColumn(5, self.delegate)
-
-class QueueProgressBarDelegate(QItemDelegate):
- """
- used to display a progressbar in the progress cell
- """
-
- def __init__(self, parent, queue):
- QItemDelegate.__init__(self, parent)
- self.queue = queue
-
- def paint(self, painter, option, index):
- """
- paint the progressbar
- """
- if not index.isValid():
- return
- if index.column() == 5:
- item = index.internalPointer()
- w = self.queue.getWaitingProgress(item)
- wait = None
- if w:
- progress = w[0]
- wait = w[1]
- else:
- progress = self.queue.getProgress(item)
- opts = QStyleOptionProgressBarV2()
- opts.maximum = 100
- opts.minimum = 0
- opts.progress = progress
- opts.rect = option.rect
- opts.rect.setRight(option.rect.right()-1)
- opts.rect.setHeight(option.rect.height()-1)
- opts.textVisible = True
- opts.textAlignment = Qt.AlignCenter
- if not wait is None:
- opts.text = QString(_("waiting %d seconds") % (wait,))
- else:
- opts.text = QString.number(opts.progress) + "%"
- QApplication.style().drawControl(QStyle.CE_ProgressBar, opts, painter)
- return
- QItemDelegate.paint(self, painter, option, index)
-
diff --git a/module/gui/SettingsWidget.py b/module/gui/SettingsWidget.py
deleted file mode 100644
index cd22a7b9e..000000000
--- a/module/gui/SettingsWidget.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from sip import delete
-
-
-class SettingsWidget(QWidget):
- def __init__(self):
- QWidget.__init__(self)
- self.connector = None
- self.sections = {}
- self.psections = {}
- self.data = None
- self.pdata = None
- self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
-
- def setConnector(self, connector):
- self.connector = connector
-
- def loadConfig(self):
- if self.sections and self.psections:
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
-
- self.reloadSection(self.sections, self.data)
- self.reloadSection(self.psections, self.pdata)
-
- return
-
- if self.layout():
- delete(self.layout())
-
- for s in self.sections.values()+self.psections.values():
- delete(s)
-
- self.sections = {}
- self.setLayout(QVBoxLayout())
- self.clearConfig()
- layout = self.layout()
- layout.setSizeConstraint(QLayout.SetMinAndMaxSize)
-
- general = QTabWidget()
- self.general = general
-
- plugins = QTabWidget()
- self.plugins = plugins
-
- tab = QTabWidget()
- self.tab = tab
-
- gw = QWidget()
- gw.setLayout(QVBoxLayout())
- gw.layout().addWidget(self.general)
- pw = QWidget()
- pw.setLayout(QVBoxLayout())
- pw.layout().addWidget(self.plugins)
- tab.addTab(gw, _("General"))
- tab.addTab(pw, _("Plugins"))
-
- layout.addWidget(tab)
-
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
- for k, section in self.data.iteritems():
- s = Section(section, general)
- self.sections[k] = s
-
- for k, section in self.pdata.iteritems():
- s = Section(section, plugins, "plugin")
- self.psections[k] = s
-
- rel = QPushButton(_("Reload"))
- save = QPushButton(_("Save"))
-
- layout.addWidget(save)
-
- cont = QHBoxLayout()
- cont.addWidget(rel)
- cont.addWidget(save)
-
- layout.addLayout(cont)
-
- self.connect(save, SIGNAL("clicked()"), self.saveConfig)
- self.connect(rel, SIGNAL("clicked()"), self.loadConfig)
-
- def clearConfig(self):
- self.sections = {}
-
- def reloadSection(self, sections, pdata):
-
- for k, section in enumerate(pdata):
- if k in sections:
- widget = sections[k]
- for item in section.items:
- if item.name in widget.inputs:
- i = widget.inputs[item.name]
-
- if item.type == "int":
- i.setValue(int(item.value))
- elif not item.type.find(";") == -1:
- i.setCurrentIndex(i.findText(item.value))
- elif item.type == "bool":
- if True if item.value.lower() in ("1","true", "on", "an","yes") else False:
- i.setCurrentIndex(0)
- else:
- i.setCurrentIndex(1)
- else:
- i.setText(item.value)
-
-
- def saveConfig(self):
- self.data = self.connector.getConfig()
- self.pdata = self.connector.getPluginConfig()
-
- self.saveSection(self.sections, self.data)
- self.saveSection(self.psections, self.pdata, "plugin")
-
-
- def saveSection(self, sections, pdata, sec="core"):
- for k, section in enumerate(pdata):
- if k in sections:
- widget = sections[k]
- for item in section.items:
- if item.name in widget.inputs:
- i = widget.inputs[item.name]
-
- #TODO : unresolved reference: option
-
- if item.type == "int":
- if i.value() != int(item.value):
- self.connector.setConfigValue(k, option, i.value(), sec)
- elif not item.type.find(";") == -1:
- if i.currentText() != item.value:
- self.connector.setConfigValue(k, option, i.currentText(), sec)
- elif item.type == "bool":
- if (True if item.value.lower() in ("1","true", "on", "an","yes") else False) ^ (not i.currentIndex()):
- self.connector.setConfigValue(k, option, not i.currentIndex(), sec)
- else:
- if i.text() != item.value:
- self.connector.setConfigValue(k, option, str(i.text()), sec)
-
-class Section(QGroupBox):
- def __init__(self, data, parent, ctype="core"):
- self.data = data
- QGroupBox.__init__(self, data.description, parent)
- self.labels = {}
- self.inputs = {}
- self.ctype = ctype
- layout = QFormLayout(self)
- self.setLayout(layout)
-
- sw = QWidget()
- sw.setLayout(QVBoxLayout())
- sw.layout().addWidget(self)
-
- sa = QScrollArea()
- sa.setWidgetResizable(True)
- sa.setWidget(sw)
- sa.setFrameShape(sa.NoFrame)
-
- parent.addTab(sa, data.description)
-
- for option in self.data.items:
- if option.type == "int":
- i = QSpinBox(self)
- i.setMaximum(999999)
- i.setValue(int(option.value))
- elif not option.type.find(";") == -1:
- choices = option.type.split(";")
- i = QComboBox(self)
- i.addItems(choices)
- i.setCurrentIndex(i.findText(option.value))
- elif option.type == "bool":
- i = QComboBox(self)
- i.addItem(_("Yes"), QVariant(True))
- i.addItem(_("No"), QVariant(False))
- if True if option.value.lower() in ("1","true", "on", "an","yes") else False:
- i.setCurrentIndex(0)
- else:
- i.setCurrentIndex(1)
- else:
- i = QLineEdit(self)
- i.setText(option.value)
- layout.addRow(option.description, i)
- layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
diff --git a/module/gui/XMLParser.py b/module/gui/XMLParser.py
deleted file mode 100644
index 5e3b7bf65..000000000
--- a/module/gui/XMLParser.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-from __future__ import with_statement
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4.QtXml import *
-
-import os
-
-class XMLParser():
- def __init__(self, data, dfile=""):
- self.mutex = QMutex()
- self.mutex.lock()
- self.xml = QDomDocument()
- self.file = data
- self.dfile = dfile
- self.mutex.unlock()
- self.loadData()
- self.root = self.xml.documentElement()
-
- def loadData(self):
- self.mutex.lock()
- f = self.file
- if not os.path.exists(f):
- f = self.dfile
- with open(f, 'r') as fh:
- content = fh.read()
- self.xml.setContent(content)
- self.mutex.unlock()
-
- def saveData(self):
- self.mutex.lock()
- content = self.xml.toString()
- with open(self.file, 'w') as fh:
- fh.write(content)
- self.mutex.unlock()
- return content
-
- def parseNode(self, node, ret_type="list"):
- if ret_type == "dict":
- childNodes = {}
- else:
- childNodes = []
- child = node.firstChild()
- while True:
- n = child.toElement()
- if n.isNull():
- break
- else:
- if ret_type == "dict":
- childNodes[str(n.tagName())] = n
- else:
- childNodes.append(n)
- child = child.nextSibling()
- return childNodes
diff --git a/module/gui/__init__.py b/module/gui/__init__.py
deleted file mode 100644
index 8d1c8b69c..000000000
--- a/module/gui/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/module/gui/connector.py b/module/gui/connector.py
deleted file mode 100644
index c16ccd08e..000000000
--- a/module/gui/connector.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-SERVER_VERSION = "0.4.9"
-
-from time import sleep
-from uuid import uuid4 as uuid
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-import socket
-
-from module.remote.thriftbackend.ThriftClient import ThriftClient, WrongLogin, NoSSL, NoConnection
-from thrift.Thrift import TException
-
-class Connector(QObject):
- """
- manages the connection to the pyload core via thrift
- """
-
- firstAttempt = True
-
- def __init__(self):
- QObject.__init__(self)
- self.mutex = QMutex()
- self.connectionID = None
- self.host = None
- self.port = None
- self.user = None
- self.password = None
- self.ssl = None
- self.running = True
- self.internal = False
- self.proxy = self.Dummy()
-
- def setConnectionData(self, host, port, user, password, ssl=False):
- """
- set connection data for connection attempt, called from slotConnect
- """
- self.host = host
- self.port = port
- self.user = user
- self.password = password
- self.ssl = ssl
-
- def connectProxy(self):
- """
- initialize thrift rpc client,
- check for ssl, check auth,
- setup dispatcher,
- connect error signals,
- check server version
- """
- if self.internal: return True
-
- err = None
- try:
- client = ThriftClient(self.host, self.port, self.user, self.password)
- except WrongLogin:
- err = _("bad login credentials")
- except NoSSL:
- err = _("no ssl support")
- except NoConnection:
- err = _("can't connect to host")
- if err:
- if not Connector.firstAttempt:
- self.emit(SIGNAL("errorBox"), err)
- Connector.firstAttempt = False
- return False
-
- self.proxy = DispatchRPC(self.mutex, client)
- self.connect(self.proxy, SIGNAL("connectionLost"), self, SIGNAL("connectionLost"))
-
- server_version = self.proxy.getServerVersion()
- self.connectionID = uuid().hex
-
- if not server_version == SERVER_VERSION:
- self.emit(SIGNAL("errorBox"), _("server is version %(new)s client accepts version %(current)s") % { "new": server_version, "current": SERVER_VERSION})
- return False
-
- return True
-
- def __getattr__(self, attr):
- """
- redirect rpc calls to dispatcher
- """
- return getattr(self.proxy, attr)
-
- class Dummy(object):
- """
- dummy rpc proxy, to prevent errors
- """
- def __nonzero__(self):
- return False
-
- def __getattr__(self, attr):
- def dummy(*args, **kwargs):
- return None
- return dummy
-
-class DispatchRPC(QObject):
- """
- wraps the thrift client, to catch critical exceptions (connection lost)
- adds thread safety
- """
-
- def __init__(self, mutex, server):
- QObject.__init__(self)
- self.mutex = mutex
- self.server = server
-
- def __getattr__(self, attr):
- """
- redirect and wrap call in Wrapper instance, locks dispatcher
- """
- self.mutex.lock()
- self.fname = attr
- f = self.Wrapper(getattr(self.server, attr), self.mutex, self)
- return f
-
- class Wrapper(object):
- """
- represents a rpc call
- """
-
- def __init__(self, f, mutex, dispatcher):
- self.f = f
- self.mutex = mutex
- self.dispatcher = dispatcher
-
- def __call__(self, *args, **kwargs):
- """
- instance is called, rpc is executed
- exceptions are processed
- finally dispatcher is unlocked
- """
- lost = False
- try:
- return self.f(*args, **kwargs)
- except socket.error: #necessary?
- lost = True
- except TException:
- lost = True
- finally:
- self.mutex.unlock()
- if lost:
- from traceback import print_exc
- print_exc()
- self.dispatcher.emit(SIGNAL("connectionLost"))
diff --git a/module/lib/BeautifulSoup.py b/module/lib/BeautifulSoup.py
deleted file mode 100644
index 55567f588..000000000
--- a/module/lib/BeautifulSoup.py
+++ /dev/null
@@ -1,2012 +0,0 @@
-"""Beautiful Soup
-Elixir and Tonic
-"The Screen-Scraper's Friend"
-http://www.crummy.com/software/BeautifulSoup/
-
-Beautiful Soup parses a (possibly invalid) XML or HTML document into a
-tree representation. It provides methods and Pythonic idioms that make
-it easy to navigate, search, and modify the tree.
-
-A well-formed XML/HTML document yields a well-formed data
-structure. An ill-formed XML/HTML document yields a correspondingly
-ill-formed data structure. If your document is only locally
-well-formed, you can use this library to find and process the
-well-formed part of it.
-
-Beautiful Soup works with Python 2.2 and up. It has no external
-dependencies, but you'll have more success at converting data to UTF-8
-if you also install these three packages:
-
-* chardet, for auto-detecting character encodings
- http://chardet.feedparser.org/
-* cjkcodecs and iconv_codec, which add more encodings to the ones supported
- by stock Python.
- http://cjkpython.i18n.org/
-
-Beautiful Soup defines classes for two main parsing strategies:
-
- * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific
- language that kind of looks like XML.
-
- * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid
- or invalid. This class has web browser-like heuristics for
- obtaining a sensible parse tree in the face of common HTML errors.
-
-Beautiful Soup also defines a class (UnicodeDammit) for autodetecting
-the encoding of an HTML or XML document, and converting it to
-Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser.
-
-For more than you ever wanted to know about Beautiful Soup, see the
-documentation:
-http://www.crummy.com/software/BeautifulSoup/documentation.html
-
-Here, have some legalese:
-
-Copyright (c) 2004-2010, Leonard Richardson
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
-
- * Neither the name of the the Beautiful Soup Consortium and All
- Night Kosher Bakery nor the names of its contributors may be
- used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT.
-
-"""
-from __future__ import generators
-
-__author__ = "Leonard Richardson (leonardr@segfault.org)"
-__version__ = "3.0.8.1"
-__copyright__ = "Copyright (c) 2004-2010 Leonard Richardson"
-__license__ = "New-style BSD"
-
-from sgmllib import SGMLParser, SGMLParseError
-import codecs
-import markupbase
-import types
-import re
-import sgmllib
-try:
- from htmlentitydefs import name2codepoint
-except ImportError:
- name2codepoint = {}
-try:
- set
-except NameError:
- from sets import Set as set
-
-#These hacks make Beautiful Soup able to parse XML with namespaces
-sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
-markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match
-
-DEFAULT_OUTPUT_ENCODING = "utf-8"
-
-def _match_css_class(str):
- """Build a RE to match the given CSS class."""
- return re.compile(r"(^|.*\s)%s($|\s)" % str)
-
-# First, the classes that represent markup elements.
-
-class PageElement(object):
- """Contains the navigational information for some part of the page
- (either a tag or a piece of text)"""
-
- def setup(self, parent=None, previous=None):
- """Sets up the initial relations between this element and
- other elements."""
- self.parent = parent
- self.previous = previous
- self.next = None
- self.previousSibling = None
- self.nextSibling = None
- if self.parent and self.parent.contents:
- self.previousSibling = self.parent.contents[-1]
- self.previousSibling.nextSibling = self
-
- def replaceWith(self, replaceWith):
- oldParent = self.parent
- myIndex = self.parent.index(self)
- if hasattr(replaceWith, "parent")\
- and replaceWith.parent is self.parent:
- # We're replacing this element with one of its siblings.
- index = replaceWith.parent.index(replaceWith)
- if index and index < myIndex:
- # Furthermore, it comes before this element. That
- # means that when we extract it, the index of this
- # element will change.
- myIndex = myIndex - 1
- self.extract()
- oldParent.insert(myIndex, replaceWith)
-
- def replaceWithChildren(self):
- myParent = self.parent
- myIndex = self.parent.index(self)
- self.extract()
- reversedChildren = list(self.contents)
- reversedChildren.reverse()
- for child in reversedChildren:
- myParent.insert(myIndex, child)
-
- def extract(self):
- """Destructively rips this element out of the tree."""
- if self.parent:
- try:
- del self.parent.contents[self.parent.index(self)]
- except ValueError:
- pass
-
- #Find the two elements that would be next to each other if
- #this element (and any children) hadn't been parsed. Connect
- #the two.
- lastChild = self._lastRecursiveChild()
- nextElement = lastChild.next
-
- if self.previous:
- self.previous.next = nextElement
- if nextElement:
- nextElement.previous = self.previous
- self.previous = None
- lastChild.next = None
-
- self.parent = None
- if self.previousSibling:
- self.previousSibling.nextSibling = self.nextSibling
- if self.nextSibling:
- self.nextSibling.previousSibling = self.previousSibling
- self.previousSibling = self.nextSibling = None
- return self
-
- def _lastRecursiveChild(self):
- "Finds the last element beneath this object to be parsed."
- lastChild = self
- while hasattr(lastChild, 'contents') and lastChild.contents:
- lastChild = lastChild.contents[-1]
- return lastChild
-
- def insert(self, position, newChild):
- if isinstance(newChild, basestring) \
- and not isinstance(newChild, NavigableString):
- newChild = NavigableString(newChild)
-
- position = min(position, len(self.contents))
- if hasattr(newChild, 'parent') and newChild.parent is not None:
- # We're 'inserting' an element that's already one
- # of this object's children.
- if newChild.parent is self:
- index = self.index(newChild)
- if index > position:
- # Furthermore we're moving it further down the
- # list of this object's children. That means that
- # when we extract this element, our target index
- # will jump down one.
- position = position - 1
- newChild.extract()
-
- newChild.parent = self
- previousChild = None
- if position == 0:
- newChild.previousSibling = None
- newChild.previous = self
- else:
- previousChild = self.contents[position-1]
- newChild.previousSibling = previousChild
- newChild.previousSibling.nextSibling = newChild
- newChild.previous = previousChild._lastRecursiveChild()
- if newChild.previous:
- newChild.previous.next = newChild
-
- newChildsLastElement = newChild._lastRecursiveChild()
-
- if position >= len(self.contents):
- newChild.nextSibling = None
-
- parent = self
- parentsNextSibling = None
- while not parentsNextSibling:
- parentsNextSibling = parent.nextSibling
- parent = parent.parent
- if not parent: # This is the last element in the document.
- break
- if parentsNextSibling:
- newChildsLastElement.next = parentsNextSibling
- else:
- newChildsLastElement.next = None
- else:
- nextChild = self.contents[position]
- newChild.nextSibling = nextChild
- if newChild.nextSibling:
- newChild.nextSibling.previousSibling = newChild
- newChildsLastElement.next = nextChild
-
- if newChildsLastElement.next:
- newChildsLastElement.next.previous = newChildsLastElement
- self.contents.insert(position, newChild)
-
- def append(self, tag):
- """Appends the given tag to the contents of this tag."""
- self.insert(len(self.contents), tag)
-
- def findNext(self, name=None, attrs={}, text=None, **kwargs):
- """Returns the first item that matches the given criteria and
- appears after this Tag in the document."""
- return self._findOne(self.findAllNext, name, attrs, text, **kwargs)
-
- def findAllNext(self, name=None, attrs={}, text=None, limit=None,
- **kwargs):
- """Returns all items that match the given criteria and appear
- after this Tag in the document."""
- return self._findAll(name, attrs, text, limit, self.nextGenerator,
- **kwargs)
-
- def findNextSibling(self, name=None, attrs={}, text=None, **kwargs):
- """Returns the closest sibling to this Tag that matches the
- given criteria and appears after this Tag in the document."""
- return self._findOne(self.findNextSiblings, name, attrs, text,
- **kwargs)
-
- def findNextSiblings(self, name=None, attrs={}, text=None, limit=None,
- **kwargs):
- """Returns the siblings of this Tag that match the given
- criteria and appear after this Tag in the document."""
- return self._findAll(name, attrs, text, limit,
- self.nextSiblingGenerator, **kwargs)
- fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x
-
- def findPrevious(self, name=None, attrs={}, text=None, **kwargs):
- """Returns the first item that matches the given criteria and
- appears before this Tag in the document."""
- return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs)
-
- def findAllPrevious(self, name=None, attrs={}, text=None, limit=None,
- **kwargs):
- """Returns all items that match the given criteria and appear
- before this Tag in the document."""
- return self._findAll(name, attrs, text, limit, self.previousGenerator,
- **kwargs)
- fetchPrevious = findAllPrevious # Compatibility with pre-3.x
-
- def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs):
- """Returns the closest sibling to this Tag that matches the
- given criteria and appears before this Tag in the document."""
- return self._findOne(self.findPreviousSiblings, name, attrs, text,
- **kwargs)
-
- def findPreviousSiblings(self, name=None, attrs={}, text=None,
- limit=None, **kwargs):
- """Returns the siblings of this Tag that match the given
- criteria and appear before this Tag in the document."""
- return self._findAll(name, attrs, text, limit,
- self.previousSiblingGenerator, **kwargs)
- fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x
-
- def findParent(self, name=None, attrs={}, **kwargs):
- """Returns the closest parent of this Tag that matches the given
- criteria."""
- # NOTE: We can't use _findOne because findParents takes a different
- # set of arguments.
- r = None
- l = self.findParents(name, attrs, 1)
- if l:
- r = l[0]
- return r
-
- def findParents(self, name=None, attrs={}, limit=None, **kwargs):
- """Returns the parents of this Tag that match the given
- criteria."""
-
- return self._findAll(name, attrs, None, limit, self.parentGenerator,
- **kwargs)
- fetchParents = findParents # Compatibility with pre-3.x
-
- #These methods do the real heavy lifting.
-
- def _findOne(self, method, name, attrs, text, **kwargs):
- r = None
- l = method(name, attrs, text, 1, **kwargs)
- if l:
- r = l[0]
- return r
-
- def _findAll(self, name, attrs, text, limit, generator, **kwargs):
- "Iterates over a generator looking for things that match."
-
- if isinstance(name, SoupStrainer):
- strainer = name
- # (Possibly) special case some findAll*(...) searches
- elif text is None and not limit and not attrs and not kwargs:
- # findAll*(True)
- if name is True:
- return [element for element in generator()
- if isinstance(element, Tag)]
- # findAll*('tag-name')
- elif isinstance(name, basestring):
- return [element for element in generator()
- if isinstance(element, Tag) and
- element.name == name]
- else:
- strainer = SoupStrainer(name, attrs, text, **kwargs)
- # Build a SoupStrainer
- else:
- strainer = SoupStrainer(name, attrs, text, **kwargs)
- results = ResultSet(strainer)
- g = generator()
- while True:
- try:
- i = g.next()
- except StopIteration:
- break
- if i:
- found = strainer.search(i)
- if found:
- results.append(found)
- if limit and len(results) >= limit:
- break
- return results
-
- #These Generators can be used to navigate starting from both
- #NavigableStrings and Tags.
- def nextGenerator(self):
- i = self
- while i is not None:
- i = i.next
- yield i
-
- def nextSiblingGenerator(self):
- i = self
- while i is not None:
- i = i.nextSibling
- yield i
-
- def previousGenerator(self):
- i = self
- while i is not None:
- i = i.previous
- yield i
-
- def previousSiblingGenerator(self):
- i = self
- while i is not None:
- i = i.previousSibling
- yield i
-
- def parentGenerator(self):
- i = self
- while i is not None:
- i = i.parent
- yield i
-
- # Utility methods
- def substituteEncoding(self, str, encoding=None):
- encoding = encoding or "utf-8"
- return str.replace("%SOUP-ENCODING%", encoding)
-
- def toEncoding(self, s, encoding=None):
- """Encodes an object to a string in some encoding, or to Unicode.
- ."""
- if isinstance(s, unicode):
- if encoding:
- s = s.encode(encoding)
- elif isinstance(s, str):
- if encoding:
- s = s.encode(encoding)
- else:
- s = unicode(s)
- else:
- if encoding:
- s = self.toEncoding(str(s), encoding)
- else:
- s = unicode(s)
- return s
-
-class NavigableString(unicode, PageElement):
-
- def __new__(cls, value):
- """Create a new NavigableString.
-
- When unpickling a NavigableString, this method is called with
- the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be
- passed in to the superclass's __new__ or the superclass won't know
- how to handle non-ASCII characters.
- """
- if isinstance(value, unicode):
- return unicode.__new__(cls, value)
- return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
-
- def __getnewargs__(self):
- return (NavigableString.__str__(self),)
-
- def __getattr__(self, attr):
- """text.string gives you text. This is for backwards
- compatibility for Navigable*String, but for CData* it lets you
- get the string without the CData wrapper."""
- if attr == 'string':
- return self
- else:
- raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)
-
- def __unicode__(self):
- return str(self).decode(DEFAULT_OUTPUT_ENCODING)
-
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- if encoding:
- return self.encode(encoding)
- else:
- return self
-
-class CData(NavigableString):
-
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- return "<![CDATA[%s]]>" % NavigableString.__str__(self, encoding)
-
-class ProcessingInstruction(NavigableString):
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- output = self
- if "%SOUP-ENCODING%" in output:
- output = self.substituteEncoding(output, encoding)
- return "<?%s?>" % self.toEncoding(output, encoding)
-
-class Comment(NavigableString):
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- return "<!--%s-->" % NavigableString.__str__(self, encoding)
-
-class Declaration(NavigableString):
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- return "<!%s>" % NavigableString.__str__(self, encoding)
-
-class Tag(PageElement):
-
- """Represents a found HTML tag with its attributes and contents."""
-
- def _invert(h):
- "Cheap function to invert a hash."
- i = {}
- for k,v in h.items():
- i[v] = k
- return i
-
- XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'",
- "quot" : '"',
- "amp" : "&",
- "lt" : "<",
- "gt" : ">" }
-
- XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS)
-
- def _convertEntities(self, match):
- """Used in a call to re.sub to replace HTML, XML, and numeric
- entities with the appropriate Unicode characters. If HTML
- entities are being converted, any unrecognized entities are
- escaped."""
- x = match.group(1)
- if self.convertHTMLEntities and x in name2codepoint:
- return unichr(name2codepoint[x])
- elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS:
- if self.convertXMLEntities:
- return self.XML_ENTITIES_TO_SPECIAL_CHARS[x]
- else:
- return u'&%s;' % x
- elif len(x) > 0 and x[0] == '#':
- # Handle numeric entities
- if len(x) > 1 and x[1] == 'x':
- return unichr(int(x[2:], 16))
- else:
- return unichr(int(x[1:]))
-
- elif self.escapeUnrecognizedEntities:
- return u'&amp;%s;' % x
- else:
- return u'&%s;' % x
-
- def __init__(self, parser, name, attrs=None, parent=None,
- previous=None):
- "Basic constructor."
-
- # We don't actually store the parser object: that lets extracted
- # chunks be garbage-collected
- self.parserClass = parser.__class__
- self.isSelfClosing = parser.isSelfClosingTag(name)
- self.name = name
- if attrs is None:
- attrs = []
- self.attrs = attrs
- self.contents = []
- self.setup(parent, previous)
- self.hidden = False
- self.containsSubstitutions = False
- self.convertHTMLEntities = parser.convertHTMLEntities
- self.convertXMLEntities = parser.convertXMLEntities
- self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities
-
- # Convert any HTML, XML, or numeric entities in the attribute values.
- convert = lambda(k, val): (k,
- re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);",
- self._convertEntities,
- val))
- self.attrs = map(convert, self.attrs)
-
- def getString(self):
- if (len(self.contents) == 1
- and isinstance(self.contents[0], NavigableString)):
- return self.contents[0]
-
- def setString(self, string):
- """Replace the contents of the tag with a string"""
- self.clear()
- self.append(string)
-
- string = property(getString, setString)
-
- def getText(self, separator=u""):
- if not len(self.contents):
- return u""
- stopNode = self._lastRecursiveChild().next
- strings = []
- current = self.contents[0]
- while current is not stopNode:
- if isinstance(current, NavigableString):
- strings.append(current.strip())
- current = current.next
- return separator.join(strings)
-
- text = property(getText)
-
- def get(self, key, default=None):
- """Returns the value of the 'key' attribute for the tag, or
- the value given for 'default' if it doesn't have that
- attribute."""
- return self._getAttrMap().get(key, default)
-
- def clear(self):
- """Extract all children."""
- for child in self.contents[:]:
- child.extract()
-
- def index(self, element):
- for i, child in enumerate(self.contents):
- if child is element:
- return i
- raise ValueError("Tag.index: element not in tag")
-
- def has_key(self, key):
- return self._getAttrMap().has_key(key)
-
- def __getitem__(self, key):
- """tag[key] returns the value of the 'key' attribute for the tag,
- and throws an exception if it's not there."""
- return self._getAttrMap()[key]
-
- def __iter__(self):
- "Iterating over a tag iterates over its contents."
- return iter(self.contents)
-
- def __len__(self):
- "The length of a tag is the length of its list of contents."
- return len(self.contents)
-
- def __contains__(self, x):
- return x in self.contents
-
- def __nonzero__(self):
- "A tag is non-None even if it has no contents."
- return True
-
- def __setitem__(self, key, value):
- """Setting tag[key] sets the value of the 'key' attribute for the
- tag."""
- self._getAttrMap()
- self.attrMap[key] = value
- found = False
- for i in range(0, len(self.attrs)):
- if self.attrs[i][0] == key:
- self.attrs[i] = (key, value)
- found = True
- if not found:
- self.attrs.append((key, value))
- self._getAttrMap()[key] = value
-
- def __delitem__(self, key):
- "Deleting tag[key] deletes all 'key' attributes for the tag."
- for item in self.attrs:
- if item[0] == key:
- self.attrs.remove(item)
- #We don't break because bad HTML can define the same
- #attribute multiple times.
- self._getAttrMap()
- if self.attrMap.has_key(key):
- del self.attrMap[key]
-
- def __call__(self, *args, **kwargs):
- """Calling a tag like a function is the same as calling its
- findAll() method. Eg. tag('a') returns a list of all the A tags
- found within this tag."""
- return apply(self.findAll, args, kwargs)
-
- def __getattr__(self, tag):
- #print "Getattr %s.%s" % (self.__class__, tag)
- if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3:
- return self.find(tag[:-3])
- elif tag.find('__') != 0:
- return self.find(tag)
- raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag)
-
- def __eq__(self, other):
- """Returns true iff this tag has the same name, the same attributes,
- and the same contents (recursively) as the given tag.
-
- NOTE: right now this will return false if two tags have the
- same attributes in a different order. Should this be fixed?"""
- if other is self:
- return True
- if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other):
- return False
- for i in range(0, len(self.contents)):
- if self.contents[i] != other.contents[i]:
- return False
- return True
-
- def __ne__(self, other):
- """Returns true iff this tag is not identical to the other tag,
- as defined in __eq__."""
- return not self == other
-
- def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING):
- """Renders this tag as a string."""
- return self.__str__(encoding)
-
- def __unicode__(self):
- return self.__str__(None)
-
- BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|"
- + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)"
- + ")")
-
- def _sub_entity(self, x):
- """Used with a regular expression to substitute the
- appropriate XML entity for an XML special character."""
- return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";"
-
- def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING,
- prettyPrint=False, indentLevel=0):
- """Returns a string or Unicode representation of this tag and
- its contents. To get Unicode, pass None for encoding.
-
- NOTE: since Python's HTML parser consumes whitespace, this
- method is not certain to reproduce the whitespace present in
- the original string."""
-
- encodedName = self.toEncoding(self.name, encoding)
-
- attrs = []
- if self.attrs:
- for key, val in self.attrs:
- fmt = '%s="%s"'
- if isinstance(val, basestring):
- if self.containsSubstitutions and '%SOUP-ENCODING%' in val:
- val = self.substituteEncoding(val, encoding)
-
- # The attribute value either:
- #
- # * Contains no embedded double quotes or single quotes.
- # No problem: we enclose it in double quotes.
- # * Contains embedded single quotes. No problem:
- # double quotes work here too.
- # * Contains embedded double quotes. No problem:
- # we enclose it in single quotes.
- # * Embeds both single _and_ double quotes. This
- # can't happen naturally, but it can happen if
- # you modify an attribute value after parsing
- # the document. Now we have a bit of a
- # problem. We solve it by enclosing the
- # attribute in single quotes, and escaping any
- # embedded single quotes to XML entities.
- if '"' in val:
- fmt = "%s='%s'"
- if "'" in val:
- # TODO: replace with apos when
- # appropriate.
- val = val.replace("'", "&squot;")
-
- # Now we're okay w/r/t quotes. But the attribute
- # value might also contain angle brackets, or
- # ampersands that aren't part of entities. We need
- # to escape those to XML entities too.
- val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val)
-
- attrs.append(fmt % (self.toEncoding(key, encoding),
- self.toEncoding(val, encoding)))
- close = ''
- closeTag = ''
- if self.isSelfClosing:
- close = ' /'
- else:
- closeTag = '</%s>' % encodedName
-
- indentTag, indentContents = 0, 0
- if prettyPrint:
- indentTag = indentLevel
- space = (' ' * (indentTag-1))
- indentContents = indentTag + 1
- contents = self.renderContents(encoding, prettyPrint, indentContents)
- if self.hidden:
- s = contents
- else:
- s = []
- attributeString = ''
- if attrs:
- attributeString = ' ' + ' '.join(attrs)
- if prettyPrint:
- s.append(space)
- s.append('<%s%s%s>' % (encodedName, attributeString, close))
- if prettyPrint:
- s.append("\n")
- s.append(contents)
- if prettyPrint and contents and contents[-1] != "\n":
- s.append("\n")
- if prettyPrint and closeTag:
- s.append(space)
- s.append(closeTag)
- if prettyPrint and closeTag and self.nextSibling:
- s.append("\n")
- s = ''.join(s)
- return s
-
- def decompose(self):
- """Recursively destroys the contents of this tree."""
- self.extract()
- if len(self.contents) == 0:
- return
- current = self.contents[0]
- while current is not None:
- next = current.next
- if isinstance(current, Tag):
- del current.contents[:]
- current.parent = None
- current.previous = None
- current.previousSibling = None
- current.next = None
- current.nextSibling = None
- current = next
-
- def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING):
- return self.__str__(encoding, True)
-
- def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING,
- prettyPrint=False, indentLevel=0):
- """Renders the contents of this tag as a string in the given
- encoding. If encoding is None, returns a Unicode string.."""
- s=[]
- for c in self:
- text = None
- if isinstance(c, NavigableString):
- text = c.__str__(encoding)
- elif isinstance(c, Tag):
- s.append(c.__str__(encoding, prettyPrint, indentLevel))
- if text and prettyPrint:
- text = text.strip()
- if text:
- if prettyPrint:
- s.append(" " * (indentLevel-1))
- s.append(text)
- if prettyPrint:
- s.append("\n")
- return ''.join(s)
-
- #Soup methods
-
- def find(self, name=None, attrs={}, recursive=True, text=None,
- **kwargs):
- """Return only the first child of this Tag matching the given
- criteria."""
- r = None
- l = self.findAll(name, attrs, recursive, text, 1, **kwargs)
- if l:
- r = l[0]
- return r
- findChild = find
-
- def findAll(self, name=None, attrs={}, recursive=True, text=None,
- limit=None, **kwargs):
- """Extracts a list of Tag objects that match the given
- criteria. You can specify the name of the Tag and any
- attributes you want the Tag to have.
-
- The value of a key-value pair in the 'attrs' map can be a
- string, a list of strings, a regular expression object, or a
- callable that takes a string and returns whether or not the
- string matches for some custom definition of 'matches'. The
- same is true of the tag name."""
- generator = self.recursiveChildGenerator
- if not recursive:
- generator = self.childGenerator
- return self._findAll(name, attrs, text, limit, generator, **kwargs)
- findChildren = findAll
-
- # Pre-3.x compatibility methods
- first = find
- fetch = findAll
-
- def fetchText(self, text=None, recursive=True, limit=None):
- return self.findAll(text=text, recursive=recursive, limit=limit)
-
- def firstText(self, text=None, recursive=True):
- return self.find(text=text, recursive=recursive)
-
- #Private methods
-
- def _getAttrMap(self):
- """Initializes a map representation of this tag's attributes,
- if not already initialized."""
- if not getattr(self, 'attrMap'):
- self.attrMap = {}
- for (key, value) in self.attrs:
- self.attrMap[key] = value
- return self.attrMap
-
- #Generator methods
- def childGenerator(self):
- # Just use the iterator from the contents
- return iter(self.contents)
-
- def recursiveChildGenerator(self):
- if not len(self.contents):
- raise StopIteration
- stopNode = self._lastRecursiveChild().next
- current = self.contents[0]
- while current is not stopNode:
- yield current
- current = current.next
-
-
-# Next, a couple classes to represent queries and their results.
-class SoupStrainer:
- """Encapsulates a number of ways of matching a markup element (tag or
- text)."""
-
- def __init__(self, name=None, attrs={}, text=None, **kwargs):
- self.name = name
- if isinstance(attrs, basestring):
- kwargs['class'] = _match_css_class(attrs)
- attrs = None
- if kwargs:
- if attrs:
- attrs = attrs.copy()
- attrs.update(kwargs)
- else:
- attrs = kwargs
- self.attrs = attrs
- self.text = text
-
- def __str__(self):
- if self.text:
- return self.text
- else:
- return "%s|%s" % (self.name, self.attrs)
-
- def searchTag(self, markupName=None, markupAttrs={}):
- found = None
- markup = None
- if isinstance(markupName, Tag):
- markup = markupName
- markupAttrs = markup
- callFunctionWithTagData = callable(self.name) \
- and not isinstance(markupName, Tag)
-
- if (not self.name) \
- or callFunctionWithTagData \
- or (markup and self._matches(markup, self.name)) \
- or (not markup and self._matches(markupName, self.name)):
- if callFunctionWithTagData:
- match = self.name(markupName, markupAttrs)
- else:
- match = True
- markupAttrMap = None
- for attr, matchAgainst in self.attrs.items():
- if not markupAttrMap:
- if hasattr(markupAttrs, 'get'):
- markupAttrMap = markupAttrs
- else:
- markupAttrMap = {}
- for k,v in markupAttrs:
- markupAttrMap[k] = v
- attrValue = markupAttrMap.get(attr)
- if not self._matches(attrValue, matchAgainst):
- match = False
- break
- if match:
- if markup:
- found = markup
- else:
- found = markupName
- return found
-
- def search(self, markup):
- #print 'looking for %s in %s' % (self, markup)
- found = None
- # If given a list of items, scan it for a text element that
- # matches.
- if hasattr(markup, "__iter__") \
- and not isinstance(markup, Tag):
- for element in markup:
- if isinstance(element, NavigableString) \
- and self.search(element):
- found = element
- break
- # If it's a Tag, make sure its name or attributes match.
- # Don't bother with Tags if we're searching for text.
- elif isinstance(markup, Tag):
- if not self.text:
- found = self.searchTag(markup)
- # If it's text, make sure the text matches.
- elif isinstance(markup, NavigableString) or \
- isinstance(markup, basestring):
- if self._matches(markup, self.text):
- found = markup
- else:
- raise Exception, "I don't know how to match against a %s" \
- % markup.__class__
- return found
-
- def _matches(self, markup, matchAgainst):
- #print "Matching %s against %s" % (markup, matchAgainst)
- result = False
- if matchAgainst is True:
- result = markup is not None
- elif callable(matchAgainst):
- result = matchAgainst(markup)
- else:
- #Custom match methods take the tag as an argument, but all
- #other ways of matching match the tag name as a string.
- if isinstance(markup, Tag):
- markup = markup.name
- if markup and not isinstance(markup, basestring):
- markup = unicode(markup)
- #Now we know that chunk is either a string, or None.
- if hasattr(matchAgainst, 'match'):
- # It's a regexp object.
- result = markup and matchAgainst.search(markup)
- elif hasattr(matchAgainst, '__iter__'): # list-like
- result = markup in matchAgainst
- elif hasattr(matchAgainst, 'items'):
- result = markup.has_key(matchAgainst)
- elif matchAgainst and isinstance(markup, basestring):
- if isinstance(markup, unicode):
- matchAgainst = unicode(matchAgainst)
- else:
- matchAgainst = str(matchAgainst)
-
- if not result:
- result = matchAgainst == markup
- return result
-
-class ResultSet(list):
- """A ResultSet is just a list that keeps track of the SoupStrainer
- that created it."""
- def __init__(self, source):
- list.__init__([])
- self.source = source
-
-# Now, some helper functions.
-
-def buildTagMap(default, *args):
- """Turns a list of maps, lists, or scalars into a single map.
- Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and
- NESTING_RESET_TAGS maps out of lists and partial maps."""
- built = {}
- for portion in args:
- if hasattr(portion, 'items'):
- #It's a map. Merge it.
- for k,v in portion.items():
- built[k] = v
- elif hasattr(portion, '__iter__'): # is a list
- #It's a list. Map each item to the default.
- for k in portion:
- built[k] = default
- else:
- #It's a scalar. Map it to the default.
- built[portion] = default
- return built
-
-# Now, the parser classes.
-
-class BeautifulStoneSoup(Tag, SGMLParser):
-
- """This class contains the basic parser and search code. It defines
- a parser that knows nothing about tag behavior except for the
- following:
-
- You can't close a tag without closing all the tags it encloses.
- That is, "<foo><bar></foo>" actually means
- "<foo><bar></bar></foo>".
-
- [Another possible explanation is "<foo><bar /></foo>", but since
- this class defines no SELF_CLOSING_TAGS, it will never use that
- explanation.]
-
- This class is useful for parsing XML or made-up markup languages,
- or when BeautifulSoup makes an assumption counter to what you were
- expecting."""
-
- SELF_CLOSING_TAGS = {}
- NESTABLE_TAGS = {}
- RESET_NESTING_TAGS = {}
- QUOTE_TAGS = {}
- PRESERVE_WHITESPACE_TAGS = []
-
- MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'),
- lambda x: x.group(1) + ' />'),
- (re.compile('<!\s+([^<>]*)>'),
- lambda x: '<!' + x.group(1) + '>')
- ]
-
- ROOT_TAG_NAME = u'[document]'
-
- HTML_ENTITIES = "html"
- XML_ENTITIES = "xml"
- XHTML_ENTITIES = "xhtml"
- # TODO: This only exists for backwards-compatibility
- ALL_ENTITIES = XHTML_ENTITIES
-
- # Used when determining whether a text node is all whitespace and
- # can be replaced with a single space. A text node that contains
- # fancy Unicode spaces (usually non-breaking) should be left
- # alone.
- STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, }
-
- def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None,
- markupMassage=True, smartQuotesTo=XML_ENTITIES,
- convertEntities=None, selfClosingTags=None, isHTML=False):
- """The Soup object is initialized as the 'root tag', and the
- provided markup (which can be a string or a file-like object)
- is fed into the underlying parser.
-
- sgmllib will process most bad HTML, and the BeautifulSoup
- class has some tricks for dealing with some HTML that kills
- sgmllib, but Beautiful Soup can nonetheless choke or lose data
- if your data uses self-closing tags or declarations
- incorrectly.
-
- By default, Beautiful Soup uses regexes to sanitize input,
- avoiding the vast majority of these problems. If the problems
- don't apply to you, pass in False for markupMassage, and
- you'll get better performance.
-
- The default parser massage techniques fix the two most common
- instances of invalid HTML that choke sgmllib:
-
- <br/> (No space between name of closing tag and tag close)
- <! --Comment--> (Extraneous whitespace in declaration)
-
- You can pass in a custom list of (RE object, replace method)
- tuples to get Beautiful Soup to scrub your input the way you
- want."""
-
- self.parseOnlyThese = parseOnlyThese
- self.fromEncoding = fromEncoding
- self.smartQuotesTo = smartQuotesTo
- self.convertEntities = convertEntities
- # Set the rules for how we'll deal with the entities we
- # encounter
- if self.convertEntities:
- # It doesn't make sense to convert encoded characters to
- # entities even while you're converting entities to Unicode.
- # Just convert it all to Unicode.
- self.smartQuotesTo = None
- if convertEntities == self.HTML_ENTITIES:
- self.convertXMLEntities = False
- self.convertHTMLEntities = True
- self.escapeUnrecognizedEntities = True
- elif convertEntities == self.XHTML_ENTITIES:
- self.convertXMLEntities = True
- self.convertHTMLEntities = True
- self.escapeUnrecognizedEntities = False
- elif convertEntities == self.XML_ENTITIES:
- self.convertXMLEntities = True
- self.convertHTMLEntities = False
- self.escapeUnrecognizedEntities = False
- else:
- self.convertXMLEntities = False
- self.convertHTMLEntities = False
- self.escapeUnrecognizedEntities = False
-
- self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags)
- SGMLParser.__init__(self)
-
- if hasattr(markup, 'read'): # It's a file-type object.
- markup = markup.read()
- self.markup = markup
- self.markupMassage = markupMassage
- try:
- self._feed(isHTML=isHTML)
- except StopParsing:
- pass
- self.markup = None # The markup can now be GCed
-
- def convert_charref(self, name):
- """This method fixes a bug in Python's SGMLParser."""
- try:
- n = int(name)
- except ValueError:
- return
- if not 0 <= n <= 127 : # ASCII ends at 127, not 255
- return
- return self.convert_codepoint(n)
-
- def _feed(self, inDocumentEncoding=None, isHTML=False):
- # Convert the document to Unicode.
- markup = self.markup
- if isinstance(markup, unicode):
- if not hasattr(self, 'originalEncoding'):
- self.originalEncoding = None
- else:
- dammit = UnicodeDammit\
- (markup, [self.fromEncoding, inDocumentEncoding],
- smartQuotesTo=self.smartQuotesTo, isHTML=isHTML)
- markup = dammit.unicode
- self.originalEncoding = dammit.originalEncoding
- self.declaredHTMLEncoding = dammit.declaredHTMLEncoding
- if markup:
- if self.markupMassage:
- if not hasattr(self.markupMassage, "__iter__"):
- self.markupMassage = self.MARKUP_MASSAGE
- for fix, m in self.markupMassage:
- markup = fix.sub(m, markup)
- # TODO: We get rid of markupMassage so that the
- # soup object can be deepcopied later on. Some
- # Python installations can't copy regexes. If anyone
- # was relying on the existence of markupMassage, this
- # might cause problems.
- del(self.markupMassage)
- self.reset()
-
- SGMLParser.feed(self, markup)
- # Close out any unfinished strings and close all the open tags.
- self.endData()
- while self.currentTag.name != self.ROOT_TAG_NAME:
- self.popTag()
-
- def __getattr__(self, methodName):
- """This method routes method call requests to either the SGMLParser
- superclass or the Tag superclass, depending on the method name."""
- #print "__getattr__ called on %s.%s" % (self.__class__, methodName)
-
- if methodName.startswith('start_') or methodName.startswith('end_') \
- or methodName.startswith('do_'):
- return SGMLParser.__getattr__(self, methodName)
- elif not methodName.startswith('__'):
- return Tag.__getattr__(self, methodName)
- else:
- raise AttributeError
-
- def isSelfClosingTag(self, name):
- """Returns true iff the given string is the name of a
- self-closing tag according to this parser."""
- return self.SELF_CLOSING_TAGS.has_key(name) \
- or self.instanceSelfClosingTags.has_key(name)
-
- def reset(self):
- Tag.__init__(self, self, self.ROOT_TAG_NAME)
- self.hidden = 1
- SGMLParser.reset(self)
- self.currentData = []
- self.currentTag = None
- self.tagStack = []
- self.quoteStack = []
- self.pushTag(self)
-
- def popTag(self):
- tag = self.tagStack.pop()
-
- #print "Pop", tag.name
- if self.tagStack:
- self.currentTag = self.tagStack[-1]
- return self.currentTag
-
- def pushTag(self, tag):
- #print "Push", tag.name
- if self.currentTag:
- self.currentTag.contents.append(tag)
- self.tagStack.append(tag)
- self.currentTag = self.tagStack[-1]
-
- def endData(self, containerClass=NavigableString):
- if self.currentData:
- currentData = u''.join(self.currentData)
- if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and
- not set([tag.name for tag in self.tagStack]).intersection(
- self.PRESERVE_WHITESPACE_TAGS)):
- if '\n' in currentData:
- currentData = '\n'
- else:
- currentData = ' '
- self.currentData = []
- if self.parseOnlyThese and len(self.tagStack) <= 1 and \
- (not self.parseOnlyThese.text or \
- not self.parseOnlyThese.search(currentData)):
- return
- o = containerClass(currentData)
- o.setup(self.currentTag, self.previous)
- if self.previous:
- self.previous.next = o
- self.previous = o
- self.currentTag.contents.append(o)
-
-
- def _popToTag(self, name, inclusivePop=True):
- """Pops the tag stack up to and including the most recent
- instance of the given tag. If inclusivePop is false, pops the tag
- stack up to but *not* including the most recent instqance of
- the given tag."""
- #print "Popping to %s" % name
- if name == self.ROOT_TAG_NAME:
- return
-
- numPops = 0
- mostRecentTag = None
- for i in range(len(self.tagStack)-1, 0, -1):
- if name == self.tagStack[i].name:
- numPops = len(self.tagStack)-i
- break
- if not inclusivePop:
- numPops = numPops - 1
-
- for i in range(0, numPops):
- mostRecentTag = self.popTag()
- return mostRecentTag
-
- def _smartPop(self, name):
-
- """We need to pop up to the previous tag of this type, unless
- one of this tag's nesting reset triggers comes between this
- tag and the previous tag of this type, OR unless this tag is a
- generic nesting trigger and another generic nesting trigger
- comes between this tag and the previous tag of this type.
-
- Examples:
- <p>Foo<b>Bar *<p>* should pop to 'p', not 'b'.
- <p>Foo<table>Bar *<p>* should pop to 'table', not 'p'.
- <p>Foo<table><tr>Bar *<p>* should pop to 'tr', not 'p'.
-
- <li><ul><li> *<li>* should pop to 'ul', not the first 'li'.
- <tr><table><tr> *<tr>* should pop to 'table', not the first 'tr'
- <td><tr><td> *<td>* should pop to 'tr', not the first 'td'
- """
-
- nestingResetTriggers = self.NESTABLE_TAGS.get(name)
- isNestable = nestingResetTriggers is not None
- isResetNesting = self.RESET_NESTING_TAGS.has_key(name)
- popTo = None
- inclusive = True
- for i in range(len(self.tagStack)-1, 0, -1):
- p = self.tagStack[i]
- if (not p or p.name == name) and not isNestable:
- #Non-nestable tags get popped to the top or to their
- #last occurance.
- popTo = name
- break
- if (nestingResetTriggers is not None
- and p.name in nestingResetTriggers) \
- or (nestingResetTriggers is None and isResetNesting
- and self.RESET_NESTING_TAGS.has_key(p.name)):
-
- #If we encounter one of the nesting reset triggers
- #peculiar to this tag, or we encounter another tag
- #that causes nesting to reset, pop up to but not
- #including that tag.
- popTo = p.name
- inclusive = False
- break
- p = p.parent
- if popTo:
- self._popToTag(popTo, inclusive)
-
- def unknown_starttag(self, name, attrs, selfClosing=0):
- #print "Start tag %s: %s" % (name, attrs)
- if self.quoteStack:
- #This is not a real tag.
- #print "<%s> is not real!" % name
- attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs])
- self.handle_data('<%s%s>' % (name, attrs))
- return
- self.endData()
-
- if not self.isSelfClosingTag(name) and not selfClosing:
- self._smartPop(name)
-
- if self.parseOnlyThese and len(self.tagStack) <= 1 \
- and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)):
- return
-
- tag = Tag(self, name, attrs, self.currentTag, self.previous)
- if self.previous:
- self.previous.next = tag
- self.previous = tag
- self.pushTag(tag)
- if selfClosing or self.isSelfClosingTag(name):
- self.popTag()
- if name in self.QUOTE_TAGS:
- #print "Beginning quote (%s)" % name
- self.quoteStack.append(name)
- self.literal = 1
- return tag
-
- def unknown_endtag(self, name):
- #print "End tag %s" % name
- if self.quoteStack and self.quoteStack[-1] != name:
- #This is not a real end tag.
- #print "</%s> is not real!" % name
- self.handle_data('</%s>' % name)
- return
- self.endData()
- self._popToTag(name)
- if self.quoteStack and self.quoteStack[-1] == name:
- self.quoteStack.pop()
- self.literal = (len(self.quoteStack) > 0)
-
- def handle_data(self, data):
- self.currentData.append(data)
-
- def _toStringSubclass(self, text, subclass):
- """Adds a certain piece of text to the tree as a NavigableString
- subclass."""
- self.endData()
- self.handle_data(text)
- self.endData(subclass)
-
- def handle_pi(self, text):
- """Handle a processing instruction as a ProcessingInstruction
- object, possibly one with a %SOUP-ENCODING% slot into which an
- encoding will be plugged later."""
- if text[:3] == "xml":
- text = u"xml version='1.0' encoding='%SOUP-ENCODING%'"
- self._toStringSubclass(text, ProcessingInstruction)
-
- def handle_comment(self, text):
- "Handle comments as Comment objects."
- self._toStringSubclass(text, Comment)
-
- def handle_charref(self, ref):
- "Handle character references as data."
- if self.convertEntities:
- data = unichr(int(ref))
- else:
- data = '&#%s;' % ref
- self.handle_data(data)
-
- def handle_entityref(self, ref):
- """Handle entity references as data, possibly converting known
- HTML and/or XML entity references to the corresponding Unicode
- characters."""
- data = None
- if self.convertHTMLEntities:
- try:
- data = unichr(name2codepoint[ref])
- except KeyError:
- pass
-
- if not data and self.convertXMLEntities:
- data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref)
-
- if not data and self.convertHTMLEntities and \
- not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref):
- # TODO: We've got a problem here. We're told this is
- # an entity reference, but it's not an XML entity
- # reference or an HTML entity reference. Nonetheless,
- # the logical thing to do is to pass it through as an
- # unrecognized entity reference.
- #
- # Except: when the input is "&carol;" this function
- # will be called with input "carol". When the input is
- # "AT&T", this function will be called with input
- # "T". We have no way of knowing whether a semicolon
- # was present originally, so we don't know whether
- # this is an unknown entity or just a misplaced
- # ampersand.
- #
- # The more common case is a misplaced ampersand, so I
- # escape the ampersand and omit the trailing semicolon.
- data = "&amp;%s" % ref
- if not data:
- # This case is different from the one above, because we
- # haven't already gone through a supposedly comprehensive
- # mapping of entities to Unicode characters. We might not
- # have gone through any mapping at all. So the chances are
- # very high that this is a real entity, and not a
- # misplaced ampersand.
- data = "&%s;" % ref
- self.handle_data(data)
-
- def handle_decl(self, data):
- "Handle DOCTYPEs and the like as Declaration objects."
- self._toStringSubclass(data, Declaration)
-
- def parse_declaration(self, i):
- """Treat a bogus SGML declaration as raw data. Treat a CDATA
- declaration as a CData object."""
- j = None
- if self.rawdata[i:i+9] == '<![CDATA[':
- k = self.rawdata.find(']]>', i)
- if k == -1:
- k = len(self.rawdata)
- data = self.rawdata[i+9:k]
- j = k+3
- self._toStringSubclass(data, CData)
- else:
- try:
- j = SGMLParser.parse_declaration(self, i)
- except SGMLParseError:
- toHandle = self.rawdata[i:]
- self.handle_data(toHandle)
- j = i + len(toHandle)
- return j
-
-class BeautifulSoup(BeautifulStoneSoup):
-
- """This parser knows the following facts about HTML:
-
- * Some tags have no closing tag and should be interpreted as being
- closed as soon as they are encountered.
-
- * The text inside some tags (ie. 'script') may contain tags which
- are not really part of the document and which should be parsed
- as text, not tags. If you want to parse the text as tags, you can
- always fetch it and parse it explicitly.
-
- * Tag nesting rules:
-
- Most tags can't be nested at all. For instance, the occurance of
- a <p> tag should implicitly close the previous <p> tag.
-
- <p>Para1<p>Para2
- should be transformed into:
- <p>Para1</p><p>Para2
-
- Some tags can be nested arbitrarily. For instance, the occurance
- of a <blockquote> tag should _not_ implicitly close the previous
- <blockquote> tag.
-
- Alice said: <blockquote>Bob said: <blockquote>Blah
- should NOT be transformed into:
- Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah
-
- Some tags can be nested, but the nesting is reset by the
- interposition of other tags. For instance, a <tr> tag should
- implicitly close the previous <tr> tag within the same <table>,
- but not close a <tr> tag in another table.
-
- <table><tr>Blah<tr>Blah
- should be transformed into:
- <table><tr>Blah</tr><tr>Blah
- but,
- <tr>Blah<table><tr>Blah
- should NOT be transformed into
- <tr>Blah<table></tr><tr>Blah
-
- Differing assumptions about tag nesting rules are a major source
- of problems with the BeautifulSoup class. If BeautifulSoup is not
- treating as nestable a tag your page author treats as nestable,
- try ICantBelieveItsBeautifulSoup, MinimalSoup, or
- BeautifulStoneSoup before writing your own subclass."""
-
- def __init__(self, *args, **kwargs):
- if not kwargs.has_key('smartQuotesTo'):
- kwargs['smartQuotesTo'] = self.HTML_ENTITIES
- kwargs['isHTML'] = True
- BeautifulStoneSoup.__init__(self, *args, **kwargs)
-
- SELF_CLOSING_TAGS = buildTagMap(None,
- ('br' , 'hr', 'input', 'img', 'meta',
- 'spacer', 'link', 'frame', 'base', 'col'))
-
- PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea'])
-
- QUOTE_TAGS = {'script' : None, 'textarea' : None}
-
- #According to the HTML standard, each of these inline tags can
- #contain another tag of the same type. Furthermore, it's common
- #to actually use these tags this way.
- NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup',
- 'center')
-
- #According to the HTML standard, these block tags can contain
- #another tag of the same type. Furthermore, it's common
- #to actually use these tags this way.
- NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del')
-
- #Lists can contain other lists, but there are restrictions.
- NESTABLE_LIST_TAGS = { 'ol' : [],
- 'ul' : [],
- 'li' : ['ul', 'ol'],
- 'dl' : [],
- 'dd' : ['dl'],
- 'dt' : ['dl'] }
-
- #Tables can contain other tables, but there are restrictions.
- NESTABLE_TABLE_TAGS = {'table' : [],
- 'tr' : ['table', 'tbody', 'tfoot', 'thead'],
- 'td' : ['tr'],
- 'th' : ['tr'],
- 'thead' : ['table'],
- 'tbody' : ['table'],
- 'tfoot' : ['table'],
- }
-
- NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre')
-
- #If one of these tags is encountered, all tags up to the next tag of
- #this type are popped.
- RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript',
- NON_NESTABLE_BLOCK_TAGS,
- NESTABLE_LIST_TAGS,
- NESTABLE_TABLE_TAGS)
-
- NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS,
- NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS)
-
- # Used to detect the charset in a META tag; see start_meta
- CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M)
-
- def start_meta(self, attrs):
- """Beautiful Soup can detect a charset included in a META tag,
- try to convert the document to that charset, and re-parse the
- document from the beginning."""
- httpEquiv = None
- contentType = None
- contentTypeIndex = None
- tagNeedsEncodingSubstitution = False
-
- for i in range(0, len(attrs)):
- key, value = attrs[i]
- key = key.lower()
- if key == 'http-equiv':
- httpEquiv = value
- elif key == 'content':
- contentType = value
- contentTypeIndex = i
-
- if httpEquiv and contentType: # It's an interesting meta tag.
- match = self.CHARSET_RE.search(contentType)
- if match:
- if (self.declaredHTMLEncoding is not None or
- self.originalEncoding == self.fromEncoding):
- # An HTML encoding was sniffed while converting
- # the document to Unicode, or an HTML encoding was
- # sniffed during a previous pass through the
- # document, or an encoding was specified
- # explicitly and it worked. Rewrite the meta tag.
- def rewrite(match):
- return match.group(1) + "%SOUP-ENCODING%"
- newAttr = self.CHARSET_RE.sub(rewrite, contentType)
- attrs[contentTypeIndex] = (attrs[contentTypeIndex][0],
- newAttr)
- tagNeedsEncodingSubstitution = True
- else:
- # This is our first pass through the document.
- # Go through it again with the encoding information.
- newCharset = match.group(3)
- if newCharset and newCharset != self.originalEncoding:
- self.declaredHTMLEncoding = newCharset
- self._feed(self.declaredHTMLEncoding)
- raise StopParsing
- pass
- tag = self.unknown_starttag("meta", attrs)
- if tag and tagNeedsEncodingSubstitution:
- tag.containsSubstitutions = True
-
-class StopParsing(Exception):
- pass
-
-class ICantBelieveItsBeautifulSoup(BeautifulSoup):
-
- """The BeautifulSoup class is oriented towards skipping over
- common HTML errors like unclosed tags. However, sometimes it makes
- errors of its own. For instance, consider this fragment:
-
- <b>Foo<b>Bar</b></b>
-
- This is perfectly valid (if bizarre) HTML. However, the
- BeautifulSoup class will implicitly close the first b tag when it
- encounters the second 'b'. It will think the author wrote
- "<b>Foo<b>Bar", and didn't close the first 'b' tag, because
- there's no real-world reason to bold something that's already
- bold. When it encounters '</b></b>' it will close two more 'b'
- tags, for a grand total of three tags closed instead of two. This
- can throw off the rest of your document structure. The same is
- true of a number of other tags, listed below.
-
- It's much more common for someone to forget to close a 'b' tag
- than to actually use nested 'b' tags, and the BeautifulSoup class
- handles the common case. This class handles the not-co-common
- case: where you can't believe someone wrote what they did, but
- it's valid HTML and BeautifulSoup screwed up by assuming it
- wouldn't be."""
-
- I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \
- ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong',
- 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b',
- 'big')
-
- I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript',)
-
- NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS,
- I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS,
- I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS)
-
-class MinimalSoup(BeautifulSoup):
- """The MinimalSoup class is for parsing HTML that contains
- pathologically bad markup. It makes no assumptions about tag
- nesting, but it does know which tags are self-closing, that
- <script> tags contain Javascript and should not be parsed, that
- META tags may contain encoding information, and so on.
-
- This also makes it better for subclassing than BeautifulStoneSoup
- or BeautifulSoup."""
-
- RESET_NESTING_TAGS = buildTagMap('noscript')
- NESTABLE_TAGS = {}
-
-class BeautifulSOAP(BeautifulStoneSoup):
- """This class will push a tag with only a single string child into
- the tag's parent as an attribute. The attribute's name is the tag
- name, and the value is the string child. An example should give
- the flavor of the change:
-
- <foo><bar>baz</bar></foo>
- =>
- <foo bar="baz"><bar>baz</bar></foo>
-
- You can then access fooTag['bar'] instead of fooTag.barTag.string.
-
- This is, of course, useful for scraping structures that tend to
- use subelements instead of attributes, such as SOAP messages. Note
- that it modifies its input, so don't print the modified version
- out.
-
- I'm not sure how many people really want to use this class; let me
- know if you do. Mainly I like the name."""
-
- def popTag(self):
- if len(self.tagStack) > 1:
- tag = self.tagStack[-1]
- parent = self.tagStack[-2]
- parent._getAttrMap()
- if (isinstance(tag, Tag) and len(tag.contents) == 1 and
- isinstance(tag.contents[0], NavigableString) and
- not parent.attrMap.has_key(tag.name)):
- parent[tag.name] = tag.contents[0]
- BeautifulStoneSoup.popTag(self)
-
-#Enterprise class names! It has come to our attention that some people
-#think the names of the Beautiful Soup parser classes are too silly
-#and "unprofessional" for use in enterprise screen-scraping. We feel
-#your pain! For such-minded folk, the Beautiful Soup Consortium And
-#All-Night Kosher Bakery recommends renaming this file to
-#"RobustParser.py" (or, in cases of extreme enterprisiness,
-#"RobustParserBeanInterface.class") and using the following
-#enterprise-friendly class aliases:
-class RobustXMLParser(BeautifulStoneSoup):
- pass
-class RobustHTMLParser(BeautifulSoup):
- pass
-class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup):
- pass
-class RobustInsanelyWackAssHTMLParser(MinimalSoup):
- pass
-class SimplifyingSOAPParser(BeautifulSOAP):
- pass
-
-######################################################
-#
-# Bonus library: Unicode, Dammit
-#
-# This class forces XML data into a standard format (usually to UTF-8
-# or Unicode). It is heavily based on code from Mark Pilgrim's
-# Universal Feed Parser. It does not rewrite the XML or HTML to
-# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi
-# (XML) and BeautifulSoup.start_meta (HTML).
-
-# Autodetects character encodings.
-# Download from http://chardet.feedparser.org/
-try:
- import chardet
-# import chardet.constants
-# chardet.constants._debug = 1
-except ImportError:
- chardet = None
-
-# cjkcodecs and iconv_codec make Python know about more character encodings.
-# Both are available from http://cjkpython.i18n.org/
-# They're built in if you use Python 2.4.
-try:
- import cjkcodecs.aliases
-except ImportError:
- pass
-try:
- import iconv_codec
-except ImportError:
- pass
-
-class UnicodeDammit:
- """A class for detecting the encoding of a *ML document and
- converting it to a Unicode string. If the source encoding is
- windows-1252, can replace MS smart quotes with their HTML or XML
- equivalents."""
-
- # This dictionary maps commonly seen values for "charset" in HTML
- # meta tags to the corresponding Python codec names. It only covers
- # values that aren't in Python's aliases and can't be determined
- # by the heuristics in find_codec.
- CHARSET_ALIASES = { "macintosh" : "mac-roman",
- "x-sjis" : "shift-jis" }
-
- def __init__(self, markup, overrideEncodings=[],
- smartQuotesTo='xml', isHTML=False):
- self.declaredHTMLEncoding = None
- self.markup, documentEncoding, sniffedEncoding = \
- self._detectEncoding(markup, isHTML)
- self.smartQuotesTo = smartQuotesTo
- self.triedEncodings = []
- if markup == '' or isinstance(markup, unicode):
- self.originalEncoding = None
- self.unicode = unicode(markup)
- return
-
- u = None
- for proposedEncoding in overrideEncodings:
- u = self._convertFrom(proposedEncoding)
- if u: break
- if not u:
- for proposedEncoding in (documentEncoding, sniffedEncoding):
- u = self._convertFrom(proposedEncoding)
- if u: break
-
- # If no luck and we have auto-detection library, try that:
- if not u and chardet and not isinstance(self.markup, unicode):
- u = self._convertFrom(chardet.detect(self.markup)['encoding'])
-
- # As a last resort, try utf-8 and windows-1252:
- if not u:
- for proposed_encoding in ("utf-8", "windows-1252"):
- u = self._convertFrom(proposed_encoding)
- if u: break
-
- self.unicode = u
- if not u: self.originalEncoding = None
-
- def _subMSChar(self, orig):
- """Changes a MS smart quote character to an XML or HTML
- entity."""
- sub = self.MS_CHARS.get(orig)
- if isinstance(sub, tuple):
- if self.smartQuotesTo == 'xml':
- sub = '&#x%s;' % sub[1]
- else:
- sub = '&%s;' % sub[0]
- return sub
-
- def _convertFrom(self, proposed):
- proposed = self.find_codec(proposed)
- if not proposed or proposed in self.triedEncodings:
- return None
- self.triedEncodings.append(proposed)
- markup = self.markup
-
- # Convert smart quotes to HTML if coming from an encoding
- # that might have them.
- if self.smartQuotesTo and proposed.lower() in("windows-1252",
- "iso-8859-1",
- "iso-8859-2"):
- markup = re.compile("([\x80-\x9f])").sub \
- (lambda(x): self._subMSChar(x.group(1)),
- markup)
-
- try:
- # print "Trying to convert document to %s" % proposed
- u = self._toUnicode(markup, proposed)
- self.markup = u
- self.originalEncoding = proposed
- except Exception, e:
- # print "That didn't work!"
- # print e
- return None
- #print "Correct encoding: %s" % proposed
- return self.markup
-
- def _toUnicode(self, data, encoding):
- '''Given a string and its encoding, decodes the string into Unicode.
- %encoding is a string recognized by encodings.aliases'''
-
- # strip Byte Order Mark (if present)
- if (len(data) >= 4) and (data[:2] == '\xfe\xff') \
- and (data[2:4] != '\x00\x00'):
- encoding = 'utf-16be'
- data = data[2:]
- elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \
- and (data[2:4] != '\x00\x00'):
- encoding = 'utf-16le'
- data = data[2:]
- elif data[:3] == '\xef\xbb\xbf':
- encoding = 'utf-8'
- data = data[3:]
- elif data[:4] == '\x00\x00\xfe\xff':
- encoding = 'utf-32be'
- data = data[4:]
- elif data[:4] == '\xff\xfe\x00\x00':
- encoding = 'utf-32le'
- data = data[4:]
- newdata = unicode(data, encoding)
- return newdata
-
- def _detectEncoding(self, xml_data, isHTML=False):
- """Given a document, tries to detect its XML encoding."""
- xml_encoding = sniffed_xml_encoding = None
- try:
- if xml_data[:4] == '\x4c\x6f\xa7\x94':
- # EBCDIC
- xml_data = self._ebcdic_to_ascii(xml_data)
- elif xml_data[:4] == '\x00\x3c\x00\x3f':
- # UTF-16BE
- sniffed_xml_encoding = 'utf-16be'
- xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
- elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \
- and (xml_data[2:4] != '\x00\x00'):
- # UTF-16BE with BOM
- sniffed_xml_encoding = 'utf-16be'
- xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
- elif xml_data[:4] == '\x3c\x00\x3f\x00':
- # UTF-16LE
- sniffed_xml_encoding = 'utf-16le'
- xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
- elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \
- (xml_data[2:4] != '\x00\x00'):
- # UTF-16LE with BOM
- sniffed_xml_encoding = 'utf-16le'
- xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
- elif xml_data[:4] == '\x00\x00\x00\x3c':
- # UTF-32BE
- sniffed_xml_encoding = 'utf-32be'
- xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
- elif xml_data[:4] == '\x3c\x00\x00\x00':
- # UTF-32LE
- sniffed_xml_encoding = 'utf-32le'
- xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
- elif xml_data[:4] == '\x00\x00\xfe\xff':
- # UTF-32BE with BOM
- sniffed_xml_encoding = 'utf-32be'
- xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
- elif xml_data[:4] == '\xff\xfe\x00\x00':
- # UTF-32LE with BOM
- sniffed_xml_encoding = 'utf-32le'
- xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
- elif xml_data[:3] == '\xef\xbb\xbf':
- # UTF-8 with BOM
- sniffed_xml_encoding = 'utf-8'
- xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
- else:
- sniffed_xml_encoding = 'ascii'
- pass
- except:
- xml_encoding_match = None
- xml_encoding_match = re.compile(
- '^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data)
- if not xml_encoding_match and isHTML:
- regexp = re.compile('<\s*meta[^>]+charset=([^>]*?)[;\'">]', re.I)
- xml_encoding_match = regexp.search(xml_data)
- if xml_encoding_match is not None:
- xml_encoding = xml_encoding_match.groups()[0].lower()
- if isHTML:
- self.declaredHTMLEncoding = xml_encoding
- if sniffed_xml_encoding and \
- (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode',
- 'iso-10646-ucs-4', 'ucs-4', 'csucs4',
- 'utf-16', 'utf-32', 'utf_16', 'utf_32',
- 'utf16', 'u16')):
- xml_encoding = sniffed_xml_encoding
- return xml_data, xml_encoding, sniffed_xml_encoding
-
-
- def find_codec(self, charset):
- return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \
- or (charset and self._codec(charset.replace("-", ""))) \
- or (charset and self._codec(charset.replace("-", "_"))) \
- or charset
-
- def _codec(self, charset):
- if not charset: return charset
- codec = None
- try:
- codecs.lookup(charset)
- codec = charset
- except (LookupError, ValueError):
- pass
- return codec
-
- EBCDIC_TO_ASCII_MAP = None
- def _ebcdic_to_ascii(self, s):
- c = self.__class__
- if not c.EBCDIC_TO_ASCII_MAP:
- emap = (0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15,
- 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31,
- 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7,
- 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26,
- 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33,
- 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94,
- 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63,
- 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34,
- 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200,
- 201,202,106,107,108,109,110,111,112,113,114,203,204,205,
- 206,207,208,209,126,115,116,117,118,119,120,121,122,210,
- 211,212,213,214,215,216,217,218,219,220,221,222,223,224,
- 225,226,227,228,229,230,231,123,65,66,67,68,69,70,71,72,
- 73,232,233,234,235,236,237,125,74,75,76,77,78,79,80,81,
- 82,238,239,240,241,242,243,92,159,83,84,85,86,87,88,89,
- 90,244,245,246,247,248,249,48,49,50,51,52,53,54,55,56,57,
- 250,251,252,253,254,255)
- import string
- c.EBCDIC_TO_ASCII_MAP = string.maketrans( \
- ''.join(map(chr, range(256))), ''.join(map(chr, emap)))
- return s.translate(c.EBCDIC_TO_ASCII_MAP)
-
- MS_CHARS = { '\x80' : ('euro', '20AC'),
- '\x81' : ' ',
- '\x82' : ('sbquo', '201A'),
- '\x83' : ('fnof', '192'),
- '\x84' : ('bdquo', '201E'),
- '\x85' : ('hellip', '2026'),
- '\x86' : ('dagger', '2020'),
- '\x87' : ('Dagger', '2021'),
- '\x88' : ('circ', '2C6'),
- '\x89' : ('permil', '2030'),
- '\x8A' : ('Scaron', '160'),
- '\x8B' : ('lsaquo', '2039'),
- '\x8C' : ('OElig', '152'),
- '\x8D' : '?',
- '\x8E' : ('#x17D', '17D'),
- '\x8F' : '?',
- '\x90' : '?',
- '\x91' : ('lsquo', '2018'),
- '\x92' : ('rsquo', '2019'),
- '\x93' : ('ldquo', '201C'),
- '\x94' : ('rdquo', '201D'),
- '\x95' : ('bull', '2022'),
- '\x96' : ('ndash', '2013'),
- '\x97' : ('mdash', '2014'),
- '\x98' : ('tilde', '2DC'),
- '\x99' : ('trade', '2122'),
- '\x9a' : ('scaron', '161'),
- '\x9b' : ('rsaquo', '203A'),
- '\x9c' : ('oelig', '153'),
- '\x9d' : '?',
- '\x9e' : ('#x17E', '17E'),
- '\x9f' : ('Yuml', ''),}
-
-#######################################################################
-
-
-#By default, act as an HTML pretty-printer.
-if __name__ == '__main__':
- import sys
- soup = BeautifulSoup(sys.stdin)
- print soup.prettify()
diff --git a/module/lib/Unzip.py b/module/lib/Unzip.py
deleted file mode 100644
index f56fbe751..000000000
--- a/module/lib/Unzip.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import zipfile
-import os
-
-class Unzip:
- def __init__(self):
- pass
-
- def extract(self, file, dir):
- if not dir.endswith(':') and not os.path.exists(dir):
- os.mkdir(dir)
-
- zf = zipfile.ZipFile(file)
-
- # create directory structure to house files
- self._createstructure(file, dir)
-
- # extract files to directory structure
- for i, name in enumerate(zf.namelist()):
-
- if not name.endswith('/') and not name.endswith("config"):
- print "extracting", name.replace("pyload/","")
- outfile = open(os.path.join(dir, name.replace("pyload/","")), 'wb')
- outfile.write(zf.read(name))
- outfile.flush()
- outfile.close()
-
- def _createstructure(self, file, dir):
- self._makedirs(self._listdirs(file), dir)
-
- def _makedirs(self, directories, basedir):
- """ Create any directories that don't currently exist """
- for dir in directories:
- curdir = os.path.join(basedir, dir)
- if not os.path.exists(curdir):
- os.mkdir(curdir)
-
- def _listdirs(self, file):
- """ Grabs all the directories in the zip structure
- This is necessary to create the structure before trying
- to extract the file to it. """
- zf = zipfile.ZipFile(file)
-
- dirs = []
-
- for name in zf.namelist():
- if name.endswith('/'):
- dirs.append(name.replace("pyload/",""))
-
- dirs.sort()
- return dirs
diff --git a/module/lib/beaker/__init__.py b/module/lib/beaker/__init__.py
deleted file mode 100644
index 792d60054..000000000
--- a/module/lib/beaker/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#
diff --git a/module/lib/beaker/cache.py b/module/lib/beaker/cache.py
deleted file mode 100644
index 4a96537ff..000000000
--- a/module/lib/beaker/cache.py
+++ /dev/null
@@ -1,459 +0,0 @@
-"""Cache object
-
-The Cache object is used to manage a set of cache files and their
-associated backend. The backends can be rotated on the fly by
-specifying an alternate type when used.
-
-Advanced users can add new backends in beaker.backends
-
-"""
-
-import warnings
-
-import beaker.container as container
-import beaker.util as util
-from beaker.exceptions import BeakerException, InvalidCacheBackendError
-
-import beaker.ext.memcached as memcached
-import beaker.ext.database as database
-import beaker.ext.sqla as sqla
-import beaker.ext.google as google
-
-# Initialize the basic available backends
-clsmap = {
- 'memory':container.MemoryNamespaceManager,
- 'dbm':container.DBMNamespaceManager,
- 'file':container.FileNamespaceManager,
- 'ext:memcached':memcached.MemcachedNamespaceManager,
- 'ext:database':database.DatabaseNamespaceManager,
- 'ext:sqla': sqla.SqlaNamespaceManager,
- 'ext:google': google.GoogleNamespaceManager,
- }
-
-# Initialize the cache region dict
-cache_regions = {}
-cache_managers = {}
-
-try:
- import pkg_resources
-
- # Load up the additional entry point defined backends
- for entry_point in pkg_resources.iter_entry_points('beaker.backends'):
- try:
- NamespaceManager = entry_point.load()
- name = entry_point.name
- if name in clsmap:
- raise BeakerException("NamespaceManager name conflict,'%s' "
- "already loaded" % name)
- clsmap[name] = NamespaceManager
- except (InvalidCacheBackendError, SyntaxError):
- # Ignore invalid backends
- pass
- except:
- import sys
- from pkg_resources import DistributionNotFound
- # Warn when there's a problem loading a NamespaceManager
- if not isinstance(sys.exc_info()[1], DistributionNotFound):
- import traceback
- from StringIO import StringIO
- tb = StringIO()
- traceback.print_exc(file=tb)
- warnings.warn("Unable to load NamespaceManager entry point: '%s': "
- "%s" % (entry_point, tb.getvalue()), RuntimeWarning,
- 2)
-except ImportError:
- pass
-
-
-
-
-def cache_region(region, *deco_args):
- """Decorate a function to cache itself using a cache region
-
- The region decorator requires arguments if there are more than
- 2 of the same named function, in the same module. This is
- because the namespace used for the functions cache is based on
- the functions name and the module.
-
-
- Example::
-
- # Add cache region settings to beaker:
- beaker.cache.cache_regions.update(dict_of_config_region_options))
-
- @cache_region('short_term', 'some_data')
- def populate_things(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- return load('rabbits', 20, 0)
-
- .. note::
-
- The function being decorated must only be called with
- positional arguments.
-
- """
- cache = [None]
-
- def decorate(func):
- namespace = util.func_namespace(func)
- def cached(*args):
- reg = cache_regions[region]
- if not reg.get('enabled', True):
- return func(*args)
-
- if not cache[0]:
- if region not in cache_regions:
- raise BeakerException('Cache region not configured: %s' % region)
- cache[0] = Cache._get_cache(namespace, reg)
-
- cache_key = " ".join(map(str, deco_args + args))
- def go():
- return func(*args)
-
- return cache[0].get_value(cache_key, createfunc=go)
- cached._arg_namespace = namespace
- cached._arg_region = region
- return cached
- return decorate
-
-
-def region_invalidate(namespace, region, *args):
- """Invalidate a cache region namespace or decorated function
-
- This function only invalidates cache spaces created with the
- cache_region decorator.
-
- :param namespace: Either the namespace of the result to invalidate, or the
- cached function reference
-
- :param region: The region the function was cached to. If the function was
- cached to a single region then this argument can be None
-
- :param args: Arguments that were used to differentiate the cached
- function as well as the arguments passed to the decorated
- function
-
- Example::
-
- # Add cache region settings to beaker:
- beaker.cache.cache_regions.update(dict_of_config_region_options))
-
- def populate_things(invalidate=False):
-
- @cache_region('short_term', 'some_data')
- def load(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- # If the results should be invalidated first
- if invalidate:
- region_invalidate(load, None, 'some_data',
- 'rabbits', 20, 0)
- return load('rabbits', 20, 0)
-
- """
- if callable(namespace):
- if not region:
- region = namespace._arg_region
- namespace = namespace._arg_namespace
-
- if not region:
- raise BeakerException("Region or callable function "
- "namespace is required")
- else:
- region = cache_regions[region]
-
- cache = Cache._get_cache(namespace, region)
- cache_key = " ".join(str(x) for x in args)
- cache.remove_value(cache_key)
-
-
-class Cache(object):
- """Front-end to the containment API implementing a data cache.
-
- :param namespace: the namespace of this Cache
-
- :param type: type of cache to use
-
- :param expire: seconds to keep cached data
-
- :param expiretime: seconds to keep cached data (legacy support)
-
- :param starttime: time when cache was cache was
-
- """
- def __init__(self, namespace, type='memory', expiretime=None,
- starttime=None, expire=None, **nsargs):
- try:
- cls = clsmap[type]
- if isinstance(cls, InvalidCacheBackendError):
- raise cls
- except KeyError:
- raise TypeError("Unknown cache implementation %r" % type)
-
- self.namespace = cls(namespace, **nsargs)
- self.expiretime = expiretime or expire
- self.starttime = starttime
- self.nsargs = nsargs
-
- @classmethod
- def _get_cache(cls, namespace, kw):
- key = namespace + str(kw)
- try:
- return cache_managers[key]
- except KeyError:
- cache_managers[key] = cache = cls(namespace, **kw)
- return cache
-
- def put(self, key, value, **kw):
- self._get_value(key, **kw).set_value(value)
- set_value = put
-
- def get(self, key, **kw):
- """Retrieve a cached value from the container"""
- return self._get_value(key, **kw).get_value()
- get_value = get
-
- def remove_value(self, key, **kw):
- mycontainer = self._get_value(key, **kw)
- if mycontainer.has_current_value():
- mycontainer.clear_value()
- remove = remove_value
-
- def _get_value(self, key, **kw):
- if isinstance(key, unicode):
- key = key.encode('ascii', 'backslashreplace')
-
- if 'type' in kw:
- return self._legacy_get_value(key, **kw)
-
- kw.setdefault('expiretime', self.expiretime)
- kw.setdefault('starttime', self.starttime)
-
- return container.Value(key, self.namespace, **kw)
-
- @util.deprecated("Specifying a "
- "'type' and other namespace configuration with cache.get()/put()/etc. "
- "is deprecated. Specify 'type' and other namespace configuration to "
- "cache_manager.get_cache() and/or the Cache constructor instead.")
- def _legacy_get_value(self, key, type, **kw):
- expiretime = kw.pop('expiretime', self.expiretime)
- starttime = kw.pop('starttime', None)
- createfunc = kw.pop('createfunc', None)
- kwargs = self.nsargs.copy()
- kwargs.update(kw)
- c = Cache(self.namespace.namespace, type=type, **kwargs)
- return c._get_value(key, expiretime=expiretime, createfunc=createfunc,
- starttime=starttime)
-
- def clear(self):
- """Clear all the values from the namespace"""
- self.namespace.remove()
-
- # dict interface
- def __getitem__(self, key):
- return self.get(key)
-
- def __contains__(self, key):
- return self._get_value(key).has_current_value()
-
- def has_key(self, key):
- return key in self
-
- def __delitem__(self, key):
- self.remove_value(key)
-
- def __setitem__(self, key, value):
- self.put(key, value)
-
-
-class CacheManager(object):
- def __init__(self, **kwargs):
- """Initialize a CacheManager object with a set of options
-
- Options should be parsed with the
- :func:`~beaker.util.parse_cache_config_options` function to
- ensure only valid options are used.
-
- """
- self.kwargs = kwargs
- self.regions = kwargs.pop('cache_regions', {})
-
- # Add these regions to the module global
- cache_regions.update(self.regions)
-
- def get_cache(self, name, **kwargs):
- kw = self.kwargs.copy()
- kw.update(kwargs)
- return Cache._get_cache(name, kw)
-
- def get_cache_region(self, name, region):
- if region not in self.regions:
- raise BeakerException('Cache region not configured: %s' % region)
- kw = self.regions[region]
- return Cache._get_cache(name, kw)
-
- def region(self, region, *args):
- """Decorate a function to cache itself using a cache region
-
- The region decorator requires arguments if there are more than
- 2 of the same named function, in the same module. This is
- because the namespace used for the functions cache is based on
- the functions name and the module.
-
-
- Example::
-
- # Assuming a cache object is available like:
- cache = CacheManager(dict_of_config_options)
-
-
- def populate_things():
-
- @cache.region('short_term', 'some_data')
- def load(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- return load('rabbits', 20, 0)
-
- .. note::
-
- The function being decorated must only be called with
- positional arguments.
-
- """
- return cache_region(region, *args)
-
- def region_invalidate(self, namespace, region, *args):
- """Invalidate a cache region namespace or decorated function
-
- This function only invalidates cache spaces created with the
- cache_region decorator.
-
- :param namespace: Either the namespace of the result to invalidate, or the
- name of the cached function
-
- :param region: The region the function was cached to. If the function was
- cached to a single region then this argument can be None
-
- :param args: Arguments that were used to differentiate the cached
- function as well as the arguments passed to the decorated
- function
-
- Example::
-
- # Assuming a cache object is available like:
- cache = CacheManager(dict_of_config_options)
-
- def populate_things(invalidate=False):
-
- @cache.region('short_term', 'some_data')
- def load(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- # If the results should be invalidated first
- if invalidate:
- cache.region_invalidate(load, None, 'some_data',
- 'rabbits', 20, 0)
- return load('rabbits', 20, 0)
-
-
- """
- return region_invalidate(namespace, region, *args)
- if callable(namespace):
- if not region:
- region = namespace._arg_region
- namespace = namespace._arg_namespace
-
- if not region:
- raise BeakerException("Region or callable function "
- "namespace is required")
- else:
- region = self.regions[region]
-
- cache = self.get_cache(namespace, **region)
- cache_key = " ".join(str(x) for x in args)
- cache.remove_value(cache_key)
-
- def cache(self, *args, **kwargs):
- """Decorate a function to cache itself with supplied parameters
-
- :param args: Used to make the key unique for this function, as in region()
- above.
-
- :param kwargs: Parameters to be passed to get_cache(), will override defaults
-
- Example::
-
- # Assuming a cache object is available like:
- cache = CacheManager(dict_of_config_options)
-
-
- def populate_things():
-
- @cache.cache('mycache', expire=15)
- def load(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- return load('rabbits', 20, 0)
-
- .. note::
-
- The function being decorated must only be called with
- positional arguments.
-
- """
- cache = [None]
- key = " ".join(str(x) for x in args)
-
- def decorate(func):
- namespace = util.func_namespace(func)
- def cached(*args):
- if not cache[0]:
- cache[0] = self.get_cache(namespace, **kwargs)
- cache_key = key + " " + " ".join(str(x) for x in args)
- def go():
- return func(*args)
- return cache[0].get_value(cache_key, createfunc=go)
- cached._arg_namespace = namespace
- return cached
- return decorate
-
- def invalidate(self, func, *args, **kwargs):
- """Invalidate a cache decorated function
-
- This function only invalidates cache spaces created with the
- cache decorator.
-
- :param func: Decorated function to invalidate
-
- :param args: Used to make the key unique for this function, as in region()
- above.
-
- :param kwargs: Parameters that were passed for use by get_cache(), note that
- this is only required if a ``type`` was specified for the
- function
-
- Example::
-
- # Assuming a cache object is available like:
- cache = CacheManager(dict_of_config_options)
-
-
- def populate_things(invalidate=False):
-
- @cache.cache('mycache', type="file", expire=15)
- def load(search_term, limit, offset):
- return load_the_data(search_term, limit, offset)
-
- # If the results should be invalidated first
- if invalidate:
- cache.invalidate(load, 'mycache', 'rabbits', 20, 0, type="file")
- return load('rabbits', 20, 0)
-
- """
- namespace = func._arg_namespace
-
- cache = self.get_cache(namespace, **kwargs)
- cache_key = " ".join(str(x) for x in args)
- cache.remove_value(cache_key)
diff --git a/module/lib/beaker/container.py b/module/lib/beaker/container.py
deleted file mode 100644
index 515e97af6..000000000
--- a/module/lib/beaker/container.py
+++ /dev/null
@@ -1,633 +0,0 @@
-"""Container and Namespace classes"""
-import anydbm
-import cPickle
-import logging
-import os
-import time
-
-import beaker.util as util
-from beaker.exceptions import CreationAbortedError, MissingCacheParameter
-from beaker.synchronization import _threading, file_synchronizer, \
- mutex_synchronizer, NameLock, null_synchronizer
-
-__all__ = ['Value', 'Container', 'ContainerContext',
- 'MemoryContainer', 'DBMContainer', 'NamespaceManager',
- 'MemoryNamespaceManager', 'DBMNamespaceManager', 'FileContainer',
- 'OpenResourceNamespaceManager',
- 'FileNamespaceManager', 'CreationAbortedError']
-
-
-logger = logging.getLogger('beaker.container')
-if logger.isEnabledFor(logging.DEBUG):
- debug = logger.debug
-else:
- def debug(message, *args):
- pass
-
-
-class NamespaceManager(object):
- """Handles dictionary operations and locking for a namespace of
- values.
-
- The implementation for setting and retrieving the namespace data is
- handled by subclasses.
-
- NamespaceManager may be used alone, or may be privately accessed by
- one or more Container objects. Container objects provide per-key
- services like expiration times and automatic recreation of values.
-
- Multiple NamespaceManagers created with a particular name will all
- share access to the same underlying datasource and will attempt to
- synchronize against a common mutex object. The scope of this
- sharing may be within a single process or across multiple
- processes, depending on the type of NamespaceManager used.
-
- The NamespaceManager itself is generally threadsafe, except in the
- case of the DBMNamespaceManager in conjunction with the gdbm dbm
- implementation.
-
- """
-
- @classmethod
- def _init_dependencies(cls):
- pass
-
- def __init__(self, namespace):
- self._init_dependencies()
- self.namespace = namespace
-
- def get_creation_lock(self, key):
- raise NotImplementedError()
-
- def do_remove(self):
- raise NotImplementedError()
-
- def acquire_read_lock(self):
- pass
-
- def release_read_lock(self):
- pass
-
- def acquire_write_lock(self, wait=True):
- return True
-
- def release_write_lock(self):
- pass
-
- def has_key(self, key):
- return self.__contains__(key)
-
- def __getitem__(self, key):
- raise NotImplementedError()
-
- def __setitem__(self, key, value):
- raise NotImplementedError()
-
- def set_value(self, key, value, expiretime=None):
- """Optional set_value() method called by Value.
-
- Allows an expiretime to be passed, for namespace
- implementations which can prune their collections
- using expiretime.
-
- """
- self[key] = value
-
- def __contains__(self, key):
- raise NotImplementedError()
-
- def __delitem__(self, key):
- raise NotImplementedError()
-
- def keys(self):
- raise NotImplementedError()
-
- def remove(self):
- self.do_remove()
-
-
-class OpenResourceNamespaceManager(NamespaceManager):
- """A NamespaceManager where read/write operations require opening/
- closing of a resource which is possibly mutexed.
-
- """
- def __init__(self, namespace):
- NamespaceManager.__init__(self, namespace)
- self.access_lock = self.get_access_lock()
- self.openers = 0
- self.mutex = _threading.Lock()
-
- def get_access_lock(self):
- raise NotImplementedError()
-
- def do_open(self, flags):
- raise NotImplementedError()
-
- def do_close(self):
- raise NotImplementedError()
-
- def acquire_read_lock(self):
- self.access_lock.acquire_read_lock()
- try:
- self.open('r', checkcount = True)
- except:
- self.access_lock.release_read_lock()
- raise
-
- def release_read_lock(self):
- try:
- self.close(checkcount = True)
- finally:
- self.access_lock.release_read_lock()
-
- def acquire_write_lock(self, wait=True):
- r = self.access_lock.acquire_write_lock(wait)
- try:
- if (wait or r):
- self.open('c', checkcount = True)
- return r
- except:
- self.access_lock.release_write_lock()
- raise
-
- def release_write_lock(self):
- try:
- self.close(checkcount=True)
- finally:
- self.access_lock.release_write_lock()
-
- def open(self, flags, checkcount=False):
- self.mutex.acquire()
- try:
- if checkcount:
- if self.openers == 0:
- self.do_open(flags)
- self.openers += 1
- else:
- self.do_open(flags)
- self.openers = 1
- finally:
- self.mutex.release()
-
- def close(self, checkcount=False):
- self.mutex.acquire()
- try:
- if checkcount:
- self.openers -= 1
- if self.openers == 0:
- self.do_close()
- else:
- if self.openers > 0:
- self.do_close()
- self.openers = 0
- finally:
- self.mutex.release()
-
- def remove(self):
- self.access_lock.acquire_write_lock()
- try:
- self.close(checkcount=False)
- self.do_remove()
- finally:
- self.access_lock.release_write_lock()
-
-class Value(object):
- __slots__ = 'key', 'createfunc', 'expiretime', 'expire_argument', 'starttime', 'storedtime',\
- 'namespace'
-
- def __init__(self, key, namespace, createfunc=None, expiretime=None, starttime=None):
- self.key = key
- self.createfunc = createfunc
- self.expire_argument = expiretime
- self.starttime = starttime
- self.storedtime = -1
- self.namespace = namespace
-
- def has_value(self):
- """return true if the container has a value stored.
-
- This is regardless of it being expired or not.
-
- """
- self.namespace.acquire_read_lock()
- try:
- return self.namespace.has_key(self.key)
- finally:
- self.namespace.release_read_lock()
-
- def can_have_value(self):
- return self.has_current_value() or self.createfunc is not None
-
- def has_current_value(self):
- self.namespace.acquire_read_lock()
- try:
- has_value = self.namespace.has_key(self.key)
- if has_value:
- try:
- stored, expired, value = self._get_value()
- return not self._is_expired(stored, expired)
- except KeyError:
- pass
- return False
- finally:
- self.namespace.release_read_lock()
-
- def _is_expired(self, storedtime, expiretime):
- """Return true if this container's value is expired."""
- return (
- (
- self.starttime is not None and
- storedtime < self.starttime
- )
- or
- (
- expiretime is not None and
- time.time() >= expiretime + storedtime
- )
- )
-
- def get_value(self):
- self.namespace.acquire_read_lock()
- try:
- has_value = self.has_value()
- if has_value:
- try:
- stored, expired, value = self._get_value()
- if not self._is_expired(stored, expired):
- return value
- except KeyError:
- # guard against un-mutexed backends raising KeyError
- has_value = False
-
- if not self.createfunc:
- raise KeyError(self.key)
- finally:
- self.namespace.release_read_lock()
-
- has_createlock = False
- creation_lock = self.namespace.get_creation_lock(self.key)
- if has_value:
- if not creation_lock.acquire(wait=False):
- debug("get_value returning old value while new one is created")
- return value
- else:
- debug("lock_creatfunc (didnt wait)")
- has_createlock = True
-
- if not has_createlock:
- debug("lock_createfunc (waiting)")
- creation_lock.acquire()
- debug("lock_createfunc (waited)")
-
- try:
- # see if someone created the value already
- self.namespace.acquire_read_lock()
- try:
- if self.has_value():
- try:
- stored, expired, value = self._get_value()
- if not self._is_expired(stored, expired):
- return value
- except KeyError:
- # guard against un-mutexed backends raising KeyError
- pass
- finally:
- self.namespace.release_read_lock()
-
- debug("get_value creating new value")
- v = self.createfunc()
- self.set_value(v)
- return v
- finally:
- creation_lock.release()
- debug("released create lock")
-
- def _get_value(self):
- value = self.namespace[self.key]
- try:
- stored, expired, value = value
- except ValueError:
- if not len(value) == 2:
- raise
- # Old format: upgrade
- stored, value = value
- expired = self.expire_argument
- debug("get_value upgrading time %r expire time %r", stored, self.expire_argument)
- self.namespace.release_read_lock()
- self.set_value(value, stored)
- self.namespace.acquire_read_lock()
- except TypeError:
- # occurs when the value is None. memcached
- # may yank the rug from under us in which case
- # that's the result
- raise KeyError(self.key)
- return stored, expired, value
-
- def set_value(self, value, storedtime=None):
- self.namespace.acquire_write_lock()
- try:
- if storedtime is None:
- storedtime = time.time()
- debug("set_value stored time %r expire time %r", storedtime, self.expire_argument)
- self.namespace.set_value(self.key, (storedtime, self.expire_argument, value))
- finally:
- self.namespace.release_write_lock()
-
- def clear_value(self):
- self.namespace.acquire_write_lock()
- try:
- debug("clear_value")
- if self.namespace.has_key(self.key):
- try:
- del self.namespace[self.key]
- except KeyError:
- # guard against un-mutexed backends raising KeyError
- pass
- self.storedtime = -1
- finally:
- self.namespace.release_write_lock()
-
-class AbstractDictionaryNSManager(NamespaceManager):
- """A subclassable NamespaceManager that places data in a dictionary.
-
- Subclasses should provide a "dictionary" attribute or descriptor
- which returns a dict-like object. The dictionary will store keys
- that are local to the "namespace" attribute of this manager, so
- ensure that the dictionary will not be used by any other namespace.
-
- e.g.::
-
- import collections
- cached_data = collections.defaultdict(dict)
-
- class MyDictionaryManager(AbstractDictionaryNSManager):
- def __init__(self, namespace):
- AbstractDictionaryNSManager.__init__(self, namespace)
- self.dictionary = cached_data[self.namespace]
-
- The above stores data in a global dictionary called "cached_data",
- which is structured as a dictionary of dictionaries, keyed
- first on namespace name to a sub-dictionary, then on actual
- cache key to value.
-
- """
-
- def get_creation_lock(self, key):
- return NameLock(
- identifier="memorynamespace/funclock/%s/%s" % (self.namespace, key),
- reentrant=True
- )
-
- def __getitem__(self, key):
- return self.dictionary[key]
-
- def __contains__(self, key):
- return self.dictionary.__contains__(key)
-
- def has_key(self, key):
- return self.dictionary.__contains__(key)
-
- def __setitem__(self, key, value):
- self.dictionary[key] = value
-
- def __delitem__(self, key):
- del self.dictionary[key]
-
- def do_remove(self):
- self.dictionary.clear()
-
- def keys(self):
- return self.dictionary.keys()
-
-class MemoryNamespaceManager(AbstractDictionaryNSManager):
- namespaces = util.SyncDict()
-
- def __init__(self, namespace, **kwargs):
- AbstractDictionaryNSManager.__init__(self, namespace)
- self.dictionary = MemoryNamespaceManager.namespaces.get(self.namespace,
- dict)
-
-class DBMNamespaceManager(OpenResourceNamespaceManager):
- def __init__(self, namespace, dbmmodule=None, data_dir=None,
- dbm_dir=None, lock_dir=None, digest_filenames=True, **kwargs):
- self.digest_filenames = digest_filenames
-
- if not dbm_dir and not data_dir:
- raise MissingCacheParameter("data_dir or dbm_dir is required")
- elif dbm_dir:
- self.dbm_dir = dbm_dir
- else:
- self.dbm_dir = data_dir + "/container_dbm"
- util.verify_directory(self.dbm_dir)
-
- if not lock_dir and not data_dir:
- raise MissingCacheParameter("data_dir or lock_dir is required")
- elif lock_dir:
- self.lock_dir = lock_dir
- else:
- self.lock_dir = data_dir + "/container_dbm_lock"
- util.verify_directory(self.lock_dir)
-
- self.dbmmodule = dbmmodule or anydbm
-
- self.dbm = None
- OpenResourceNamespaceManager.__init__(self, namespace)
-
- self.file = util.encoded_path(root= self.dbm_dir,
- identifiers=[self.namespace],
- extension='.dbm',
- digest_filenames=self.digest_filenames)
-
- debug("data file %s", self.file)
- self._checkfile()
-
- def get_access_lock(self):
- return file_synchronizer(identifier=self.namespace,
- lock_dir=self.lock_dir)
-
- def get_creation_lock(self, key):
- return file_synchronizer(
- identifier = "dbmcontainer/funclock/%s" % self.namespace,
- lock_dir=self.lock_dir
- )
-
- def file_exists(self, file):
- if os.access(file, os.F_OK):
- return True
- else:
- for ext in ('db', 'dat', 'pag', 'dir'):
- if os.access(file + os.extsep + ext, os.F_OK):
- return True
-
- return False
-
- def _checkfile(self):
- if not self.file_exists(self.file):
- g = self.dbmmodule.open(self.file, 'c')
- g.close()
-
- def get_filenames(self):
- list = []
- if os.access(self.file, os.F_OK):
- list.append(self.file)
-
- for ext in ('pag', 'dir', 'db', 'dat'):
- if os.access(self.file + os.extsep + ext, os.F_OK):
- list.append(self.file + os.extsep + ext)
- return list
-
- def do_open(self, flags):
- debug("opening dbm file %s", self.file)
- try:
- self.dbm = self.dbmmodule.open(self.file, flags)
- except:
- self._checkfile()
- self.dbm = self.dbmmodule.open(self.file, flags)
-
- def do_close(self):
- if self.dbm is not None:
- debug("closing dbm file %s", self.file)
- self.dbm.close()
-
- def do_remove(self):
- for f in self.get_filenames():
- os.remove(f)
-
- def __getitem__(self, key):
- return cPickle.loads(self.dbm[key])
-
- def __contains__(self, key):
- return self.dbm.has_key(key)
-
- def __setitem__(self, key, value):
- self.dbm[key] = cPickle.dumps(value)
-
- def __delitem__(self, key):
- del self.dbm[key]
-
- def keys(self):
- return self.dbm.keys()
-
-
-class FileNamespaceManager(OpenResourceNamespaceManager):
- def __init__(self, namespace, data_dir=None, file_dir=None, lock_dir=None,
- digest_filenames=True, **kwargs):
- self.digest_filenames = digest_filenames
-
- if not file_dir and not data_dir:
- raise MissingCacheParameter("data_dir or file_dir is required")
- elif file_dir:
- self.file_dir = file_dir
- else:
- self.file_dir = data_dir + "/container_file"
- util.verify_directory(self.file_dir)
-
- if not lock_dir and not data_dir:
- raise MissingCacheParameter("data_dir or lock_dir is required")
- elif lock_dir:
- self.lock_dir = lock_dir
- else:
- self.lock_dir = data_dir + "/container_file_lock"
- util.verify_directory(self.lock_dir)
- OpenResourceNamespaceManager.__init__(self, namespace)
-
- self.file = util.encoded_path(root=self.file_dir,
- identifiers=[self.namespace],
- extension='.cache',
- digest_filenames=self.digest_filenames)
- self.hash = {}
-
- debug("data file %s", self.file)
-
- def get_access_lock(self):
- return file_synchronizer(identifier=self.namespace,
- lock_dir=self.lock_dir)
-
- def get_creation_lock(self, key):
- return file_synchronizer(
- identifier = "filecontainer/funclock/%s" % self.namespace,
- lock_dir = self.lock_dir
- )
-
- def file_exists(self, file):
- return os.access(file, os.F_OK)
-
- def do_open(self, flags):
- if self.file_exists(self.file):
- fh = open(self.file, 'rb')
- try:
- self.hash = cPickle.load(fh)
- except (IOError, OSError, EOFError, cPickle.PickleError, ValueError):
- pass
- fh.close()
-
- self.flags = flags
-
- def do_close(self):
- if self.flags == 'c' or self.flags == 'w':
- fh = open(self.file, 'wb')
- cPickle.dump(self.hash, fh)
- fh.close()
-
- self.hash = {}
- self.flags = None
-
- def do_remove(self):
- try:
- os.remove(self.file)
- except OSError, err:
- # for instance, because we haven't yet used this cache,
- # but client code has asked for a clear() operation...
- pass
- self.hash = {}
-
- def __getitem__(self, key):
- return self.hash[key]
-
- def __contains__(self, key):
- return self.hash.has_key(key)
-
- def __setitem__(self, key, value):
- self.hash[key] = value
-
- def __delitem__(self, key):
- del self.hash[key]
-
- def keys(self):
- return self.hash.keys()
-
-
-#### legacy stuff to support the old "Container" class interface
-
-namespace_classes = {}
-
-ContainerContext = dict
-
-class ContainerMeta(type):
- def __init__(cls, classname, bases, dict_):
- namespace_classes[cls] = cls.namespace_class
- return type.__init__(cls, classname, bases, dict_)
- def __call__(self, key, context, namespace, createfunc=None,
- expiretime=None, starttime=None, **kwargs):
- if namespace in context:
- ns = context[namespace]
- else:
- nscls = namespace_classes[self]
- context[namespace] = ns = nscls(namespace, **kwargs)
- return Value(key, ns, createfunc=createfunc,
- expiretime=expiretime, starttime=starttime)
-
-class Container(object):
- __metaclass__ = ContainerMeta
- namespace_class = NamespaceManager
-
-class FileContainer(Container):
- namespace_class = FileNamespaceManager
-
-class MemoryContainer(Container):
- namespace_class = MemoryNamespaceManager
-
-class DBMContainer(Container):
- namespace_class = DBMNamespaceManager
-
-DbmContainer = DBMContainer
diff --git a/module/lib/beaker/converters.py b/module/lib/beaker/converters.py
deleted file mode 100644
index f0ad34963..000000000
--- a/module/lib/beaker/converters.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-def asbool(obj):
- if isinstance(obj, (str, unicode)):
- obj = obj.strip().lower()
- if obj in ['true', 'yes', 'on', 'y', 't', '1']:
- return True
- elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
- return False
- else:
- raise ValueError(
- "String is not true/false: %r" % obj)
- return bool(obj)
-
-def aslist(obj, sep=None, strip=True):
- if isinstance(obj, (str, unicode)):
- lst = obj.split(sep)
- if strip:
- lst = [v.strip() for v in lst]
- return lst
- elif isinstance(obj, (list, tuple)):
- return obj
- elif obj is None:
- return []
- else:
- return [obj]
diff --git a/module/lib/beaker/crypto/__init__.py b/module/lib/beaker/crypto/__init__.py
deleted file mode 100644
index 3e26b0c13..000000000
--- a/module/lib/beaker/crypto/__init__.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from warnings import warn
-
-from beaker.crypto.pbkdf2 import PBKDF2, strxor
-from beaker.crypto.util import hmac, sha1, hmac_sha1, md5
-from beaker import util
-
-keyLength = None
-
-if util.jython:
- try:
- from beaker.crypto.jcecrypto import getKeyLength, aesEncrypt
- keyLength = getKeyLength()
- except ImportError:
- pass
-else:
- try:
- from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, aesDecrypt
- keyLength = getKeyLength()
- except ImportError:
- pass
-
-if not keyLength:
- has_aes = False
-else:
- has_aes = True
-
-if has_aes and keyLength < 32:
- warn('Crypto implementation only supports key lengths up to %d bits. '
- 'Generated session cookies may be incompatible with other '
- 'environments' % (keyLength * 8))
-
-
-def generateCryptoKeys(master_key, salt, iterations):
- # NB: We XOR parts of the keystream into the randomly-generated parts, just
- # in case os.urandom() isn't as random as it should be. Note that if
- # os.urandom() returns truly random data, this will have no effect on the
- # overall security.
- keystream = PBKDF2(master_key, salt, iterations=iterations)
- cipher_key = keystream.read(keyLength)
- return cipher_key
diff --git a/module/lib/beaker/crypto/jcecrypto.py b/module/lib/beaker/crypto/jcecrypto.py
deleted file mode 100644
index 4062d513e..000000000
--- a/module/lib/beaker/crypto/jcecrypto.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""
-Encryption module that uses the Java Cryptography Extensions (JCE).
-
-Note that in default installations of the Java Runtime Environment, the
-maximum key length is limited to 128 bits due to US export
-restrictions. This makes the generated keys incompatible with the ones
-generated by pycryptopp, which has no such restrictions. To fix this,
-download the "Unlimited Strength Jurisdiction Policy Files" from Sun,
-which will allow encryption using 256 bit AES keys.
-"""
-from javax.crypto import Cipher
-from javax.crypto.spec import SecretKeySpec, IvParameterSpec
-
-import jarray
-
-# Initialization vector filled with zeros
-_iv = IvParameterSpec(jarray.zeros(16, 'b'))
-
-def aesEncrypt(data, key):
- cipher = Cipher.getInstance('AES/CTR/NoPadding')
- skeySpec = SecretKeySpec(key, 'AES')
- cipher.init(Cipher.ENCRYPT_MODE, skeySpec, _iv)
- return cipher.doFinal(data).tostring()
-
-# magic.
-aesDecrypt = aesEncrypt
-
-def getKeyLength():
- maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding')
- return min(maxlen, 256) / 8
diff --git a/module/lib/beaker/crypto/pbkdf2.py b/module/lib/beaker/crypto/pbkdf2.py
deleted file mode 100644
index 96dc5fbb2..000000000
--- a/module/lib/beaker/crypto/pbkdf2.py
+++ /dev/null
@@ -1,342 +0,0 @@
-#!/usr/bin/python
-# -*- coding: ascii -*-
-###########################################################################
-# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
-#
-# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
-# All rights reserved.
-#
-# Permission to use, copy, modify, and distribute this software and its
-# documentation for any purpose and without fee is hereby granted,
-# provided that the above copyright notice appear in all copies and that
-# both that copyright notice and this permission notice appear in
-# supporting documentation.
-#
-# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-# Country of origin: Canada
-#
-###########################################################################
-# Sample PBKDF2 usage:
-# from Crypto.Cipher import AES
-# from PBKDF2 import PBKDF2
-# import os
-#
-# salt = os.urandom(8) # 64-bit salt
-# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
-# iv = os.urandom(16) # 128-bit IV
-# cipher = AES.new(key, AES.MODE_CBC, iv)
-# ...
-#
-# Sample crypt() usage:
-# from PBKDF2 import crypt
-# pwhash = crypt("secret")
-# alleged_pw = raw_input("Enter password: ")
-# if pwhash == crypt(alleged_pw, pwhash):
-# print "Password good"
-# else:
-# print "Invalid password"
-#
-###########################################################################
-# History:
-#
-# 2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
-# - Initial Release (v1.0)
-#
-# 2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
-# - Bugfix release (v1.1)
-# - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
-# function in the previous release) silently truncates all keys to 64
-# bytes. The way it was used in the previous release, this would only be
-# problem if the pseudorandom function that returned values larger than
-# 64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
-# anything that silently reduces the security margin from what is
-# expected.
-#
-###########################################################################
-
-__version__ = "1.1"
-
-from struct import pack
-from binascii import b2a_hex
-from random import randint
-
-from base64 import b64encode
-
-from beaker.crypto.util import hmac as HMAC, hmac_sha1 as SHA1
-
-def strxor(a, b):
- return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
-
-class PBKDF2(object):
- """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
-
- This implementation takes a passphrase and a salt (and optionally an
- iteration count, a digest module, and a MAC module) and provides a
- file-like object from which an arbitrarily-sized key can be read.
-
- If the passphrase and/or salt are unicode objects, they are encoded as
- UTF-8 before they are processed.
-
- The idea behind PBKDF2 is to derive a cryptographic key from a
- passphrase and a salt.
-
- PBKDF2 may also be used as a strong salted password hash. The
- 'crypt' function is provided for that purpose.
-
- Remember: Keys generated using PBKDF2 are only as strong as the
- passphrases they are derived from.
- """
-
- def __init__(self, passphrase, salt, iterations=1000,
- digestmodule=SHA1, macmodule=HMAC):
- if not callable(macmodule):
- macmodule = macmodule.new
- self.__macmodule = macmodule
- self.__digestmodule = digestmodule
- self._setup(passphrase, salt, iterations, self._pseudorandom)
-
- def _pseudorandom(self, key, msg):
- """Pseudorandom function. e.g. HMAC-SHA1"""
- return self.__macmodule(key=key, msg=msg,
- digestmod=self.__digestmodule).digest()
-
- def read(self, bytes):
- """Read the specified number of key bytes."""
- if self.closed:
- raise ValueError("file-like object is closed")
-
- size = len(self.__buf)
- blocks = [self.__buf]
- i = self.__blockNum
- while size < bytes:
- i += 1
- if i > 0xffffffff:
- # We could return "" here, but
- raise OverflowError("derived key too long")
- block = self.__f(i)
- blocks.append(block)
- size += len(block)
- buf = "".join(blocks)
- retval = buf[:bytes]
- self.__buf = buf[bytes:]
- self.__blockNum = i
- return retval
-
- def __f(self, i):
- # i must fit within 32 bits
- assert (1 <= i <= 0xffffffff)
- U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
- result = U
- for j in xrange(2, 1+self.__iterations):
- U = self.__prf(self.__passphrase, U)
- result = strxor(result, U)
- return result
-
- def hexread(self, octets):
- """Read the specified number of octets. Return them as hexadecimal.
-
- Note that len(obj.hexread(n)) == 2*n.
- """
- return b2a_hex(self.read(octets))
-
- def _setup(self, passphrase, salt, iterations, prf):
- # Sanity checks:
-
- # passphrase and salt must be str or unicode (in the latter
- # case, we convert to UTF-8)
- if isinstance(passphrase, unicode):
- passphrase = passphrase.encode("UTF-8")
- if not isinstance(passphrase, str):
- raise TypeError("passphrase must be str or unicode")
- if isinstance(salt, unicode):
- salt = salt.encode("UTF-8")
- if not isinstance(salt, str):
- raise TypeError("salt must be str or unicode")
-
- # iterations must be an integer >= 1
- if not isinstance(iterations, (int, long)):
- raise TypeError("iterations must be an integer")
- if iterations < 1:
- raise ValueError("iterations must be at least 1")
-
- # prf must be callable
- if not callable(prf):
- raise TypeError("prf must be callable")
-
- self.__passphrase = passphrase
- self.__salt = salt
- self.__iterations = iterations
- self.__prf = prf
- self.__blockNum = 0
- self.__buf = ""
- self.closed = False
-
- def close(self):
- """Close the stream."""
- if not self.closed:
- del self.__passphrase
- del self.__salt
- del self.__iterations
- del self.__prf
- del self.__blockNum
- del self.__buf
- self.closed = True
-
-def crypt(word, salt=None, iterations=None):
- """PBKDF2-based unix crypt(3) replacement.
-
- The number of iterations specified in the salt overrides the 'iterations'
- parameter.
-
- The effective hash length is 192 bits.
- """
-
- # Generate a (pseudo-)random salt if the user hasn't provided one.
- if salt is None:
- salt = _makesalt()
-
- # salt must be a string or the us-ascii subset of unicode
- if isinstance(salt, unicode):
- salt = salt.encode("us-ascii")
- if not isinstance(salt, str):
- raise TypeError("salt must be a string")
-
- # word must be a string or unicode (in the latter case, we convert to UTF-8)
- if isinstance(word, unicode):
- word = word.encode("UTF-8")
- if not isinstance(word, str):
- raise TypeError("word must be a string or unicode")
-
- # Try to extract the real salt and iteration count from the salt
- if salt.startswith("$p5k2$"):
- (iterations, salt, dummy) = salt.split("$")[2:5]
- if iterations == "":
- iterations = 400
- else:
- converted = int(iterations, 16)
- if iterations != "%x" % converted: # lowercase hex, minimum digits
- raise ValueError("Invalid salt")
- iterations = converted
- if not (iterations >= 1):
- raise ValueError("Invalid salt")
-
- # Make sure the salt matches the allowed character set
- allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
- for ch in salt:
- if ch not in allowed:
- raise ValueError("Illegal character %r in salt" % (ch,))
-
- if iterations is None or iterations == 400:
- iterations = 400
- salt = "$p5k2$$" + salt
- else:
- salt = "$p5k2$%x$%s" % (iterations, salt)
- rawhash = PBKDF2(word, salt, iterations).read(24)
- return salt + "$" + b64encode(rawhash, "./")
-
-# Add crypt as a static method of the PBKDF2 class
-# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
-# crypt.
-PBKDF2.crypt = staticmethod(crypt)
-
-def _makesalt():
- """Return a 48-bit pseudorandom salt for crypt().
-
- This function is not suitable for generating cryptographic secrets.
- """
- binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
- return b64encode(binarysalt, "./")
-
-def test_pbkdf2():
- """Module self-test"""
- from binascii import a2b_hex
-
- #
- # Test vectors from RFC 3962
- #
-
- # Test 1
- result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
- expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # Test 2
- result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
- expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
- "a7e52ddbc5e5142f708a31e2e62b1e13")
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # Test 3
- result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
- expected = ("139c30c0966bc32ba55fdbf212530ac9"
- "c5ec59f1a452f5cc9ad940fea0598ed1")
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # Test 4
- result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
- expected = ("9ccad6d468770cd51b10e6a68721be61"
- "1a8b4d282601db3b36be9246915ec82a")
- if result != expected:
- raise RuntimeError("self-test failed")
-
- #
- # Other test vectors
- #
-
- # Chunked read
- f = PBKDF2("kickstart", "workbench", 256)
- result = f.read(17)
- result += f.read(17)
- result += f.read(1)
- result += f.read(2)
- result += f.read(3)
- expected = PBKDF2("kickstart", "workbench", 256).read(40)
- if result != expected:
- raise RuntimeError("self-test failed")
-
- #
- # crypt() test vectors
- #
-
- # crypt 1
- result = crypt("cloadm", "exec")
- expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # crypt 2
- result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
- expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # crypt 3
- result = crypt("dcl", "tUsch7fU", iterations=13)
- expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
- if result != expected:
- raise RuntimeError("self-test failed")
-
- # crypt 4 (unicode)
- result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
- '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
- expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
- if result != expected:
- raise RuntimeError("self-test failed")
-
-if __name__ == '__main__':
- test_pbkdf2()
-
-# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/module/lib/beaker/crypto/pycrypto.py b/module/lib/beaker/crypto/pycrypto.py
deleted file mode 100644
index a3eb4d9db..000000000
--- a/module/lib/beaker/crypto/pycrypto.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""Encryption module that uses pycryptopp or pycrypto"""
-try:
- # Pycryptopp is preferred over Crypto because Crypto has had
- # various periods of not being maintained, and pycryptopp uses
- # the Crypto++ library which is generally considered the 'gold standard'
- # of crypto implementations
- from pycryptopp.cipher import aes
-
- def aesEncrypt(data, key):
- cipher = aes.AES(key)
- return cipher.process(data)
-
- # magic.
- aesDecrypt = aesEncrypt
-
-except ImportError:
- from Crypto.Cipher import AES
-
- def aesEncrypt(data, key):
- cipher = AES.new(key)
-
- data = data + (" " * (16 - (len(data) % 16)))
- return cipher.encrypt(data)
-
- def aesDecrypt(data, key):
- cipher = AES.new(key)
-
- return cipher.decrypt(data).rstrip()
-
-def getKeyLength():
- return 32
diff --git a/module/lib/beaker/crypto/util.py b/module/lib/beaker/crypto/util.py
deleted file mode 100644
index d97e8ce6f..000000000
--- a/module/lib/beaker/crypto/util.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from warnings import warn
-from beaker import util
-
-
-try:
- # Use PyCrypto (if available)
- from Crypto.Hash import HMAC as hmac, SHA as hmac_sha1
- sha1 = hmac_sha1.new
-
-except ImportError:
-
- # PyCrypto not available. Use the Python standard library.
- import hmac
-
- # When using the stdlib, we have to make sure the hmac version and sha
- # version are compatible
- if util.py24:
- from sha import sha as sha1
- import sha as hmac_sha1
- else:
- # NOTE: We have to use the callable with hashlib (hashlib.sha1),
- # otherwise hmac only accepts the sha module object itself
- from hashlib import sha1
- hmac_sha1 = sha1
-
-
-if util.py24:
- from md5 import md5
-else:
- from hashlib import md5
diff --git a/module/lib/beaker/exceptions.py b/module/lib/beaker/exceptions.py
deleted file mode 100644
index cc0eed286..000000000
--- a/module/lib/beaker/exceptions.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Beaker exception classes"""
-
-class BeakerException(Exception):
- pass
-
-
-class CreationAbortedError(Exception):
- """Deprecated."""
-
-
-class InvalidCacheBackendError(BeakerException, ImportError):
- pass
-
-
-class MissingCacheParameter(BeakerException):
- pass
-
-
-class LockError(BeakerException):
- pass
-
-
-class InvalidCryptoBackendError(BeakerException):
- pass
diff --git a/module/lib/beaker/ext/database.py b/module/lib/beaker/ext/database.py
deleted file mode 100644
index 701e6f7d2..000000000
--- a/module/lib/beaker/ext/database.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import cPickle
-import logging
-import pickle
-from datetime import datetime
-
-from beaker.container import OpenResourceNamespaceManager, Container
-from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
-from beaker.synchronization import file_synchronizer, null_synchronizer
-from beaker.util import verify_directory, SyncDict
-
-log = logging.getLogger(__name__)
-
-sa = None
-pool = None
-types = None
-
-class DatabaseNamespaceManager(OpenResourceNamespaceManager):
- metadatas = SyncDict()
- tables = SyncDict()
-
- @classmethod
- def _init_dependencies(cls):
- global sa, pool, types
- if sa is not None:
- return
- try:
- import sqlalchemy as sa
- import sqlalchemy.pool as pool
- from sqlalchemy import types
- except ImportError:
- raise InvalidCacheBackendError("Database cache backend requires "
- "the 'sqlalchemy' library")
-
- def __init__(self, namespace, url=None, sa_opts=None, optimistic=False,
- table_name='beaker_cache', data_dir=None, lock_dir=None,
- **params):
- """Creates a database namespace manager
-
- ``url``
- SQLAlchemy compliant db url
- ``sa_opts``
- A dictionary of SQLAlchemy keyword options to initialize the engine
- with.
- ``optimistic``
- Use optimistic session locking, note that this will result in an
- additional select when updating a cache value to compare version
- numbers.
- ``table_name``
- The table name to use in the database for the cache.
- """
- OpenResourceNamespaceManager.__init__(self, namespace)
-
- if sa_opts is None:
- sa_opts = params
-
- if lock_dir:
- self.lock_dir = lock_dir
- elif data_dir:
- self.lock_dir = data_dir + "/container_db_lock"
- if self.lock_dir:
- verify_directory(self.lock_dir)
-
- # Check to see if the table's been created before
- url = url or sa_opts['sa.url']
- table_key = url + table_name
- def make_cache():
- # Check to see if we have a connection pool open already
- meta_key = url + table_name
- def make_meta():
- # SQLAlchemy pops the url, this ensures it sticks around
- # later
- sa_opts['sa.url'] = url
- engine = sa.engine_from_config(sa_opts, 'sa.')
- meta = sa.MetaData()
- meta.bind = engine
- return meta
- meta = DatabaseNamespaceManager.metadatas.get(meta_key, make_meta)
- # Create the table object and cache it now
- cache = sa.Table(table_name, meta,
- sa.Column('id', types.Integer, primary_key=True),
- sa.Column('namespace', types.String(255), nullable=False),
- sa.Column('accessed', types.DateTime, nullable=False),
- sa.Column('created', types.DateTime, nullable=False),
- sa.Column('data', types.PickleType, nullable=False),
- sa.UniqueConstraint('namespace')
- )
- cache.create(checkfirst=True)
- return cache
- self.hash = {}
- self._is_new = False
- self.loaded = False
- self.cache = DatabaseNamespaceManager.tables.get(table_key, make_cache)
-
- def get_access_lock(self):
- return null_synchronizer()
-
- def get_creation_lock(self, key):
- return file_synchronizer(
- identifier ="databasecontainer/funclock/%s" % self.namespace,
- lock_dir = self.lock_dir)
-
- def do_open(self, flags):
- # If we already loaded the data, don't bother loading it again
- if self.loaded:
- self.flags = flags
- return
-
- cache = self.cache
- result = sa.select([cache.c.data],
- cache.c.namespace==self.namespace
- ).execute().fetchone()
- if not result:
- self._is_new = True
- self.hash = {}
- else:
- self._is_new = False
- try:
- self.hash = result['data']
- except (IOError, OSError, EOFError, cPickle.PickleError,
- pickle.PickleError):
- log.debug("Couln't load pickle data, creating new storage")
- self.hash = {}
- self._is_new = True
- self.flags = flags
- self.loaded = True
-
- def do_close(self):
- if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
- cache = self.cache
- if self._is_new:
- cache.insert().execute(namespace=self.namespace, data=self.hash,
- accessed=datetime.now(),
- created=datetime.now())
- self._is_new = False
- else:
- cache.update(cache.c.namespace==self.namespace).execute(
- data=self.hash, accessed=datetime.now())
- self.flags = None
-
- def do_remove(self):
- cache = self.cache
- cache.delete(cache.c.namespace==self.namespace).execute()
- self.hash = {}
-
- # We can retain the fact that we did a load attempt, but since the
- # file is gone this will be a new namespace should it be saved.
- self._is_new = True
-
- def __getitem__(self, key):
- return self.hash[key]
-
- def __contains__(self, key):
- return self.hash.has_key(key)
-
- def __setitem__(self, key, value):
- self.hash[key] = value
-
- def __delitem__(self, key):
- del self.hash[key]
-
- def keys(self):
- return self.hash.keys()
-
-class DatabaseContainer(Container):
- namespace_manager = DatabaseNamespaceManager
diff --git a/module/lib/beaker/ext/google.py b/module/lib/beaker/ext/google.py
deleted file mode 100644
index dd8380d7f..000000000
--- a/module/lib/beaker/ext/google.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import cPickle
-import logging
-from datetime import datetime
-
-from beaker.container import OpenResourceNamespaceManager, Container
-from beaker.exceptions import InvalidCacheBackendError
-from beaker.synchronization import null_synchronizer
-
-log = logging.getLogger(__name__)
-
-db = None
-
-class GoogleNamespaceManager(OpenResourceNamespaceManager):
- tables = {}
-
- @classmethod
- def _init_dependencies(cls):
- global db
- if db is not None:
- return
- try:
- db = __import__('google.appengine.ext.db').appengine.ext.db
- except ImportError:
- raise InvalidCacheBackendError("Datastore cache backend requires the "
- "'google.appengine.ext' library")
-
- def __init__(self, namespace, table_name='beaker_cache', **params):
- """Creates a datastore namespace manager"""
- OpenResourceNamespaceManager.__init__(self, namespace)
-
- def make_cache():
- table_dict = dict(created=db.DateTimeProperty(),
- accessed=db.DateTimeProperty(),
- data=db.BlobProperty())
- table = type(table_name, (db.Model,), table_dict)
- return table
- self.table_name = table_name
- self.cache = GoogleNamespaceManager.tables.setdefault(table_name, make_cache())
- self.hash = {}
- self._is_new = False
- self.loaded = False
- self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
-
- # Google wants namespaces to start with letters, change the namespace
- # to start with a letter
- self.namespace = 'p%s' % self.namespace
-
- def get_access_lock(self):
- return null_synchronizer()
-
- def get_creation_lock(self, key):
- # this is weird, should probably be present
- return null_synchronizer()
-
- def do_open(self, flags):
- # If we already loaded the data, don't bother loading it again
- if self.loaded:
- self.flags = flags
- return
-
- item = self.cache.get_by_key_name(self.namespace)
-
- if not item:
- self._is_new = True
- self.hash = {}
- else:
- self._is_new = False
- try:
- self.hash = cPickle.loads(str(item.data))
- except (IOError, OSError, EOFError, cPickle.PickleError):
- if self.log_debug:
- log.debug("Couln't load pickle data, creating new storage")
- self.hash = {}
- self._is_new = True
- self.flags = flags
- self.loaded = True
-
- def do_close(self):
- if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
- if self._is_new:
- item = self.cache(key_name=self.namespace)
- item.data = cPickle.dumps(self.hash)
- item.created = datetime.now()
- item.accessed = datetime.now()
- item.put()
- self._is_new = False
- else:
- item = self.cache.get_by_key_name(self.namespace)
- item.data = cPickle.dumps(self.hash)
- item.accessed = datetime.now()
- item.put()
- self.flags = None
-
- def do_remove(self):
- item = self.cache.get_by_key_name(self.namespace)
- item.delete()
- self.hash = {}
-
- # We can retain the fact that we did a load attempt, but since the
- # file is gone this will be a new namespace should it be saved.
- self._is_new = True
-
- def __getitem__(self, key):
- return self.hash[key]
-
- def __contains__(self, key):
- return self.hash.has_key(key)
-
- def __setitem__(self, key, value):
- self.hash[key] = value
-
- def __delitem__(self, key):
- del self.hash[key]
-
- def keys(self):
- return self.hash.keys()
-
-
-class GoogleContainer(Container):
- namespace_class = GoogleNamespaceManager
diff --git a/module/lib/beaker/ext/memcached.py b/module/lib/beaker/ext/memcached.py
deleted file mode 100644
index 96516953f..000000000
--- a/module/lib/beaker/ext/memcached.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from beaker.container import NamespaceManager, Container
-from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
-from beaker.synchronization import file_synchronizer, null_synchronizer
-from beaker.util import verify_directory, SyncDict
-import warnings
-
-memcache = None
-
-class MemcachedNamespaceManager(NamespaceManager):
- clients = SyncDict()
-
- @classmethod
- def _init_dependencies(cls):
- global memcache
- if memcache is not None:
- return
- try:
- import pylibmc as memcache
- except ImportError:
- try:
- import cmemcache as memcache
- warnings.warn("cmemcache is known to have serious "
- "concurrency issues; consider using 'memcache' or 'pylibmc'")
- except ImportError:
- try:
- import memcache
- except ImportError:
- raise InvalidCacheBackendError("Memcached cache backend requires either "
- "the 'memcache' or 'cmemcache' library")
-
- def __init__(self, namespace, url=None, data_dir=None, lock_dir=None, **params):
- NamespaceManager.__init__(self, namespace)
-
- if not url:
- raise MissingCacheParameter("url is required")
-
- if lock_dir:
- self.lock_dir = lock_dir
- elif data_dir:
- self.lock_dir = data_dir + "/container_mcd_lock"
- if self.lock_dir:
- verify_directory(self.lock_dir)
-
- self.mc = MemcachedNamespaceManager.clients.get(url, memcache.Client, url.split(';'))
-
- def get_creation_lock(self, key):
- return file_synchronizer(
- identifier="memcachedcontainer/funclock/%s" % self.namespace,lock_dir = self.lock_dir)
-
- def _format_key(self, key):
- return self.namespace + '_' + key.replace(' ', '\302\267')
-
- def __getitem__(self, key):
- return self.mc.get(self._format_key(key))
-
- def __contains__(self, key):
- value = self.mc.get(self._format_key(key))
- return value is not None
-
- def has_key(self, key):
- return key in self
-
- def set_value(self, key, value, expiretime=None):
- if expiretime:
- self.mc.set(self._format_key(key), value, time=expiretime)
- else:
- self.mc.set(self._format_key(key), value)
-
- def __setitem__(self, key, value):
- self.set_value(key, value)
-
- def __delitem__(self, key):
- self.mc.delete(self._format_key(key))
-
- def do_remove(self):
- self.mc.flush_all()
-
- def keys(self):
- raise NotImplementedError("Memcache caching does not support iteration of all cache keys")
-
-class MemcachedContainer(Container):
- namespace_class = MemcachedNamespaceManager
diff --git a/module/lib/beaker/ext/sqla.py b/module/lib/beaker/ext/sqla.py
deleted file mode 100644
index 8c79633c1..000000000
--- a/module/lib/beaker/ext/sqla.py
+++ /dev/null
@@ -1,133 +0,0 @@
-import cPickle
-import logging
-import pickle
-from datetime import datetime
-
-from beaker.container import OpenResourceNamespaceManager, Container
-from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
-from beaker.synchronization import file_synchronizer, null_synchronizer
-from beaker.util import verify_directory, SyncDict
-
-
-log = logging.getLogger(__name__)
-
-sa = None
-
-class SqlaNamespaceManager(OpenResourceNamespaceManager):
- binds = SyncDict()
- tables = SyncDict()
-
- @classmethod
- def _init_dependencies(cls):
- global sa
- if sa is not None:
- return
- try:
- import sqlalchemy as sa
- except ImportError:
- raise InvalidCacheBackendError("SQLAlchemy, which is required by "
- "this backend, is not installed")
-
- def __init__(self, namespace, bind, table, data_dir=None, lock_dir=None,
- **kwargs):
- """Create a namespace manager for use with a database table via
- SQLAlchemy.
-
- ``bind``
- SQLAlchemy ``Engine`` or ``Connection`` object
-
- ``table``
- SQLAlchemy ``Table`` object in which to store namespace data.
- This should usually be something created by ``make_cache_table``.
- """
- OpenResourceNamespaceManager.__init__(self, namespace)
-
- if lock_dir:
- self.lock_dir = lock_dir
- elif data_dir:
- self.lock_dir = data_dir + "/container_db_lock"
- if self.lock_dir:
- verify_directory(self.lock_dir)
-
- self.bind = self.__class__.binds.get(str(bind.url), lambda: bind)
- self.table = self.__class__.tables.get('%s:%s' % (bind.url, table.name),
- lambda: table)
- self.hash = {}
- self._is_new = False
- self.loaded = False
-
- def get_access_lock(self):
- return null_synchronizer()
-
- def get_creation_lock(self, key):
- return file_synchronizer(
- identifier ="databasecontainer/funclock/%s" % self.namespace,
- lock_dir=self.lock_dir)
-
- def do_open(self, flags):
- if self.loaded:
- self.flags = flags
- return
- select = sa.select([self.table.c.data],
- (self.table.c.namespace == self.namespace))
- result = self.bind.execute(select).fetchone()
- if not result:
- self._is_new = True
- self.hash = {}
- else:
- self._is_new = False
- try:
- self.hash = result['data']
- except (IOError, OSError, EOFError, cPickle.PickleError,
- pickle.PickleError):
- log.debug("Couln't load pickle data, creating new storage")
- self.hash = {}
- self._is_new = True
- self.flags = flags
- self.loaded = True
-
- def do_close(self):
- if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
- if self._is_new:
- insert = self.table.insert()
- self.bind.execute(insert, namespace=self.namespace, data=self.hash,
- accessed=datetime.now(), created=datetime.now())
- self._is_new = False
- else:
- update = self.table.update(self.table.c.namespace == self.namespace)
- self.bind.execute(update, data=self.hash, accessed=datetime.now())
- self.flags = None
-
- def do_remove(self):
- delete = self.table.delete(self.table.c.namespace == self.namespace)
- self.bind.execute(delete)
- self.hash = {}
- self._is_new = True
-
- def __getitem__(self, key):
- return self.hash[key]
-
- def __contains__(self, key):
- return self.hash.has_key(key)
-
- def __setitem__(self, key, value):
- self.hash[key] = value
-
- def __delitem__(self, key):
- del self.hash[key]
-
- def keys(self):
- return self.hash.keys()
-
-
-class SqlaContainer(Container):
- namespace_manager = SqlaNamespaceManager
-
-def make_cache_table(metadata, table_name='beaker_cache'):
- """Return a ``Table`` object suitable for storing cached values for the
- namespace manager. Do not create the table."""
- return sa.Table(table_name, metadata,
- sa.Column('namespace', sa.String(255), primary_key=True),
- sa.Column('accessed', sa.DateTime, nullable=False),
- sa.Column('created', sa.DateTime, nullable=False),
- sa.Column('data', sa.PickleType, nullable=False))
diff --git a/module/lib/beaker/middleware.py b/module/lib/beaker/middleware.py
deleted file mode 100644
index 7ba88b37d..000000000
--- a/module/lib/beaker/middleware.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import warnings
-
-try:
- from paste.registry import StackedObjectProxy
- beaker_session = StackedObjectProxy(name="Beaker Session")
- beaker_cache = StackedObjectProxy(name="Cache Manager")
-except:
- beaker_cache = None
- beaker_session = None
-
-from beaker.cache import CacheManager
-from beaker.session import Session, SessionObject
-from beaker.util import coerce_cache_params, coerce_session_params, \
- parse_cache_config_options
-
-
-class CacheMiddleware(object):
- cache = beaker_cache
-
- def __init__(self, app, config=None, environ_key='beaker.cache', **kwargs):
- """Initialize the Cache Middleware
-
- The Cache middleware will make a Cache instance available
- every request under the ``environ['beaker.cache']`` key by
- default. The location in environ can be changed by setting
- ``environ_key``.
-
- ``config``
- dict All settings should be prefixed by 'cache.'. This
- method of passing variables is intended for Paste and other
- setups that accumulate multiple component settings in a
- single dictionary. If config contains *no cache. prefixed
- args*, then *all* of the config options will be used to
- intialize the Cache objects.
-
- ``environ_key``
- Location where the Cache instance will keyed in the WSGI
- environ
-
- ``**kwargs``
- All keyword arguments are assumed to be cache settings and
- will override any settings found in ``config``
-
- """
- self.app = app
- config = config or {}
-
- self.options = {}
-
- # Update the options with the parsed config
- self.options.update(parse_cache_config_options(config))
-
- # Add any options from kwargs, but leave out the defaults this
- # time
- self.options.update(
- parse_cache_config_options(kwargs, include_defaults=False))
-
- # Assume all keys are intended for cache if none are prefixed with
- # 'cache.'
- if not self.options and config:
- self.options = config
-
- self.options.update(kwargs)
- self.cache_manager = CacheManager(**self.options)
- self.environ_key = environ_key
-
- def __call__(self, environ, start_response):
- if environ.get('paste.registry'):
- if environ['paste.registry'].reglist:
- environ['paste.registry'].register(self.cache,
- self.cache_manager)
- environ[self.environ_key] = self.cache_manager
- return self.app(environ, start_response)
-
-
-class SessionMiddleware(object):
- session = beaker_session
-
- def __init__(self, wrap_app, config=None, environ_key='beaker.session',
- **kwargs):
- """Initialize the Session Middleware
-
- The Session middleware will make a lazy session instance
- available every request under the ``environ['beaker.session']``
- key by default. The location in environ can be changed by
- setting ``environ_key``.
-
- ``config``
- dict All settings should be prefixed by 'session.'. This
- method of passing variables is intended for Paste and other
- setups that accumulate multiple component settings in a
- single dictionary. If config contains *no cache. prefixed
- args*, then *all* of the config options will be used to
- intialize the Cache objects.
-
- ``environ_key``
- Location where the Session instance will keyed in the WSGI
- environ
-
- ``**kwargs``
- All keyword arguments are assumed to be session settings and
- will override any settings found in ``config``
-
- """
- config = config or {}
-
- # Load up the default params
- self.options = dict(invalidate_corrupt=True, type=None,
- data_dir=None, key='beaker.session.id',
- timeout=None, secret=None, log_file=None)
-
- # Pull out any config args meant for beaker session. if there are any
- for dct in [config, kwargs]:
- for key, val in dct.iteritems():
- if key.startswith('beaker.session.'):
- self.options[key[15:]] = val
- if key.startswith('session.'):
- self.options[key[8:]] = val
- if key.startswith('session_'):
- warnings.warn('Session options should start with session. '
- 'instead of session_.', DeprecationWarning, 2)
- self.options[key[8:]] = val
-
- # Coerce and validate session params
- coerce_session_params(self.options)
-
- # Assume all keys are intended for cache if none are prefixed with
- # 'cache.'
- if not self.options and config:
- self.options = config
-
- self.options.update(kwargs)
- self.wrap_app = wrap_app
- self.environ_key = environ_key
-
- def __call__(self, environ, start_response):
- session = SessionObject(environ, **self.options)
- if environ.get('paste.registry'):
- if environ['paste.registry'].reglist:
- environ['paste.registry'].register(self.session, session)
- environ[self.environ_key] = session
- environ['beaker.get_session'] = self._get_session
-
- def session_start_response(status, headers, exc_info = None):
- if session.accessed():
- session.persist()
- if session.__dict__['_headers']['set_cookie']:
- cookie = session.__dict__['_headers']['cookie_out']
- if cookie:
- headers.append(('Set-cookie', cookie))
- return start_response(status, headers, exc_info)
- return self.wrap_app(environ, session_start_response)
-
- def _get_session(self):
- return Session({}, use_cookies=False, **self.options)
-
-
-def session_filter_factory(global_conf, **kwargs):
- def filter(app):
- return SessionMiddleware(app, global_conf, **kwargs)
- return filter
-
-
-def session_filter_app_factory(app, global_conf, **kwargs):
- return SessionMiddleware(app, global_conf, **kwargs)
diff --git a/module/lib/beaker/session.py b/module/lib/beaker/session.py
deleted file mode 100644
index 7d465530b..000000000
--- a/module/lib/beaker/session.py
+++ /dev/null
@@ -1,618 +0,0 @@
-import Cookie
-import os
-import random
-import time
-from datetime import datetime, timedelta
-
-from beaker.crypto import hmac as HMAC, hmac_sha1 as SHA1, md5
-from beaker.util import pickle
-
-from beaker import crypto
-from beaker.cache import clsmap
-from beaker.exceptions import BeakerException, InvalidCryptoBackendError
-from base64 import b64encode, b64decode
-
-
-__all__ = ['SignedCookie', 'Session']
-
-getpid = hasattr(os, 'getpid') and os.getpid or (lambda : '')
-
-class SignedCookie(Cookie.BaseCookie):
- """Extends python cookie to give digital signature support"""
- def __init__(self, secret, input=None):
- self.secret = secret
- Cookie.BaseCookie.__init__(self, input)
-
- def value_decode(self, val):
- val = val.strip('"')
- sig = HMAC.new(self.secret, val[40:], SHA1).hexdigest()
-
- # Avoid timing attacks
- invalid_bits = 0
- input_sig = val[:40]
- if len(sig) != len(input_sig):
- return None, val
-
- for a, b in zip(sig, input_sig):
- invalid_bits += a != b
-
- if invalid_bits:
- return None, val
- else:
- return val[40:], val
-
- def value_encode(self, val):
- sig = HMAC.new(self.secret, val, SHA1).hexdigest()
- return str(val), ("%s%s" % (sig, val))
-
-
-class Session(dict):
- """Session object that uses container package for storage.
-
- ``key``
- The name the cookie should be set to.
- ``timeout``
- How long session data is considered valid. This is used
- regardless of the cookie being present or not to determine
- whether session data is still valid.
- ``cookie_domain``
- Domain to use for the cookie.
- ``secure``
- Whether or not the cookie should only be sent over SSL.
- """
- def __init__(self, request, id=None, invalidate_corrupt=False,
- use_cookies=True, type=None, data_dir=None,
- key='beaker.session.id', timeout=None, cookie_expires=True,
- cookie_domain=None, secret=None, secure=False,
- namespace_class=None, **namespace_args):
- if not type:
- if data_dir:
- self.type = 'file'
- else:
- self.type = 'memory'
- else:
- self.type = type
-
- self.namespace_class = namespace_class or clsmap[self.type]
-
- self.namespace_args = namespace_args
-
- self.request = request
- self.data_dir = data_dir
- self.key = key
-
- self.timeout = timeout
- self.use_cookies = use_cookies
- self.cookie_expires = cookie_expires
-
- # Default cookie domain/path
- self._domain = cookie_domain
- self._path = '/'
- self.was_invalidated = False
- self.secret = secret
- self.secure = secure
- self.id = id
- self.accessed_dict = {}
-
- if self.use_cookies:
- cookieheader = request.get('cookie', '')
- if secret:
- try:
- self.cookie = SignedCookie(secret, input=cookieheader)
- except Cookie.CookieError:
- self.cookie = SignedCookie(secret, input=None)
- else:
- self.cookie = Cookie.SimpleCookie(input=cookieheader)
-
- if not self.id and self.key in self.cookie:
- self.id = self.cookie[self.key].value
-
- self.is_new = self.id is None
- if self.is_new:
- self._create_id()
- self['_accessed_time'] = self['_creation_time'] = time.time()
- else:
- try:
- self.load()
- except:
- if invalidate_corrupt:
- self.invalidate()
- else:
- raise
-
- def _create_id(self):
- self.id = md5(
- md5("%f%s%f%s" % (time.time(), id({}), random.random(),
- getpid())).hexdigest(),
- ).hexdigest()
- self.is_new = True
- self.last_accessed = None
- if self.use_cookies:
- self.cookie[self.key] = self.id
- if self._domain:
- self.cookie[self.key]['domain'] = self._domain
- if self.secure:
- self.cookie[self.key]['secure'] = True
- self.cookie[self.key]['path'] = self._path
- if self.cookie_expires is not True:
- if self.cookie_expires is False:
- expires = datetime.fromtimestamp( 0x7FFFFFFF )
- elif isinstance(self.cookie_expires, timedelta):
- expires = datetime.today() + self.cookie_expires
- elif isinstance(self.cookie_expires, datetime):
- expires = self.cookie_expires
- else:
- raise ValueError("Invalid argument for cookie_expires: %s"
- % repr(self.cookie_expires))
- self.cookie[self.key]['expires'] = \
- expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
- self.request['cookie_out'] = self.cookie[self.key].output(header='')
- self.request['set_cookie'] = False
-
- def created(self):
- return self['_creation_time']
- created = property(created)
-
- def _set_domain(self, domain):
- self['_domain'] = domain
- self.cookie[self.key]['domain'] = domain
- self.request['cookie_out'] = self.cookie[self.key].output(header='')
- self.request['set_cookie'] = True
-
- def _get_domain(self):
- return self._domain
-
- domain = property(_get_domain, _set_domain)
-
- def _set_path(self, path):
- self['_path'] = path
- self.cookie[self.key]['path'] = path
- self.request['cookie_out'] = self.cookie[self.key].output(header='')
- self.request['set_cookie'] = True
-
- def _get_path(self):
- return self._path
-
- path = property(_get_path, _set_path)
-
- def _delete_cookie(self):
- self.request['set_cookie'] = True
- self.cookie[self.key] = self.id
- if self._domain:
- self.cookie[self.key]['domain'] = self._domain
- if self.secure:
- self.cookie[self.key]['secure'] = True
- self.cookie[self.key]['path'] = '/'
- expires = datetime.today().replace(year=2003)
- self.cookie[self.key]['expires'] = \
- expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
- self.request['cookie_out'] = self.cookie[self.key].output(header='')
- self.request['set_cookie'] = True
-
- def delete(self):
- """Deletes the session from the persistent storage, and sends
- an expired cookie out"""
- if self.use_cookies:
- self._delete_cookie()
- self.clear()
-
- def invalidate(self):
- """Invalidates this session, creates a new session id, returns
- to the is_new state"""
- self.clear()
- self.was_invalidated = True
- self._create_id()
- self.load()
-
- def load(self):
- "Loads the data from this session from persistent storage"
- self.namespace = self.namespace_class(self.id,
- data_dir=self.data_dir, digest_filenames=False,
- **self.namespace_args)
- now = time.time()
- self.request['set_cookie'] = True
-
- self.namespace.acquire_read_lock()
- timed_out = False
- try:
- self.clear()
- try:
- session_data = self.namespace['session']
-
- # Memcached always returns a key, its None when its not
- # present
- if session_data is None:
- session_data = {
- '_creation_time':now,
- '_accessed_time':now
- }
- self.is_new = True
- except (KeyError, TypeError):
- session_data = {
- '_creation_time':now,
- '_accessed_time':now
- }
- self.is_new = True
-
- if self.timeout is not None and \
- now - session_data['_accessed_time'] > self.timeout:
- timed_out= True
- else:
- # Properly set the last_accessed time, which is different
- # than the *currently* _accessed_time
- if self.is_new or '_accessed_time' not in session_data:
- self.last_accessed = None
- else:
- self.last_accessed = session_data['_accessed_time']
-
- # Update the current _accessed_time
- session_data['_accessed_time'] = now
- self.update(session_data)
- self.accessed_dict = session_data.copy()
- finally:
- self.namespace.release_read_lock()
- if timed_out:
- self.invalidate()
-
- def save(self, accessed_only=False):
- """Saves the data for this session to persistent storage
-
- If accessed_only is True, then only the original data loaded
- at the beginning of the request will be saved, with the updated
- last accessed time.
-
- """
- # Look to see if its a new session that was only accessed
- # Don't save it under that case
- if accessed_only and self.is_new:
- return None
-
- if not hasattr(self, 'namespace'):
- self.namespace = self.namespace_class(
- self.id,
- data_dir=self.data_dir,
- digest_filenames=False,
- **self.namespace_args)
-
- self.namespace.acquire_write_lock()
- try:
- if accessed_only:
- data = dict(self.accessed_dict.items())
- else:
- data = dict(self.items())
-
- # Save the data
- if not data and 'session' in self.namespace:
- del self.namespace['session']
- else:
- self.namespace['session'] = data
- finally:
- self.namespace.release_write_lock()
- if self.is_new:
- self.request['set_cookie'] = True
-
- def revert(self):
- """Revert the session to its original state from its first
- access in the request"""
- self.clear()
- self.update(self.accessed_dict)
-
- # TODO: I think both these methods should be removed. They're from
- # the original mod_python code i was ripping off but they really
- # have no use here.
- def lock(self):
- """Locks this session against other processes/threads. This is
- automatic when load/save is called.
-
- ***use with caution*** and always with a corresponding 'unlock'
- inside a "finally:" block, as a stray lock typically cannot be
- unlocked without shutting down the whole application.
-
- """
- self.namespace.acquire_write_lock()
-
- def unlock(self):
- """Unlocks this session against other processes/threads. This
- is automatic when load/save is called.
-
- ***use with caution*** and always within a "finally:" block, as
- a stray lock typically cannot be unlocked without shutting down
- the whole application.
-
- """
- self.namespace.release_write_lock()
-
-class CookieSession(Session):
- """Pure cookie-based session
-
- Options recognized when using cookie-based sessions are slightly
- more restricted than general sessions.
-
- ``key``
- The name the cookie should be set to.
- ``timeout``
- How long session data is considered valid. This is used
- regardless of the cookie being present or not to determine
- whether session data is still valid.
- ``encrypt_key``
- The key to use for the session encryption, if not provided the
- session will not be encrypted.
- ``validate_key``
- The key used to sign the encrypted session
- ``cookie_domain``
- Domain to use for the cookie.
- ``secure``
- Whether or not the cookie should only be sent over SSL.
-
- """
- def __init__(self, request, key='beaker.session.id', timeout=None,
- cookie_expires=True, cookie_domain=None, encrypt_key=None,
- validate_key=None, secure=False, **kwargs):
-
- if not crypto.has_aes and encrypt_key:
- raise InvalidCryptoBackendError("No AES library is installed, can't generate "
- "encrypted cookie-only Session.")
-
- self.request = request
- self.key = key
- self.timeout = timeout
- self.cookie_expires = cookie_expires
- self.encrypt_key = encrypt_key
- self.validate_key = validate_key
- self.request['set_cookie'] = False
- self.secure = secure
- self._domain = cookie_domain
- self._path = '/'
-
- try:
- cookieheader = request['cookie']
- except KeyError:
- cookieheader = ''
-
- if validate_key is None:
- raise BeakerException("No validate_key specified for Cookie only "
- "Session.")
-
- try:
- self.cookie = SignedCookie(validate_key, input=cookieheader)
- except Cookie.CookieError:
- self.cookie = SignedCookie(validate_key, input=None)
-
- self['_id'] = self._make_id()
- self.is_new = True
-
- # If we have a cookie, load it
- if self.key in self.cookie and self.cookie[self.key].value is not None:
- self.is_new = False
- try:
- self.update(self._decrypt_data())
- except:
- pass
- if self.timeout is not None and time.time() - \
- self['_accessed_time'] > self.timeout:
- self.clear()
- self.accessed_dict = self.copy()
- self._create_cookie()
-
- def created(self):
- return self['_creation_time']
- created = property(created)
-
- def id(self):
- return self['_id']
- id = property(id)
-
- def _set_domain(self, domain):
- self['_domain'] = domain
- self._domain = domain
-
- def _get_domain(self):
- return self._domain
-
- domain = property(_get_domain, _set_domain)
-
- def _set_path(self, path):
- self['_path'] = path
- self._path = path
-
- def _get_path(self):
- return self._path
-
- path = property(_get_path, _set_path)
-
- def _encrypt_data(self):
- """Serialize, encipher, and base64 the session dict"""
- if self.encrypt_key:
- nonce = b64encode(os.urandom(40))[:8]
- encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
- self.validate_key + nonce, 1)
- data = pickle.dumps(self.copy(), 2)
- return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key))
- else:
- data = pickle.dumps(self.copy(), 2)
- return b64encode(data)
-
- def _decrypt_data(self):
- """Bas64, decipher, then un-serialize the data for the session
- dict"""
- if self.encrypt_key:
- nonce = self.cookie[self.key].value[:8]
- encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
- self.validate_key + nonce, 1)
- payload = b64decode(self.cookie[self.key].value[8:])
- data = crypto.aesDecrypt(payload, encrypt_key)
- return pickle.loads(data)
- else:
- data = b64decode(self.cookie[self.key].value)
- return pickle.loads(data)
-
- def _make_id(self):
- return md5(md5(
- "%f%s%f%s" % (time.time(), id({}), random.random(), getpid())
- ).hexdigest()
- ).hexdigest()
-
- def save(self, accessed_only=False):
- """Saves the data for this session to persistent storage"""
- if accessed_only and self.is_new:
- return
- if accessed_only:
- self.clear()
- self.update(self.accessed_dict)
- self._create_cookie()
-
- def expire(self):
- """Delete the 'expires' attribute on this Session, if any."""
-
- self.pop('_expires', None)
-
- def _create_cookie(self):
- if '_creation_time' not in self:
- self['_creation_time'] = time.time()
- if '_id' not in self:
- self['_id'] = self._make_id()
- self['_accessed_time'] = time.time()
-
- if self.cookie_expires is not True:
- if self.cookie_expires is False:
- expires = datetime.fromtimestamp( 0x7FFFFFFF )
- elif isinstance(self.cookie_expires, timedelta):
- expires = datetime.today() + self.cookie_expires
- elif isinstance(self.cookie_expires, datetime):
- expires = self.cookie_expires
- else:
- raise ValueError("Invalid argument for cookie_expires: %s"
- % repr(self.cookie_expires))
- self['_expires'] = expires
- elif '_expires' in self:
- expires = self['_expires']
- else:
- expires = None
-
- val = self._encrypt_data()
- if len(val) > 4064:
- raise BeakerException("Cookie value is too long to store")
-
- self.cookie[self.key] = val
- if '_domain' in self:
- self.cookie[self.key]['domain'] = self['_domain']
- elif self._domain:
- self.cookie[self.key]['domain'] = self._domain
- if self.secure:
- self.cookie[self.key]['secure'] = True
-
- self.cookie[self.key]['path'] = self.get('_path', '/')
-
- if expires:
- self.cookie[self.key]['expires'] = \
- expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
- self.request['cookie_out'] = self.cookie[self.key].output(header='')
- self.request['set_cookie'] = True
-
- def delete(self):
- """Delete the cookie, and clear the session"""
- # Send a delete cookie request
- self._delete_cookie()
- self.clear()
-
- def invalidate(self):
- """Clear the contents and start a new session"""
- self.delete()
- self['_id'] = self._make_id()
-
-
-class SessionObject(object):
- """Session proxy/lazy creator
-
- This object proxies access to the actual session object, so that in
- the case that the session hasn't been used before, it will be
- setup. This avoid creating and loading the session from persistent
- storage unless its actually used during the request.
-
- """
- def __init__(self, environ, **params):
- self.__dict__['_params'] = params
- self.__dict__['_environ'] = environ
- self.__dict__['_sess'] = None
- self.__dict__['_headers'] = []
-
- def _session(self):
- """Lazy initial creation of session object"""
- if self.__dict__['_sess'] is None:
- params = self.__dict__['_params']
- environ = self.__dict__['_environ']
- self.__dict__['_headers'] = req = {'cookie_out':None}
- req['cookie'] = environ.get('HTTP_COOKIE')
- if params.get('type') == 'cookie':
- self.__dict__['_sess'] = CookieSession(req, **params)
- else:
- self.__dict__['_sess'] = Session(req, use_cookies=True,
- **params)
- return self.__dict__['_sess']
-
- def __getattr__(self, attr):
- return getattr(self._session(), attr)
-
- def __setattr__(self, attr, value):
- setattr(self._session(), attr, value)
-
- def __delattr__(self, name):
- self._session().__delattr__(name)
-
- def __getitem__(self, key):
- return self._session()[key]
-
- def __setitem__(self, key, value):
- self._session()[key] = value
-
- def __delitem__(self, key):
- self._session().__delitem__(key)
-
- def __repr__(self):
- return self._session().__repr__()
-
- def __iter__(self):
- """Only works for proxying to a dict"""
- return iter(self._session().keys())
-
- def __contains__(self, key):
- return self._session().has_key(key)
-
- def get_by_id(self, id):
- """Loads a session given a session ID"""
- params = self.__dict__['_params']
- session = Session({}, use_cookies=False, id=id, **params)
- if session.is_new:
- return None
- return session
-
- def save(self):
- self.__dict__['_dirty'] = True
-
- def delete(self):
- self.__dict__['_dirty'] = True
- self._session().delete()
-
- def persist(self):
- """Persist the session to the storage
-
- If its set to autosave, then the entire session will be saved
- regardless of if save() has been called. Otherwise, just the
- accessed time will be updated if save() was not called, or
- the session will be saved if save() was called.
-
- """
- if self.__dict__['_params'].get('auto'):
- self._session().save()
- else:
- if self.__dict__.get('_dirty'):
- self._session().save()
- else:
- self._session().save(accessed_only=True)
-
- def dirty(self):
- return self.__dict__.get('_dirty', False)
-
- def accessed(self):
- """Returns whether or not the session has been accessed"""
- return self.__dict__['_sess'] is not None
diff --git a/module/lib/beaker/synchronization.py b/module/lib/beaker/synchronization.py
deleted file mode 100644
index 761303707..000000000
--- a/module/lib/beaker/synchronization.py
+++ /dev/null
@@ -1,381 +0,0 @@
-"""Synchronization functions.
-
-File- and mutex-based mutual exclusion synchronizers are provided,
-as well as a name-based mutex which locks within an application
-based on a string name.
-
-"""
-
-import os
-import sys
-import tempfile
-
-try:
- import threading as _threading
-except ImportError:
- import dummy_threading as _threading
-
-# check for fcntl module
-try:
- sys.getwindowsversion()
- has_flock = False
-except:
- try:
- import fcntl
- has_flock = True
- except ImportError:
- has_flock = False
-
-from beaker import util
-from beaker.exceptions import LockError
-
-__all__ = ["file_synchronizer", "mutex_synchronizer", "null_synchronizer",
- "NameLock", "_threading"]
-
-
-class NameLock(object):
- """a proxy for an RLock object that is stored in a name based
- registry.
-
- Multiple threads can get a reference to the same RLock based on the
- name alone, and synchronize operations related to that name.
-
- """
- locks = util.WeakValuedRegistry()
-
- class NLContainer(object):
- def __init__(self, reentrant):
- if reentrant:
- self.lock = _threading.RLock()
- else:
- self.lock = _threading.Lock()
- def __call__(self):
- return self.lock
-
- def __init__(self, identifier = None, reentrant = False):
- if identifier is None:
- self._lock = NameLock.NLContainer(reentrant)
- else:
- self._lock = NameLock.locks.get(identifier, NameLock.NLContainer,
- reentrant)
-
- def acquire(self, wait = True):
- return self._lock().acquire(wait)
-
- def release(self):
- self._lock().release()
-
-
-_synchronizers = util.WeakValuedRegistry()
-def _synchronizer(identifier, cls, **kwargs):
- return _synchronizers.sync_get((identifier, cls), cls, identifier, **kwargs)
-
-
-def file_synchronizer(identifier, **kwargs):
- if not has_flock or 'lock_dir' not in kwargs:
- return mutex_synchronizer(identifier)
- else:
- return _synchronizer(identifier, FileSynchronizer, **kwargs)
-
-
-def mutex_synchronizer(identifier, **kwargs):
- return _synchronizer(identifier, ConditionSynchronizer, **kwargs)
-
-
-class null_synchronizer(object):
- def acquire_write_lock(self, wait=True):
- return True
- def acquire_read_lock(self):
- pass
- def release_write_lock(self):
- pass
- def release_read_lock(self):
- pass
- acquire = acquire_write_lock
- release = release_write_lock
-
-
-class SynchronizerImpl(object):
- def __init__(self):
- self._state = util.ThreadLocal()
-
- class SyncState(object):
- __slots__ = 'reentrantcount', 'writing', 'reading'
-
- def __init__(self):
- self.reentrantcount = 0
- self.writing = False
- self.reading = False
-
- def state(self):
- if not self._state.has():
- state = SynchronizerImpl.SyncState()
- self._state.put(state)
- return state
- else:
- return self._state.get()
- state = property(state)
-
- def release_read_lock(self):
- state = self.state
-
- if state.writing:
- raise LockError("lock is in writing state")
- if not state.reading:
- raise LockError("lock is not in reading state")
-
- if state.reentrantcount == 1:
- self.do_release_read_lock()
- state.reading = False
-
- state.reentrantcount -= 1
-
- def acquire_read_lock(self, wait = True):
- state = self.state
-
- if state.writing:
- raise LockError("lock is in writing state")
-
- if state.reentrantcount == 0:
- x = self.do_acquire_read_lock(wait)
- if (wait or x):
- state.reentrantcount += 1
- state.reading = True
- return x
- elif state.reading:
- state.reentrantcount += 1
- return True
-
- def release_write_lock(self):
- state = self.state
-
- if state.reading:
- raise LockError("lock is in reading state")
- if not state.writing:
- raise LockError("lock is not in writing state")
-
- if state.reentrantcount == 1:
- self.do_release_write_lock()
- state.writing = False
-
- state.reentrantcount -= 1
-
- release = release_write_lock
-
- def acquire_write_lock(self, wait = True):
- state = self.state
-
- if state.reading:
- raise LockError("lock is in reading state")
-
- if state.reentrantcount == 0:
- x = self.do_acquire_write_lock(wait)
- if (wait or x):
- state.reentrantcount += 1
- state.writing = True
- return x
- elif state.writing:
- state.reentrantcount += 1
- return True
-
- acquire = acquire_write_lock
-
- def do_release_read_lock(self):
- raise NotImplementedError()
-
- def do_acquire_read_lock(self):
- raise NotImplementedError()
-
- def do_release_write_lock(self):
- raise NotImplementedError()
-
- def do_acquire_write_lock(self):
- raise NotImplementedError()
-
-
-class FileSynchronizer(SynchronizerImpl):
- """a synchronizer which locks using flock().
-
- Adapted for Python/multithreads from Apache::Session::Lock::File,
- http://search.cpan.org/src/CWEST/Apache-Session-1.81/Session/Lock/File.pm
-
- This module does not unlink temporary files,
- because it interferes with proper locking. This can cause
- problems on certain systems (Linux) whose file systems (ext2) do not
- perform well with lots of files in one directory. To prevent this
- you should use a script to clean out old files from your lock directory.
-
- """
- def __init__(self, identifier, lock_dir):
- super(FileSynchronizer, self).__init__()
- self._filedescriptor = util.ThreadLocal()
-
- if lock_dir is None:
- lock_dir = tempfile.gettempdir()
- else:
- lock_dir = lock_dir
-
- self.filename = util.encoded_path(
- lock_dir,
- [identifier],
- extension='.lock'
- )
-
- def _filedesc(self):
- return self._filedescriptor.get()
- _filedesc = property(_filedesc)
-
- def _open(self, mode):
- filedescriptor = self._filedesc
- if filedescriptor is None:
- filedescriptor = os.open(self.filename, mode)
- self._filedescriptor.put(filedescriptor)
- return filedescriptor
-
- def do_acquire_read_lock(self, wait):
- filedescriptor = self._open(os.O_CREAT | os.O_RDONLY)
- if not wait:
- try:
- fcntl.flock(filedescriptor, fcntl.LOCK_SH | fcntl.LOCK_NB)
- return True
- except IOError:
- os.close(filedescriptor)
- self._filedescriptor.remove()
- return False
- else:
- fcntl.flock(filedescriptor, fcntl.LOCK_SH)
- return True
-
- def do_acquire_write_lock(self, wait):
- filedescriptor = self._open(os.O_CREAT | os.O_WRONLY)
- if not wait:
- try:
- fcntl.flock(filedescriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
- return True
- except IOError:
- os.close(filedescriptor)
- self._filedescriptor.remove()
- return False
- else:
- fcntl.flock(filedescriptor, fcntl.LOCK_EX)
- return True
-
- def do_release_read_lock(self):
- self._release_all_locks()
-
- def do_release_write_lock(self):
- self._release_all_locks()
-
- def _release_all_locks(self):
- filedescriptor = self._filedesc
- if filedescriptor is not None:
- fcntl.flock(filedescriptor, fcntl.LOCK_UN)
- os.close(filedescriptor)
- self._filedescriptor.remove()
-
-
-class ConditionSynchronizer(SynchronizerImpl):
- """a synchronizer using a Condition."""
-
- def __init__(self, identifier):
- super(ConditionSynchronizer, self).__init__()
-
- # counts how many asynchronous methods are executing
- self.async = 0
-
- # pointer to thread that is the current sync operation
- self.current_sync_operation = None
-
- # condition object to lock on
- self.condition = _threading.Condition(_threading.Lock())
-
- def do_acquire_read_lock(self, wait = True):
- self.condition.acquire()
- try:
- # see if a synchronous operation is waiting to start
- # or is already running, in which case we wait (or just
- # give up and return)
- if wait:
- while self.current_sync_operation is not None:
- self.condition.wait()
- else:
- if self.current_sync_operation is not None:
- return False
-
- self.async += 1
- finally:
- self.condition.release()
-
- if not wait:
- return True
-
- def do_release_read_lock(self):
- self.condition.acquire()
- try:
- self.async -= 1
-
- # check if we are the last asynchronous reader thread
- # out the door.
- if self.async == 0:
- # yes. so if a sync operation is waiting, notifyAll to wake
- # it up
- if self.current_sync_operation is not None:
- self.condition.notifyAll()
- elif self.async < 0:
- raise LockError("Synchronizer error - too many "
- "release_read_locks called")
- finally:
- self.condition.release()
-
- def do_acquire_write_lock(self, wait = True):
- self.condition.acquire()
- try:
- # here, we are not a synchronous reader, and after returning,
- # assuming waiting or immediate availability, we will be.
-
- if wait:
- # if another sync is working, wait
- while self.current_sync_operation is not None:
- self.condition.wait()
- else:
- # if another sync is working,
- # we dont want to wait, so forget it
- if self.current_sync_operation is not None:
- return False
-
- # establish ourselves as the current sync
- # this indicates to other read/write operations
- # that they should wait until this is None again
- self.current_sync_operation = _threading.currentThread()
-
- # now wait again for asyncs to finish
- if self.async > 0:
- if wait:
- # wait
- self.condition.wait()
- else:
- # we dont want to wait, so forget it
- self.current_sync_operation = None
- return False
- finally:
- self.condition.release()
-
- if not wait:
- return True
-
- def do_release_write_lock(self):
- self.condition.acquire()
- try:
- if self.current_sync_operation is not _threading.currentThread():
- raise LockError("Synchronizer error - current thread doesnt "
- "have the write lock")
-
- # reset the current sync operation so
- # another can get it
- self.current_sync_operation = None
-
- # tell everyone to get ready
- self.condition.notifyAll()
- finally:
- # everyone go !!
- self.condition.release()
diff --git a/module/lib/beaker/util.py b/module/lib/beaker/util.py
deleted file mode 100644
index 04c9617c5..000000000
--- a/module/lib/beaker/util.py
+++ /dev/null
@@ -1,302 +0,0 @@
-"""Beaker utilities"""
-
-try:
- import thread as _thread
- import threading as _threading
-except ImportError:
- import dummy_thread as _thread
- import dummy_threading as _threading
-
-from datetime import datetime, timedelta
-import os
-import string
-import types
-import weakref
-import warnings
-import sys
-
-py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
-py24 = sys.version_info < (2,5)
-jython = sys.platform.startswith('java')
-
-if py3k or jython:
- import pickle
-else:
- import cPickle as pickle
-
-from beaker.converters import asbool
-from threading import local as _tlocal
-
-
-__all__ = ["ThreadLocal", "Registry", "WeakValuedRegistry", "SyncDict",
- "encoded_path", "verify_directory"]
-
-
-def verify_directory(dir):
- """verifies and creates a directory. tries to
- ignore collisions with other threads and processes."""
-
- tries = 0
- while not os.access(dir, os.F_OK):
- try:
- tries += 1
- os.makedirs(dir)
- except:
- if tries > 5:
- raise
-
-
-def deprecated(message):
- def wrapper(fn):
- def deprecated_method(*args, **kargs):
- warnings.warn(message, DeprecationWarning, 2)
- return fn(*args, **kargs)
- # TODO: use decorator ? functools.wrapper ?
- deprecated_method.__name__ = fn.__name__
- deprecated_method.__doc__ = "%s\n\n%s" % (message, fn.__doc__)
- return deprecated_method
- return wrapper
-
-class ThreadLocal(object):
- """stores a value on a per-thread basis"""
-
- __slots__ = '_tlocal'
-
- def __init__(self):
- self._tlocal = _tlocal()
-
- def put(self, value):
- self._tlocal.value = value
-
- def has(self):
- return hasattr(self._tlocal, 'value')
-
- def get(self, default=None):
- return getattr(self._tlocal, 'value', default)
-
- def remove(self):
- del self._tlocal.value
-
-class SyncDict(object):
- """
- An efficient/threadsafe singleton map algorithm, a.k.a.
- "get a value based on this key, and create if not found or not
- valid" paradigm:
-
- exists && isvalid ? get : create
-
- Designed to work with weakref dictionaries to expect items
- to asynchronously disappear from the dictionary.
-
- Use python 2.3.3 or greater ! a major bug was just fixed in Nov.
- 2003 that was driving me nuts with garbage collection/weakrefs in
- this section.
-
- """
- def __init__(self):
- self.mutex = _thread.allocate_lock()
- self.dict = {}
-
- def get(self, key, createfunc, *args, **kwargs):
- try:
- if self.has_key(key):
- return self.dict[key]
- else:
- return self.sync_get(key, createfunc, *args, **kwargs)
- except KeyError:
- return self.sync_get(key, createfunc, *args, **kwargs)
-
- def sync_get(self, key, createfunc, *args, **kwargs):
- self.mutex.acquire()
- try:
- try:
- if self.has_key(key):
- return self.dict[key]
- else:
- return self._create(key, createfunc, *args, **kwargs)
- except KeyError:
- return self._create(key, createfunc, *args, **kwargs)
- finally:
- self.mutex.release()
-
- def _create(self, key, createfunc, *args, **kwargs):
- self[key] = obj = createfunc(*args, **kwargs)
- return obj
-
- def has_key(self, key):
- return self.dict.has_key(key)
-
- def __contains__(self, key):
- return self.dict.__contains__(key)
- def __getitem__(self, key):
- return self.dict.__getitem__(key)
- def __setitem__(self, key, value):
- self.dict.__setitem__(key, value)
- def __delitem__(self, key):
- return self.dict.__delitem__(key)
- def clear(self):
- self.dict.clear()
-
-
-class WeakValuedRegistry(SyncDict):
- def __init__(self):
- self.mutex = _threading.RLock()
- self.dict = weakref.WeakValueDictionary()
-
-sha1 = None
-def encoded_path(root, identifiers, extension = ".enc", depth = 3,
- digest_filenames=True):
-
- """Generate a unique file-accessible path from the given list of
- identifiers starting at the given root directory."""
- ident = "_".join(identifiers)
-
- global sha1
- if sha1 is None:
- from beaker.crypto import sha1
-
- if digest_filenames:
- if py3k:
- ident = sha1(ident.encode('utf-8')).hexdigest()
- else:
- ident = sha1(ident).hexdigest()
-
- ident = os.path.basename(ident)
-
- tokens = []
- for d in range(1, depth):
- tokens.append(ident[0:d])
-
- dir = os.path.join(root, *tokens)
- verify_directory(dir)
-
- return os.path.join(dir, ident + extension)
-
-
-def verify_options(opt, types, error):
- if not isinstance(opt, types):
- if not isinstance(types, tuple):
- types = (types,)
- coerced = False
- for typ in types:
- try:
- if typ in (list, tuple):
- opt = [x.strip() for x in opt.split(',')]
- else:
- if typ == bool:
- typ = asbool
- opt = typ(opt)
- coerced = True
- except:
- pass
- if coerced:
- break
- if not coerced:
- raise Exception(error)
- elif isinstance(opt, str) and not opt.strip():
- raise Exception("Empty strings are invalid for: %s" % error)
- return opt
-
-
-def verify_rules(params, ruleset):
- for key, types, message in ruleset:
- if key in params:
- params[key] = verify_options(params[key], types, message)
- return params
-
-
-def coerce_session_params(params):
- rules = [
- ('data_dir', (str, types.NoneType), "data_dir must be a string "
- "referring to a directory."),
- ('lock_dir', (str, types.NoneType), "lock_dir must be a string referring to a "
- "directory."),
- ('type', (str, types.NoneType), "Session type must be a string."),
- ('cookie_expires', (bool, datetime, timedelta), "Cookie expires was "
- "not a boolean, datetime, or timedelta instance."),
- ('cookie_domain', (str, types.NoneType), "Cookie domain must be a "
- "string."),
- ('id', (str,), "Session id must be a string."),
- ('key', (str,), "Session key must be a string."),
- ('secret', (str, types.NoneType), "Session secret must be a string."),
- ('validate_key', (str, types.NoneType), "Session encrypt_key must be "
- "a string."),
- ('encrypt_key', (str, types.NoneType), "Session validate_key must be "
- "a string."),
- ('secure', (bool, types.NoneType), "Session secure must be a boolean."),
- ('timeout', (int, types.NoneType), "Session timeout must be an "
- "integer."),
- ('auto', (bool, types.NoneType), "Session is created if accessed."),
- ]
- return verify_rules(params, rules)
-
-
-def coerce_cache_params(params):
- rules = [
- ('data_dir', (str, types.NoneType), "data_dir must be a string "
- "referring to a directory."),
- ('lock_dir', (str, types.NoneType), "lock_dir must be a string referring to a "
- "directory."),
- ('type', (str,), "Cache type must be a string."),
- ('enabled', (bool, types.NoneType), "enabled must be true/false "
- "if present."),
- ('expire', (int, types.NoneType), "expire must be an integer representing "
- "how many seconds the cache is valid for"),
- ('regions', (list, tuple, types.NoneType), "Regions must be a "
- "comma seperated list of valid regions")
- ]
- return verify_rules(params, rules)
-
-
-def parse_cache_config_options(config, include_defaults=True):
- """Parse configuration options and validate for use with the
- CacheManager"""
-
- # Load default cache options
- if include_defaults:
- options= dict(type='memory', data_dir=None, expire=None,
- log_file=None)
- else:
- options = {}
- for key, val in config.iteritems():
- if key.startswith('beaker.cache.'):
- options[key[13:]] = val
- if key.startswith('cache.'):
- options[key[6:]] = val
- coerce_cache_params(options)
-
- # Set cache to enabled if not turned off
- if 'enabled' not in options:
- options['enabled'] = True
-
- # Configure region dict if regions are available
- regions = options.pop('regions', None)
- if regions:
- region_configs = {}
- for region in regions:
- # Setup the default cache options
- region_options = dict(data_dir=options.get('data_dir'),
- lock_dir=options.get('lock_dir'),
- type=options.get('type'),
- enabled=options['enabled'],
- expire=options.get('expire'))
- region_len = len(region) + 1
- for key in options.keys():
- if key.startswith('%s.' % region):
- region_options[key[region_len:]] = options.pop(key)
- coerce_cache_params(region_options)
- region_configs[region] = region_options
- options['cache_regions'] = region_configs
- return options
-
-def func_namespace(func):
- """Generates a unique namespace for a function"""
- kls = None
- if hasattr(func, 'im_func'):
- kls = func.im_class
- func = func.im_func
-
- if kls:
- return '%s.%s' % (kls.__module__, kls.__name__)
- else:
- return '%s.%s' % (func.__module__, func.__name__)
diff --git a/module/lib/bottle.py b/module/lib/bottle.py
deleted file mode 100644
index 2c243278e..000000000
--- a/module/lib/bottle.py
+++ /dev/null
@@ -1,2922 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-Bottle is a fast and simple micro-framework for small web applications. It
-offers request dispatching (Routes) with url parameter support, templates,
-a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
-template engines - all in a single file and with no dependencies other than the
-Python Standard Library.
-
-Homepage and documentation: http://bottlepy.org/
-
-Copyright (c) 2011, Marcel Hellkamp.
-License: MIT (see LICENSE.txt for details)
-"""
-
-from __future__ import with_statement
-
-__author__ = 'Marcel Hellkamp'
-__version__ = '0.10.2'
-__license__ = 'MIT'
-
-# The gevent server adapter needs to patch some modules before they are imported
-# This is why we parse the commandline parameters here but handle them later
-if __name__ == '__main__':
- from optparse import OptionParser
- _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app")
- _opt = _cmd_parser.add_option
- _opt("--version", action="store_true", help="show version number.")
- _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
- _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
- _opt("-p", "--plugin", action="append", help="install additional plugin/s.")
- _opt("--debug", action="store_true", help="start server in debug mode.")
- _opt("--reload", action="store_true", help="auto-reload on file changes.")
- _cmd_options, _cmd_args = _cmd_parser.parse_args()
- if _cmd_options.server and _cmd_options.server.startswith('gevent'):
- import gevent.monkey; gevent.monkey.patch_all()
-
-import sys
-import base64
-import cgi
-import email.utils
-import functools
-import hmac
-import httplib
-import imp
-import itertools
-import mimetypes
-import os
-import re
-import subprocess
-import tempfile
-import thread
-import threading
-import time
-import warnings
-
-from Cookie import SimpleCookie
-from datetime import date as datedate, datetime, timedelta
-from tempfile import TemporaryFile
-from traceback import format_exc, print_exc
-from urlparse import urljoin, SplitResult as UrlSplitResult
-
-# Workaround for a bug in some versions of lib2to3 (fixed on CPython 2.7 and 3.2)
-import urllib
-urlencode = urllib.urlencode
-urlquote = urllib.quote
-urlunquote = urllib.unquote
-
-try: from collections import MutableMapping as DictMixin
-except ImportError: # pragma: no cover
- from UserDict import DictMixin
-
-try: from urlparse import parse_qs
-except ImportError: # pragma: no cover
- from cgi import parse_qs
-
-try: import cPickle as pickle
-except ImportError: # pragma: no cover
- import pickle
-
-try: from json import dumps as json_dumps, loads as json_lds
-except ImportError: # pragma: no cover
- try: from simplejson import dumps as json_dumps, loads as json_lds
- except ImportError: # pragma: no cover
- try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds
- except ImportError: # pragma: no cover
- def json_dumps(data):
- raise ImportError("JSON support requires Python 2.6 or simplejson.")
- json_lds = json_dumps
-
-py3k = sys.version_info >= (3,0,0)
-NCTextIOWrapper = None
-
-if py3k: # pragma: no cover
- json_loads = lambda s: json_lds(touni(s))
- # See Request.POST
- from io import BytesIO
- def touni(x, enc='utf8', err='strict'):
- """ Convert anything to unicode """
- return str(x, enc, err) if isinstance(x, bytes) else str(x)
- if sys.version_info < (3,2,0):
- from io import TextIOWrapper
- class NCTextIOWrapper(TextIOWrapper):
- ''' Garbage collecting an io.TextIOWrapper(buffer) instance closes
- the wrapped buffer. This subclass keeps it open. '''
- def close(self): pass
-else:
- json_loads = json_lds
- from StringIO import StringIO as BytesIO
- bytes = str
- def touni(x, enc='utf8', err='strict'):
- """ Convert anything to unicode """
- return x if isinstance(x, unicode) else unicode(str(x), enc, err)
-
-def tob(data, enc='utf8'):
- """ Convert anything to bytes """
- return data.encode(enc) if isinstance(data, unicode) else bytes(data)
-
-tonat = touni if py3k else tob
-tonat.__doc__ = """ Convert anything to native strings """
-
-def try_update_wrapper(wrapper, wrapped, *a, **ka):
- try: # Bug: functools breaks if wrapper is an instane method
- functools.update_wrapper(wrapper, wrapped, *a, **ka)
- except AttributeError: pass
-
-# Backward compatibility
-def depr(message):
- warnings.warn(message, DeprecationWarning, stacklevel=3)
-
-
-# Small helpers
-def makelist(data):
- if isinstance(data, (tuple, list, set, dict)): return list(data)
- elif data: return [data]
- else: return []
-
-
-class DictProperty(object):
- ''' Property that maps to a key in a local dict-like attribute. '''
- def __init__(self, attr, key=None, read_only=False):
- self.attr, self.key, self.read_only = attr, key, read_only
-
- def __call__(self, func):
- functools.update_wrapper(self, func, updated=[])
- self.getter, self.key = func, self.key or func.__name__
- return self
-
- def __get__(self, obj, cls):
- if obj is None: return self
- key, storage = self.key, getattr(obj, self.attr)
- if key not in storage: storage[key] = self.getter(obj)
- return storage[key]
-
- def __set__(self, obj, value):
- if self.read_only: raise AttributeError("Read-Only property.")
- getattr(obj, self.attr)[self.key] = value
-
- def __delete__(self, obj):
- if self.read_only: raise AttributeError("Read-Only property.")
- del getattr(obj, self.attr)[self.key]
-
-
-class CachedProperty(object):
- ''' A property that is only computed once per instance and then replaces
- itself with an ordinary attribute. Deleting the attribute resets the
- property. '''
-
- def __init__(self, func):
- self.func = func
-
- def __get__(self, obj, cls):
- if obj is None: return self
- value = obj.__dict__[self.func.__name__] = self.func(obj)
- return value
-
-cached_property = CachedProperty
-
-
-class lazy_attribute(object): # Does not need configuration -> lower-case name
- ''' A property that caches itself to the class object. '''
- def __init__(self, func):
- functools.update_wrapper(self, func, updated=[])
- self.getter = func
-
- def __get__(self, obj, cls):
- value = self.getter(cls)
- setattr(cls, self.__name__, value)
- return value
-
-
-
-
-
-
-###############################################################################
-# Exceptions and Events ########################################################
-###############################################################################
-
-
-class BottleException(Exception):
- """ A base class for exceptions used by bottle. """
- pass
-
-
-#TODO: These should subclass BaseRequest
-
-class HTTPResponse(BottleException):
- """ Used to break execution and immediately finish the response """
- def __init__(self, output='', status=200, header=None):
- super(BottleException, self).__init__("HTTP Response %d" % status)
- self.status = int(status)
- self.output = output
- self.headers = HeaderDict(header) if header else None
-
- def apply(self, response):
- if self.headers:
- for key, value in self.headers.iterallitems():
- response.headers[key] = value
- response.status = self.status
-
-
-class HTTPError(HTTPResponse):
- """ Used to generate an error page """
- def __init__(self, code=500, output='Unknown Error', exception=None,
- traceback=None, header=None):
- super(HTTPError, self).__init__(output, code, header)
- self.exception = exception
- self.traceback = traceback
-
- def __repr__(self):
- return template(ERROR_PAGE_TEMPLATE, e=self)
-
-
-
-
-
-
-###############################################################################
-# Routing ######################################################################
-###############################################################################
-
-
-class RouteError(BottleException):
- """ This is a base class for all routing related exceptions """
-
-
-class RouteReset(BottleException):
- """ If raised by a plugin or request handler, the route is reset and all
- plugins are re-applied. """
-
-class RouterUnknownModeError(RouteError): pass
-
-class RouteSyntaxError(RouteError):
- """ The route parser found something not supported by this router """
-
-class RouteBuildError(RouteError):
- """ The route could not been built """
-
-class Router(object):
- ''' A Router is an ordered collection of route->target pairs. It is used to
- efficiently match WSGI requests against a number of routes and return
- the first target that satisfies the request. The target may be anything,
- usually a string, ID or callable object. A route consists of a path-rule
- and a HTTP method.
-
- The path-rule is either a static path (e.g. `/contact`) or a dynamic
- path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
- and details on the matching order are described in docs:`routing`.
- '''
-
- default_pattern = '[^/]+'
- default_filter = 're'
- #: Sorry for the mess. It works. Trust me.
- rule_syntax = re.compile('(\\\\*)'\
- '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
- '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\
- '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
-
- def __init__(self, strict=False):
- self.rules = {} # A {rule: Rule} mapping
- self.builder = {} # A rule/name->build_info mapping
- self.static = {} # Cache for static routes: {path: {method: target}}
- self.dynamic = [] # Cache for dynamic routes. See _compile()
- #: If true, static routes are no longer checked first.
- self.strict_order = strict
- self.filters = {'re': self.re_filter, 'int': self.int_filter,
- 'float': self.re_filter, 'path': self.path_filter}
-
- def re_filter(self, conf):
- return conf or self.default_pattern, None, None
-
- def int_filter(self, conf):
- return r'-?\d+', int, lambda x: str(int(x))
-
- def float_filter(self, conf):
- return r'-?\d*\.\d+', float, lambda x: str(float(x))
-
- def path_filter(self, conf):
- return r'.*?', None, None
-
- def add_filter(self, name, func):
- ''' Add a filter. The provided function is called with the configuration
- string as parameter and must return a (regexp, to_python, to_url) tuple.
- The first element is a string, the last two are callables or None. '''
- self.filters[name] = func
-
- def parse_rule(self, rule):
- ''' Parses a rule into a (name, filter, conf) token stream. If mode is
- None, name contains a static rule part. '''
- offset, prefix = 0, ''
- for match in self.rule_syntax.finditer(rule):
- prefix += rule[offset:match.start()]
- g = match.groups()
- if len(g[0])%2: # Escaped wildcard
- prefix += match.group(0)[len(g[0]):]
- offset = match.end()
- continue
- if prefix: yield prefix, None, None
- name, filtr, conf = g[1:4] if not g[2] is None else g[4:7]
- if not filtr: filtr = self.default_filter
- yield name, filtr, conf or None
- offset, prefix = match.end(), ''
- if offset <= len(rule) or prefix:
- yield prefix+rule[offset:], None, None
-
- def add(self, rule, method, target, name=None):
- ''' Add a new route or replace the target for an existing route. '''
- if rule in self.rules:
- self.rules[rule][method] = target
- if name: self.builder[name] = self.builder[rule]
- return
-
- target = self.rules[rule] = {method: target}
-
- # Build pattern and other structures for dynamic routes
- anons = 0 # Number of anonymous wildcards
- pattern = '' # Regular expression pattern
- filters = [] # Lists of wildcard input filters
- builder = [] # Data structure for the URL builder
- is_static = True
- for key, mode, conf in self.parse_rule(rule):
- if mode:
- is_static = False
- mask, in_filter, out_filter = self.filters[mode](conf)
- if key:
- pattern += '(?P<%s>%s)' % (key, mask)
- else:
- pattern += '(?:%s)' % mask
- key = 'anon%d' % anons; anons += 1
- if in_filter: filters.append((key, in_filter))
- builder.append((key, out_filter or str))
- elif key:
- pattern += re.escape(key)
- builder.append((None, key))
- self.builder[rule] = builder
- if name: self.builder[name] = builder
-
- if is_static and not self.strict_order:
- self.static[self.build(rule)] = target
- return
-
- def fpat_sub(m):
- return m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:'
- flat_pattern = re.sub(r'(\\*)(\(\?P<[^>]*>|\((?!\?))', fpat_sub, pattern)
-
- try:
- re_match = re.compile('^(%s)$' % pattern).match
- except re.error, e:
- raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, e))
-
- def match(path):
- """ Return an url-argument dictionary. """
- url_args = re_match(path).groupdict()
- for name, wildcard_filter in filters:
- try:
- url_args[name] = wildcard_filter(url_args[name])
- except ValueError:
- raise HTTPError(400, 'Path has wrong format.')
- return url_args
-
- try:
- combined = '%s|(^%s$)' % (self.dynamic[-1][0].pattern, flat_pattern)
- self.dynamic[-1] = (re.compile(combined), self.dynamic[-1][1])
- self.dynamic[-1][1].append((match, target))
- except (AssertionError, IndexError), e: # AssertionError: Too many groups
- self.dynamic.append((re.compile('(^%s$)' % flat_pattern),
- [(match, target)]))
- return match
-
- def build(self, _name, *anons, **query):
- ''' Build an URL by filling the wildcards in a rule. '''
- builder = self.builder.get(_name)
- if not builder: raise RouteBuildError("No route with that name.", _name)
- try:
- for i, value in enumerate(anons): query['anon%d'%i] = value
- url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder])
- return url if not query else url+'?'+urlencode(query)
- except KeyError, e:
- raise RouteBuildError('Missing URL argument: %r' % e.args[0])
-
- def match(self, environ):
- ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
- path, targets, urlargs = environ['PATH_INFO'] or '/', None, {}
- if path in self.static:
- targets = self.static[path]
- else:
- for combined, rules in self.dynamic:
- match = combined.match(path)
- if not match: continue
- getargs, targets = rules[match.lastindex - 1]
- urlargs = getargs(path) if getargs else {}
- break
-
- if not targets:
- raise HTTPError(404, "Not found: " + repr(environ['PATH_INFO']))
- method = environ['REQUEST_METHOD'].upper()
- if method in targets:
- return targets[method], urlargs
- if method == 'HEAD' and 'GET' in targets:
- return targets['GET'], urlargs
- if 'ANY' in targets:
- return targets['ANY'], urlargs
- allowed = [verb for verb in targets if verb != 'ANY']
- if 'GET' in allowed and 'HEAD' not in allowed:
- allowed.append('HEAD')
- raise HTTPError(405, "Method not allowed.",
- header=[('Allow',",".join(allowed))])
-
-
-
-class Route(object):
- ''' This class wraps a route callback along with route specific metadata and
- configuration and applies Plugins on demand. It is also responsible for
- turing an URL path rule into a regular expression usable by the Router.
- '''
-
-
- def __init__(self, app, rule, method, callback, name=None,
- plugins=None, skiplist=None, **config):
- #: The application this route is installed to.
- self.app = app
- #: The path-rule string (e.g. ``/wiki/:page``).
- self.rule = rule
- #: The HTTP method as a string (e.g. ``GET``).
- self.method = method
- #: The original callback with no plugins applied. Useful for introspection.
- self.callback = callback
- #: The name of the route (if specified) or ``None``.
- self.name = name or None
- #: A list of route-specific plugins (see :meth:`Bottle.route`).
- self.plugins = plugins or []
- #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
- self.skiplist = skiplist or []
- #: Additional keyword arguments passed to the :meth:`Bottle.route`
- #: decorator are stored in this dictionary. Used for route-specific
- #: plugin configuration and meta-data.
- self.config = ConfigDict(config)
-
- def __call__(self, *a, **ka):
- depr("Some APIs changed to return Route() instances instead of"\
- " callables. Make sure to use the Route.call method and not to"\
- " call Route instances directly.")
- return self.call(*a, **ka)
-
- @cached_property
- def call(self):
- ''' The route callback with all plugins applied. This property is
- created on demand and then cached to speed up subsequent requests.'''
- return self._make_callback()
-
- def reset(self):
- ''' Forget any cached values. The next time :attr:`call` is accessed,
- all plugins are re-applied. '''
- self.__dict__.pop('call', None)
-
- def prepare(self):
- ''' Do all on-demand work immediately (useful for debugging).'''
- self.call
-
- @property
- def _context(self):
- depr('Switch to Plugin API v2 and access the Route object directly.')
- return dict(rule=self.rule, method=self.method, callback=self.callback,
- name=self.name, app=self.app, config=self.config,
- apply=self.plugins, skip=self.skiplist)
-
- def all_plugins(self):
- ''' Yield all Plugins affecting this route. '''
- unique = set()
- for p in reversed(self.app.plugins + self.plugins):
- if True in self.skiplist: break
- name = getattr(p, 'name', False)
- if name and (name in self.skiplist or name in unique): continue
- if p in self.skiplist or type(p) in self.skiplist: continue
- if name: unique.add(name)
- yield p
-
- def _make_callback(self):
- callback = self.callback
- for plugin in self.all_plugins():
- try:
- if hasattr(plugin, 'apply'):
- api = getattr(plugin, 'api', 1)
- context = self if api > 1 else self._context
- callback = plugin.apply(callback, context)
- else:
- callback = plugin(callback)
- except RouteReset: # Try again with changed configuration.
- return self._make_callback()
- if not callback is self.callback:
- try_update_wrapper(callback, self.callback)
- return callback
-
-
-
-
-
-
-###############################################################################
-# Application Object ###########################################################
-###############################################################################
-
-
-class Bottle(object):
- """ WSGI application """
-
- def __init__(self, catchall=True, autojson=True, config=None):
- """ Create a new bottle instance.
- You usually don't do that. Use `bottle.app.push()` instead.
- """
- self.routes = [] # List of installed :class:`Route` instances.
- self.router = Router() # Maps requests to :class:`Route` instances.
- self.plugins = [] # List of installed plugins.
-
- self.error_handler = {}
- #: If true, most exceptions are catched and returned as :exc:`HTTPError`
- self.config = ConfigDict(config or {})
- self.catchall = catchall
- #: An instance of :class:`HooksPlugin`. Empty by default.
- self.hooks = HooksPlugin()
- self.install(self.hooks)
- if autojson:
- self.install(JSONPlugin())
- self.install(TemplatePlugin())
-
- def mount(self, prefix, app, **options):
- ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific
- URL prefix. Example::
-
- root_app.mount('/admin/', admin_app)
-
- :param prefix: path prefix or `mount-point`. If it ends in a slash,
- that slash is mandatory.
- :param app: an instance of :class:`Bottle` or a WSGI application.
-
- All other parameters are passed to the underlying :meth:`route` call.
- '''
- if isinstance(app, basestring):
- prefix, app = app, prefix
- depr('Parameter order of Bottle.mount() changed.') # 0.10
-
- parts = filter(None, prefix.split('/'))
- if not parts: raise ValueError('Empty path prefix.')
- path_depth = len(parts)
- options.setdefault('skip', True)
- options.setdefault('method', 'ANY')
-
- @self.route('/%s/:#.*#' % '/'.join(parts), **options)
- def mountpoint():
- try:
- request.path_shift(path_depth)
- rs = BaseResponse([], 200)
- def start_response(status, header):
- rs.status = status
- for name, value in header: rs.add_header(name, value)
- return rs.body.append
- rs.body = itertools.chain(rs.body, app(request.environ, start_response))
- return HTTPResponse(rs.body, rs.status, rs.headers)
- finally:
- request.path_shift(-path_depth)
-
- if not prefix.endswith('/'):
- self.route('/' + '/'.join(parts), callback=mountpoint, **options)
-
- def install(self, plugin):
- ''' Add a plugin to the list of plugins and prepare it for being
- applied to all routes of this application. A plugin may be a simple
- decorator or an object that implements the :class:`Plugin` API.
- '''
- if hasattr(plugin, 'setup'): plugin.setup(self)
- if not callable(plugin) and not hasattr(plugin, 'apply'):
- raise TypeError("Plugins must be callable or implement .apply()")
- self.plugins.append(plugin)
- self.reset()
- return plugin
-
- def uninstall(self, plugin):
- ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type
- object to remove all plugins that match that type, a string to remove
- all plugins with a matching ``name`` attribute or ``True`` to remove all
- plugins. Return the list of removed plugins. '''
- removed, remove = [], plugin
- for i, plugin in list(enumerate(self.plugins))[::-1]:
- if remove is True or remove is plugin or remove is type(plugin) \
- or getattr(plugin, 'name', True) == remove:
- removed.append(plugin)
- del self.plugins[i]
- if hasattr(plugin, 'close'): plugin.close()
- if removed: self.reset()
- return removed
-
- def reset(self, route=None):
- ''' Reset all routes (force plugins to be re-applied) and clear all
- caches. If an ID or route object is given, only that specific route
- is affected. '''
- if route is None: routes = self.routes
- elif isinstance(route, Route): routes = [route]
- else: routes = [self.routes[route]]
- for route in routes: route.reset()
- if DEBUG:
- for route in routes: route.prepare()
- self.hooks.trigger('app_reset')
-
- def close(self):
- ''' Close the application and all installed plugins. '''
- for plugin in self.plugins:
- if hasattr(plugin, 'close'): plugin.close()
- self.stopped = True
-
- def match(self, environ):
- """ Search for a matching route and return a (:class:`Route` , urlargs)
- tuple. The second value is a dictionary with parameters extracted
- from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
- return self.router.match(environ)
-
- def get_url(self, routename, **kargs):
- """ Return a string that matches a named route """
- scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
- location = self.router.build(routename, **kargs).lstrip('/')
- return urljoin(urljoin('/', scriptname), location)
-
- def route(self, path=None, method='GET', callback=None, name=None,
- apply=None, skip=None, **config):
- """ A decorator to bind a function to a request URL. Example::
-
- @app.route('/hello/:name')
- def hello(name):
- return 'Hello %s' % name
-
- The ``:name`` part is a wildcard. See :class:`Router` for syntax
- details.
-
- :param path: Request path or a list of paths to listen to. If no
- path is specified, it is automatically generated from the
- signature of the function.
- :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
- methods to listen to. (default: `GET`)
- :param callback: An optional shortcut to avoid the decorator
- syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
- :param name: The name for this route. (default: None)
- :param apply: A decorator or plugin or a list of plugins. These are
- applied to the route callback in addition to installed plugins.
- :param skip: A list of plugins, plugin classes or names. Matching
- plugins are not installed to this route. ``True`` skips all.
-
- Any additional keyword arguments are stored as route-specific
- configuration and passed to plugins (see :meth:`Plugin.apply`).
- """
- if callable(path): path, callback = None, path
- plugins = makelist(apply)
- skiplist = makelist(skip)
- def decorator(callback):
- # TODO: Documentation and tests
- if isinstance(callback, basestring): callback = load(callback)
- for rule in makelist(path) or yieldroutes(callback):
- for verb in makelist(method):
- verb = verb.upper()
- route = Route(self, rule, verb, callback, name=name,
- plugins=plugins, skiplist=skiplist, **config)
- self.routes.append(route)
- self.router.add(rule, verb, route, name=name)
- if DEBUG: route.prepare()
- return callback
- return decorator(callback) if callback else decorator
-
- def get(self, path=None, method='GET', **options):
- """ Equals :meth:`route`. """
- return self.route(path, method, **options)
-
- def post(self, path=None, method='POST', **options):
- """ Equals :meth:`route` with a ``POST`` method parameter. """
- return self.route(path, method, **options)
-
- def put(self, path=None, method='PUT', **options):
- """ Equals :meth:`route` with a ``PUT`` method parameter. """
- return self.route(path, method, **options)
-
- def delete(self, path=None, method='DELETE', **options):
- """ Equals :meth:`route` with a ``DELETE`` method parameter. """
- return self.route(path, method, **options)
-
- def error(self, code=500):
- """ Decorator: Register an output handler for a HTTP error code"""
- def wrapper(handler):
- self.error_handler[int(code)] = handler
- return handler
- return wrapper
-
- def hook(self, name):
- """ Return a decorator that attaches a callback to a hook. """
- def wrapper(func):
- self.hooks.add(name, func)
- return func
- return wrapper
-
- def handle(self, path, method='GET'):
- """ (deprecated) Execute the first matching route callback and return
- the result. :exc:`HTTPResponse` exceptions are catched and returned.
- If :attr:`Bottle.catchall` is true, other exceptions are catched as
- well and returned as :exc:`HTTPError` instances (500).
- """
- depr("This method will change semantics in 0.10. Try to avoid it.")
- if isinstance(path, dict):
- return self._handle(path)
- return self._handle({'PATH_INFO': path, 'REQUEST_METHOD': method.upper()})
-
- def _handle(self, environ):
- try:
- route, args = self.router.match(environ)
- environ['route.handle'] = environ['bottle.route'] = route
- environ['route.url_args'] = args
- return route.call(**args)
- except HTTPResponse, r:
- return r
- except RouteReset:
- route.reset()
- return self._handle(environ)
- except (KeyboardInterrupt, SystemExit, MemoryError):
- raise
- except Exception, e:
- if not self.catchall: raise
- stacktrace = format_exc(10)
- environ['wsgi.errors'].write(stacktrace)
- return HTTPError(500, "Internal Server Error", e, stacktrace)
-
- def _cast(self, out, request, response, peek=None):
- """ Try to convert the parameter into something WSGI compatible and set
- correct HTTP headers when possible.
- Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
- iterable of strings and iterable of unicodes
- """
-
- # Empty output is done here
- if not out:
- response['Content-Length'] = 0
- return []
- # Join lists of byte or unicode strings. Mixed lists are NOT supported
- if isinstance(out, (tuple, list))\
- and isinstance(out[0], (bytes, unicode)):
- out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
- # Encode unicode strings
- if isinstance(out, unicode):
- out = out.encode(response.charset)
- # Byte Strings are just returned
- if isinstance(out, bytes):
- response['Content-Length'] = len(out)
- return [out]
- # HTTPError or HTTPException (recursive, because they may wrap anything)
- # TODO: Handle these explicitly in handle() or make them iterable.
- if isinstance(out, HTTPError):
- out.apply(response)
- out = self.error_handler.get(out.status, repr)(out)
- if isinstance(out, HTTPResponse):
- depr('Error handlers must not return :exc:`HTTPResponse`.') #0.9
- return self._cast(out, request, response)
- if isinstance(out, HTTPResponse):
- out.apply(response)
- return self._cast(out.output, request, response)
-
- # File-like objects.
- if hasattr(out, 'read'):
- if 'wsgi.file_wrapper' in request.environ:
- return request.environ['wsgi.file_wrapper'](out)
- elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
- return WSGIFileWrapper(out)
-
- # Handle Iterables. We peek into them to detect their inner type.
- try:
- out = iter(out)
- first = out.next()
- while not first:
- first = out.next()
- except StopIteration:
- return self._cast('', request, response)
- except HTTPResponse, e:
- first = e
- except Exception, e:
- first = HTTPError(500, 'Unhandled exception', e, format_exc(10))
- if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\
- or not self.catchall:
- raise
- # These are the inner types allowed in iterator or generator objects.
- if isinstance(first, HTTPResponse):
- return self._cast(first, request, response)
- if isinstance(first, bytes):
- return itertools.chain([first], out)
- if isinstance(first, unicode):
- return itertools.imap(lambda x: x.encode(response.charset),
- itertools.chain([first], out))
- return self._cast(HTTPError(500, 'Unsupported response type: %s'\
- % type(first)), request, response)
-
- def wsgi(self, environ, start_response):
- """ The bottle WSGI-interface. """
- try:
- environ['bottle.app'] = self
- request.bind(environ)
- response.bind()
- out = self._cast(self._handle(environ), request, response)
- # rfc2616 section 4.3
- if response._status_code in (100, 101, 204, 304)\
- or request.method == 'HEAD':
- if hasattr(out, 'close'): out.close()
- out = []
- start_response(response._status_line, list(response.iter_headers()))
- return out
- except (KeyboardInterrupt, SystemExit, MemoryError):
- raise
- except Exception, e:
- if not self.catchall: raise
- err = '<h1>Critical error while processing request: %s</h1>' \
- % environ.get('PATH_INFO', '/')
- if DEBUG:
- err += '<h2>Error:</h2>\n<pre>%s</pre>\n' % repr(e)
- err += '<h2>Traceback:</h2>\n<pre>%s</pre>\n' % format_exc(10)
- environ['wsgi.errors'].write(err) #TODO: wsgi.error should not get html
- start_response('500 INTERNAL SERVER ERROR', [('Content-Type', 'text/html')])
- return [tob(err)]
-
- def __call__(self, environ, start_response):
- ''' Each instance of :class:'Bottle' is a WSGI application. '''
- return self.wsgi(environ, start_response)
-
-
-
-
-
-
-###############################################################################
-# HTTP and WSGI Tools ##########################################################
-###############################################################################
-
-
-class BaseRequest(DictMixin):
- """ A wrapper for WSGI environment dictionaries that adds a lot of
- convenient access methods and properties. Most of them are read-only."""
-
- #: Maximum size of memory buffer for :attr:`body` in bytes.
- MEMFILE_MAX = 102400
-
- def __init__(self, environ):
- """ Wrap a WSGI environ dictionary. """
- #: The wrapped WSGI environ dictionary. This is the only real attribute.
- #: All other attributes actually are read-only properties.
- self.environ = environ
- environ['bottle.request'] = self
-
- @property
- def path(self):
- ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
- broken clients and avoid the "empty path" edge case). '''
- return '/' + self.environ.get('PATH_INFO','').lstrip('/')
-
- @property
- def method(self):
- ''' The ``REQUEST_METHOD`` value as an uppercase string. '''
- return self.environ.get('REQUEST_METHOD', 'GET').upper()
-
- @DictProperty('environ', 'bottle.request.headers', read_only=True)
- def headers(self):
- ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to
- HTTP request headers. '''
- return WSGIHeaderDict(self.environ)
-
- def get_header(self, name, default=None):
- ''' Return the value of a request header, or a given default value. '''
- return self.headers.get(name, default)
-
- @DictProperty('environ', 'bottle.request.cookies', read_only=True)
- def cookies(self):
- """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
- decoded. Use :meth:`get_cookie` if you expect signed cookies. """
- cookies = SimpleCookie(self.environ.get('HTTP_COOKIE',''))
- return FormsDict((c.key, c.value) for c in cookies.itervalues())
-
- def get_cookie(self, key, default=None, secret=None):
- """ Return the content of a cookie. To read a `Signed Cookie`, the
- `secret` must match the one used to create the cookie (see
- :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
- cookie or wrong signature), return a default value. """
- value = self.cookies.get(key)
- if secret and value:
- dec = cookie_decode(value, secret) # (key, value) tuple or None
- return dec[1] if dec and dec[0] == key else default
- return value or default
-
- @DictProperty('environ', 'bottle.request.query', read_only=True)
- def query(self):
- ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These
- values are sometimes called "URL arguments" or "GET parameters", but
- not to be confused with "URL wildcards" as they are provided by the
- :class:`Router`. '''
- data = parse_qs(self.query_string, keep_blank_values=True)
- get = self.environ['bottle.get'] = FormsDict()
- for key, values in data.iteritems():
- for value in values:
- get[key] = value
- return get
-
- @DictProperty('environ', 'bottle.request.forms', read_only=True)
- def forms(self):
- """ Form values parsed from an `url-encoded` or `multipart/form-data`
- encoded POST or PUT request body. The result is retuned as a
- :class:`FormsDict`. All keys and values are strings. File uploads
- are stored separately in :attr:`files`. """
- forms = FormsDict()
- for name, item in self.POST.iterallitems():
- if not hasattr(item, 'filename'):
- forms[name] = item
- return forms
-
- @DictProperty('environ', 'bottle.request.params', read_only=True)
- def params(self):
- """ A :class:`FormsDict` with the combined values of :attr:`query` and
- :attr:`forms`. File uploads are stored in :attr:`files`. """
- params = FormsDict()
- for key, value in self.query.iterallitems():
- params[key] = value
- for key, value in self.forms.iterallitems():
- params[key] = value
- return params
-
- @DictProperty('environ', 'bottle.request.files', read_only=True)
- def files(self):
- """ File uploads parsed from an `url-encoded` or `multipart/form-data`
- encoded POST or PUT request body. The values are instances of
- :class:`cgi.FieldStorage`. The most important attributes are:
-
- filename
- The filename, if specified; otherwise None; this is the client
- side filename, *not* the file name on which it is stored (that's
- a temporary file you don't deal with)
- file
- The file(-like) object from which you can read the data.
- value
- The value as a *string*; for file uploads, this transparently
- reads the file every time you request the value. Do not do this
- on big files.
- """
- files = FormsDict()
- for name, item in self.POST.iterallitems():
- if hasattr(item, 'filename'):
- files[name] = item
- return files
-
- @DictProperty('environ', 'bottle.request.json', read_only=True)
- def json(self):
- ''' If the ``Content-Type`` header is ``application/json``, this
- property holds the parsed content of the request body. Only requests
- smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
- exhaustion. '''
- if 'application/json' in self.environ.get('CONTENT_TYPE', '') \
- and 0 < self.content_length < self.MEMFILE_MAX:
- return json_loads(self.body.read(self.MEMFILE_MAX))
- return None
-
- @DictProperty('environ', 'bottle.request.body', read_only=True)
- def _body(self):
- maxread = max(0, self.content_length)
- stream = self.environ['wsgi.input']
- body = BytesIO() if maxread < self.MEMFILE_MAX else TemporaryFile(mode='w+b')
- while maxread > 0:
- part = stream.read(min(maxread, self.MEMFILE_MAX))
- if not part: break
- body.write(part)
- maxread -= len(part)
- self.environ['wsgi.input'] = body
- body.seek(0)
- return body
-
- @property
- def body(self):
- """ The HTTP request body as a seek-able file-like object. Depending on
- :attr:`MEMFILE_MAX`, this is either a temporary file or a
- :class:`io.BytesIO` instance. Accessing this property for the first
- time reads and replaces the ``wsgi.input`` environ variable.
- Subsequent accesses just do a `seek(0)` on the file object. """
- self._body.seek(0)
- return self._body
-
- #: An alias for :attr:`query`.
- GET = query
-
- @DictProperty('environ', 'bottle.request.post', read_only=True)
- def POST(self):
- """ The values of :attr:`forms` and :attr:`files` combined into a single
- :class:`FormsDict`. Values are either strings (form values) or
- instances of :class:`cgi.FieldStorage` (file uploads).
- """
- post = FormsDict()
- safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
- for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
- if key in self.environ: safe_env[key] = self.environ[key]
- if NCTextIOWrapper:
- fb = NCTextIOWrapper(self.body, encoding='ISO-8859-1', newline='\n')
- else:
- fb = self.body
- data = cgi.FieldStorage(fp=fb, environ=safe_env, keep_blank_values=True)
- for item in data.list or []:
- post[item.name] = item if item.filename else item.value
- return post
-
- @property
- def COOKIES(self):
- ''' Alias for :attr:`cookies` (deprecated). '''
- depr('BaseRequest.COOKIES was renamed to BaseRequest.cookies (lowercase).')
- return self.cookies
-
- @property
- def url(self):
- """ The full request URI including hostname and scheme. If your app
- lives behind a reverse proxy or load balancer and you get confusing
- results, make sure that the ``X-Forwarded-Host`` header is set
- correctly. """
- return self.urlparts.geturl()
-
- @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
- def urlparts(self):
- ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
- The tuple contains (scheme, host, path, query_string and fragment),
- but the fragment is always empty because it is not visible to the
- server. '''
- env = self.environ
- http = env.get('wsgi.url_scheme', 'http')
- host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
- if not host:
- # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
- host = env.get('SERVER_NAME', '127.0.0.1')
- port = env.get('SERVER_PORT')
- if port and port != ('80' if http == 'http' else '443'):
- host += ':' + port
- path = urlquote(self.fullpath)
- return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
-
- @property
- def fullpath(self):
- """ Request path including :attr:`script_name` (if present). """
- return urljoin(self.script_name, self.path.lstrip('/'))
-
- @property
- def query_string(self):
- """ The raw :attr:`query` part of the URL (everything in between ``?``
- and ``#``) as a string. """
- return self.environ.get('QUERY_STRING', '')
-
- @property
- def script_name(self):
- ''' The initial portion of the URL's `path` that was removed by a higher
- level (server or routing middleware) before the application was
- called. This script path is returned with leading and tailing
- slashes. '''
- script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
- return '/' + script_name + '/' if script_name else '/'
-
- def path_shift(self, shift=1):
- ''' Shift path segments from :attr:`path` to :attr:`script_name` and
- vice versa.
-
- :param shift: The number of path segments to shift. May be negative
- to change the shift direction. (default: 1)
- '''
- script = self.environ.get('SCRIPT_NAME','/')
- self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift)
-
- @property
- def content_length(self):
- ''' The request body length as an integer. The client is responsible to
- set this header. Otherwise, the real length of the body is unknown
- and -1 is returned. In this case, :attr:`body` will be empty. '''
- return int(self.environ.get('CONTENT_LENGTH') or -1)
-
- @property
- def is_xhr(self):
- ''' True if the request was triggered by a XMLHttpRequest. This only
- works with JavaScript libraries that support the `X-Requested-With`
- header (most of the popular libraries do). '''
- requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','')
- return requested_with.lower() == 'xmlhttprequest'
-
- @property
- def is_ajax(self):
- ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. '''
- return self.is_xhr
-
- @property
- def auth(self):
- """ HTTP authentication data as a (user, password) tuple. This
- implementation currently supports basic (not digest) authentication
- only. If the authentication happened at a higher level (e.g. in the
- front web-server or a middleware), the password field is None, but
- the user field is looked up from the ``REMOTE_USER`` environ
- variable. On any errors, None is returned. """
- basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION',''))
- if basic: return basic
- ruser = self.environ.get('REMOTE_USER')
- if ruser: return (ruser, None)
- return None
-
- @property
- def remote_route(self):
- """ A list of all IPs that were involved in this request, starting with
- the client IP and followed by zero or more proxies. This does only
- work if all proxies support the ```X-Forwarded-For`` header. Note
- that this information can be forged by malicious clients. """
- proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
- if proxy: return [ip.strip() for ip in proxy.split(',')]
- remote = self.environ.get('REMOTE_ADDR')
- return [remote] if remote else []
-
- @property
- def remote_addr(self):
- """ The client IP as a string. Note that this information can be forged
- by malicious clients. """
- route = self.remote_route
- return route[0] if route else None
-
- def copy(self):
- """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
- return Request(self.environ.copy())
-
- def __getitem__(self, key): return self.environ[key]
- def __delitem__(self, key): self[key] = ""; del(self.environ[key])
- def __iter__(self): return iter(self.environ)
- def __len__(self): return len(self.environ)
- def keys(self): return self.environ.keys()
- def __setitem__(self, key, value):
- """ Change an environ value and clear all caches that depend on it. """
-
- if self.environ.get('bottle.request.readonly'):
- raise KeyError('The environ dictionary is read-only.')
-
- self.environ[key] = value
- todelete = ()
-
- if key == 'wsgi.input':
- todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
- elif key == 'QUERY_STRING':
- todelete = ('query', 'params')
- elif key.startswith('HTTP_'):
- todelete = ('headers', 'cookies')
-
- for key in todelete:
- self.environ.pop('bottle.request.'+key, None)
-
- def __repr__(self):
- return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
-
-def _hkey(s):
- return s.title().replace('_','-')
-
-
-class HeaderProperty(object):
- def __init__(self, name, reader=None, writer=str, default=''):
- self.name, self.reader, self.writer, self.default = name, reader, writer, default
- self.__doc__ = 'Current value of the %r header.' % name.title()
-
- def __get__(self, obj, cls):
- if obj is None: return self
- value = obj.headers.get(self.name)
- return self.reader(value) if (value and self.reader) else (value or self.default)
-
- def __set__(self, obj, value):
- if self.writer: value = self.writer(value)
- obj.headers[self.name] = value
-
- def __delete__(self, obj):
- if self.name in obj.headers:
- del obj.headers[self.name]
-
-
-class BaseResponse(object):
- """ Storage class for a response body as well as headers and cookies.
-
- This class does support dict-like case-insensitive item-access to
- headers, but is NOT a dict. Most notably, iterating over a response
- yields parts of the body and not the headers.
- """
-
- default_status = 200
- default_content_type = 'text/html; charset=UTF-8'
-
- # Header blacklist for specific response codes
- # (rfc2616 section 10.2.3 and 10.3.5)
- bad_headers = {
- 204: set(('Content-Type',)),
- 304: set(('Allow', 'Content-Encoding', 'Content-Language',
- 'Content-Length', 'Content-Range', 'Content-Type',
- 'Content-Md5', 'Last-Modified'))}
-
- def __init__(self, body='', status=None, **headers):
- self._status_line = None
- self._status_code = None
- self.body = body
- self._cookies = None
- self._headers = {'Content-Type': [self.default_content_type]}
- self.status = status or self.default_status
- if headers:
- for name, value in headers.items():
- self[name] = value
-
- def copy(self):
- ''' Returns a copy of self. '''
- copy = Response()
- copy.status = self.status
- copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
- return copy
-
- def __iter__(self):
- return iter(self.body)
-
- def close(self):
- if hasattr(self.body, 'close'):
- self.body.close()
-
- @property
- def status_line(self):
- ''' The HTTP status line as a string (e.g. ``404 Not Found``).'''
- return self._status_line
-
- @property
- def status_code(self):
- ''' The HTTP status code as an integer (e.g. 404).'''
- return self._status_code
-
- def _set_status(self, status):
- if isinstance(status, int):
- code, status = status, _HTTP_STATUS_LINES.get(status)
- elif ' ' in status:
- status = status.strip()
- code = int(status.split()[0])
- else:
- raise ValueError('String status line without a reason phrase.')
- if not 100 <= code <= 999: raise ValueError('Status code out of range.')
- self._status_code = code
- self._status_line = status or ('%d Unknown' % code)
-
- def _get_status(self):
- depr('BaseReuqest.status will change to return a string in 0.11. Use'\
- ' status_line and status_code to make sure.') #0.10
- return self._status_code
-
- status = property(_get_status, _set_status, None,
- ''' A writeable property to change the HTTP response status. It accepts
- either a numeric code (100-999) or a string with a custom reason
- phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
- :data:`status_code` are updates accordingly. The return value is
- always a numeric code. ''')
- del _get_status, _set_status
-
- @property
- def headers(self):
- ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like
- view on the response headers. '''
- self.__dict__['headers'] = hdict = HeaderDict()
- hdict.dict = self._headers
- return hdict
-
- def __contains__(self, name): return _hkey(name) in self._headers
- def __delitem__(self, name): del self._headers[_hkey(name)]
- def __getitem__(self, name): return self._headers[_hkey(name)][-1]
- def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)]
-
- def get_header(self, name, default=None):
- ''' Return the value of a previously defined header. If there is no
- header with that name, return a default value. '''
- return self._headers.get(_hkey(name), [default])[-1]
-
- def set_header(self, name, value, append=False):
- ''' Create a new response header, replacing any previously defined
- headers with the same name. '''
- if append:
- self.add_header(name, value)
- else:
- self._headers[_hkey(name)] = [str(value)]
-
- def add_header(self, name, value):
- ''' Add an additional response header, not removing duplicates. '''
- self._headers.setdefault(_hkey(name), []).append(str(value))
-
- def iter_headers(self):
- ''' Yield (header, value) tuples, skipping headers that are not
- allowed with the current response status code. '''
- headers = self._headers.iteritems()
- bad_headers = self.bad_headers.get(self.status_code)
- if bad_headers:
- headers = [h for h in headers if h[0] not in bad_headers]
- for name, values in headers:
- for value in values:
- yield name, value
- if self._cookies:
- for c in self._cookies.values():
- yield 'Set-Cookie', c.OutputString()
-
- def wsgiheader(self):
- depr('The wsgiheader method is deprecated. See headerlist.') #0.10
- return self.headerlist
-
- @property
- def headerlist(self):
- ''' WSGI conform list of (header, value) tuples. '''
- return list(self.iter_headers())
-
- content_type = HeaderProperty('Content-Type')
- content_length = HeaderProperty('Content-Length', reader=int)
-
- @property
- def charset(self):
- """ Return the charset specified in the content-type header (default: utf8). """
- if 'charset=' in self.content_type:
- return self.content_type.split('charset=')[-1].split(';')[0].strip()
- return 'UTF-8'
-
- @property
- def COOKIES(self):
- """ A dict-like SimpleCookie instance. This should not be used directly.
- See :meth:`set_cookie`. """
- depr('The COOKIES dict is deprecated. Use `set_cookie()` instead.') # 0.10
- if not self._cookies:
- self._cookies = SimpleCookie()
- return self._cookies
-
- def set_cookie(self, name, value, secret=None, **options):
- ''' Create a new cookie or replace an old one. If the `secret` parameter is
- set, create a `Signed Cookie` (described below).
-
- :param name: the name of the cookie.
- :param value: the value of the cookie.
- :param secret: a signature key required for signed cookies.
-
- Additionally, this method accepts all RFC 2109 attributes that are
- supported by :class:`cookie.Morsel`, including:
-
- :param max_age: maximum age in seconds. (default: None)
- :param expires: a datetime object or UNIX timestamp. (default: None)
- :param domain: the domain that is allowed to read the cookie.
- (default: current domain)
- :param path: limits the cookie to a given path (default: current path)
- :param secure: limit the cookie to HTTPS connections (default: off).
- :param httponly: prevents client-side javascript to read this cookie
- (default: off, requires Python 2.6 or newer).
-
- If neither `expires` nor `max_age` is set (default), the cookie will
- expire at the end of the browser session (as soon as the browser
- window is closed).
-
- Signed cookies may store any pickle-able object and are
- cryptographically signed to prevent manipulation. Keep in mind that
- cookies are limited to 4kb in most browsers.
-
- Warning: Signed cookies are not encrypted (the client can still see
- the content) and not copy-protected (the client can restore an old
- cookie). The main intention is to make pickling and unpickling
- save, not to store secret information at client side.
- '''
- if not self._cookies:
- self._cookies = SimpleCookie()
-
- if secret:
- value = touni(cookie_encode((name, value), secret))
- elif not isinstance(value, basestring):
- raise TypeError('Secret key missing for non-string Cookie.')
-
- if len(value) > 4096: raise ValueError('Cookie value to long.')
- self._cookies[name] = value
-
- for key, value in options.iteritems():
- if key == 'max_age':
- if isinstance(value, timedelta):
- value = value.seconds + value.days * 24 * 3600
- if key == 'expires':
- if isinstance(value, (datedate, datetime)):
- value = value.timetuple()
- elif isinstance(value, (int, float)):
- value = time.gmtime(value)
- value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
- self._cookies[name][key.replace('_', '-')] = value
-
- def delete_cookie(self, key, **kwargs):
- ''' Delete a cookie. Be sure to use the same `domain` and `path`
- settings as used to create the cookie. '''
- kwargs['max_age'] = -1
- kwargs['expires'] = 0
- self.set_cookie(key, '', **kwargs)
-
- def __repr__(self):
- out = ''
- for name, value in self.headerlist:
- out += '%s: %s\n' % (name.title(), value.strip())
- return out
-
-
-class LocalRequest(BaseRequest, threading.local):
- ''' A thread-local subclass of :class:`BaseRequest`. '''
- def __init__(self): pass
- bind = BaseRequest.__init__
-
-
-class LocalResponse(BaseResponse, threading.local):
- ''' A thread-local subclass of :class:`BaseResponse`. '''
- bind = BaseResponse.__init__
-
-Response = LocalResponse # BC 0.9
-Request = LocalRequest # BC 0.9
-
-
-
-
-
-
-###############################################################################
-# Plugins ######################################################################
-###############################################################################
-
-class PluginError(BottleException): pass
-
-class JSONPlugin(object):
- name = 'json'
- api = 2
-
- def __init__(self, json_dumps=json_dumps):
- self.json_dumps = json_dumps
-
- def apply(self, callback, context):
- dumps = self.json_dumps
- if not dumps: return callback
- def wrapper(*a, **ka):
- rv = callback(*a, **ka)
- if isinstance(rv, dict):
- #Attempt to serialize, raises exception on failure
- json_response = dumps(rv)
- #Set content type only if serialization succesful
- response.content_type = 'application/json'
- return json_response
- return rv
- return wrapper
-
-
-class HooksPlugin(object):
- name = 'hooks'
- api = 2
-
- _names = 'before_request', 'after_request', 'app_reset'
-
- def __init__(self):
- self.hooks = dict((name, []) for name in self._names)
- self.app = None
-
- def _empty(self):
- return not (self.hooks['before_request'] or self.hooks['after_request'])
-
- def setup(self, app):
- self.app = app
-
- def add(self, name, func):
- ''' Attach a callback to a hook. '''
- was_empty = self._empty()
- self.hooks.setdefault(name, []).append(func)
- if self.app and was_empty and not self._empty(): self.app.reset()
-
- def remove(self, name, func):
- ''' Remove a callback from a hook. '''
- was_empty = self._empty()
- if name in self.hooks and func in self.hooks[name]:
- self.hooks[name].remove(func)
- if self.app and not was_empty and self._empty(): self.app.reset()
-
- def trigger(self, name, *a, **ka):
- ''' Trigger a hook and return a list of results. '''
- hooks = self.hooks[name]
- if ka.pop('reversed', False): hooks = hooks[::-1]
- return [hook(*a, **ka) for hook in hooks]
-
- def apply(self, callback, context):
- if self._empty(): return callback
- def wrapper(*a, **ka):
- self.trigger('before_request')
- rv = callback(*a, **ka)
- self.trigger('after_request', reversed=True)
- return rv
- return wrapper
-
-
-class TemplatePlugin(object):
- ''' This plugin applies the :func:`view` decorator to all routes with a
- `template` config parameter. If the parameter is a tuple, the second
- element must be a dict with additional options (e.g. `template_engine`)
- or default variables for the template. '''
- name = 'template'
- api = 2
-
- def apply(self, callback, route):
- conf = route.config.get('template')
- if isinstance(conf, (tuple, list)) and len(conf) == 2:
- return view(conf[0], **conf[1])(callback)
- elif isinstance(conf, str) and 'template_opts' in route.config:
- depr('The `template_opts` parameter is deprecated.') #0.9
- return view(conf, **route.config['template_opts'])(callback)
- elif isinstance(conf, str):
- return view(conf)(callback)
- else:
- return callback
-
-
-#: Not a plugin, but part of the plugin API. TODO: Find a better place.
-class _ImportRedirect(object):
- def __init__(self, name, impmask):
- ''' Create a virtual package that redirects imports (see PEP 302). '''
- self.name = name
- self.impmask = impmask
- self.module = sys.modules.setdefault(name, imp.new_module(name))
- self.module.__dict__.update({'__file__': __file__, '__path__': [],
- '__all__': [], '__loader__': self})
- sys.meta_path.append(self)
-
- def find_module(self, fullname, path=None):
- if '.' not in fullname: return
- packname, modname = fullname.rsplit('.', 1)
- if packname != self.name: return
- return self
-
- def load_module(self, fullname):
- if fullname in sys.modules: return sys.modules[fullname]
- packname, modname = fullname.rsplit('.', 1)
- realname = self.impmask % modname
- __import__(realname)
- module = sys.modules[fullname] = sys.modules[realname]
- setattr(self.module, modname, module)
- module.__loader__ = self
- return module
-
-
-
-
-
-
-###############################################################################
-# Common Utilities #############################################################
-###############################################################################
-
-
-class MultiDict(DictMixin):
- """ This dict stores multiple values per key, but behaves exactly like a
- normal dict in that it returns only the newest value for any given key.
- There are special methods available to access the full list of values.
- """
-
- def __init__(self, *a, **k):
- self.dict = dict((k, [v]) for k, v in dict(*a, **k).iteritems())
- def __len__(self): return len(self.dict)
- def __iter__(self): return iter(self.dict)
- def __contains__(self, key): return key in self.dict
- def __delitem__(self, key): del self.dict[key]
- def __getitem__(self, key): return self.dict[key][-1]
- def __setitem__(self, key, value): self.append(key, value)
- def iterkeys(self): return self.dict.iterkeys()
- def itervalues(self): return (v[-1] for v in self.dict.itervalues())
- def iteritems(self): return ((k, v[-1]) for (k, v) in self.dict.iteritems())
- def iterallitems(self):
- for key, values in self.dict.iteritems():
- for value in values:
- yield key, value
-
- # 2to3 is not able to fix these automatically.
- keys = iterkeys if py3k else lambda self: list(self.iterkeys())
- values = itervalues if py3k else lambda self: list(self.itervalues())
- items = iteritems if py3k else lambda self: list(self.iteritems())
- allitems = iterallitems if py3k else lambda self: list(self.iterallitems())
-
- def get(self, key, default=None, index=-1, type=None):
- ''' Return the most recent value for a key.
-
- :param default: The default value to be returned if the key is not
- present or the type conversion fails.
- :param index: An index for the list of available values.
- :param type: If defined, this callable is used to cast the value
- into a specific type. Exception are suppressed and result in
- the default value to be returned.
- '''
- try:
- val = self.dict[key][index]
- return type(val) if type else val
- except Exception, e:
- pass
- return default
-
- def append(self, key, value):
- ''' Add a new value to the list of values for this key. '''
- self.dict.setdefault(key, []).append(value)
-
- def replace(self, key, value):
- ''' Replace the list of values with a single value. '''
- self.dict[key] = [value]
-
- def getall(self, key):
- ''' Return a (possibly empty) list of values for a key. '''
- return self.dict.get(key) or []
-
- #: Aliases for WTForms to mimic other multi-dict APIs (Django)
- getone = get
- getlist = getall
-
-
-
-class FormsDict(MultiDict):
- ''' This :class:`MultiDict` subclass is used to store request form data.
- Additionally to the normal dict-like item access methods (which return
- unmodified data as native strings), this container also supports
- attribute-like access to its values. Attribues are automatiically de- or
- recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
- attributes default to an empty string. '''
-
- #: Encoding used for attribute values.
- input_encoding = 'utf8'
-
- def getunicode(self, name, default=None, encoding=None):
- value, enc = self.get(name, default), encoding or self.input_encoding
- try:
- if isinstance(value, bytes): # Python 2 WSGI
- return value.decode(enc)
- elif isinstance(value, unicode): # Python 3 WSGI
- return value.encode('latin1').decode(enc)
- return value
- except UnicodeError, e:
- return default
-
- def __getattr__(self, name): return self.getunicode(name, default=u'')
-
-
-class HeaderDict(MultiDict):
- """ A case-insensitive version of :class:`MultiDict` that defaults to
- replace the old value instead of appending it. """
-
- def __init__(self, *a, **ka):
- self.dict = {}
- if a or ka: self.update(*a, **ka)
-
- def __contains__(self, key): return _hkey(key) in self.dict
- def __delitem__(self, key): del self.dict[_hkey(key)]
- def __getitem__(self, key): return self.dict[_hkey(key)][-1]
- def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)]
- def append(self, key, value):
- self.dict.setdefault(_hkey(key), []).append(str(value))
- def replace(self, key, value): self.dict[_hkey(key)] = [str(value)]
- def getall(self, key): return self.dict.get(_hkey(key)) or []
- def get(self, key, default=None, index=-1):
- return MultiDict.get(self, _hkey(key), default, index)
- def filter(self, names):
- for name in map(_hkey, names):
- if name in self.dict:
- del self.dict[name]
-
-
-class WSGIHeaderDict(DictMixin):
- ''' This dict-like class wraps a WSGI environ dict and provides convenient
- access to HTTP_* fields. Keys and values are native strings
- (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
- environment contains non-native string values, these are de- or encoded
- using a lossless 'latin1' character set.
-
- The API will remain stable even on changes to the relevant PEPs.
- Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
- that uses non-native strings.)
- '''
- #: List of keys that do not have a 'HTTP_' prefix.
- cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
-
- def __init__(self, environ):
- self.environ = environ
-
- def _ekey(self, key):
- ''' Translate header field name to CGI/WSGI environ key. '''
- key = key.replace('-','_').upper()
- if key in self.cgikeys:
- return key
- return 'HTTP_' + key
-
- def raw(self, key, default=None):
- ''' Return the header value as is (may be bytes or unicode). '''
- return self.environ.get(self._ekey(key), default)
-
- def __getitem__(self, key):
- return tonat(self.environ[self._ekey(key)], 'latin1')
-
- def __setitem__(self, key, value):
- raise TypeError("%s is read-only." % self.__class__)
-
- def __delitem__(self, key):
- raise TypeError("%s is read-only." % self.__class__)
-
- def __iter__(self):
- for key in self.environ:
- if key[:5] == 'HTTP_':
- yield key[5:].replace('_', '-').title()
- elif key in self.cgikeys:
- yield key.replace('_', '-').title()
-
- def keys(self): return [x for x in self]
- def __len__(self): return len(self.keys())
- def __contains__(self, key): return self._ekey(key) in self.environ
-
-
-class ConfigDict(dict):
- ''' A dict-subclass with some extras: You can access keys like attributes.
- Uppercase attributes create new ConfigDicts and act as name-spaces.
- Other missing attributes return None. Calling a ConfigDict updates its
- values and returns itself.
-
- >>> cfg = ConfigDict()
- >>> cfg.Namespace.value = 5
- >>> cfg.OtherNamespace(a=1, b=2)
- >>> cfg
- {'Namespace': {'value': 5}, 'OtherNamespace': {'a': 1, 'b': 2}}
- '''
-
- def __getattr__(self, key):
- if key not in self and key[0].isupper():
- self[key] = ConfigDict()
- return self.get(key)
-
- def __setattr__(self, key, value):
- if hasattr(dict, key):
- raise AttributeError('Read-only attribute.')
- if key in self and self[key] and isinstance(self[key], ConfigDict):
- raise AttributeError('Non-empty namespace attribute.')
- self[key] = value
-
- def __delattr__(self, key):
- if key in self: del self[key]
-
- def __call__(self, *a, **ka):
- for key, value in dict(*a, **ka).iteritems(): setattr(self, key, value)
- return self
-
-
-class AppStack(list):
- """ A stack-like list. Calling it returns the head of the stack. """
-
- def __call__(self):
- """ Return the current default application. """
- return self[-1]
-
- def push(self, value=None):
- """ Add a new :class:`Bottle` instance to the stack """
- if not isinstance(value, Bottle):
- value = Bottle()
- self.append(value)
- return value
-
-
-class WSGIFileWrapper(object):
-
- def __init__(self, fp, buffer_size=1024*64):
- self.fp, self.buffer_size = fp, buffer_size
- for attr in ('fileno', 'close', 'read', 'readlines'):
- if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
-
- def __iter__(self):
- read, buff = self.fp.read, self.buffer_size
- while True:
- part = read(buff)
- if not part: break
- yield part
-
-
-
-
-
-
-###############################################################################
-# Application Helper ###########################################################
-###############################################################################
-
-
-def abort(code=500, text='Unknown Error: Application stopped.'):
- """ Aborts execution and causes a HTTP error. """
- raise HTTPError(code, text)
-
-
-def redirect(url, code=None):
- """ Aborts execution and causes a 303 or 302 redirect, depending on
- the HTTP protocol version. """
- if code is None:
- code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
- location = urljoin(request.url, url)
- raise HTTPResponse("", status=code, header=dict(Location=location))
-
-
-def static_file(filename, root, mimetype='auto', download=False):
- """ Open a file in a safe way and return :exc:`HTTPResponse` with status
- code 200, 305, 401 or 404. Set Content-Type, Content-Encoding,
- Content-Length and Last-Modified header. Obey If-Modified-Since header
- and HEAD requests.
- """
- root = os.path.abspath(root) + os.sep
- filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
- header = dict()
-
- if not filename.startswith(root):
- return HTTPError(403, "Access denied.")
- if not os.path.exists(filename) or not os.path.isfile(filename):
- return HTTPError(404, "File does not exist.")
- if not os.access(filename, os.R_OK):
- return HTTPError(403, "You do not have permission to access this file.")
-
- if mimetype == 'auto':
- mimetype, encoding = mimetypes.guess_type(filename)
- if mimetype: header['Content-Type'] = mimetype
- if encoding: header['Content-Encoding'] = encoding
- elif mimetype:
- header['Content-Type'] = mimetype
-
- if download:
- download = os.path.basename(filename if download == True else download)
- header['Content-Disposition'] = 'attachment; filename="%s"' % download
-
- stats = os.stat(filename)
- header['Content-Length'] = stats.st_size
- lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
- header['Last-Modified'] = lm
-
- ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
- if ims:
- ims = parse_date(ims.split(";")[0].strip())
- if ims is not None and ims >= int(stats.st_mtime):
- header['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
- return HTTPResponse(status=304, header=header)
-
- body = '' if request.method == 'HEAD' else open(filename, 'rb')
- return HTTPResponse(body, header=header)
-
-
-
-
-
-
-###############################################################################
-# HTTP Utilities and MISC (TODO) ###############################################
-###############################################################################
-
-
-def debug(mode=True):
- """ Change the debug level.
- There is only one debug level supported at the moment."""
- global DEBUG
- DEBUG = bool(mode)
-
-
-def parse_date(ims):
- """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
- try:
- ts = email.utils.parsedate_tz(ims)
- return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone
- except (TypeError, ValueError, IndexError, OverflowError):
- return None
-
-
-def parse_auth(header):
- """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
- try:
- method, data = header.split(None, 1)
- if method.lower() == 'basic':
- #TODO: Add 2to3 save base64[encode/decode] functions.
- user, pwd = touni(base64.b64decode(tob(data))).split(':',1)
- return user, pwd
- except (KeyError, ValueError):
- return None
-
-
-def _lscmp(a, b):
- ''' Compares two strings in a cryptographically save way:
- Runtime is not affected by length of common prefix. '''
- return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b)
-
-
-def cookie_encode(data, key):
- ''' Encode and sign a pickle-able object. Return a (byte) string '''
- msg = base64.b64encode(pickle.dumps(data, -1))
- sig = base64.b64encode(hmac.new(tob(key), msg).digest())
- return tob('!') + sig + tob('?') + msg
-
-
-def cookie_decode(data, key):
- ''' Verify and decode an encoded string. Return an object or None.'''
- data = tob(data)
- if cookie_is_encoded(data):
- sig, msg = data.split(tob('?'), 1)
- if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
- return pickle.loads(base64.b64decode(msg))
- return None
-
-
-def cookie_is_encoded(data):
- ''' Return True if the argument looks like a encoded cookie.'''
- return bool(data.startswith(tob('!')) and tob('?') in data)
-
-
-def html_escape(string):
- ''' Escape HTML special characters ``&<>`` and quotes ``'"``. '''
- return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')\
- .replace('"','&quot;').replace("'",'&#039;')
-
-
-def html_quote(string):
- ''' Escape and quote a string to be used as an HTTP attribute.'''
- return '"%s"' % html_escape(string).replace('\n','%#10;')\
- .replace('\r','&#13;').replace('\t','&#9;')
-
-
-def yieldroutes(func):
- """ Return a generator for routes that match the signature (name, args)
- of the func parameter. This may yield more than one route if the function
- takes optional keyword arguments. The output is best described by example::
-
- a() -> '/a'
- b(x, y) -> '/b/:x/:y'
- c(x, y=5) -> '/c/:x' and '/c/:x/:y'
- d(x=5, y=6) -> '/d' and '/d/:x' and '/d/:x/:y'
- """
- import inspect # Expensive module. Only import if necessary.
- path = '/' + func.__name__.replace('__','/').lstrip('/')
- spec = inspect.getargspec(func)
- argc = len(spec[0]) - len(spec[3] or [])
- path += ('/:%s' * argc) % tuple(spec[0][:argc])
- yield path
- for arg in spec[0][argc:]:
- path += '/:%s' % arg
- yield path
-
-
-def path_shift(script_name, path_info, shift=1):
- ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
-
- :return: The modified paths.
- :param script_name: The SCRIPT_NAME path.
- :param script_name: The PATH_INFO path.
- :param shift: The number of path fragments to shift. May be negative to
- change the shift direction. (default: 1)
- '''
- if shift == 0: return script_name, path_info
- pathlist = path_info.strip('/').split('/')
- scriptlist = script_name.strip('/').split('/')
- if pathlist and pathlist[0] == '': pathlist = []
- if scriptlist and scriptlist[0] == '': scriptlist = []
- if shift > 0 and shift <= len(pathlist):
- moved = pathlist[:shift]
- scriptlist = scriptlist + moved
- pathlist = pathlist[shift:]
- elif shift < 0 and shift >= -len(scriptlist):
- moved = scriptlist[shift:]
- pathlist = moved + pathlist
- scriptlist = scriptlist[:shift]
- else:
- empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
- raise AssertionError("Cannot shift. Nothing left from %s" % empty)
- new_script_name = '/' + '/'.join(scriptlist)
- new_path_info = '/' + '/'.join(pathlist)
- if path_info.endswith('/') and pathlist: new_path_info += '/'
- return new_script_name, new_path_info
-
-
-def validate(**vkargs):
- """
- Validates and manipulates keyword arguments by user defined callables.
- Handles ValueError and missing arguments by raising HTTPError(403).
- """
- depr('Use route wildcard filters instead.')
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args, **kargs):
- for key, value in vkargs.iteritems():
- if key not in kargs:
- abort(403, 'Missing parameter: %s' % key)
- try:
- kargs[key] = value(kargs[key])
- except ValueError:
- abort(403, 'Wrong parameter format for: %s' % key)
- return func(*args, **kargs)
- return wrapper
- return decorator
-
-
-def auth_basic(check, realm="private", text="Access denied"):
- ''' Callback decorator to require HTTP auth (basic).
- TODO: Add route(check_auth=...) parameter. '''
- def decorator(func):
- def wrapper(*a, **ka):
- user, password = request.auth or (None, None)
- if user is None or not check(user, password):
- response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % realm
- return HTTPError(401, text)
- return func(*a, **ka)
- return wrapper
- return decorator
-
-
-def make_default_app_wrapper(name):
- ''' Return a callable that relays calls to the current default app. '''
- @functools.wraps(getattr(Bottle, name))
- def wrapper(*a, **ka):
- return getattr(app(), name)(*a, **ka)
- return wrapper
-
-
-for name in '''route get post put delete error mount
- hook install uninstall'''.split():
- globals()[name] = make_default_app_wrapper(name)
-url = make_default_app_wrapper('get_url')
-del name
-
-
-
-
-
-
-###############################################################################
-# Server Adapter ###############################################################
-###############################################################################
-
-
-class ServerAdapter(object):
- quiet = False
- def __init__(self, host='127.0.0.1', port=8080, **config):
- self.options = config
- self.host = host
- self.port = int(port)
-
- def run(self, handler): # pragma: no cover
- pass
-
- def __repr__(self):
- args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()])
- return "%s(%s)" % (self.__class__.__name__, args)
-
-
-class CGIServer(ServerAdapter):
- quiet = True
- def run(self, handler): # pragma: no cover
- from wsgiref.handlers import CGIHandler
- def fixed_environ(environ, start_response):
- environ.setdefault('PATH_INFO', '')
- return handler(environ, start_response)
- CGIHandler().run(fixed_environ)
-
-
-class FlupFCGIServer(ServerAdapter):
- def run(self, handler): # pragma: no cover
- import flup.server.fcgi
- self.options.setdefault('bindAddress', (self.host, self.port))
- flup.server.fcgi.WSGIServer(handler, **self.options).run()
-
-
-class WSGIRefServer(ServerAdapter):
- def run(self, handler): # pragma: no cover
- from wsgiref.simple_server import make_server, WSGIRequestHandler
- if self.quiet:
- class QuietHandler(WSGIRequestHandler):
- def log_request(*args, **kw): pass
- self.options['handler_class'] = QuietHandler
- srv = make_server(self.host, self.port, handler, **self.options)
- srv.serve_forever()
-
-
-class CherryPyServer(ServerAdapter):
- def run(self, handler): # pragma: no cover
- from cherrypy import wsgiserver
- server = wsgiserver.CherryPyWSGIServer((self.host, self.port), handler)
- try:
- server.start()
- finally:
- server.stop()
-
-
-class PasteServer(ServerAdapter):
- def run(self, handler): # pragma: no cover
- from paste import httpserver
- if not self.quiet:
- from paste.translogger import TransLogger
- handler = TransLogger(handler)
- httpserver.serve(handler, host=self.host, port=str(self.port),
- **self.options)
-
-
-class MeinheldServer(ServerAdapter):
- def run(self, handler):
- from meinheld import server
- server.listen((self.host, self.port))
- server.run(handler)
-
-
-class FapwsServer(ServerAdapter):
- """ Extremely fast webserver using libev. See http://www.fapws.org/ """
- def run(self, handler): # pragma: no cover
- import fapws._evwsgi as evwsgi
- from fapws import base, config
- port = self.port
- if float(config.SERVER_IDENT[-2:]) > 0.4:
- # fapws3 silently changed its API in 0.5
- port = str(port)
- evwsgi.start(self.host, port)
- # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
- if 'BOTTLE_CHILD' in os.environ and not self.quiet:
- print "WARNING: Auto-reloading does not work with Fapws3."
- print " (Fapws3 breaks python thread support)"
- evwsgi.set_base_module(base)
- def app(environ, start_response):
- environ['wsgi.multiprocess'] = False
- return handler(environ, start_response)
- evwsgi.wsgi_cb(('', app))
- evwsgi.run()
-
-
-class TornadoServer(ServerAdapter):
- """ The super hyped asynchronous server by facebook. Untested. """
- def run(self, handler): # pragma: no cover
- import tornado.wsgi, tornado.httpserver, tornado.ioloop
- container = tornado.wsgi.WSGIContainer(handler)
- server = tornado.httpserver.HTTPServer(container)
- server.listen(port=self.port)
- tornado.ioloop.IOLoop.instance().start()
-
-
-class AppEngineServer(ServerAdapter):
- """ Adapter for Google App Engine. """
- quiet = True
- def run(self, handler):
- from google.appengine.ext.webapp import util
- # A main() function in the handler script enables 'App Caching'.
- # Lets makes sure it is there. This _really_ improves performance.
- module = sys.modules.get('__main__')
- if module and not hasattr(module, 'main'):
- module.main = lambda: util.run_wsgi_app(handler)
- util.run_wsgi_app(handler)
-
-
-class TwistedServer(ServerAdapter):
- """ Untested. """
- def run(self, handler):
- from twisted.web import server, wsgi
- from twisted.python.threadpool import ThreadPool
- from twisted.internet import reactor
- thread_pool = ThreadPool()
- thread_pool.start()
- reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
- factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
- reactor.listenTCP(self.port, factory, interface=self.host)
- reactor.run()
-
-
-class DieselServer(ServerAdapter):
- """ Untested. """
- def run(self, handler):
- from diesel.protocols.wsgi import WSGIApplication
- app = WSGIApplication(handler, port=self.port)
- app.run()
-
-
-class GeventServer(ServerAdapter):
- """ Untested. Options:
-
- * `monkey` (default: True) fixes the stdlib to use greenthreads.
- * `fast` (default: False) uses libevent's http server, but has some
- issues: No streaming, no pipelining, no SSL.
- """
- def run(self, handler):
- from gevent import wsgi as wsgi_fast, pywsgi, monkey, local
- if self.options.get('monkey', True):
- if not threading.local is local.local: monkey.patch_all()
- wsgi = wsgi_fast if self.options.get('fast') else pywsgi
- wsgi.WSGIServer((self.host, self.port), handler).serve_forever()
-
-
-class GunicornServer(ServerAdapter):
- """ Untested. See http://gunicorn.org/configure.html for options. """
- def run(self, handler):
- from gunicorn.app.base import Application
-
- config = {'bind': "%s:%d" % (self.host, int(self.port))}
- config.update(self.options)
-
- class GunicornApplication(Application):
- def init(self, parser, opts, args):
- return config
-
- def load(self):
- return handler
-
- GunicornApplication().run()
-
-
-class EventletServer(ServerAdapter):
- """ Untested """
- def run(self, handler):
- from eventlet import wsgi, listen
- wsgi.server(listen((self.host, self.port)), handler)
-
-
-class RocketServer(ServerAdapter):
- """ Untested. """
- def run(self, handler):
- from rocket import Rocket
- server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler })
- server.start()
-
-
-class BjoernServer(ServerAdapter):
- """ Fast server written in C: https://github.com/jonashaag/bjoern """
- def run(self, handler):
- from bjoern import run
- run(handler, self.host, self.port)
-
-
-class AutoServer(ServerAdapter):
- """ Untested. """
- adapters = [PasteServer, CherryPyServer, TwistedServer, WSGIRefServer]
- def run(self, handler):
- for sa in self.adapters:
- try:
- return sa(self.host, self.port, **self.options).run(handler)
- except ImportError:
- pass
-
-server_names = {
- 'cgi': CGIServer,
- 'flup': FlupFCGIServer,
- 'wsgiref': WSGIRefServer,
- 'cherrypy': CherryPyServer,
- 'paste': PasteServer,
- 'fapws3': FapwsServer,
- 'tornado': TornadoServer,
- 'gae': AppEngineServer,
- 'twisted': TwistedServer,
- 'diesel': DieselServer,
- 'meinheld': MeinheldServer,
- 'gunicorn': GunicornServer,
- 'eventlet': EventletServer,
- 'gevent': GeventServer,
- 'rocket': RocketServer,
- 'bjoern' : BjoernServer,
- 'auto': AutoServer,
-}
-
-
-
-
-
-
-###############################################################################
-# Application Control ##########################################################
-###############################################################################
-
-
-def load(target, **namespace):
- """ Import a module or fetch an object from a module.
-
- * ``package.module`` returns `module` as a module object.
- * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
- * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
-
- The last form accepts not only function calls, but any type of
- expression. Keyword arguments passed to this function are available as
- local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
- """
- module, target = target.split(":", 1) if ':' in target else (target, None)
- if module not in sys.modules: __import__(module)
- if not target: return sys.modules[module]
- if target.isalnum(): return getattr(sys.modules[module], target)
- package_name = module.split('.')[0]
- namespace[package_name] = sys.modules[package_name]
- return eval('%s.%s' % (module, target), namespace)
-
-
-def load_app(target):
- """ Load a bottle application from a module and make sure that the import
- does not affect the current default application, but returns a separate
- application object. See :func:`load` for the target parameter. """
- global NORUN; NORUN, nr_old = True, NORUN
- try:
- tmp = default_app.push() # Create a new "default application"
- rv = load(target) # Import the target module
- return rv if callable(rv) else tmp
- finally:
- default_app.remove(tmp) # Remove the temporary added default application
- NORUN = nr_old
-
-def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
- interval=1, reloader=False, quiet=False, plugins=None, **kargs):
- """ Start a server instance. This method blocks until the server terminates.
-
- :param app: WSGI application or target string supported by
- :func:`load_app`. (default: :func:`default_app`)
- :param server: Server adapter to use. See :data:`server_names` keys
- for valid names or pass a :class:`ServerAdapter` subclass.
- (default: `wsgiref`)
- :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
- all interfaces including the external one. (default: 127.0.0.1)
- :param port: Server port to bind to. Values below 1024 require root
- privileges. (default: 8080)
- :param reloader: Start auto-reloading server? (default: False)
- :param interval: Auto-reloader interval in seconds (default: 1)
- :param quiet: Suppress output to stdout and stderr? (default: False)
- :param options: Options passed to the server adapter.
- """
- if NORUN: return
- if reloader and not os.environ.get('BOTTLE_CHILD'):
- try:
- fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
- os.close(fd) # We only need this file to exist. We never write to it
- while os.path.exists(lockfile):
- args = [sys.executable] + sys.argv
- environ = os.environ.copy()
- environ['BOTTLE_CHILD'] = 'true'
- environ['BOTTLE_LOCKFILE'] = lockfile
- p = subprocess.Popen(args, env=environ)
- while p.poll() is None: # Busy wait...
- os.utime(lockfile, None) # I am alive!
- time.sleep(interval)
- if p.poll() != 3:
- if os.path.exists(lockfile): os.unlink(lockfile)
- sys.exit(p.poll())
- except KeyboardInterrupt:
- pass
- finally:
- if os.path.exists(lockfile):
- os.unlink(lockfile)
- return
-
- stderr = sys.stderr.write
-
- try:
- app = app or default_app()
- if isinstance(app, basestring):
- app = load_app(app)
- if not callable(app):
- raise ValueError("Application is not callable: %r" % app)
-
- for plugin in plugins or []:
- app.install(plugin)
-
- if server in server_names:
- server = server_names.get(server)
- if isinstance(server, basestring):
- server = load(server)
- if isinstance(server, type):
- server = server(host=host, port=port, **kargs)
- if not isinstance(server, ServerAdapter):
- raise ValueError("Unknown or unsupported server: %r" % server)
-
- server.quiet = server.quiet or quiet
- if not server.quiet:
- stderr("Bottle server starting up (using %s)...\n" % repr(server))
- stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
- stderr("Hit Ctrl-C to quit.\n\n")
-
- if reloader:
- lockfile = os.environ.get('BOTTLE_LOCKFILE')
- bgcheck = FileCheckerThread(lockfile, interval)
- with bgcheck:
- server.run(app)
- if bgcheck.status == 'reload':
- sys.exit(3)
- else:
- server.run(app)
- except KeyboardInterrupt:
- pass
- except (SyntaxError, ImportError):
- if not reloader: raise
- if not getattr(server, 'quiet', False): print_exc()
- sys.exit(3)
- finally:
- if not getattr(server, 'quiet', False): stderr('Shutdown...\n')
-
-
-class FileCheckerThread(threading.Thread):
- ''' Interrupt main-thread as soon as a changed module file is detected,
- the lockfile gets deleted or gets to old. '''
-
- def __init__(self, lockfile, interval):
- threading.Thread.__init__(self)
- self.lockfile, self.interval = lockfile, interval
- #: Is one of 'reload', 'error' or 'exit'
- self.status = None
-
- def run(self):
- exists = os.path.exists
- mtime = lambda path: os.stat(path).st_mtime
- files = dict()
-
- for module in sys.modules.values():
- path = getattr(module, '__file__', '')
- if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
- if path and exists(path): files[path] = mtime(path)
-
- while not self.status:
- if not exists(self.lockfile)\
- or mtime(self.lockfile) < time.time() - self.interval - 5:
- self.status = 'error'
- thread.interrupt_main()
- for path, lmtime in files.iteritems():
- if not exists(path) or mtime(path) > lmtime:
- self.status = 'reload'
- thread.interrupt_main()
- break
- time.sleep(self.interval)
-
- def __enter__(self):
- self.start()
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if not self.status: self.status = 'exit' # silent exit
- self.join()
- return issubclass(exc_type, KeyboardInterrupt)
-
-
-
-
-
-###############################################################################
-# Template Adapters ############################################################
-###############################################################################
-
-
-class TemplateError(HTTPError):
- def __init__(self, message):
- HTTPError.__init__(self, 500, message)
-
-
-class BaseTemplate(object):
- """ Base class and minimal API for template adapters """
- extensions = ['tpl','html','thtml','stpl']
- settings = {} #used in prepare()
- defaults = {} #used in render()
-
- def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings):
- """ Create a new template.
- If the source parameter (str or buffer) is missing, the name argument
- is used to guess a template filename. Subclasses can assume that
- self.source and/or self.filename are set. Both are strings.
- The lookup, encoding and settings parameters are stored as instance
- variables.
- The lookup parameter stores a list containing directory paths.
- The encoding parameter should be used to decode byte strings or files.
- The settings parameter contains a dict for engine-specific settings.
- """
- self.name = name
- self.source = source.read() if hasattr(source, 'read') else source
- self.filename = source.filename if hasattr(source, 'filename') else None
- self.lookup = map(os.path.abspath, lookup)
- self.encoding = encoding
- self.settings = self.settings.copy() # Copy from class variable
- self.settings.update(settings) # Apply
- if not self.source and self.name:
- self.filename = self.search(self.name, self.lookup)
- if not self.filename:
- raise TemplateError('Template %s not found.' % repr(name))
- if not self.source and not self.filename:
- raise TemplateError('No template specified.')
- self.prepare(**self.settings)
-
- @classmethod
- def search(cls, name, lookup=[]):
- """ Search name in all directories specified in lookup.
- First without, then with common extensions. Return first hit. """
- if os.path.isfile(name): return name
- for spath in lookup:
- fname = os.path.join(spath, name)
- if os.path.isfile(fname):
- return fname
- for ext in cls.extensions:
- if os.path.isfile('%s.%s' % (fname, ext)):
- return '%s.%s' % (fname, ext)
-
- @classmethod
- def global_config(cls, key, *args):
- ''' This reads or sets the global settings stored in class.settings. '''
- if args:
- cls.settings = cls.settings.copy() # Make settings local to class
- cls.settings[key] = args[0]
- else:
- return cls.settings[key]
-
- def prepare(self, **options):
- """ Run preparations (parsing, caching, ...).
- It should be possible to call this again to refresh a template or to
- update settings.
- """
- raise NotImplementedError
-
- def render(self, *args, **kwargs):
- """ Render the template with the specified local variables and return
- a single byte or unicode string. If it is a byte string, the encoding
- must match self.encoding. This method must be thread-safe!
- Local variables may be provided in dictionaries (*args)
- or directly, as keywords (**kwargs).
- """
- raise NotImplementedError
-
-
-class MakoTemplate(BaseTemplate):
- def prepare(self, **options):
- from mako.template import Template
- from mako.lookup import TemplateLookup
- options.update({'input_encoding':self.encoding})
- options.setdefault('format_exceptions', bool(DEBUG))
- lookup = TemplateLookup(directories=self.lookup, **options)
- if self.source:
- self.tpl = Template(self.source, lookup=lookup, **options)
- else:
- self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options)
-
- def render(self, *args, **kwargs):
- for dictarg in args: kwargs.update(dictarg)
- _defaults = self.defaults.copy()
- _defaults.update(kwargs)
- return self.tpl.render(**_defaults)
-
-
-class CheetahTemplate(BaseTemplate):
- def prepare(self, **options):
- from Cheetah.Template import Template
- self.context = threading.local()
- self.context.vars = {}
- options['searchList'] = [self.context.vars]
- if self.source:
- self.tpl = Template(source=self.source, **options)
- else:
- self.tpl = Template(file=self.filename, **options)
-
- def render(self, *args, **kwargs):
- for dictarg in args: kwargs.update(dictarg)
- self.context.vars.update(self.defaults)
- self.context.vars.update(kwargs)
- out = str(self.tpl)
- self.context.vars.clear()
- return out
-
-
-class Jinja2Template(BaseTemplate):
- def prepare(self, filters=None, tests=None, **kwargs):
- from jinja2 import Environment, FunctionLoader
- if 'prefix' in kwargs: # TODO: to be removed after a while
- raise RuntimeError('The keyword argument `prefix` has been removed. '
- 'Use the full jinja2 environment name line_statement_prefix instead.')
- self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
- if filters: self.env.filters.update(filters)
- if tests: self.env.tests.update(tests)
- if self.source:
- self.tpl = self.env.from_string(self.source)
- else:
- self.tpl = self.env.get_template(self.filename)
-
- def render(self, *args, **kwargs):
- for dictarg in args: kwargs.update(dictarg)
- _defaults = self.defaults.copy()
- _defaults.update(kwargs)
- return self.tpl.render(**_defaults)
-
- def loader(self, name):
- fname = self.search(name, self.lookup)
- if fname:
- with open(fname, "rb") as f:
- return f.read().decode(self.encoding)
-
-
-class SimpleTALTemplate(BaseTemplate):
- ''' Untested! '''
- def prepare(self, **options):
- from simpletal import simpleTAL
- # TODO: add option to load METAL files during render
- if self.source:
- self.tpl = simpleTAL.compileHTMLTemplate(self.source)
- else:
- with open(self.filename, 'rb') as fp:
- self.tpl = simpleTAL.compileHTMLTemplate(tonat(fp.read()))
-
- def render(self, *args, **kwargs):
- from simpletal import simpleTALES
- for dictarg in args: kwargs.update(dictarg)
- # TODO: maybe reuse a context instead of always creating one
- context = simpleTALES.Context()
- for k,v in self.defaults.items():
- context.addGlobal(k, v)
- for k,v in kwargs.items():
- context.addGlobal(k, v)
- output = StringIO()
- self.tpl.expand(context, output)
- return output.getvalue()
-
-
-class SimpleTemplate(BaseTemplate):
- blocks = ('if', 'elif', 'else', 'try', 'except', 'finally', 'for', 'while',
- 'with', 'def', 'class')
- dedent_blocks = ('elif', 'else', 'except', 'finally')
-
- @lazy_attribute
- def re_pytokens(cls):
- ''' This matches comments and all kinds of quoted strings but does
- NOT match comments (#...) within quoted strings. (trust me) '''
- return re.compile(r'''
- (''(?!')|""(?!")|'{6}|"{6} # Empty strings (all 4 types)
- |'(?:[^\\']|\\.)+?' # Single quotes (')
- |"(?:[^\\"]|\\.)+?" # Double quotes (")
- |'{3}(?:[^\\]|\\.|\n)+?'{3} # Triple-quoted strings (')
- |"{3}(?:[^\\]|\\.|\n)+?"{3} # Triple-quoted strings (")
- |\#.* # Comments
- )''', re.VERBOSE)
-
- def prepare(self, escape_func=html_escape, noescape=False, **kwargs):
- self.cache = {}
- enc = self.encoding
- self._str = lambda x: touni(x, enc)
- self._escape = lambda x: escape_func(touni(x, enc))
- if noescape:
- self._str, self._escape = self._escape, self._str
-
- @classmethod
- def split_comment(cls, code):
- """ Removes comments (#...) from python code. """
- if '#' not in code: return code
- #: Remove comments only (leave quoted strings as they are)
- subf = lambda m: '' if m.group(0)[0]=='#' else m.group(0)
- return re.sub(cls.re_pytokens, subf, code)
-
- @cached_property
- def co(self):
- return compile(self.code, self.filename or '<string>', 'exec')
-
- @cached_property
- def code(self):
- stack = [] # Current Code indentation
- lineno = 0 # Current line of code
- ptrbuffer = [] # Buffer for printable strings and token tuple instances
- codebuffer = [] # Buffer for generated python code
- multiline = dedent = oneline = False
- template = self.source or open(self.filename, 'rb').read()
-
- def yield_tokens(line):
- for i, part in enumerate(re.split(r'\{\{(.*?)\}\}', line)):
- if i % 2:
- if part.startswith('!'): yield 'RAW', part[1:]
- else: yield 'CMD', part
- else: yield 'TXT', part
-
- def flush(): # Flush the ptrbuffer
- if not ptrbuffer: return
- cline = ''
- for line in ptrbuffer:
- for token, value in line:
- if token == 'TXT': cline += repr(value)
- elif token == 'RAW': cline += '_str(%s)' % value
- elif token == 'CMD': cline += '_escape(%s)' % value
- cline += ', '
- cline = cline[:-2] + '\\\n'
- cline = cline[:-2]
- if cline[:-1].endswith('\\\\\\\\\\n'):
- cline = cline[:-7] + cline[-1] # 'nobr\\\\\n' --> 'nobr'
- cline = '_printlist([' + cline + '])'
- del ptrbuffer[:] # Do this before calling code() again
- code(cline)
-
- def code(stmt):
- for line in stmt.splitlines():
- codebuffer.append(' ' * len(stack) + line.strip())
-
- for line in template.splitlines(True):
- lineno += 1
- line = line if isinstance(line, unicode)\
- else unicode(line, encoding=self.encoding)
- if lineno <= 2:
- m = re.search(r"%.*coding[:=]\s*([-\w\.]+)", line)
- if m: self.encoding = m.group(1)
- if m: line = line.replace('coding','coding (removed)')
- if line.strip()[:2].count('%') == 1:
- line = line.split('%',1)[1].lstrip() # Full line following the %
- cline = self.split_comment(line).strip()
- cmd = re.split(r'[^a-zA-Z0-9_]', cline)[0]
- flush() # You are actually reading this? Good luck, it's a mess :)
- if cmd in self.blocks or multiline:
- cmd = multiline or cmd
- dedent = cmd in self.dedent_blocks # "else:"
- if dedent and not oneline and not multiline:
- cmd = stack.pop()
- code(line)
- oneline = not cline.endswith(':') # "if 1: pass"
- multiline = cmd if cline.endswith('\\') else False
- if not oneline and not multiline:
- stack.append(cmd)
- elif cmd == 'end' and stack:
- code('#end(%s) %s' % (stack.pop(), line.strip()[3:]))
- elif cmd == 'include':
- p = cline.split(None, 2)[1:]
- if len(p) == 2:
- code("_=_include(%s, _stdout, %s)" % (repr(p[0]), p[1]))
- elif p:
- code("_=_include(%s, _stdout)" % repr(p[0]))
- else: # Empty %include -> reverse of %rebase
- code("_printlist(_base)")
- elif cmd == 'rebase':
- p = cline.split(None, 2)[1:]
- if len(p) == 2:
- code("globals()['_rebase']=(%s, dict(%s))" % (repr(p[0]), p[1]))
- elif p:
- code("globals()['_rebase']=(%s, {})" % repr(p[0]))
- else:
- code(line)
- else: # Line starting with text (not '%') or '%%' (escaped)
- if line.strip().startswith('%%'):
- line = line.replace('%%', '%', 1)
- ptrbuffer.append(yield_tokens(line))
- flush()
- return '\n'.join(codebuffer) + '\n'
-
- def subtemplate(self, _name, _stdout, *args, **kwargs):
- for dictarg in args: kwargs.update(dictarg)
- if _name not in self.cache:
- self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
- return self.cache[_name].execute(_stdout, kwargs)
-
- def execute(self, _stdout, *args, **kwargs):
- for dictarg in args: kwargs.update(dictarg)
- env = self.defaults.copy()
- env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
- '_include': self.subtemplate, '_str': self._str,
- '_escape': self._escape, 'get': env.get,
- 'setdefault': env.setdefault, 'defined': env.__contains__})
- env.update(kwargs)
- eval(self.co, env)
- if '_rebase' in env:
- subtpl, rargs = env['_rebase']
- rargs['_base'] = _stdout[:] #copy stdout
- del _stdout[:] # clear stdout
- return self.subtemplate(subtpl,_stdout,rargs)
- return env
-
- def render(self, *args, **kwargs):
- """ Render the template using keyword arguments as local variables. """
- for dictarg in args: kwargs.update(dictarg)
- stdout = []
- self.execute(stdout, kwargs)
- return ''.join(stdout)
-
-
-def template(*args, **kwargs):
- '''
- Get a rendered template as a string iterator.
- You can use a name, a filename or a template string as first parameter.
- Template rendering arguments can be passed as dictionaries
- or directly (as keyword arguments).
- '''
- tpl = args[0] if args else None
- template_adapter = kwargs.pop('template_adapter', SimpleTemplate)
- if tpl not in TEMPLATES or DEBUG:
- settings = kwargs.pop('template_settings', {})
- lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
- if isinstance(tpl, template_adapter):
- TEMPLATES[tpl] = tpl
- if settings: TEMPLATES[tpl].prepare(**settings)
- elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
- TEMPLATES[tpl] = template_adapter(source=tpl, lookup=lookup, **settings)
- else:
- TEMPLATES[tpl] = template_adapter(name=tpl, lookup=lookup, **settings)
- if not TEMPLATES[tpl]:
- abort(500, 'Template (%s) not found' % tpl)
- for dictarg in args[1:]: kwargs.update(dictarg)
- return TEMPLATES[tpl].render(kwargs)
-
-mako_template = functools.partial(template, template_adapter=MakoTemplate)
-cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
-jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
-simpletal_template = functools.partial(template, template_adapter=SimpleTALTemplate)
-
-
-def view(tpl_name, **defaults):
- ''' Decorator: renders a template for a handler.
- The handler can control its behavior like that:
-
- - return a dict of template vars to fill out the template
- - return something other than a dict and the view decorator will not
- process the template, but return the handler result as is.
- This includes returning a HTTPResponse(dict) to get,
- for instance, JSON with autojson or other castfilters.
- '''
- def decorator(func):
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- result = func(*args, **kwargs)
- if isinstance(result, (dict, DictMixin)):
- tplvars = defaults.copy()
- tplvars.update(result)
- return template(tpl_name, **tplvars)
- return result
- return wrapper
- return decorator
-
-mako_view = functools.partial(view, template_adapter=MakoTemplate)
-cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
-jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
-simpletal_view = functools.partial(view, template_adapter=SimpleTALTemplate)
-
-
-
-
-
-
-###############################################################################
-# Constants and Globals ########################################################
-###############################################################################
-
-
-TEMPLATE_PATH = ['./', './views/']
-TEMPLATES = {}
-DEBUG = False
-NORUN = False # If set, run() does nothing. Used by load_app()
-
-#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
-HTTP_CODES = httplib.responses
-HTTP_CODES[418] = "I'm a teapot" # RFC 2324
-HTTP_CODES[428] = "Precondition Required"
-HTTP_CODES[429] = "Too Many Requests"
-HTTP_CODES[431] = "Request Header Fields Too Large"
-HTTP_CODES[511] = "Network Authentication Required"
-_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.iteritems())
-
-#: The default template used for error pages. Override with @error()
-ERROR_PAGE_TEMPLATE = """
-%try:
- %from bottle import DEBUG, HTTP_CODES, request, touni
- %status_name = HTTP_CODES.get(e.status, 'Unknown').title()
- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
- <html>
- <head>
- <title>Error {{e.status}}: {{status_name}}</title>
- <style type="text/css">
- html {background-color: #eee; font-family: sans;}
- body {background-color: #fff; border: 1px solid #ddd;
- padding: 15px; margin: 15px;}
- pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
- </style>
- </head>
- <body>
- <h1>Error {{e.status}}: {{status_name}}</h1>
- <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
- caused an error:</p>
- <pre>{{e.output}}</pre>
- %if DEBUG and e.exception:
- <h2>Exception:</h2>
- <pre>{{repr(e.exception)}}</pre>
- %end
- %if DEBUG and e.traceback:
- <h2>Traceback:</h2>
- <pre>{{e.traceback}}</pre>
- %end
- </body>
- </html>
-%except ImportError:
- <b>ImportError:</b> Could not generate the error page. Please add bottle to
- the import path.
-%end
-"""
-
-#: A thread-safe instance of :class:`Request` representing the `current` request.
-request = Request()
-
-#: A thread-safe instance of :class:`Response` used to build the HTTP response.
-response = Response()
-
-#: A thread-safe namespace. Not used by Bottle.
-local = threading.local()
-
-# Initialize app stack (create first empty Bottle app)
-# BC: 0.6.4 and needed for run()
-app = default_app = AppStack()
-app.push()
-
-#: A virtual package that redirects import statements.
-#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
-ext = _ImportRedirect(__name__+'.ext', 'bottle_%s').module
-
-if __name__ == '__main__':
- opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
- if opt.version:
- print 'Bottle', __version__; sys.exit(0)
- if not args:
- parser.print_help()
- print '\nError: No application specified.\n'
- sys.exit(1)
-
- try:
- sys.path.insert(0, '.')
- sys.modules.setdefault('bottle', sys.modules['__main__'])
- except (AttributeError, ImportError), e:
- parser.error(e.args[0])
-
- if opt.bind and ':' in opt.bind:
- host, port = opt.bind.rsplit(':', 1)
- else:
- host, port = (opt.bind or 'localhost'), 8080
-
- debug(opt.debug)
- run(args[0], host=host, port=port, server=opt.server, reloader=opt.reload, plugins=opt.plugin)
-
-# THE END
diff --git a/module/lib/feedparser.py b/module/lib/feedparser.py
deleted file mode 100644
index a746ed8f5..000000000
--- a/module/lib/feedparser.py
+++ /dev/null
@@ -1,3885 +0,0 @@
-#!/usr/bin/env python
-"""Universal feed parser
-
-Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds
-
-Visit http://feedparser.org/ for the latest version
-Visit http://feedparser.org/docs/ for the latest documentation
-
-Required: Python 2.4 or later
-Recommended: CJKCodecs and iconv_codec <http://cjkpython.i18n.org/>
-"""
-
-__version__ = "5.0"
-__license__ = """Copyright (c) 2002-2008, Mark Pilgrim, All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGE."""
-__author__ = "Mark Pilgrim <http://diveintomark.org/>"
-__contributors__ = ["Jason Diamond <http://injektilo.org/>",
- "John Beimler <http://john.beimler.org/>",
- "Fazal Majid <http://www.majid.info/mylos/weblog/>",
- "Aaron Swartz <http://aaronsw.com/>",
- "Kevin Marks <http://epeus.blogspot.com/>",
- "Sam Ruby <http://intertwingly.net/>",
- "Ade Oshineye <http://blog.oshineye.com/>",
- "Martin Pool <http://sourcefrog.net/>",
- "Kurt McKee <http://kurtmckee.org/>"]
-_debug = 0
-
-# HTTP "User-Agent" header to send to servers when downloading feeds.
-# If you are embedding feedparser in a larger application, you should
-# change this to your application name and URL.
-USER_AGENT = "UniversalFeedParser/%s +http://feedparser.org/" % __version__
-
-# HTTP "Accept" header to send to servers when downloading feeds. If you don't
-# want to send an Accept header, set this to None.
-ACCEPT_HEADER = "application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1"
-
-# List of preferred XML parsers, by SAX driver name. These will be tried first,
-# but if they're not installed, Python will keep searching through its own list
-# of pre-installed parsers until it finds one that supports everything we need.
-PREFERRED_XML_PARSERS = ["drv_libxml2"]
-
-# If you want feedparser to automatically run HTML markup through HTML Tidy, set
-# this to 1. Requires mxTidy <http://www.egenix.com/files/python/mxTidy.html>
-# or utidylib <http://utidylib.berlios.de/>.
-TIDY_MARKUP = 0
-
-# List of Python interfaces for HTML Tidy, in order of preference. Only useful
-# if TIDY_MARKUP = 1
-PREFERRED_TIDY_INTERFACES = ["uTidy", "mxTidy"]
-
-# If you want feedparser to automatically resolve all relative URIs, set this
-# to 1.
-RESOLVE_RELATIVE_URIS = 1
-
-# If you want feedparser to automatically sanitize all potentially unsafe
-# HTML content, set this to 1.
-SANITIZE_HTML = 1
-
-# ---------- Python 3 modules (make it work if possible) ----------
-try:
- import rfc822
-except ImportError:
- from email import _parseaddr as rfc822
-
-try:
- # Python 3.1 introduces bytes.maketrans and simultaneously
- # deprecates string.maketrans; use bytes.maketrans if possible
- _maketrans = bytes.maketrans
-except (NameError, AttributeError):
- import string
- _maketrans = string.maketrans
-
-# base64 support for Atom feeds that contain embedded binary data
-try:
- import base64, binascii
- # Python 3.1 deprecates decodestring in favor of decodebytes
- _base64decode = getattr(base64, 'decodebytes', base64.decodestring)
-except:
- base64 = binascii = None
-
-def _s2bytes(s):
- # Convert a UTF-8 str to bytes if the interpreter is Python 3
- try:
- return bytes(s, 'utf8')
- except (NameError, TypeError):
- # In Python 2.5 and below, bytes doesn't exist (NameError)
- # In Python 2.6 and above, bytes and str are the same (TypeError)
- return s
-
-def _l2bytes(l):
- # Convert a list of ints to bytes if the interpreter is Python 3
- try:
- if bytes is not str:
- # In Python 2.6 and above, this call won't raise an exception
- # but it will return bytes([65]) as '[65]' instead of 'A'
- return bytes(l)
- raise NameError
- except NameError:
- return ''.join(map(chr, l))
-
-# If you want feedparser to allow all URL schemes, set this to ()
-# List culled from Python's urlparse documentation at:
-# http://docs.python.org/library/urlparse.html
-# as well as from "URI scheme" at Wikipedia:
-# https://secure.wikimedia.org/wikipedia/en/wiki/URI_scheme
-# Many more will likely need to be added!
-ACCEPTABLE_URI_SCHEMES = (
- 'file', 'ftp', 'gopher', 'h323', 'hdl', 'http', 'https', 'imap', 'mailto',
- 'mms', 'news', 'nntp', 'prospero', 'rsync', 'rtsp', 'rtspu', 'sftp',
- 'shttp', 'sip', 'sips', 'snews', 'svn', 'svn+ssh', 'telnet', 'wais',
- # Additional common-but-unofficial schemes
- 'aim', 'callto', 'cvs', 'facetime', 'feed', 'git', 'gtalk', 'irc', 'ircs',
- 'irc6', 'itms', 'mms', 'msnim', 'skype', 'ssh', 'smb', 'svn', 'ymsg',
-)
-#ACCEPTABLE_URI_SCHEMES = ()
-
-# ---------- required modules (should come with any Python distribution) ----------
-import sgmllib, re, sys, copy, urlparse, time, types, cgi, urllib, urllib2, datetime
-try:
- from io import BytesIO as _StringIO
-except ImportError:
- try:
- from cStringIO import StringIO as _StringIO
- except:
- from StringIO import StringIO as _StringIO
-
-# ---------- optional modules (feedparser will work without these, but with reduced functionality) ----------
-
-# gzip is included with most Python distributions, but may not be available if you compiled your own
-try:
- import gzip
-except:
- gzip = None
-try:
- import zlib
-except:
- zlib = None
-
-# If a real XML parser is available, feedparser will attempt to use it. feedparser has
-# been tested with the built-in SAX parser, PyXML, and libxml2. On platforms where the
-# Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some
-# versions of FreeBSD), feedparser will quietly fall back on regex-based parsing.
-try:
- import xml.sax
- xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers
- from xml.sax.saxutils import escape as _xmlescape
- _XML_AVAILABLE = 1
-except:
- _XML_AVAILABLE = 0
- def _xmlescape(data,entities={}):
- data = data.replace('&', '&amp;')
- data = data.replace('>', '&gt;')
- data = data.replace('<', '&lt;')
- for char, entity in entities:
- data = data.replace(char, entity)
- return data
-
-# cjkcodecs and iconv_codec provide support for more character encodings.
-# Both are available from http://cjkpython.i18n.org/
-try:
- import cjkcodecs.aliases
-except:
- pass
-try:
- import iconv_codec
-except:
- pass
-
-# chardet library auto-detects character encodings
-# Download from http://chardet.feedparser.org/
-try:
- import chardet
- if _debug:
- import chardet.constants
- chardet.constants._debug = 1
-except:
- chardet = None
-
-# reversable htmlentitydefs mappings for Python 2.2
-try:
- from htmlentitydefs import name2codepoint, codepoint2name
-except:
- import htmlentitydefs
- name2codepoint={}
- codepoint2name={}
- for (name,codepoint) in htmlentitydefs.entitydefs.iteritems():
- if codepoint.startswith('&#'): codepoint=unichr(int(codepoint[2:-1]))
- name2codepoint[name]=ord(codepoint)
- codepoint2name[ord(codepoint)]=name
-
-# BeautifulSoup parser used for parsing microformats from embedded HTML content
-# http://www.crummy.com/software/BeautifulSoup/
-# feedparser is tested with BeautifulSoup 3.0.x, but it might work with the
-# older 2.x series. If it doesn't, and you can figure out why, I'll accept a
-# patch and modify the compatibility statement accordingly.
-try:
- import BeautifulSoup
-except:
- BeautifulSoup = None
-
-# ---------- don't touch these ----------
-class ThingsNobodyCaresAboutButMe(Exception): pass
-class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): pass
-class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): pass
-class NonXMLContentType(ThingsNobodyCaresAboutButMe): pass
-class UndeclaredNamespace(Exception): pass
-
-sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
-sgmllib.special = re.compile('<!')
-sgmllib.charref = re.compile('&#(\d+|[xX][0-9a-fA-F]+);')
-
-if sgmllib.endbracket.search(' <').start(0):
- class EndBracketRegEx:
- def __init__(self):
- # Overriding the built-in sgmllib.endbracket regex allows the
- # parser to find angle brackets embedded in element attributes.
- self.endbracket = re.compile('''([^'"<>]|"[^"]*"(?=>|/|\s|\w+=)|'[^']*'(?=>|/|\s|\w+=))*(?=[<>])|.*?(?=[<>])''')
- def search(self,string,index=0):
- match = self.endbracket.match(string,index)
- if match is not None:
- # Returning a new object in the calling thread's context
- # resolves a thread-safety.
- return EndBracketMatch(match)
- return None
- class EndBracketMatch:
- def __init__(self, match):
- self.match = match
- def start(self, n):
- return self.match.end(n)
- sgmllib.endbracket = EndBracketRegEx()
-
-SUPPORTED_VERSIONS = {'': 'unknown',
- 'rss090': 'RSS 0.90',
- 'rss091n': 'RSS 0.91 (Netscape)',
- 'rss091u': 'RSS 0.91 (Userland)',
- 'rss092': 'RSS 0.92',
- 'rss093': 'RSS 0.93',
- 'rss094': 'RSS 0.94',
- 'rss20': 'RSS 2.0',
- 'rss10': 'RSS 1.0',
- 'rss': 'RSS (unknown version)',
- 'atom01': 'Atom 0.1',
- 'atom02': 'Atom 0.2',
- 'atom03': 'Atom 0.3',
- 'atom10': 'Atom 1.0',
- 'atom': 'Atom (unknown version)',
- 'cdf': 'CDF',
- 'hotrss': 'Hot RSS'
- }
-
-try:
- UserDict = dict
-except NameError:
- # Python 2.1 does not have dict
- from UserDict import UserDict
- def dict(aList):
- rc = {}
- for k, v in aList:
- rc[k] = v
- return rc
-
-class FeedParserDict(UserDict):
- keymap = {'channel': 'feed',
- 'items': 'entries',
- 'guid': 'id',
- 'date': 'updated',
- 'date_parsed': 'updated_parsed',
- 'description': ['summary', 'subtitle'],
- 'url': ['href'],
- 'modified': 'updated',
- 'modified_parsed': 'updated_parsed',
- 'issued': 'published',
- 'issued_parsed': 'published_parsed',
- 'copyright': 'rights',
- 'copyright_detail': 'rights_detail',
- 'tagline': 'subtitle',
- 'tagline_detail': 'subtitle_detail'}
- def __getitem__(self, key):
- if key == 'category':
- return UserDict.__getitem__(self, 'tags')[0]['term']
- if key == 'enclosures':
- norel = lambda link: FeedParserDict([(name,value) for (name,value) in link.items() if name!='rel'])
- return [norel(link) for link in UserDict.__getitem__(self, 'links') if link['rel']=='enclosure']
- if key == 'license':
- for link in UserDict.__getitem__(self, 'links'):
- if link['rel']=='license' and link.has_key('href'):
- return link['href']
- if key == 'categories':
- return [(tag['scheme'], tag['term']) for tag in UserDict.__getitem__(self, 'tags')]
- realkey = self.keymap.get(key, key)
- if type(realkey) == types.ListType:
- for k in realkey:
- if UserDict.__contains__(self, k):
- return UserDict.__getitem__(self, k)
- if UserDict.__contains__(self, key):
- return UserDict.__getitem__(self, key)
- return UserDict.__getitem__(self, realkey)
-
- def __setitem__(self, key, value):
- for k in self.keymap.keys():
- if key == k:
- key = self.keymap[k]
- if type(key) == types.ListType:
- key = key[0]
- return UserDict.__setitem__(self, key, value)
-
- def get(self, key, default=None):
- if self.has_key(key):
- return self[key]
- else:
- return default
-
- def setdefault(self, key, value):
- if not self.has_key(key):
- self[key] = value
- return self[key]
-
- def has_key(self, key):
- try:
- return hasattr(self, key) or UserDict.__contains__(self, key)
- except AttributeError:
- return False
- # This alias prevents the 2to3 tool from changing the semantics of the
- # __contains__ function below and exhausting the maximum recursion depth
- __has_key = has_key
-
- def __getattr__(self, key):
- try:
- return self.__dict__[key]
- except KeyError:
- pass
- try:
- assert not key.startswith('_')
- return self.__getitem__(key)
- except:
- raise AttributeError, "object has no attribute '%s'" % key
-
- def __setattr__(self, key, value):
- if key.startswith('_') or key == 'data':
- self.__dict__[key] = value
- else:
- return self.__setitem__(key, value)
-
- def __contains__(self, key):
- return self.__has_key(key)
-
-def zopeCompatibilityHack():
- global FeedParserDict
- del FeedParserDict
- def FeedParserDict(aDict=None):
- rc = {}
- if aDict:
- rc.update(aDict)
- return rc
-
-_ebcdic_to_ascii_map = None
-def _ebcdic_to_ascii(s):
- global _ebcdic_to_ascii_map
- if not _ebcdic_to_ascii_map:
- emap = (
- 0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15,
- 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31,
- 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7,
- 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26,
- 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33,
- 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94,
- 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63,
- 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34,
- 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200,201,
- 202,106,107,108,109,110,111,112,113,114,203,204,205,206,207,208,
- 209,126,115,116,117,118,119,120,121,122,210,211,212,213,214,215,
- 216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,
- 123,65,66,67,68,69,70,71,72,73,232,233,234,235,236,237,
- 125,74,75,76,77,78,79,80,81,82,238,239,240,241,242,243,
- 92,159,83,84,85,86,87,88,89,90,244,245,246,247,248,249,
- 48,49,50,51,52,53,54,55,56,57,250,251,252,253,254,255
- )
- _ebcdic_to_ascii_map = _maketrans( \
- _l2bytes(range(256)), _l2bytes(emap))
- return s.translate(_ebcdic_to_ascii_map)
-
-_cp1252 = {
- unichr(128): unichr(8364), # euro sign
- unichr(130): unichr(8218), # single low-9 quotation mark
- unichr(131): unichr( 402), # latin small letter f with hook
- unichr(132): unichr(8222), # double low-9 quotation mark
- unichr(133): unichr(8230), # horizontal ellipsis
- unichr(134): unichr(8224), # dagger
- unichr(135): unichr(8225), # double dagger
- unichr(136): unichr( 710), # modifier letter circumflex accent
- unichr(137): unichr(8240), # per mille sign
- unichr(138): unichr( 352), # latin capital letter s with caron
- unichr(139): unichr(8249), # single left-pointing angle quotation mark
- unichr(140): unichr( 338), # latin capital ligature oe
- unichr(142): unichr( 381), # latin capital letter z with caron
- unichr(145): unichr(8216), # left single quotation mark
- unichr(146): unichr(8217), # right single quotation mark
- unichr(147): unichr(8220), # left double quotation mark
- unichr(148): unichr(8221), # right double quotation mark
- unichr(149): unichr(8226), # bullet
- unichr(150): unichr(8211), # en dash
- unichr(151): unichr(8212), # em dash
- unichr(152): unichr( 732), # small tilde
- unichr(153): unichr(8482), # trade mark sign
- unichr(154): unichr( 353), # latin small letter s with caron
- unichr(155): unichr(8250), # single right-pointing angle quotation mark
- unichr(156): unichr( 339), # latin small ligature oe
- unichr(158): unichr( 382), # latin small letter z with caron
- unichr(159): unichr( 376)} # latin capital letter y with diaeresis
-
-_urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
-def _urljoin(base, uri):
- uri = _urifixer.sub(r'\1\3', uri)
- try:
- return urlparse.urljoin(base, uri)
- except:
- uri = urlparse.urlunparse([urllib.quote(part) for part in urlparse.urlparse(uri)])
- return urlparse.urljoin(base, uri)
-
-class _FeedParserMixin:
- namespaces = {'': '',
- 'http://backend.userland.com/rss': '',
- 'http://blogs.law.harvard.edu/tech/rss': '',
- 'http://purl.org/rss/1.0/': '',
- 'http://my.netscape.com/rdf/simple/0.9/': '',
- 'http://example.com/newformat#': '',
- 'http://example.com/necho': '',
- 'http://purl.org/echo/': '',
- 'uri/of/echo/namespace#': '',
- 'http://purl.org/pie/': '',
- 'http://purl.org/atom/ns#': '',
- 'http://www.w3.org/2005/Atom': '',
- 'http://purl.org/rss/1.0/modules/rss091#': '',
-
- 'http://webns.net/mvcb/': 'admin',
- 'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
- 'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
- 'http://media.tangent.org/rss/1.0/': 'audio',
- 'http://backend.userland.com/blogChannelModule': 'blogChannel',
- 'http://web.resource.org/cc/': 'cc',
- 'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
- 'http://purl.org/rss/1.0/modules/company': 'co',
- 'http://purl.org/rss/1.0/modules/content/': 'content',
- 'http://my.theinfo.org/changed/1.0/rss/': 'cp',
- 'http://purl.org/dc/elements/1.1/': 'dc',
- 'http://purl.org/dc/terms/': 'dcterms',
- 'http://purl.org/rss/1.0/modules/email/': 'email',
- 'http://purl.org/rss/1.0/modules/event/': 'ev',
- 'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
- 'http://freshmeat.net/rss/fm/': 'fm',
- 'http://xmlns.com/foaf/0.1/': 'foaf',
- 'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
- 'http://postneo.com/icbm/': 'icbm',
- 'http://purl.org/rss/1.0/modules/image/': 'image',
- 'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
- 'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
- 'http://purl.org/rss/1.0/modules/link/': 'l',
- 'http://search.yahoo.com/mrss': 'media',
- #Version 1.1.2 of the Media RSS spec added the trailing slash on the namespace
- 'http://search.yahoo.com/mrss/': 'media',
- 'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
- 'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
- 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
- 'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
- 'http://purl.org/rss/1.0/modules/reference/': 'ref',
- 'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
- 'http://purl.org/rss/1.0/modules/search/': 'search',
- 'http://purl.org/rss/1.0/modules/slash/': 'slash',
- 'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
- 'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
- 'http://hacks.benhammersley.com/rss/streaming/': 'str',
- 'http://purl.org/rss/1.0/modules/subscription/': 'sub',
- 'http://purl.org/rss/1.0/modules/syndication/': 'sy',
- 'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf',
- 'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
- 'http://purl.org/rss/1.0/modules/threading/': 'thr',
- 'http://purl.org/rss/1.0/modules/textinput/': 'ti',
- 'http://madskills.com/public/xml/rss/module/trackback/':'trackback',
- 'http://wellformedweb.org/commentAPI/': 'wfw',
- 'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
- 'http://www.w3.org/1999/xhtml': 'xhtml',
- 'http://www.w3.org/1999/xlink': 'xlink',
- 'http://www.w3.org/XML/1998/namespace': 'xml'
-}
- _matchnamespaces = {}
-
- can_be_relative_uri = ['link', 'id', 'wfw_comment', 'wfw_commentrss', 'docs', 'url', 'href', 'comments', 'icon', 'logo']
- can_contain_relative_uris = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description']
- can_contain_dangerous_markup = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description']
- html_types = ['text/html', 'application/xhtml+xml']
-
- def __init__(self, baseuri=None, baselang=None, encoding='utf-8'):
- if _debug: sys.stderr.write('initializing FeedParser\n')
- if not self._matchnamespaces:
- for k, v in self.namespaces.items():
- self._matchnamespaces[k.lower()] = v
- self.feeddata = FeedParserDict() # feed-level data
- self.encoding = encoding # character encoding
- self.entries = [] # list of entry-level data
- self.version = '' # feed type/version, see SUPPORTED_VERSIONS
- self.namespacesInUse = {} # dictionary of namespaces defined by the feed
-
- # the following are used internally to track state;
- # this is really out of control and should be refactored
- self.infeed = 0
- self.inentry = 0
- self.incontent = 0
- self.intextinput = 0
- self.inimage = 0
- self.inauthor = 0
- self.incontributor = 0
- self.inpublisher = 0
- self.insource = 0
- self.sourcedata = FeedParserDict()
- self.contentparams = FeedParserDict()
- self._summaryKey = None
- self.namespacemap = {}
- self.elementstack = []
- self.basestack = []
- self.langstack = []
- self.baseuri = baseuri or ''
- self.lang = baselang or None
- self.svgOK = 0
- self.hasTitle = 0
- if baselang:
- self.feeddata['language'] = baselang.replace('_','-')
-
- def unknown_starttag(self, tag, attrs):
- if _debug: sys.stderr.write('start %s with %s\n' % (tag, attrs))
- # normalize attrs
- attrs = [(k.lower(), v) for k, v in attrs]
- attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs]
- # the sgml parser doesn't handle entities in attributes, but
- # strict xml parsers do -- account for this difference
- if isinstance(self, _LooseFeedParser):
- attrs = [(k, v.replace('&amp;', '&')) for k, v in attrs]
-
- # track xml:base and xml:lang
- attrsD = dict(attrs)
- baseuri = attrsD.get('xml:base', attrsD.get('base')) or self.baseuri
- if type(baseuri) != type(u''):
- try:
- baseuri = unicode(baseuri, self.encoding)
- except:
- baseuri = unicode(baseuri, 'iso-8859-1')
- # ensure that self.baseuri is always an absolute URI that
- # uses a whitelisted URI scheme (e.g. not `javscript:`)
- if self.baseuri:
- self.baseuri = _makeSafeAbsoluteURI(self.baseuri, baseuri) or self.baseuri
- else:
- self.baseuri = _urljoin(self.baseuri, baseuri)
- lang = attrsD.get('xml:lang', attrsD.get('lang'))
- if lang == '':
- # xml:lang could be explicitly set to '', we need to capture that
- lang = None
- elif lang is None:
- # if no xml:lang is specified, use parent lang
- lang = self.lang
- if lang:
- if tag in ('feed', 'rss', 'rdf:RDF'):
- self.feeddata['language'] = lang.replace('_','-')
- self.lang = lang
- self.basestack.append(self.baseuri)
- self.langstack.append(lang)
-
- # track namespaces
- for prefix, uri in attrs:
- if prefix.startswith('xmlns:'):
- self.trackNamespace(prefix[6:], uri)
- elif prefix == 'xmlns':
- self.trackNamespace(None, uri)
-
- # track inline content
- if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
- if tag in ['xhtml:div', 'div']: return # typepad does this 10/2007
- # element declared itself as escaped markup, but it isn't really
- self.contentparams['type'] = 'application/xhtml+xml'
- if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
- if tag.find(':') <> -1:
- prefix, tag = tag.split(':', 1)
- namespace = self.namespacesInUse.get(prefix, '')
- if tag=='math' and namespace=='http://www.w3.org/1998/Math/MathML':
- attrs.append(('xmlns',namespace))
- if tag=='svg' and namespace=='http://www.w3.org/2000/svg':
- attrs.append(('xmlns',namespace))
- if tag == 'svg': self.svgOK += 1
- return self.handle_data('<%s%s>' % (tag, self.strattrs(attrs)), escape=0)
-
- # match namespaces
- if tag.find(':') <> -1:
- prefix, suffix = tag.split(':', 1)
- else:
- prefix, suffix = '', tag
- prefix = self.namespacemap.get(prefix, prefix)
- if prefix:
- prefix = prefix + '_'
-
- # special hack for better tracking of empty textinput/image elements in illformed feeds
- if (not prefix) and tag not in ('title', 'link', 'description', 'name'):
- self.intextinput = 0
- if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
- self.inimage = 0
-
- # call special handler (if defined) or default handler
- methodname = '_start_' + prefix + suffix
- try:
- method = getattr(self, methodname)
- return method(attrsD)
- except AttributeError:
- # Since there's no handler or something has gone wrong we explicitly add the element and its attributes
- unknown_tag = prefix + suffix
- if len(attrsD) == 0:
- # No attributes so merge it into the encosing dictionary
- return self.push(unknown_tag, 1)
- else:
- # Has attributes so create it in its own dictionary
- context = self._getContext()
- context[unknown_tag] = attrsD
-
- def unknown_endtag(self, tag):
- if _debug: sys.stderr.write('end %s\n' % tag)
- # match namespaces
- if tag.find(':') <> -1:
- prefix, suffix = tag.split(':', 1)
- else:
- prefix, suffix = '', tag
- prefix = self.namespacemap.get(prefix, prefix)
- if prefix:
- prefix = prefix + '_'
- if suffix == 'svg' and self.svgOK: self.svgOK -= 1
-
- # call special handler (if defined) or default handler
- methodname = '_end_' + prefix + suffix
- try:
- if self.svgOK: raise AttributeError()
- method = getattr(self, methodname)
- method()
- except AttributeError:
- self.pop(prefix + suffix)
-
- # track inline content
- if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
- # element declared itself as escaped markup, but it isn't really
- if tag in ['xhtml:div', 'div']: return # typepad does this 10/2007
- self.contentparams['type'] = 'application/xhtml+xml'
- if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
- tag = tag.split(':')[-1]
- self.handle_data('</%s>' % tag, escape=0)
-
- # track xml:base and xml:lang going out of scope
- if self.basestack:
- self.basestack.pop()
- if self.basestack and self.basestack[-1]:
- self.baseuri = self.basestack[-1]
- if self.langstack:
- self.langstack.pop()
- if self.langstack: # and (self.langstack[-1] is not None):
- self.lang = self.langstack[-1]
-
- def handle_charref(self, ref):
- # called for each character reference, e.g. for '&#160;', ref will be '160'
- if not self.elementstack: return
- ref = ref.lower()
- if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
- text = '&#%s;' % ref
- else:
- if ref[0] == 'x':
- c = int(ref[1:], 16)
- else:
- c = int(ref)
- text = unichr(c).encode('utf-8')
- self.elementstack[-1][2].append(text)
-
- def handle_entityref(self, ref):
- # called for each entity reference, e.g. for '&copy;', ref will be 'copy'
- if not self.elementstack: return
- if _debug: sys.stderr.write('entering handle_entityref with %s\n' % ref)
- if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
- text = '&%s;' % ref
- elif ref in self.entities.keys():
- text = self.entities[ref]
- if text.startswith('&#') and text.endswith(';'):
- return self.handle_entityref(text)
- else:
- try: name2codepoint[ref]
- except KeyError: text = '&%s;' % ref
- else: text = unichr(name2codepoint[ref]).encode('utf-8')
- self.elementstack[-1][2].append(text)
-
- def handle_data(self, text, escape=1):
- # called for each block of plain text, i.e. outside of any tag and
- # not containing any character or entity references
- if not self.elementstack: return
- if escape and self.contentparams.get('type') == 'application/xhtml+xml':
- text = _xmlescape(text)
- self.elementstack[-1][2].append(text)
-
- def handle_comment(self, text):
- # called for each comment, e.g. <!-- insert message here -->
- pass
-
- def handle_pi(self, text):
- # called for each processing instruction, e.g. <?instruction>
- pass
-
- def handle_decl(self, text):
- pass
-
- def parse_declaration(self, i):
- # override internal declaration handler to handle CDATA blocks
- if _debug: sys.stderr.write('entering parse_declaration\n')
- if self.rawdata[i:i+9] == '<![CDATA[':
- k = self.rawdata.find(']]>', i)
- if k == -1:
- # CDATA block began but didn't finish
- k = len(self.rawdata)
- return k
- self.handle_data(_xmlescape(self.rawdata[i+9:k]), 0)
- return k+3
- else:
- k = self.rawdata.find('>', i)
- if k >= 0:
- return k+1
- else:
- # We have an incomplete CDATA block.
- return k
-
- def mapContentType(self, contentType):
- contentType = contentType.lower()
- if contentType == 'text':
- contentType = 'text/plain'
- elif contentType == 'html':
- contentType = 'text/html'
- elif contentType == 'xhtml':
- contentType = 'application/xhtml+xml'
- return contentType
-
- def trackNamespace(self, prefix, uri):
- loweruri = uri.lower()
- if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not self.version:
- self.version = 'rss090'
- if loweruri == 'http://purl.org/rss/1.0/' and not self.version:
- self.version = 'rss10'
- if loweruri == 'http://www.w3.org/2005/atom' and not self.version:
- self.version = 'atom10'
- if loweruri.find('backend.userland.com/rss') <> -1:
- # match any backend.userland.com namespace
- uri = 'http://backend.userland.com/rss'
- loweruri = uri
- if self._matchnamespaces.has_key(loweruri):
- self.namespacemap[prefix] = self._matchnamespaces[loweruri]
- self.namespacesInUse[self._matchnamespaces[loweruri]] = uri
- else:
- self.namespacesInUse[prefix or ''] = uri
-
- def resolveURI(self, uri):
- return _urljoin(self.baseuri or '', uri)
-
- def decodeEntities(self, element, data):
- return data
-
- def strattrs(self, attrs):
- return ''.join([' %s="%s"' % (t[0],_xmlescape(t[1],{'"':'&quot;'})) for t in attrs])
-
- def push(self, element, expectingText):
- self.elementstack.append([element, expectingText, []])
-
- def pop(self, element, stripWhitespace=1):
- if not self.elementstack: return
- if self.elementstack[-1][0] != element: return
-
- element, expectingText, pieces = self.elementstack.pop()
-
- if self.version == 'atom10' and self.contentparams.get('type','text') == 'application/xhtml+xml':
- # remove enclosing child element, but only if it is a <div> and
- # only if all the remaining content is nested underneath it.
- # This means that the divs would be retained in the following:
- # <div>foo</div><div>bar</div>
- while pieces and len(pieces)>1 and not pieces[-1].strip():
- del pieces[-1]
- while pieces and len(pieces)>1 and not pieces[0].strip():
- del pieces[0]
- if pieces and (pieces[0] == '<div>' or pieces[0].startswith('<div ')) and pieces[-1]=='</div>':
- depth = 0
- for piece in pieces[:-1]:
- if piece.startswith('</'):
- depth -= 1
- if depth == 0: break
- elif piece.startswith('<') and not piece.endswith('/>'):
- depth += 1
- else:
- pieces = pieces[1:-1]
-
- # Ensure each piece is a str for Python 3
- for (i, v) in enumerate(pieces):
- if not isinstance(v, basestring):
- pieces[i] = v.decode('utf-8')
-
- output = ''.join(pieces)
- if stripWhitespace:
- output = output.strip()
- if not expectingText: return output
-
- # decode base64 content
- if base64 and self.contentparams.get('base64', 0):
- try:
- output = _base64decode(output)
- except binascii.Error:
- pass
- except binascii.Incomplete:
- pass
- except TypeError:
- # In Python 3, base64 takes and outputs bytes, not str
- # This may not be the most correct way to accomplish this
- output = _base64decode(output.encode('utf-8')).decode('utf-8')
-
- # resolve relative URIs
- if (element in self.can_be_relative_uri) and output:
- output = self.resolveURI(output)
-
- # decode entities within embedded markup
- if not self.contentparams.get('base64', 0):
- output = self.decodeEntities(element, output)
-
- if self.lookslikehtml(output):
- self.contentparams['type']='text/html'
-
- # remove temporary cruft from contentparams
- try:
- del self.contentparams['mode']
- except KeyError:
- pass
- try:
- del self.contentparams['base64']
- except KeyError:
- pass
-
- is_htmlish = self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types
- # resolve relative URIs within embedded markup
- if is_htmlish and RESOLVE_RELATIVE_URIS:
- if element in self.can_contain_relative_uris:
- output = _resolveRelativeURIs(output, self.baseuri, self.encoding, self.contentparams.get('type', 'text/html'))
-
- # parse microformats
- # (must do this before sanitizing because some microformats
- # rely on elements that we sanitize)
- if is_htmlish and element in ['content', 'description', 'summary']:
- mfresults = _parseMicroformats(output, self.baseuri, self.encoding)
- if mfresults:
- for tag in mfresults.get('tags', []):
- self._addTag(tag['term'], tag['scheme'], tag['label'])
- for enclosure in mfresults.get('enclosures', []):
- self._start_enclosure(enclosure)
- for xfn in mfresults.get('xfn', []):
- self._addXFN(xfn['relationships'], xfn['href'], xfn['name'])
- vcard = mfresults.get('vcard')
- if vcard:
- self._getContext()['vcard'] = vcard
-
- # sanitize embedded markup
- if is_htmlish and SANITIZE_HTML:
- if element in self.can_contain_dangerous_markup:
- output = _sanitizeHTML(output, self.encoding, self.contentparams.get('type', 'text/html'))
-
- if self.encoding and type(output) != type(u''):
- try:
- output = unicode(output, self.encoding)
- except:
- pass
-
- # address common error where people take data that is already
- # utf-8, presume that it is iso-8859-1, and re-encode it.
- if self.encoding in ('utf-8', 'utf-8_INVALID_PYTHON_3') and type(output) == type(u''):
- try:
- output = unicode(output.encode('iso-8859-1'), 'utf-8')
- except:
- pass
-
- # map win-1252 extensions to the proper code points
- if type(output) == type(u''):
- output = u''.join([c in _cp1252.keys() and _cp1252[c] or c for c in output])
-
- # categories/tags/keywords/whatever are handled in _end_category
- if element == 'category':
- return output
-
- if element == 'title' and self.hasTitle:
- return output
-
- # store output in appropriate place(s)
- if self.inentry and not self.insource:
- if element == 'content':
- self.entries[-1].setdefault(element, [])
- contentparams = copy.deepcopy(self.contentparams)
- contentparams['value'] = output
- self.entries[-1][element].append(contentparams)
- elif element == 'link':
- if not self.inimage:
- # query variables in urls in link elements are improperly
- # converted from `?a=1&b=2` to `?a=1&b;=2` as if they're
- # unhandled character references. fix this special case.
- output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output)
- self.entries[-1][element] = output
- if output:
- self.entries[-1]['links'][-1]['href'] = output
- else:
- if element == 'description':
- element = 'summary'
- self.entries[-1][element] = output
- if self.incontent:
- contentparams = copy.deepcopy(self.contentparams)
- contentparams['value'] = output
- self.entries[-1][element + '_detail'] = contentparams
- elif (self.infeed or self.insource):# and (not self.intextinput) and (not self.inimage):
- context = self._getContext()
- if element == 'description':
- element = 'subtitle'
- context[element] = output
- if element == 'link':
- # fix query variables; see above for the explanation
- output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output)
- context[element] = output
- context['links'][-1]['href'] = output
- elif self.incontent:
- contentparams = copy.deepcopy(self.contentparams)
- contentparams['value'] = output
- context[element + '_detail'] = contentparams
- return output
-
- def pushContent(self, tag, attrsD, defaultContentType, expectingText):
- self.incontent += 1
- if self.lang: self.lang=self.lang.replace('_','-')
- self.contentparams = FeedParserDict({
- 'type': self.mapContentType(attrsD.get('type', defaultContentType)),
- 'language': self.lang,
- 'base': self.baseuri})
- self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
- self.push(tag, expectingText)
-
- def popContent(self, tag):
- value = self.pop(tag)
- self.incontent -= 1
- self.contentparams.clear()
- return value
-
- # a number of elements in a number of RSS variants are nominally plain
- # text, but this is routinely ignored. This is an attempt to detect
- # the most common cases. As false positives often result in silent
- # data loss, this function errs on the conservative side.
- def lookslikehtml(self, s):
- if self.version.startswith('atom'): return
- if self.contentparams.get('type','text/html') != 'text/plain': return
-
- # must have a close tag or a entity reference to qualify
- if not (re.search(r'</(\w+)>',s) or re.search("&#?\w+;",s)): return
-
- # all tags must be in a restricted subset of valid HTML tags
- if filter(lambda t: t.lower() not in _HTMLSanitizer.acceptable_elements,
- re.findall(r'</?(\w+)',s)): return
-
- # all entities must have been defined as valid HTML entities
- from htmlentitydefs import entitydefs
- if filter(lambda e: e not in entitydefs.keys(),
- re.findall(r'&(\w+);',s)): return
-
- return 1
-
- def _mapToStandardPrefix(self, name):
- colonpos = name.find(':')
- if colonpos <> -1:
- prefix = name[:colonpos]
- suffix = name[colonpos+1:]
- prefix = self.namespacemap.get(prefix, prefix)
- name = prefix + ':' + suffix
- return name
-
- def _getAttribute(self, attrsD, name):
- return attrsD.get(self._mapToStandardPrefix(name))
-
- def _isBase64(self, attrsD, contentparams):
- if attrsD.get('mode', '') == 'base64':
- return 1
- if self.contentparams['type'].startswith('text/'):
- return 0
- if self.contentparams['type'].endswith('+xml'):
- return 0
- if self.contentparams['type'].endswith('/xml'):
- return 0
- return 1
-
- def _itsAnHrefDamnIt(self, attrsD):
- href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
- if href:
- try:
- del attrsD['url']
- except KeyError:
- pass
- try:
- del attrsD['uri']
- except KeyError:
- pass
- attrsD['href'] = href
- return attrsD
-
- def _save(self, key, value, overwrite=False):
- context = self._getContext()
- if overwrite:
- context[key] = value
- else:
- context.setdefault(key, value)
-
- def _start_rss(self, attrsD):
- versionmap = {'0.91': 'rss091u',
- '0.92': 'rss092',
- '0.93': 'rss093',
- '0.94': 'rss094'}
- #If we're here then this is an RSS feed.
- #If we don't have a version or have a version that starts with something
- #other than RSS then there's been a mistake. Correct it.
- if not self.version or not self.version.startswith('rss'):
- attr_version = attrsD.get('version', '')
- version = versionmap.get(attr_version)
- if version:
- self.version = version
- elif attr_version.startswith('2.'):
- self.version = 'rss20'
- else:
- self.version = 'rss'
-
- def _start_dlhottitles(self, attrsD):
- self.version = 'hotrss'
-
- def _start_channel(self, attrsD):
- self.infeed = 1
- self._cdf_common(attrsD)
- _start_feedinfo = _start_channel
-
- def _cdf_common(self, attrsD):
- if attrsD.has_key('lastmod'):
- self._start_modified({})
- self.elementstack[-1][-1] = attrsD['lastmod']
- self._end_modified()
- if attrsD.has_key('href'):
- self._start_link({})
- self.elementstack[-1][-1] = attrsD['href']
- self._end_link()
-
- def _start_feed(self, attrsD):
- self.infeed = 1
- versionmap = {'0.1': 'atom01',
- '0.2': 'atom02',
- '0.3': 'atom03'}
- if not self.version:
- attr_version = attrsD.get('version')
- version = versionmap.get(attr_version)
- if version:
- self.version = version
- else:
- self.version = 'atom'
-
- def _end_channel(self):
- self.infeed = 0
- _end_feed = _end_channel
-
- def _start_image(self, attrsD):
- context = self._getContext()
- if not self.inentry:
- context.setdefault('image', FeedParserDict())
- self.inimage = 1
- self.hasTitle = 0
- self.push('image', 0)
-
- def _end_image(self):
- self.pop('image')
- self.inimage = 0
-
- def _start_textinput(self, attrsD):
- context = self._getContext()
- context.setdefault('textinput', FeedParserDict())
- self.intextinput = 1
- self.hasTitle = 0
- self.push('textinput', 0)
- _start_textInput = _start_textinput
-
- def _end_textinput(self):
- self.pop('textinput')
- self.intextinput = 0
- _end_textInput = _end_textinput
-
- def _start_author(self, attrsD):
- self.inauthor = 1
- self.push('author', 1)
- # Append a new FeedParserDict when expecting an author
- context = self._getContext()
- context.setdefault('authors', [])
- context['authors'].append(FeedParserDict())
- _start_managingeditor = _start_author
- _start_dc_author = _start_author
- _start_dc_creator = _start_author
- _start_itunes_author = _start_author
-
- def _end_author(self):
- self.pop('author')
- self.inauthor = 0
- self._sync_author_detail()
- _end_managingeditor = _end_author
- _end_dc_author = _end_author
- _end_dc_creator = _end_author
- _end_itunes_author = _end_author
-
- def _start_itunes_owner(self, attrsD):
- self.inpublisher = 1
- self.push('publisher', 0)
-
- def _end_itunes_owner(self):
- self.pop('publisher')
- self.inpublisher = 0
- self._sync_author_detail('publisher')
-
- def _start_contributor(self, attrsD):
- self.incontributor = 1
- context = self._getContext()
- context.setdefault('contributors', [])
- context['contributors'].append(FeedParserDict())
- self.push('contributor', 0)
-
- def _end_contributor(self):
- self.pop('contributor')
- self.incontributor = 0
-
- def _start_dc_contributor(self, attrsD):
- self.incontributor = 1
- context = self._getContext()
- context.setdefault('contributors', [])
- context['contributors'].append(FeedParserDict())
- self.push('name', 0)
-
- def _end_dc_contributor(self):
- self._end_name()
- self.incontributor = 0
-
- def _start_name(self, attrsD):
- self.push('name', 0)
- _start_itunes_name = _start_name
-
- def _end_name(self):
- value = self.pop('name')
- if self.inpublisher:
- self._save_author('name', value, 'publisher')
- elif self.inauthor:
- self._save_author('name', value)
- elif self.incontributor:
- self._save_contributor('name', value)
- elif self.intextinput:
- context = self._getContext()
- context['name'] = value
- _end_itunes_name = _end_name
-
- def _start_width(self, attrsD):
- self.push('width', 0)
-
- def _end_width(self):
- value = self.pop('width')
- try:
- value = int(value)
- except:
- value = 0
- if self.inimage:
- context = self._getContext()
- context['width'] = value
-
- def _start_height(self, attrsD):
- self.push('height', 0)
-
- def _end_height(self):
- value = self.pop('height')
- try:
- value = int(value)
- except:
- value = 0
- if self.inimage:
- context = self._getContext()
- context['height'] = value
-
- def _start_url(self, attrsD):
- self.push('href', 1)
- _start_homepage = _start_url
- _start_uri = _start_url
-
- def _end_url(self):
- value = self.pop('href')
- if self.inauthor:
- self._save_author('href', value)
- elif self.incontributor:
- self._save_contributor('href', value)
- _end_homepage = _end_url
- _end_uri = _end_url
-
- def _start_email(self, attrsD):
- self.push('email', 0)
- _start_itunes_email = _start_email
-
- def _end_email(self):
- value = self.pop('email')
- if self.inpublisher:
- self._save_author('email', value, 'publisher')
- elif self.inauthor:
- self._save_author('email', value)
- elif self.incontributor:
- self._save_contributor('email', value)
- _end_itunes_email = _end_email
-
- def _getContext(self):
- if self.insource:
- context = self.sourcedata
- elif self.inimage and self.feeddata.has_key('image'):
- context = self.feeddata['image']
- elif self.intextinput:
- context = self.feeddata['textinput']
- elif self.inentry:
- context = self.entries[-1]
- else:
- context = self.feeddata
- return context
-
- def _save_author(self, key, value, prefix='author'):
- context = self._getContext()
- context.setdefault(prefix + '_detail', FeedParserDict())
- context[prefix + '_detail'][key] = value
- self._sync_author_detail()
- context.setdefault('authors', [FeedParserDict()])
- context['authors'][-1][key] = value
-
- def _save_contributor(self, key, value):
- context = self._getContext()
- context.setdefault('contributors', [FeedParserDict()])
- context['contributors'][-1][key] = value
-
- def _sync_author_detail(self, key='author'):
- context = self._getContext()
- detail = context.get('%s_detail' % key)
- if detail:
- name = detail.get('name')
- email = detail.get('email')
- if name and email:
- context[key] = '%s (%s)' % (name, email)
- elif name:
- context[key] = name
- elif email:
- context[key] = email
- else:
- author, email = context.get(key), None
- if not author: return
- emailmatch = re.search(r'''(([a-zA-Z0-9\_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))(\?subject=\S+)?''', author)
- if emailmatch:
- email = emailmatch.group(0)
- # probably a better way to do the following, but it passes all the tests
- author = author.replace(email, '')
- author = author.replace('()', '')
- author = author.replace('<>', '')
- author = author.replace('&lt;&gt;', '')
- author = author.strip()
- if author and (author[0] == '('):
- author = author[1:]
- if author and (author[-1] == ')'):
- author = author[:-1]
- author = author.strip()
- if author or email:
- context.setdefault('%s_detail' % key, FeedParserDict())
- if author:
- context['%s_detail' % key]['name'] = author
- if email:
- context['%s_detail' % key]['email'] = email
-
- def _start_subtitle(self, attrsD):
- self.pushContent('subtitle', attrsD, 'text/plain', 1)
- _start_tagline = _start_subtitle
- _start_itunes_subtitle = _start_subtitle
-
- def _end_subtitle(self):
- self.popContent('subtitle')
- _end_tagline = _end_subtitle
- _end_itunes_subtitle = _end_subtitle
-
- def _start_rights(self, attrsD):
- self.pushContent('rights', attrsD, 'text/plain', 1)
- _start_dc_rights = _start_rights
- _start_copyright = _start_rights
-
- def _end_rights(self):
- self.popContent('rights')
- _end_dc_rights = _end_rights
- _end_copyright = _end_rights
-
- def _start_item(self, attrsD):
- self.entries.append(FeedParserDict())
- self.push('item', 0)
- self.inentry = 1
- self.guidislink = 0
- self.hasTitle = 0
- id = self._getAttribute(attrsD, 'rdf:about')
- if id:
- context = self._getContext()
- context['id'] = id
- self._cdf_common(attrsD)
- _start_entry = _start_item
- _start_product = _start_item
-
- def _end_item(self):
- self.pop('item')
- self.inentry = 0
- _end_entry = _end_item
-
- def _start_dc_language(self, attrsD):
- self.push('language', 1)
- _start_language = _start_dc_language
-
- def _end_dc_language(self):
- self.lang = self.pop('language')
- _end_language = _end_dc_language
-
- def _start_dc_publisher(self, attrsD):
- self.push('publisher', 1)
- _start_webmaster = _start_dc_publisher
-
- def _end_dc_publisher(self):
- self.pop('publisher')
- self._sync_author_detail('publisher')
- _end_webmaster = _end_dc_publisher
-
- def _start_published(self, attrsD):
- self.push('published', 1)
- _start_dcterms_issued = _start_published
- _start_issued = _start_published
-
- def _end_published(self):
- value = self.pop('published')
- self._save('published_parsed', _parse_date(value), overwrite=True)
- _end_dcterms_issued = _end_published
- _end_issued = _end_published
-
- def _start_updated(self, attrsD):
- self.push('updated', 1)
- _start_modified = _start_updated
- _start_dcterms_modified = _start_updated
- _start_pubdate = _start_updated
- _start_dc_date = _start_updated
- _start_lastbuilddate = _start_updated
-
- def _end_updated(self):
- value = self.pop('updated')
- parsed_value = _parse_date(value)
- self._save('updated_parsed', parsed_value, overwrite=True)
- _end_modified = _end_updated
- _end_dcterms_modified = _end_updated
- _end_pubdate = _end_updated
- _end_dc_date = _end_updated
- _end_lastbuilddate = _end_updated
-
- def _start_created(self, attrsD):
- self.push('created', 1)
- _start_dcterms_created = _start_created
-
- def _end_created(self):
- value = self.pop('created')
- self._save('created_parsed', _parse_date(value), overwrite=True)
- _end_dcterms_created = _end_created
-
- def _start_expirationdate(self, attrsD):
- self.push('expired', 1)
-
- def _end_expirationdate(self):
- self._save('expired_parsed', _parse_date(self.pop('expired')), overwrite=True)
-
- def _start_cc_license(self, attrsD):
- context = self._getContext()
- value = self._getAttribute(attrsD, 'rdf:resource')
- attrsD = FeedParserDict()
- attrsD['rel']='license'
- if value: attrsD['href']=value
- context.setdefault('links', []).append(attrsD)
-
- def _start_creativecommons_license(self, attrsD):
- self.push('license', 1)
- _start_creativeCommons_license = _start_creativecommons_license
-
- def _end_creativecommons_license(self):
- value = self.pop('license')
- context = self._getContext()
- attrsD = FeedParserDict()
- attrsD['rel']='license'
- if value: attrsD['href']=value
- context.setdefault('links', []).append(attrsD)
- del context['license']
- _end_creativeCommons_license = _end_creativecommons_license
-
- def _addXFN(self, relationships, href, name):
- context = self._getContext()
- xfn = context.setdefault('xfn', [])
- value = FeedParserDict({'relationships': relationships, 'href': href, 'name': name})
- if value not in xfn:
- xfn.append(value)
-
- def _addTag(self, term, scheme, label):
- context = self._getContext()
- tags = context.setdefault('tags', [])
- if (not term) and (not scheme) and (not label): return
- value = FeedParserDict({'term': term, 'scheme': scheme, 'label': label})
- if value not in tags:
- tags.append(value)
-
- def _start_category(self, attrsD):
- if _debug: sys.stderr.write('entering _start_category with %s\n' % repr(attrsD))
- term = attrsD.get('term')
- scheme = attrsD.get('scheme', attrsD.get('domain'))
- label = attrsD.get('label')
- self._addTag(term, scheme, label)
- self.push('category', 1)
- _start_dc_subject = _start_category
- _start_keywords = _start_category
-
- def _start_media_category(self, attrsD):
- attrsD.setdefault('scheme', 'http://search.yahoo.com/mrss/category_schema')
- self._start_category(attrsD)
-
- def _end_itunes_keywords(self):
- for term in self.pop('itunes_keywords').split():
- self._addTag(term, 'http://www.itunes.com/', None)
-
- def _start_itunes_category(self, attrsD):
- self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
- self.push('category', 1)
-
- def _end_category(self):
- value = self.pop('category')
- if not value: return
- context = self._getContext()
- tags = context['tags']
- if value and len(tags) and not tags[-1]['term']:
- tags[-1]['term'] = value
- else:
- self._addTag(value, None, None)
- _end_dc_subject = _end_category
- _end_keywords = _end_category
- _end_itunes_category = _end_category
- _end_media_category = _end_category
-
- def _start_cloud(self, attrsD):
- self._getContext()['cloud'] = FeedParserDict(attrsD)
-
- def _start_link(self, attrsD):
- attrsD.setdefault('rel', 'alternate')
- if attrsD['rel'] == 'self':
- attrsD.setdefault('type', 'application/atom+xml')
- else:
- attrsD.setdefault('type', 'text/html')
- context = self._getContext()
- attrsD = self._itsAnHrefDamnIt(attrsD)
- if attrsD.has_key('href'):
- attrsD['href'] = self.resolveURI(attrsD['href'])
- expectingText = self.infeed or self.inentry or self.insource
- context.setdefault('links', [])
- if not (self.inentry and self.inimage):
- context['links'].append(FeedParserDict(attrsD))
- if attrsD.has_key('href'):
- expectingText = 0
- if (attrsD.get('rel') == 'alternate') and (self.mapContentType(attrsD.get('type')) in self.html_types):
- context['link'] = attrsD['href']
- else:
- self.push('link', expectingText)
- _start_producturl = _start_link
-
- def _end_link(self):
- value = self.pop('link')
- context = self._getContext()
- _end_producturl = _end_link
-
- def _start_guid(self, attrsD):
- self.guidislink = (attrsD.get('ispermalink', 'true') == 'true')
- self.push('id', 1)
-
- def _end_guid(self):
- value = self.pop('id')
- self._save('guidislink', self.guidislink and not self._getContext().has_key('link'))
- if self.guidislink:
- # guid acts as link, but only if 'ispermalink' is not present or is 'true',
- # and only if the item doesn't already have a link element
- self._save('link', value)
-
- def _start_title(self, attrsD):
- if self.svgOK: return self.unknown_starttag('title', attrsD.items())
- self.pushContent('title', attrsD, 'text/plain', self.infeed or self.inentry or self.insource)
- _start_dc_title = _start_title
- _start_media_title = _start_title
-
- def _end_title(self):
- if self.svgOK: return
- value = self.popContent('title')
- if not value: return
- context = self._getContext()
- self.hasTitle = 1
- _end_dc_title = _end_title
-
- def _end_media_title(self):
- hasTitle = self.hasTitle
- self._end_title()
- self.hasTitle = hasTitle
-
- def _start_description(self, attrsD):
- context = self._getContext()
- if context.has_key('summary'):
- self._summaryKey = 'content'
- self._start_content(attrsD)
- else:
- self.pushContent('description', attrsD, 'text/html', self.infeed or self.inentry or self.insource)
- _start_dc_description = _start_description
-
- def _start_abstract(self, attrsD):
- self.pushContent('description', attrsD, 'text/plain', self.infeed or self.inentry or self.insource)
-
- def _end_description(self):
- if self._summaryKey == 'content':
- self._end_content()
- else:
- value = self.popContent('description')
- self._summaryKey = None
- _end_abstract = _end_description
- _end_dc_description = _end_description
-
- def _start_info(self, attrsD):
- self.pushContent('info', attrsD, 'text/plain', 1)
- _start_feedburner_browserfriendly = _start_info
-
- def _end_info(self):
- self.popContent('info')
- _end_feedburner_browserfriendly = _end_info
-
- def _start_generator(self, attrsD):
- if attrsD:
- attrsD = self._itsAnHrefDamnIt(attrsD)
- if attrsD.has_key('href'):
- attrsD['href'] = self.resolveURI(attrsD['href'])
- self._getContext()['generator_detail'] = FeedParserDict(attrsD)
- self.push('generator', 1)
-
- def _end_generator(self):
- value = self.pop('generator')
- context = self._getContext()
- if context.has_key('generator_detail'):
- context['generator_detail']['name'] = value
-
- def _start_admin_generatoragent(self, attrsD):
- self.push('generator', 1)
- value = self._getAttribute(attrsD, 'rdf:resource')
- if value:
- self.elementstack[-1][2].append(value)
- self.pop('generator')
- self._getContext()['generator_detail'] = FeedParserDict({'href': value})
-
- def _start_admin_errorreportsto(self, attrsD):
- self.push('errorreportsto', 1)
- value = self._getAttribute(attrsD, 'rdf:resource')
- if value:
- self.elementstack[-1][2].append(value)
- self.pop('errorreportsto')
-
- def _start_summary(self, attrsD):
- context = self._getContext()
- if context.has_key('summary'):
- self._summaryKey = 'content'
- self._start_content(attrsD)
- else:
- self._summaryKey = 'summary'
- self.pushContent(self._summaryKey, attrsD, 'text/plain', 1)
- _start_itunes_summary = _start_summary
-
- def _end_summary(self):
- if self._summaryKey == 'content':
- self._end_content()
- else:
- self.popContent(self._summaryKey or 'summary')
- self._summaryKey = None
- _end_itunes_summary = _end_summary
-
- def _start_enclosure(self, attrsD):
- attrsD = self._itsAnHrefDamnIt(attrsD)
- context = self._getContext()
- attrsD['rel']='enclosure'
- context.setdefault('links', []).append(FeedParserDict(attrsD))
-
- def _start_source(self, attrsD):
- if 'url' in attrsD:
- # This means that we're processing a source element from an RSS 2.0 feed
- self.sourcedata['href'] = attrsD[u'url']
- self.push('source', 1)
- self.insource = 1
- self.hasTitle = 0
-
- def _end_source(self):
- self.insource = 0
- value = self.pop('source')
- if value:
- self.sourcedata['title'] = value
- self._getContext()['source'] = copy.deepcopy(self.sourcedata)
- self.sourcedata.clear()
-
- def _start_content(self, attrsD):
- self.pushContent('content', attrsD, 'text/plain', 1)
- src = attrsD.get('src')
- if src:
- self.contentparams['src'] = src
- self.push('content', 1)
-
- def _start_prodlink(self, attrsD):
- self.pushContent('content', attrsD, 'text/html', 1)
-
- def _start_body(self, attrsD):
- self.pushContent('content', attrsD, 'application/xhtml+xml', 1)
- _start_xhtml_body = _start_body
-
- def _start_content_encoded(self, attrsD):
- self.pushContent('content', attrsD, 'text/html', 1)
- _start_fullitem = _start_content_encoded
-
- def _end_content(self):
- copyToSummary = self.mapContentType(self.contentparams.get('type')) in (['text/plain'] + self.html_types)
- value = self.popContent('content')
- if copyToSummary:
- self._save('summary', value)
-
- _end_body = _end_content
- _end_xhtml_body = _end_content
- _end_content_encoded = _end_content
- _end_fullitem = _end_content
- _end_prodlink = _end_content
-
- def _start_itunes_image(self, attrsD):
- self.push('itunes_image', 0)
- if attrsD.get('href'):
- self._getContext()['image'] = FeedParserDict({'href': attrsD.get('href')})
- _start_itunes_link = _start_itunes_image
-
- def _end_itunes_block(self):
- value = self.pop('itunes_block', 0)
- self._getContext()['itunes_block'] = (value == 'yes') and 1 or 0
-
- def _end_itunes_explicit(self):
- value = self.pop('itunes_explicit', 0)
- # Convert 'yes' -> True, 'clean' to False, and any other value to None
- # False and None both evaluate as False, so the difference can be ignored
- # by applications that only need to know if the content is explicit.
- self._getContext()['itunes_explicit'] = (None, False, True)[(value == 'yes' and 2) or value == 'clean' or 0]
-
- def _start_media_content(self, attrsD):
- context = self._getContext()
- context.setdefault('media_content', [])
- context['media_content'].append(attrsD)
-
- def _start_media_thumbnail(self, attrsD):
- context = self._getContext()
- context.setdefault('media_thumbnail', [])
- self.push('url', 1) # new
- context['media_thumbnail'].append(attrsD)
-
- def _end_media_thumbnail(self):
- url = self.pop('url')
- context = self._getContext()
- if url is not None and len(url.strip()) != 0:
- if not context['media_thumbnail'][-1].has_key('url'):
- context['media_thumbnail'][-1]['url'] = url
-
- def _start_media_player(self, attrsD):
- self.push('media_player', 0)
- self._getContext()['media_player'] = FeedParserDict(attrsD)
-
- def _end_media_player(self):
- value = self.pop('media_player')
- context = self._getContext()
- context['media_player']['content'] = value
-
- def _start_newlocation(self, attrsD):
- self.push('newlocation', 1)
-
- def _end_newlocation(self):
- url = self.pop('newlocation')
- context = self._getContext()
- # don't set newlocation if the context isn't right
- if context is not self.feeddata:
- return
- context['newlocation'] = _makeSafeAbsoluteURI(self.baseuri, url.strip())
-
-if _XML_AVAILABLE:
- class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
- def __init__(self, baseuri, baselang, encoding):
- if _debug: sys.stderr.write('trying StrictFeedParser\n')
- xml.sax.handler.ContentHandler.__init__(self)
- _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
- self.bozo = 0
- self.exc = None
- self.decls = {}
-
- def startPrefixMapping(self, prefix, uri):
- self.trackNamespace(prefix, uri)
- if uri == 'http://www.w3.org/1999/xlink':
- self.decls['xmlns:'+prefix] = uri
-
- def startElementNS(self, name, qname, attrs):
- namespace, localname = name
- lowernamespace = str(namespace or '').lower()
- if lowernamespace.find('backend.userland.com/rss') <> -1:
- # match any backend.userland.com namespace
- namespace = 'http://backend.userland.com/rss'
- lowernamespace = namespace
- if qname and qname.find(':') > 0:
- givenprefix = qname.split(':')[0]
- else:
- givenprefix = None
- prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
- if givenprefix and (prefix is None or (prefix == '' and lowernamespace == '')) and not self.namespacesInUse.has_key(givenprefix):
- raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
- localname = str(localname).lower()
-
- # qname implementation is horribly broken in Python 2.1 (it
- # doesn't report any), and slightly broken in Python 2.2 (it
- # doesn't report the xml: namespace). So we match up namespaces
- # with a known list first, and then possibly override them with
- # the qnames the SAX parser gives us (if indeed it gives us any
- # at all). Thanks to MatejC for helping me test this and
- # tirelessly telling me that it didn't work yet.
- attrsD, self.decls = self.decls, {}
- if localname=='math' and namespace=='http://www.w3.org/1998/Math/MathML':
- attrsD['xmlns']=namespace
- if localname=='svg' and namespace=='http://www.w3.org/2000/svg':
- attrsD['xmlns']=namespace
-
- if prefix:
- localname = prefix.lower() + ':' + localname
- elif namespace and not qname: #Expat
- for name,value in self.namespacesInUse.items():
- if name and value == namespace:
- localname = name + ':' + localname
- break
- if _debug: sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname))
-
- for (namespace, attrlocalname), attrvalue in attrs._attrs.items():
- lowernamespace = (namespace or '').lower()
- prefix = self._matchnamespaces.get(lowernamespace, '')
- if prefix:
- attrlocalname = prefix + ':' + attrlocalname
- attrsD[str(attrlocalname).lower()] = attrvalue
- for qname in attrs.getQNames():
- attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
- self.unknown_starttag(localname, attrsD.items())
-
- def characters(self, text):
- self.handle_data(text)
-
- def endElementNS(self, name, qname):
- namespace, localname = name
- lowernamespace = str(namespace or '').lower()
- if qname and qname.find(':') > 0:
- givenprefix = qname.split(':')[0]
- else:
- givenprefix = ''
- prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
- if prefix:
- localname = prefix + ':' + localname
- elif namespace and not qname: #Expat
- for name,value in self.namespacesInUse.items():
- if name and value == namespace:
- localname = name + ':' + localname
- break
- localname = str(localname).lower()
- self.unknown_endtag(localname)
-
- def error(self, exc):
- self.bozo = 1
- self.exc = exc
-
- def fatalError(self, exc):
- self.error(exc)
- raise exc
-
-class _BaseHTMLProcessor(sgmllib.SGMLParser):
- special = re.compile('''[<>'"]''')
- bare_ampersand = re.compile("&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)")
- elements_no_end_tag = [
- 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
- 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
- 'source', 'track', 'wbr'
- ]
-
- def __init__(self, encoding, _type):
- self.encoding = encoding
- self._type = _type
- if _debug: sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
- sgmllib.SGMLParser.__init__(self)
-
- def reset(self):
- self.pieces = []
- sgmllib.SGMLParser.reset(self)
-
- def _shorttag_replace(self, match):
- tag = match.group(1)
- if tag in self.elements_no_end_tag:
- return '<' + tag + ' />'
- else:
- return '<' + tag + '></' + tag + '>'
-
- def parse_starttag(self,i):
- j=sgmllib.SGMLParser.parse_starttag(self, i)
- if self._type == 'application/xhtml+xml':
- if j>2 and self.rawdata[j-2:j]=='/>':
- self.unknown_endtag(self.lasttag)
- return j
-
- def feed(self, data):
- data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'&lt;!\1', data)
- #data = re.sub(r'<(\S+?)\s*?/>', self._shorttag_replace, data) # bug [ 1399464 ] Bad regexp for _shorttag_replace
- data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
- data = data.replace('&#39;', "'")
- data = data.replace('&#34;', '"')
- try:
- bytes
- if bytes is str:
- raise NameError
- self.encoding = self.encoding + '_INVALID_PYTHON_3'
- except NameError:
- if self.encoding and type(data) == type(u''):
- data = data.encode(self.encoding)
- sgmllib.SGMLParser.feed(self, data)
- sgmllib.SGMLParser.close(self)
-
- def normalize_attrs(self, attrs):
- if not attrs: return attrs
- # utility method to be called by descendants
- attrs = dict([(k.lower(), v) for k, v in attrs]).items()
- attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs]
- attrs.sort()
- return attrs
-
- def unknown_starttag(self, tag, attrs):
- # called for each start tag
- # attrs is a list of (attr, value) tuples
- # e.g. for <pre class='screen'>, tag='pre', attrs=[('class', 'screen')]
- if _debug: sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
- uattrs = []
- strattrs=''
- if attrs:
- for key, value in attrs:
- value=value.replace('>','&gt;').replace('<','&lt;').replace('"','&quot;')
- value = self.bare_ampersand.sub("&amp;", value)
- # thanks to Kevin Marks for this breathtaking hack to deal with (valid) high-bit attribute values in UTF-8 feeds
- if type(value) != type(u''):
- try:
- value = unicode(value, self.encoding)
- except:
- value = unicode(value, 'iso-8859-1')
- try:
- # Currently, in Python 3 the key is already a str, and cannot be decoded again
- uattrs.append((unicode(key, self.encoding), value))
- except TypeError:
- uattrs.append((key, value))
- strattrs = u''.join([u' %s="%s"' % (key, value) for key, value in uattrs])
- if self.encoding:
- try:
- strattrs=strattrs.encode(self.encoding)
- except:
- pass
- if tag in self.elements_no_end_tag:
- self.pieces.append('<%(tag)s%(strattrs)s />' % locals())
- else:
- self.pieces.append('<%(tag)s%(strattrs)s>' % locals())
-
- def unknown_endtag(self, tag):
- # called for each end tag, e.g. for </pre>, tag will be 'pre'
- # Reconstruct the original end tag.
- if tag not in self.elements_no_end_tag:
- self.pieces.append("</%(tag)s>" % locals())
-
- def handle_charref(self, ref):
- # called for each character reference, e.g. for '&#160;', ref will be '160'
- # Reconstruct the original character reference.
- if ref.startswith('x'):
- value = unichr(int(ref[1:],16))
- else:
- value = unichr(int(ref))
-
- if value in _cp1252.keys():
- self.pieces.append('&#%s;' % hex(ord(_cp1252[value]))[1:])
- else:
- self.pieces.append('&#%(ref)s;' % locals())
-
- def handle_entityref(self, ref):
- # called for each entity reference, e.g. for '&copy;', ref will be 'copy'
- # Reconstruct the original entity reference.
- if name2codepoint.has_key(ref):
- self.pieces.append('&%(ref)s;' % locals())
- else:
- self.pieces.append('&amp;%(ref)s' % locals())
-
- def handle_data(self, text):
- # called for each block of plain text, i.e. outside of any tag and
- # not containing any character or entity references
- # Store the original text verbatim.
- if _debug: sys.stderr.write('_BaseHTMLProcessor, handle_data, text=%s\n' % text)
- self.pieces.append(text)
-
- def handle_comment(self, text):
- # called for each HTML comment, e.g. <!-- insert Javascript code here -->
- # Reconstruct the original comment.
- self.pieces.append('<!--%(text)s-->' % locals())
-
- def handle_pi(self, text):
- # called for each processing instruction, e.g. <?instruction>
- # Reconstruct original processing instruction.
- self.pieces.append('<?%(text)s>' % locals())
-
- def handle_decl(self, text):
- # called for the DOCTYPE, if present, e.g.
- # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- # "http://www.w3.org/TR/html4/loose.dtd">
- # Reconstruct original DOCTYPE
- self.pieces.append('<!%(text)s>' % locals())
-
- _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match
- def _scan_name(self, i, declstartpos):
- rawdata = self.rawdata
- n = len(rawdata)
- if i == n:
- return None, -1
- m = self._new_declname_match(rawdata, i)
- if m:
- s = m.group()
- name = s.strip()
- if (i + len(s)) == n:
- return None, -1 # end of buffer
- return name.lower(), m.end()
- else:
- self.handle_data(rawdata)
-# self.updatepos(declstartpos, i)
- return None, -1
-
- def convert_charref(self, name):
- return '&#%s;' % name
-
- def convert_entityref(self, name):
- return '&%s;' % name
-
- def output(self):
- '''Return processed HTML as a single string'''
- return ''.join([str(p) for p in self.pieces])
-
-class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
- def __init__(self, baseuri, baselang, encoding, entities):
- sgmllib.SGMLParser.__init__(self)
- _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
- _BaseHTMLProcessor.__init__(self, encoding, 'application/xhtml+xml')
- self.entities=entities
-
- def decodeEntities(self, element, data):
- data = data.replace('&#60;', '&lt;')
- data = data.replace('&#x3c;', '&lt;')
- data = data.replace('&#x3C;', '&lt;')
- data = data.replace('&#62;', '&gt;')
- data = data.replace('&#x3e;', '&gt;')
- data = data.replace('&#x3E;', '&gt;')
- data = data.replace('&#38;', '&amp;')
- data = data.replace('&#x26;', '&amp;')
- data = data.replace('&#34;', '&quot;')
- data = data.replace('&#x22;', '&quot;')
- data = data.replace('&#39;', '&apos;')
- data = data.replace('&#x27;', '&apos;')
- if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
- data = data.replace('&lt;', '<')
- data = data.replace('&gt;', '>')
- data = data.replace('&amp;', '&')
- data = data.replace('&quot;', '"')
- data = data.replace('&apos;', "'")
- return data
-
- def strattrs(self, attrs):
- return ''.join([' %s="%s"' % (n,v.replace('"','&quot;')) for n,v in attrs])
-
-class _MicroformatsParser:
- STRING = 1
- DATE = 2
- URI = 3
- NODE = 4
- EMAIL = 5
-
- known_xfn_relationships = ['contact', 'acquaintance', 'friend', 'met', 'co-worker', 'coworker', 'colleague', 'co-resident', 'coresident', 'neighbor', 'child', 'parent', 'sibling', 'brother', 'sister', 'spouse', 'wife', 'husband', 'kin', 'relative', 'muse', 'crush', 'date', 'sweetheart', 'me']
- known_binary_extensions = ['zip','rar','exe','gz','tar','tgz','tbz2','bz2','z','7z','dmg','img','sit','sitx','hqx','deb','rpm','bz2','jar','rar','iso','bin','msi','mp2','mp3','ogg','ogm','mp4','m4v','m4a','avi','wma','wmv']
-
- def __init__(self, data, baseuri, encoding):
- self.document = BeautifulSoup.BeautifulSoup(data)
- self.baseuri = baseuri
- self.encoding = encoding
- if type(data) == type(u''):
- data = data.encode(encoding)
- self.tags = []
- self.enclosures = []
- self.xfn = []
- self.vcard = None
-
- def vcardEscape(self, s):
- if type(s) in (type(''), type(u'')):
- s = s.replace(',', '\\,').replace(';', '\\;').replace('\n', '\\n')
- return s
-
- def vcardFold(self, s):
- s = re.sub(';+$', '', s)
- sFolded = ''
- iMax = 75
- sPrefix = ''
- while len(s) > iMax:
- sFolded += sPrefix + s[:iMax] + '\n'
- s = s[iMax:]
- sPrefix = ' '
- iMax = 74
- sFolded += sPrefix + s
- return sFolded
-
- def normalize(self, s):
- return re.sub(r'\s+', ' ', s).strip()
-
- def unique(self, aList):
- results = []
- for element in aList:
- if element not in results:
- results.append(element)
- return results
-
- def toISO8601(self, dt):
- return time.strftime('%Y-%m-%dT%H:%M:%SZ', dt)
-
- def getPropertyValue(self, elmRoot, sProperty, iPropertyType=4, bAllowMultiple=0, bAutoEscape=0):
- all = lambda x: 1
- sProperty = sProperty.lower()
- bFound = 0
- bNormalize = 1
- propertyMatch = {'class': re.compile(r'\b%s\b' % sProperty)}
- if bAllowMultiple and (iPropertyType != self.NODE):
- snapResults = []
- containers = elmRoot(['ul', 'ol'], propertyMatch)
- for container in containers:
- snapResults.extend(container('li'))
- bFound = (len(snapResults) != 0)
- if not bFound:
- snapResults = elmRoot(all, propertyMatch)
- bFound = (len(snapResults) != 0)
- if (not bFound) and (sProperty == 'value'):
- snapResults = elmRoot('pre')
- bFound = (len(snapResults) != 0)
- bNormalize = not bFound
- if not bFound:
- snapResults = [elmRoot]
- bFound = (len(snapResults) != 0)
- arFilter = []
- if sProperty == 'vcard':
- snapFilter = elmRoot(all, propertyMatch)
- for node in snapFilter:
- if node.findParent(all, propertyMatch):
- arFilter.append(node)
- arResults = []
- for node in snapResults:
- if node not in arFilter:
- arResults.append(node)
- bFound = (len(arResults) != 0)
- if not bFound:
- if bAllowMultiple: return []
- elif iPropertyType == self.STRING: return ''
- elif iPropertyType == self.DATE: return None
- elif iPropertyType == self.URI: return ''
- elif iPropertyType == self.NODE: return None
- else: return None
- arValues = []
- for elmResult in arResults:
- sValue = None
- if iPropertyType == self.NODE:
- if bAllowMultiple:
- arValues.append(elmResult)
- continue
- else:
- return elmResult
- sNodeName = elmResult.name.lower()
- if (iPropertyType == self.EMAIL) and (sNodeName == 'a'):
- sValue = (elmResult.get('href') or '').split('mailto:').pop().split('?')[0]
- if sValue:
- sValue = bNormalize and self.normalize(sValue) or sValue.strip()
- if (not sValue) and (sNodeName == 'abbr'):
- sValue = elmResult.get('title')
- if sValue:
- sValue = bNormalize and self.normalize(sValue) or sValue.strip()
- if (not sValue) and (iPropertyType == self.URI):
- if sNodeName == 'a': sValue = elmResult.get('href')
- elif sNodeName == 'img': sValue = elmResult.get('src')
- elif sNodeName == 'object': sValue = elmResult.get('data')
- if sValue:
- sValue = bNormalize and self.normalize(sValue) or sValue.strip()
- if (not sValue) and (sNodeName == 'img'):
- sValue = elmResult.get('alt')
- if sValue:
- sValue = bNormalize and self.normalize(sValue) or sValue.strip()
- if not sValue:
- sValue = elmResult.renderContents()
- sValue = re.sub(r'<\S[^>]*>', '', sValue)
- sValue = sValue.replace('\r\n', '\n')
- sValue = sValue.replace('\r', '\n')
- if sValue:
- sValue = bNormalize and self.normalize(sValue) or sValue.strip()
- if not sValue: continue
- if iPropertyType == self.DATE:
- sValue = _parse_date_iso8601(sValue)
- if bAllowMultiple:
- arValues.append(bAutoEscape and self.vcardEscape(sValue) or sValue)
- else:
- return bAutoEscape and self.vcardEscape(sValue) or sValue
- return arValues
-
- def findVCards(self, elmRoot, bAgentParsing=0):
- sVCards = ''
-
- if not bAgentParsing:
- arCards = self.getPropertyValue(elmRoot, 'vcard', bAllowMultiple=1)
- else:
- arCards = [elmRoot]
-
- for elmCard in arCards:
- arLines = []
-
- def processSingleString(sProperty):
- sValue = self.getPropertyValue(elmCard, sProperty, self.STRING, bAutoEscape=1).decode(self.encoding)
- if sValue:
- arLines.append(self.vcardFold(sProperty.upper() + ':' + sValue))
- return sValue or u''
-
- def processSingleURI(sProperty):
- sValue = self.getPropertyValue(elmCard, sProperty, self.URI)
- if sValue:
- sContentType = ''
- sEncoding = ''
- sValueKey = ''
- if sValue.startswith('data:'):
- sEncoding = ';ENCODING=b'
- sContentType = sValue.split(';')[0].split('/').pop()
- sValue = sValue.split(',', 1).pop()
- else:
- elmValue = self.getPropertyValue(elmCard, sProperty)
- if elmValue:
- if sProperty != 'url':
- sValueKey = ';VALUE=uri'
- sContentType = elmValue.get('type', '').strip().split('/').pop().strip()
- sContentType = sContentType.upper()
- if sContentType == 'OCTET-STREAM':
- sContentType = ''
- if sContentType:
- sContentType = ';TYPE=' + sContentType.upper()
- arLines.append(self.vcardFold(sProperty.upper() + sEncoding + sContentType + sValueKey + ':' + sValue))
-
- def processTypeValue(sProperty, arDefaultType, arForceType=None):
- arResults = self.getPropertyValue(elmCard, sProperty, bAllowMultiple=1)
- for elmResult in arResults:
- arType = self.getPropertyValue(elmResult, 'type', self.STRING, 1, 1)
- if arForceType:
- arType = self.unique(arForceType + arType)
- if not arType:
- arType = arDefaultType
- sValue = self.getPropertyValue(elmResult, 'value', self.EMAIL, 0)
- if sValue:
- arLines.append(self.vcardFold(sProperty.upper() + ';TYPE=' + ','.join(arType) + ':' + sValue))
-
- # AGENT
- # must do this before all other properties because it is destructive
- # (removes nested class="vcard" nodes so they don't interfere with
- # this vcard's other properties)
- arAgent = self.getPropertyValue(elmCard, 'agent', bAllowMultiple=1)
- for elmAgent in arAgent:
- if re.compile(r'\bvcard\b').search(elmAgent.get('class')):
- sAgentValue = self.findVCards(elmAgent, 1) + '\n'
- sAgentValue = sAgentValue.replace('\n', '\\n')
- sAgentValue = sAgentValue.replace(';', '\\;')
- if sAgentValue:
- arLines.append(self.vcardFold('AGENT:' + sAgentValue))
- # Completely remove the agent element from the parse tree
- elmAgent.extract()
- else:
- sAgentValue = self.getPropertyValue(elmAgent, 'value', self.URI, bAutoEscape=1);
- if sAgentValue:
- arLines.append(self.vcardFold('AGENT;VALUE=uri:' + sAgentValue))
-
- # FN (full name)
- sFN = processSingleString('fn')
-
- # N (name)
- elmName = self.getPropertyValue(elmCard, 'n')
- if elmName:
- sFamilyName = self.getPropertyValue(elmName, 'family-name', self.STRING, bAutoEscape=1)
- sGivenName = self.getPropertyValue(elmName, 'given-name', self.STRING, bAutoEscape=1)
- arAdditionalNames = self.getPropertyValue(elmName, 'additional-name', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'additional-names', self.STRING, 1, 1)
- arHonorificPrefixes = self.getPropertyValue(elmName, 'honorific-prefix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-prefixes', self.STRING, 1, 1)
- arHonorificSuffixes = self.getPropertyValue(elmName, 'honorific-suffix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-suffixes', self.STRING, 1, 1)
- arLines.append(self.vcardFold('N:' + sFamilyName + ';' +
- sGivenName + ';' +
- ','.join(arAdditionalNames) + ';' +
- ','.join(arHonorificPrefixes) + ';' +
- ','.join(arHonorificSuffixes)))
- elif sFN:
- # implied "N" optimization
- # http://microformats.org/wiki/hcard#Implied_.22N.22_Optimization
- arNames = self.normalize(sFN).split()
- if len(arNames) == 2:
- bFamilyNameFirst = (arNames[0].endswith(',') or
- len(arNames[1]) == 1 or
- ((len(arNames[1]) == 2) and (arNames[1].endswith('.'))))
- if bFamilyNameFirst:
- arLines.append(self.vcardFold('N:' + arNames[0] + ';' + arNames[1]))
- else:
- arLines.append(self.vcardFold('N:' + arNames[1] + ';' + arNames[0]))
-
- # SORT-STRING
- sSortString = self.getPropertyValue(elmCard, 'sort-string', self.STRING, bAutoEscape=1)
- if sSortString:
- arLines.append(self.vcardFold('SORT-STRING:' + sSortString))
-
- # NICKNAME
- arNickname = self.getPropertyValue(elmCard, 'nickname', self.STRING, 1, 1)
- if arNickname:
- arLines.append(self.vcardFold('NICKNAME:' + ','.join(arNickname)))
-
- # PHOTO
- processSingleURI('photo')
-
- # BDAY
- dtBday = self.getPropertyValue(elmCard, 'bday', self.DATE)
- if dtBday:
- arLines.append(self.vcardFold('BDAY:' + self.toISO8601(dtBday)))
-
- # ADR (address)
- arAdr = self.getPropertyValue(elmCard, 'adr', bAllowMultiple=1)
- for elmAdr in arAdr:
- arType = self.getPropertyValue(elmAdr, 'type', self.STRING, 1, 1)
- if not arType:
- arType = ['intl','postal','parcel','work'] # default adr types, see RFC 2426 section 3.2.1
- sPostOfficeBox = self.getPropertyValue(elmAdr, 'post-office-box', self.STRING, 0, 1)
- sExtendedAddress = self.getPropertyValue(elmAdr, 'extended-address', self.STRING, 0, 1)
- sStreetAddress = self.getPropertyValue(elmAdr, 'street-address', self.STRING, 0, 1)
- sLocality = self.getPropertyValue(elmAdr, 'locality', self.STRING, 0, 1)
- sRegion = self.getPropertyValue(elmAdr, 'region', self.STRING, 0, 1)
- sPostalCode = self.getPropertyValue(elmAdr, 'postal-code', self.STRING, 0, 1)
- sCountryName = self.getPropertyValue(elmAdr, 'country-name', self.STRING, 0, 1)
- arLines.append(self.vcardFold('ADR;TYPE=' + ','.join(arType) + ':' +
- sPostOfficeBox + ';' +
- sExtendedAddress + ';' +
- sStreetAddress + ';' +
- sLocality + ';' +
- sRegion + ';' +
- sPostalCode + ';' +
- sCountryName))
-
- # LABEL
- processTypeValue('label', ['intl','postal','parcel','work'])
-
- # TEL (phone number)
- processTypeValue('tel', ['voice'])
-
- # EMAIL
- processTypeValue('email', ['internet'], ['internet'])
-
- # MAILER
- processSingleString('mailer')
-
- # TZ (timezone)
- processSingleString('tz')
-
- # GEO (geographical information)
- elmGeo = self.getPropertyValue(elmCard, 'geo')
- if elmGeo:
- sLatitude = self.getPropertyValue(elmGeo, 'latitude', self.STRING, 0, 1)
- sLongitude = self.getPropertyValue(elmGeo, 'longitude', self.STRING, 0, 1)
- arLines.append(self.vcardFold('GEO:' + sLatitude + ';' + sLongitude))
-
- # TITLE
- processSingleString('title')
-
- # ROLE
- processSingleString('role')
-
- # LOGO
- processSingleURI('logo')
-
- # ORG (organization)
- elmOrg = self.getPropertyValue(elmCard, 'org')
- if elmOrg:
- sOrganizationName = self.getPropertyValue(elmOrg, 'organization-name', self.STRING, 0, 1)
- if not sOrganizationName:
- # implied "organization-name" optimization
- # http://microformats.org/wiki/hcard#Implied_.22organization-name.22_Optimization
- sOrganizationName = self.getPropertyValue(elmCard, 'org', self.STRING, 0, 1)
- if sOrganizationName:
- arLines.append(self.vcardFold('ORG:' + sOrganizationName))
- else:
- arOrganizationUnit = self.getPropertyValue(elmOrg, 'organization-unit', self.STRING, 1, 1)
- arLines.append(self.vcardFold('ORG:' + sOrganizationName + ';' + ';'.join(arOrganizationUnit)))
-
- # CATEGORY
- arCategory = self.getPropertyValue(elmCard, 'category', self.STRING, 1, 1) + self.getPropertyValue(elmCard, 'categories', self.STRING, 1, 1)
- if arCategory:
- arLines.append(self.vcardFold('CATEGORIES:' + ','.join(arCategory)))
-
- # NOTE
- processSingleString('note')
-
- # REV
- processSingleString('rev')
-
- # SOUND
- processSingleURI('sound')
-
- # UID
- processSingleString('uid')
-
- # URL
- processSingleURI('url')
-
- # CLASS
- processSingleString('class')
-
- # KEY
- processSingleURI('key')
-
- if arLines:
- arLines = [u'BEGIN:vCard',u'VERSION:3.0'] + arLines + [u'END:vCard']
- sVCards += u'\n'.join(arLines) + u'\n'
-
- return sVCards.strip()
-
- def isProbablyDownloadable(self, elm):
- attrsD = elm.attrMap
- if not attrsD.has_key('href'): return 0
- linktype = attrsD.get('type', '').strip()
- if linktype.startswith('audio/') or \
- linktype.startswith('video/') or \
- (linktype.startswith('application/') and not linktype.endswith('xml')):
- return 1
- path = urlparse.urlparse(attrsD['href'])[2]
- if path.find('.') == -1: return 0
- fileext = path.split('.').pop().lower()
- return fileext in self.known_binary_extensions
-
- def findTags(self):
- all = lambda x: 1
- for elm in self.document(all, {'rel': re.compile(r'\btag\b')}):
- href = elm.get('href')
- if not href: continue
- urlscheme, domain, path, params, query, fragment = \
- urlparse.urlparse(_urljoin(self.baseuri, href))
- segments = path.split('/')
- tag = segments.pop()
- if not tag:
- tag = segments.pop()
- tagscheme = urlparse.urlunparse((urlscheme, domain, '/'.join(segments), '', '', ''))
- if not tagscheme.endswith('/'):
- tagscheme += '/'
- self.tags.append(FeedParserDict({"term": tag, "scheme": tagscheme, "label": elm.string or ''}))
-
- def findEnclosures(self):
- all = lambda x: 1
- enclosure_match = re.compile(r'\benclosure\b')
- for elm in self.document(all, {'href': re.compile(r'.+')}):
- if not enclosure_match.search(elm.get('rel', '')) and not self.isProbablyDownloadable(elm): continue
- if elm.attrMap not in self.enclosures:
- self.enclosures.append(elm.attrMap)
- if elm.string and not elm.get('title'):
- self.enclosures[-1]['title'] = elm.string
-
- def findXFN(self):
- all = lambda x: 1
- for elm in self.document(all, {'rel': re.compile('.+'), 'href': re.compile('.+')}):
- rels = elm.get('rel', '').split()
- xfn_rels = []
- for rel in rels:
- if rel in self.known_xfn_relationships:
- xfn_rels.append(rel)
- if xfn_rels:
- self.xfn.append({"relationships": xfn_rels, "href": elm.get('href', ''), "name": elm.string})
-
-def _parseMicroformats(htmlSource, baseURI, encoding):
- if not BeautifulSoup: return
- if _debug: sys.stderr.write('entering _parseMicroformats\n')
- try:
- p = _MicroformatsParser(htmlSource, baseURI, encoding)
- except UnicodeEncodeError:
- # sgmllib throws this exception when performing lookups of tags
- # with non-ASCII characters in them.
- return
- p.vcard = p.findVCards(p.document)
- p.findTags()
- p.findEnclosures()
- p.findXFN()
- return {"tags": p.tags, "enclosures": p.enclosures, "xfn": p.xfn, "vcard": p.vcard}
-
-class _RelativeURIResolver(_BaseHTMLProcessor):
- relative_uris = [('a', 'href'),
- ('applet', 'codebase'),
- ('area', 'href'),
- ('blockquote', 'cite'),
- ('body', 'background'),
- ('del', 'cite'),
- ('form', 'action'),
- ('frame', 'longdesc'),
- ('frame', 'src'),
- ('iframe', 'longdesc'),
- ('iframe', 'src'),
- ('head', 'profile'),
- ('img', 'longdesc'),
- ('img', 'src'),
- ('img', 'usemap'),
- ('input', 'src'),
- ('input', 'usemap'),
- ('ins', 'cite'),
- ('link', 'href'),
- ('object', 'classid'),
- ('object', 'codebase'),
- ('object', 'data'),
- ('object', 'usemap'),
- ('q', 'cite'),
- ('script', 'src')]
-
- def __init__(self, baseuri, encoding, _type):
- _BaseHTMLProcessor.__init__(self, encoding, _type)
- self.baseuri = baseuri
-
- def resolveURI(self, uri):
- return _makeSafeAbsoluteURI(_urljoin(self.baseuri, uri.strip()))
-
- def unknown_starttag(self, tag, attrs):
- if _debug:
- sys.stderr.write('tag: [%s] with attributes: [%s]\n' % (tag, str(attrs)))
- attrs = self.normalize_attrs(attrs)
- attrs = [(key, ((tag, key) in self.relative_uris) and self.resolveURI(value) or value) for key, value in attrs]
- _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
-
-def _resolveRelativeURIs(htmlSource, baseURI, encoding, _type):
- if _debug:
- sys.stderr.write('entering _resolveRelativeURIs\n')
-
- p = _RelativeURIResolver(baseURI, encoding, _type)
- p.feed(htmlSource)
- return p.output()
-
-def _makeSafeAbsoluteURI(base, rel=None):
- # bail if ACCEPTABLE_URI_SCHEMES is empty
- if not ACCEPTABLE_URI_SCHEMES:
- return _urljoin(base, rel or u'')
- if not base:
- return rel or u''
- if not rel:
- if base.strip().split(':', 1)[0] not in ACCEPTABLE_URI_SCHEMES:
- return u''
- return base
- uri = _urljoin(base, rel)
- if uri.strip().split(':', 1)[0] not in ACCEPTABLE_URI_SCHEMES:
- return u''
- return uri
-
-class _HTMLSanitizer(_BaseHTMLProcessor):
- acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
- 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
- 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
- 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn',
- 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset',
- 'figcaption', 'figure', 'footer', 'font', 'form', 'header', 'h1',
- 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins',
- 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter',
- 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option',
- 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select',
- 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong',
- 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
- 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video', 'noscript']
-
- acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey',
- 'action', 'align', 'alt', 'autocomplete', 'autofocus', 'axis',
- 'background', 'balance', 'bgcolor', 'bgproperties', 'border',
- 'bordercolor', 'bordercolordark', 'bordercolorlight', 'bottompadding',
- 'cellpadding', 'cellspacing', 'ch', 'challenge', 'char', 'charoff',
- 'choff', 'charset', 'checked', 'cite', 'class', 'clear', 'color', 'cols',
- 'colspan', 'compact', 'contenteditable', 'controls', 'coords', 'data',
- 'datafld', 'datapagesize', 'datasrc', 'datetime', 'default', 'delay',
- 'dir', 'disabled', 'draggable', 'dynsrc', 'enctype', 'end', 'face', 'for',
- 'form', 'frame', 'galleryimg', 'gutter', 'headers', 'height', 'hidefocus',
- 'hidden', 'high', 'href', 'hreflang', 'hspace', 'icon', 'id', 'inputmode',
- 'ismap', 'keytype', 'label', 'leftspacing', 'lang', 'list', 'longdesc',
- 'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max',
- 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref',
- 'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size',
- 'prompt', 'pqg', 'radiogroup', 'readonly', 'rel', 'repeat-max',
- 'repeat-min', 'replace', 'required', 'rev', 'rightspacing', 'rows',
- 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src',
- 'start', 'step', 'summary', 'suppress', 'tabindex', 'target', 'template',
- 'title', 'toppadding', 'type', 'unselectable', 'usemap', 'urn', 'valign',
- 'value', 'variable', 'volume', 'vspace', 'vrml', 'width', 'wrap',
- 'xml:lang']
-
- unacceptable_elements_with_end_tag = ['script', 'applet', 'style']
-
- acceptable_css_properties = ['azimuth', 'background-color',
- 'border-bottom-color', 'border-collapse', 'border-color',
- 'border-left-color', 'border-right-color', 'border-top-color', 'clear',
- 'color', 'cursor', 'direction', 'display', 'elevation', 'float', 'font',
- 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight',
- 'height', 'letter-spacing', 'line-height', 'overflow', 'pause',
- 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness',
- 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation',
- 'speech-rate', 'stress', 'text-align', 'text-decoration', 'text-indent',
- 'unicode-bidi', 'vertical-align', 'voice-family', 'volume',
- 'white-space', 'width']
-
- # survey of common keywords found in feeds
- acceptable_css_keywords = ['auto', 'aqua', 'black', 'block', 'blue',
- 'bold', 'both', 'bottom', 'brown', 'center', 'collapse', 'dashed',
- 'dotted', 'fuchsia', 'gray', 'green', '!important', 'italic', 'left',
- 'lime', 'maroon', 'medium', 'none', 'navy', 'normal', 'nowrap', 'olive',
- 'pointer', 'purple', 'red', 'right', 'solid', 'silver', 'teal', 'top',
- 'transparent', 'underline', 'white', 'yellow']
-
- valid_css_values = re.compile('^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|' +
- '\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$')
-
- mathml_elements = ['annotation', 'annotation-xml', 'maction', 'math',
- 'merror', 'mfenced', 'mfrac', 'mi', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded',
- 'mphantom', 'mprescripts', 'mroot', 'mrow', 'mspace', 'msqrt', 'mstyle',
- 'msub', 'msubsup', 'msup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder',
- 'munderover', 'none', 'semantics']
-
- mathml_attributes = ['actiontype', 'align', 'columnalign', 'columnalign',
- 'columnalign', 'close', 'columnlines', 'columnspacing', 'columnspan', 'depth',
- 'display', 'displaystyle', 'encoding', 'equalcolumns', 'equalrows',
- 'fence', 'fontstyle', 'fontweight', 'frame', 'height', 'linethickness',
- 'lspace', 'mathbackground', 'mathcolor', 'mathvariant', 'mathvariant',
- 'maxsize', 'minsize', 'open', 'other', 'rowalign', 'rowalign', 'rowalign',
- 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'scriptlevel', 'selection',
- 'separator', 'separators', 'stretchy', 'width', 'width', 'xlink:href',
- 'xlink:show', 'xlink:type', 'xmlns', 'xmlns:xlink']
-
- # svgtiny - foreignObject + linearGradient + radialGradient + stop
- svg_elements = ['a', 'animate', 'animateColor', 'animateMotion',
- 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'foreignObject',
- 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern',
- 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', 'mpath',
- 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop',
- 'svg', 'switch', 'text', 'title', 'tspan', 'use']
-
- # svgtiny + class + opacity + offset + xmlns + xmlns:xlink
- svg_attributes = ['accent-height', 'accumulate', 'additive', 'alphabetic',
- 'arabic-form', 'ascent', 'attributeName', 'attributeType',
- 'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height',
- 'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx',
- 'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-opacity',
- 'fill-rule', 'font-family', 'font-size', 'font-stretch', 'font-style',
- 'font-variant', 'font-weight', 'from', 'fx', 'fy', 'g1', 'g2',
- 'glyph-name', 'gradientUnits', 'hanging', 'height', 'horiz-adv-x',
- 'horiz-origin-x', 'id', 'ideographic', 'k', 'keyPoints', 'keySplines',
- 'keyTimes', 'lang', 'mathematical', 'marker-end', 'marker-mid',
- 'marker-start', 'markerHeight', 'markerUnits', 'markerWidth', 'max',
- 'min', 'name', 'offset', 'opacity', 'orient', 'origin',
- 'overline-position', 'overline-thickness', 'panose-1', 'path',
- 'pathLength', 'points', 'preserveAspectRatio', 'r', 'refX', 'refY',
- 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures',
- 'restart', 'rotate', 'rx', 'ry', 'slope', 'stemh', 'stemv',
- 'stop-color', 'stop-opacity', 'strikethrough-position',
- 'strikethrough-thickness', 'stroke', 'stroke-dasharray',
- 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin',
- 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage',
- 'target', 'text-anchor', 'to', 'transform', 'type', 'u1', 'u2',
- 'underline-position', 'underline-thickness', 'unicode', 'unicode-range',
- 'units-per-em', 'values', 'version', 'viewBox', 'visibility', 'width',
- 'widths', 'x', 'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole',
- 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type',
- 'xml:base', 'xml:lang', 'xml:space', 'xmlns', 'xmlns:xlink', 'y', 'y1',
- 'y2', 'zoomAndPan']
-
- svg_attr_map = None
- svg_elem_map = None
-
- acceptable_svg_properties = [ 'fill', 'fill-opacity', 'fill-rule',
- 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin',
- 'stroke-opacity']
-
- def reset(self):
- _BaseHTMLProcessor.reset(self)
- self.unacceptablestack = 0
- self.mathmlOK = 0
- self.svgOK = 0
-
- def unknown_starttag(self, tag, attrs):
- acceptable_attributes = self.acceptable_attributes
- keymap = {}
- if not tag in self.acceptable_elements or self.svgOK:
- if tag in self.unacceptable_elements_with_end_tag:
- self.unacceptablestack += 1
-
- # add implicit namespaces to html5 inline svg/mathml
- if self._type.endswith('html'):
- if not dict(attrs).get('xmlns'):
- if tag=='svg':
- attrs.append( ('xmlns','http://www.w3.org/2000/svg') )
- if tag=='math':
- attrs.append( ('xmlns','http://www.w3.org/1998/Math/MathML') )
-
- # not otherwise acceptable, perhaps it is MathML or SVG?
- if tag=='math' and ('xmlns','http://www.w3.org/1998/Math/MathML') in attrs:
- self.mathmlOK += 1
- if tag=='svg' and ('xmlns','http://www.w3.org/2000/svg') in attrs:
- self.svgOK += 1
-
- # chose acceptable attributes based on tag class, else bail
- if self.mathmlOK and tag in self.mathml_elements:
- acceptable_attributes = self.mathml_attributes
- elif self.svgOK and tag in self.svg_elements:
- # for most vocabularies, lowercasing is a good idea. Many
- # svg elements, however, are camel case
- if not self.svg_attr_map:
- lower=[attr.lower() for attr in self.svg_attributes]
- mix=[a for a in self.svg_attributes if a not in lower]
- self.svg_attributes = lower
- self.svg_attr_map = dict([(a.lower(),a) for a in mix])
-
- lower=[attr.lower() for attr in self.svg_elements]
- mix=[a for a in self.svg_elements if a not in lower]
- self.svg_elements = lower
- self.svg_elem_map = dict([(a.lower(),a) for a in mix])
- acceptable_attributes = self.svg_attributes
- tag = self.svg_elem_map.get(tag,tag)
- keymap = self.svg_attr_map
- elif not tag in self.acceptable_elements:
- return
-
- # declare xlink namespace, if needed
- if self.mathmlOK or self.svgOK:
- if filter(lambda (n,v): n.startswith('xlink:'),attrs):
- if not ('xmlns:xlink','http://www.w3.org/1999/xlink') in attrs:
- attrs.append(('xmlns:xlink','http://www.w3.org/1999/xlink'))
-
- clean_attrs = []
- for key, value in self.normalize_attrs(attrs):
- if key in acceptable_attributes:
- key=keymap.get(key,key)
- clean_attrs.append((key,value))
- elif key=='style':
- clean_value = self.sanitize_style(value)
- if clean_value: clean_attrs.append((key,clean_value))
- _BaseHTMLProcessor.unknown_starttag(self, tag, clean_attrs)
-
- def unknown_endtag(self, tag):
- if not tag in self.acceptable_elements:
- if tag in self.unacceptable_elements_with_end_tag:
- self.unacceptablestack -= 1
- if self.mathmlOK and tag in self.mathml_elements:
- if tag == 'math' and self.mathmlOK: self.mathmlOK -= 1
- elif self.svgOK and tag in self.svg_elements:
- tag = self.svg_elem_map.get(tag,tag)
- if tag == 'svg' and self.svgOK: self.svgOK -= 1
- else:
- return
- _BaseHTMLProcessor.unknown_endtag(self, tag)
-
- def handle_pi(self, text):
- pass
-
- def handle_decl(self, text):
- pass
-
- def handle_data(self, text):
- if not self.unacceptablestack:
- _BaseHTMLProcessor.handle_data(self, text)
-
- def sanitize_style(self, style):
- # disallow urls
- style=re.compile('url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ',style)
-
- # gauntlet
- if not re.match("""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style): return ''
- # This replaced a regexp that used re.match and was prone to pathological back-tracking.
- if re.sub("\s*[-\w]+\s*:\s*[^:;]*;?", '', style).strip(): return ''
-
- clean = []
- for prop,value in re.findall("([-\w]+)\s*:\s*([^:;]*)",style):
- if not value: continue
- if prop.lower() in self.acceptable_css_properties:
- clean.append(prop + ': ' + value + ';')
- elif prop.split('-')[0].lower() in ['background','border','margin','padding']:
- for keyword in value.split():
- if not keyword in self.acceptable_css_keywords and \
- not self.valid_css_values.match(keyword):
- break
- else:
- clean.append(prop + ': ' + value + ';')
- elif self.svgOK and prop.lower() in self.acceptable_svg_properties:
- clean.append(prop + ': ' + value + ';')
-
- return ' '.join(clean)
-
-
-def _sanitizeHTML(htmlSource, encoding, _type):
- p = _HTMLSanitizer(encoding, _type)
- htmlSource = htmlSource.replace('<![CDATA[', '&lt;![CDATA[')
- p.feed(htmlSource)
- data = p.output()
- if TIDY_MARKUP:
- # loop through list of preferred Tidy interfaces looking for one that's installed,
- # then set up a common _tidy function to wrap the interface-specific API.
- _tidy = None
- for tidy_interface in PREFERRED_TIDY_INTERFACES:
- try:
- if tidy_interface == "uTidy":
- from tidy import parseString as _utidy
- def _tidy(data, **kwargs):
- return str(_utidy(data, **kwargs))
- break
- elif tidy_interface == "mxTidy":
- from mx.Tidy import Tidy as _mxtidy
- def _tidy(data, **kwargs):
- nerrors, nwarnings, data, errordata = _mxtidy.tidy(data, **kwargs)
- return data
- break
- except:
- pass
- if _tidy:
- utf8 = type(data) == type(u'')
- if utf8:
- data = data.encode('utf-8')
- data = _tidy(data, output_xhtml=1, numeric_entities=1, wrap=0, char_encoding="utf8")
- if utf8:
- data = unicode(data, 'utf-8')
- if data.count('<body'):
- data = data.split('<body', 1)[1]
- if data.count('>'):
- data = data.split('>', 1)[1]
- if data.count('</body'):
- data = data.split('</body', 1)[0]
- data = data.strip().replace('\r\n', '\n')
- return data
-
-class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
- def http_error_default(self, req, fp, code, msg, headers):
- if ((code / 100) == 3) and (code != 304):
- return self.http_error_302(req, fp, code, msg, headers)
- infourl = urllib.addinfourl(fp, headers, req.get_full_url())
- infourl.status = code
- return infourl
-
- def http_error_302(self, req, fp, code, msg, headers):
- if headers.dict.has_key('location'):
- infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
- else:
- infourl = urllib.addinfourl(fp, headers, req.get_full_url())
- if not hasattr(infourl, 'status'):
- infourl.status = code
- return infourl
-
- def http_error_301(self, req, fp, code, msg, headers):
- if headers.dict.has_key('location'):
- infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
- else:
- infourl = urllib.addinfourl(fp, headers, req.get_full_url())
- if not hasattr(infourl, 'status'):
- infourl.status = code
- return infourl
-
- http_error_300 = http_error_302
- http_error_303 = http_error_302
- http_error_307 = http_error_302
-
- def http_error_401(self, req, fp, code, msg, headers):
- # Check if
- # - server requires digest auth, AND
- # - we tried (unsuccessfully) with basic auth, AND
- # - we're using Python 2.3.3 or later (digest auth is irreparably broken in earlier versions)
- # If all conditions hold, parse authentication information
- # out of the Authorization header we sent the first time
- # (for the username and password) and the WWW-Authenticate
- # header the server sent back (for the realm) and retry
- # the request with the appropriate digest auth headers instead.
- # This evil genius hack has been brought to you by Aaron Swartz.
- host = urlparse.urlparse(req.get_full_url())[1]
- try:
- assert sys.version.split()[0] >= '2.3.3'
- assert base64 is not None
- user, passw = _base64decode(req.headers['Authorization'].split(' ')[1]).split(':')
- realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
- self.add_password(realm, host, user, passw)
- retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
- self.reset_retry_count()
- return retry
- except:
- return self.http_error_default(req, fp, code, msg, headers)
-
-def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers):
- """URL, filename, or string --> stream
-
- This function lets you define parsers that take any input source
- (URL, pathname to local or network file, or actual data as a string)
- and deal with it in a uniform manner. Returned object is guaranteed
- to have all the basic stdio read methods (read, readline, readlines).
- Just .close() the object when you're done with it.
-
- If the etag argument is supplied, it will be used as the value of an
- If-None-Match request header.
-
- If the modified argument is supplied, it can be a tuple of 9 integers
- (as returned by gmtime() in the standard Python time module) or a date
- string in any format supported by feedparser. Regardless, it MUST
- be in GMT (Greenwich Mean Time). It will be reformatted into an
- RFC 1123-compliant date and used as the value of an If-Modified-Since
- request header.
-
- If the agent argument is supplied, it will be used as the value of a
- User-Agent request header.
-
- If the referrer argument is supplied, it will be used as the value of a
- Referer[sic] request header.
-
- If handlers is supplied, it is a list of handlers used to build a
- urllib2 opener.
-
- if request_headers is supplied it is a dictionary of HTTP request headers
- that will override the values generated by FeedParser.
- """
-
- if hasattr(url_file_stream_or_string, 'read'):
- return url_file_stream_or_string
-
- if url_file_stream_or_string == '-':
- return sys.stdin
-
- if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp', 'file', 'feed'):
- # Deal with the feed URI scheme
- if url_file_stream_or_string.startswith('feed:http'):
- url_file_stream_or_string = url_file_stream_or_string[5:]
- elif url_file_stream_or_string.startswith('feed:'):
- url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:]
- if not agent:
- agent = USER_AGENT
- # test for inline user:password for basic auth
- auth = None
- if base64:
- urltype, rest = urllib.splittype(url_file_stream_or_string)
- realhost, rest = urllib.splithost(rest)
- if realhost:
- user_passwd, realhost = urllib.splituser(realhost)
- if user_passwd:
- url_file_stream_or_string = '%s://%s%s' % (urltype, realhost, rest)
- auth = base64.standard_b64encode(user_passwd).strip()
-
- # iri support
- try:
- if isinstance(url_file_stream_or_string,unicode):
- url_file_stream_or_string = url_file_stream_or_string.encode('idna').decode('utf-8')
- else:
- url_file_stream_or_string = url_file_stream_or_string.decode('utf-8').encode('idna').decode('utf-8')
- except:
- pass
-
- # try to open with urllib2 (to use optional headers)
- request = _build_urllib2_request(url_file_stream_or_string, agent, etag, modified, referrer, auth, request_headers)
- opener = apply(urllib2.build_opener, tuple(handlers + [_FeedURLHandler()]))
- opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent
- try:
- return opener.open(request)
- finally:
- opener.close() # JohnD
-
- # try to open with native open function (if url_file_stream_or_string is a filename)
- try:
- return open(url_file_stream_or_string, 'rb')
- except:
- pass
-
- # treat url_file_stream_or_string as string
- return _StringIO(str(url_file_stream_or_string))
-
-def _build_urllib2_request(url, agent, etag, modified, referrer, auth, request_headers):
- request = urllib2.Request(url)
- request.add_header('User-Agent', agent)
- if etag:
- request.add_header('If-None-Match', etag)
- if type(modified) == type(''):
- modified = _parse_date(modified)
- elif isinstance(modified, datetime.datetime):
- modified = modified.utctimetuple()
- if modified:
- # format into an RFC 1123-compliant timestamp. We can't use
- # time.strftime() since the %a and %b directives can be affected
- # by the current locale, but RFC 2616 states that dates must be
- # in English.
- short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5]))
- if referrer:
- request.add_header('Referer', referrer)
- if gzip and zlib:
- request.add_header('Accept-encoding', 'gzip, deflate')
- elif gzip:
- request.add_header('Accept-encoding', 'gzip')
- elif zlib:
- request.add_header('Accept-encoding', 'deflate')
- else:
- request.add_header('Accept-encoding', '')
- if auth:
- request.add_header('Authorization', 'Basic %s' % auth)
- if ACCEPT_HEADER:
- request.add_header('Accept', ACCEPT_HEADER)
- # use this for whatever -- cookies, special headers, etc
- # [('Cookie','Something'),('x-special-header','Another Value')]
- for header_name, header_value in request_headers.items():
- request.add_header(header_name, header_value)
- request.add_header('A-IM', 'feed') # RFC 3229 support
- return request
-
-_date_handlers = []
-def registerDateHandler(func):
- '''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
- _date_handlers.insert(0, func)
-
-# ISO-8601 date parsing routines written by Fazal Majid.
-# The ISO 8601 standard is very convoluted and irregular - a full ISO 8601
-# parser is beyond the scope of feedparser and would be a worthwhile addition
-# to the Python library.
-# A single regular expression cannot parse ISO 8601 date formats into groups
-# as the standard is highly irregular (for instance is 030104 2003-01-04 or
-# 0301-04-01), so we use templates instead.
-# Please note the order in templates is significant because we need a
-# greedy match.
-_iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-0MM?-?DD', 'YYYY-MM', 'YYYY-?OOO',
- 'YY-?MM-?DD', 'YY-?OOO', 'YYYY',
- '-YY-?MM', '-OOO', '-YY',
- '--MM-?DD', '--MM',
- '---DD',
- 'CC', '']
-_iso8601_re = [
- tmpl.replace(
- 'YYYY', r'(?P<year>\d{4})').replace(
- 'YY', r'(?P<year>\d\d)').replace(
- 'MM', r'(?P<month>[01]\d)').replace(
- 'DD', r'(?P<day>[0123]\d)').replace(
- 'OOO', r'(?P<ordinal>[0123]\d\d)').replace(
- 'CC', r'(?P<century>\d\d$)')
- + r'(T?(?P<hour>\d{2}):(?P<minute>\d{2})'
- + r'(:(?P<second>\d{2}))?'
- + r'(\.(?P<fracsecond>\d+))?'
- + r'(?P<tz>[+-](?P<tzhour>\d{2})(:(?P<tzmin>\d{2}))?|Z)?)?'
- for tmpl in _iso8601_tmpl]
-try:
- del tmpl
-except NameError:
- pass
-_iso8601_matches = [re.compile(regex).match for regex in _iso8601_re]
-try:
- del regex
-except NameError:
- pass
-def _parse_date_iso8601(dateString):
- '''Parse a variety of ISO-8601-compatible formats like 20040105'''
- m = None
- for _iso8601_match in _iso8601_matches:
- m = _iso8601_match(dateString)
- if m: break
- if not m: return
- if m.span() == (0, 0): return
- params = m.groupdict()
- ordinal = params.get('ordinal', 0)
- if ordinal:
- ordinal = int(ordinal)
- else:
- ordinal = 0
- year = params.get('year', '--')
- if not year or year == '--':
- year = time.gmtime()[0]
- elif len(year) == 2:
- # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993
- year = 100 * int(time.gmtime()[0] / 100) + int(year)
- else:
- year = int(year)
- month = params.get('month', '-')
- if not month or month == '-':
- # ordinals are NOT normalized by mktime, we simulate them
- # by setting month=1, day=ordinal
- if ordinal:
- month = 1
- else:
- month = time.gmtime()[1]
- month = int(month)
- day = params.get('day', 0)
- if not day:
- # see above
- if ordinal:
- day = ordinal
- elif params.get('century', 0) or \
- params.get('year', 0) or params.get('month', 0):
- day = 1
- else:
- day = time.gmtime()[2]
- else:
- day = int(day)
- # special case of the century - is the first year of the 21st century
- # 2000 or 2001 ? The debate goes on...
- if 'century' in params.keys():
- year = (int(params['century']) - 1) * 100 + 1
- # in ISO 8601 most fields are optional
- for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']:
- if not params.get(field, None):
- params[field] = 0
- hour = int(params.get('hour', 0))
- minute = int(params.get('minute', 0))
- second = int(float(params.get('second', 0)))
- # weekday is normalized by mktime(), we can ignore it
- weekday = 0
- daylight_savings_flag = -1
- tm = [year, month, day, hour, minute, second, weekday,
- ordinal, daylight_savings_flag]
- # ISO 8601 time zone adjustments
- tz = params.get('tz')
- if tz and tz != 'Z':
- if tz[0] == '-':
- tm[3] += int(params.get('tzhour', 0))
- tm[4] += int(params.get('tzmin', 0))
- elif tz[0] == '+':
- tm[3] -= int(params.get('tzhour', 0))
- tm[4] -= int(params.get('tzmin', 0))
- else:
- return None
- # Python's time.mktime() is a wrapper around the ANSI C mktime(3c)
- # which is guaranteed to normalize d/m/y/h/m/s.
- # Many implementations have bugs, but we'll pretend they don't.
- return time.localtime(time.mktime(tuple(tm)))
-registerDateHandler(_parse_date_iso8601)
-
-# 8-bit date handling routines written by ytrewq1.
-_korean_year = u'\ub144' # b3e2 in euc-kr
-_korean_month = u'\uc6d4' # bff9 in euc-kr
-_korean_day = u'\uc77c' # c0cf in euc-kr
-_korean_am = u'\uc624\uc804' # bfc0 c0fc in euc-kr
-_korean_pm = u'\uc624\ud6c4' # bfc0 c8c4 in euc-kr
-
-_korean_onblog_date_re = \
- re.compile('(\d{4})%s\s+(\d{2})%s\s+(\d{2})%s\s+(\d{2}):(\d{2}):(\d{2})' % \
- (_korean_year, _korean_month, _korean_day))
-_korean_nate_date_re = \
- re.compile(u'(\d{4})-(\d{2})-(\d{2})\s+(%s|%s)\s+(\d{,2}):(\d{,2}):(\d{,2})' % \
- (_korean_am, _korean_pm))
-def _parse_date_onblog(dateString):
- '''Parse a string according to the OnBlog 8-bit date format'''
- m = _korean_onblog_date_re.match(dateString)
- if not m: return
- w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \
- {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\
- 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\
- 'zonediff': '+09:00'}
- if _debug: sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate)
- return _parse_date_w3dtf(w3dtfdate)
-registerDateHandler(_parse_date_onblog)
-
-def _parse_date_nate(dateString):
- '''Parse a string according to the Nate 8-bit date format'''
- m = _korean_nate_date_re.match(dateString)
- if not m: return
- hour = int(m.group(5))
- ampm = m.group(4)
- if (ampm == _korean_pm):
- hour += 12
- hour = str(hour)
- if len(hour) == 1:
- hour = '0' + hour
- w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \
- {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\
- 'hour': hour, 'minute': m.group(6), 'second': m.group(7),\
- 'zonediff': '+09:00'}
- if _debug: sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate)
- return _parse_date_w3dtf(w3dtfdate)
-registerDateHandler(_parse_date_nate)
-
-_mssql_date_re = \
- re.compile('(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(\.\d+)?')
-def _parse_date_mssql(dateString):
- '''Parse a string according to the MS SQL date format'''
- m = _mssql_date_re.match(dateString)
- if not m: return
- w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \
- {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\
- 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\
- 'zonediff': '+09:00'}
- if _debug: sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate)
- return _parse_date_w3dtf(w3dtfdate)
-registerDateHandler(_parse_date_mssql)
-
-# Unicode strings for Greek date strings
-_greek_months = \
- { \
- u'\u0399\u03b1\u03bd': u'Jan', # c9e1ed in iso-8859-7
- u'\u03a6\u03b5\u03b2': u'Feb', # d6e5e2 in iso-8859-7
- u'\u039c\u03ac\u03ce': u'Mar', # ccdcfe in iso-8859-7
- u'\u039c\u03b1\u03ce': u'Mar', # cce1fe in iso-8859-7
- u'\u0391\u03c0\u03c1': u'Apr', # c1f0f1 in iso-8859-7
- u'\u039c\u03ac\u03b9': u'May', # ccdce9 in iso-8859-7
- u'\u039c\u03b1\u03ca': u'May', # cce1fa in iso-8859-7
- u'\u039c\u03b1\u03b9': u'May', # cce1e9 in iso-8859-7
- u'\u0399\u03bf\u03cd\u03bd': u'Jun', # c9effded in iso-8859-7
- u'\u0399\u03bf\u03bd': u'Jun', # c9efed in iso-8859-7
- u'\u0399\u03bf\u03cd\u03bb': u'Jul', # c9effdeb in iso-8859-7
- u'\u0399\u03bf\u03bb': u'Jul', # c9f9eb in iso-8859-7
- u'\u0391\u03cd\u03b3': u'Aug', # c1fde3 in iso-8859-7
- u'\u0391\u03c5\u03b3': u'Aug', # c1f5e3 in iso-8859-7
- u'\u03a3\u03b5\u03c0': u'Sep', # d3e5f0 in iso-8859-7
- u'\u039f\u03ba\u03c4': u'Oct', # cfeaf4 in iso-8859-7
- u'\u039d\u03bf\u03ad': u'Nov', # cdefdd in iso-8859-7
- u'\u039d\u03bf\u03b5': u'Nov', # cdefe5 in iso-8859-7
- u'\u0394\u03b5\u03ba': u'Dec', # c4e5ea in iso-8859-7
- }
-
-_greek_wdays = \
- { \
- u'\u039a\u03c5\u03c1': u'Sun', # caf5f1 in iso-8859-7
- u'\u0394\u03b5\u03c5': u'Mon', # c4e5f5 in iso-8859-7
- u'\u03a4\u03c1\u03b9': u'Tue', # d4f1e9 in iso-8859-7
- u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7
- u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7
- u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7
- u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7
- }
-
-_greek_date_format_re = \
- re.compile(u'([^,]+),\s+(\d{2})\s+([^\s]+)\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+([^\s]+)')
-
-def _parse_date_greek(dateString):
- '''Parse a string according to a Greek 8-bit date format.'''
- m = _greek_date_format_re.match(dateString)
- if not m: return
- try:
- wday = _greek_wdays[m.group(1)]
- month = _greek_months[m.group(3)]
- except:
- return
- rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % \
- {'wday': wday, 'day': m.group(2), 'month': month, 'year': m.group(4),\
- 'hour': m.group(5), 'minute': m.group(6), 'second': m.group(7),\
- 'zonediff': m.group(8)}
- if _debug: sys.stderr.write('Greek date parsed as: %s\n' % rfc822date)
- return _parse_date_rfc822(rfc822date)
-registerDateHandler(_parse_date_greek)
-
-# Unicode strings for Hungarian date strings
-_hungarian_months = \
- { \
- u'janu\u00e1r': u'01', # e1 in iso-8859-2
- u'febru\u00e1ri': u'02', # e1 in iso-8859-2
- u'm\u00e1rcius': u'03', # e1 in iso-8859-2
- u'\u00e1prilis': u'04', # e1 in iso-8859-2
- u'm\u00e1ujus': u'05', # e1 in iso-8859-2
- u'j\u00fanius': u'06', # fa in iso-8859-2
- u'j\u00falius': u'07', # fa in iso-8859-2
- u'augusztus': u'08',
- u'szeptember': u'09',
- u'okt\u00f3ber': u'10', # f3 in iso-8859-2
- u'november': u'11',
- u'december': u'12',
- }
-
-_hungarian_date_format_re = \
- re.compile(u'(\d{4})-([^-]+)-(\d{,2})T(\d{,2}):(\d{2})((\+|-)(\d{,2}:\d{2}))')
-
-def _parse_date_hungarian(dateString):
- '''Parse a string according to a Hungarian 8-bit date format.'''
- m = _hungarian_date_format_re.match(dateString)
- if not m: return
- try:
- month = _hungarian_months[m.group(2)]
- day = m.group(3)
- if len(day) == 1:
- day = '0' + day
- hour = m.group(4)
- if len(hour) == 1:
- hour = '0' + hour
- except:
- return
- w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % \
- {'year': m.group(1), 'month': month, 'day': day,\
- 'hour': hour, 'minute': m.group(5),\
- 'zonediff': m.group(6)}
- if _debug: sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate)
- return _parse_date_w3dtf(w3dtfdate)
-registerDateHandler(_parse_date_hungarian)
-
-# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
-# Drake and licensed under the Python license. Removed all range checking
-# for month, day, hour, minute, and second, since mktime will normalize
-# these later
-def _parse_date_w3dtf(dateString):
- def __extract_date(m):
- year = int(m.group('year'))
- if year < 100:
- year = 100 * int(time.gmtime()[0] / 100) + int(year)
- if year < 1000:
- return 0, 0, 0
- julian = m.group('julian')
- if julian:
- julian = int(julian)
- month = julian / 30 + 1
- day = julian % 30 + 1
- jday = None
- while jday != julian:
- t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
- jday = time.gmtime(t)[-2]
- diff = abs(jday - julian)
- if jday > julian:
- if diff < day:
- day = day - diff
- else:
- month = month - 1
- day = 31
- elif jday < julian:
- if day + diff < 28:
- day = day + diff
- else:
- month = month + 1
- return year, month, day
- month = m.group('month')
- day = 1
- if month is None:
- month = 1
- else:
- month = int(month)
- day = m.group('day')
- if day:
- day = int(day)
- else:
- day = 1
- return year, month, day
-
- def __extract_time(m):
- if not m:
- return 0, 0, 0
- hours = m.group('hours')
- if not hours:
- return 0, 0, 0
- hours = int(hours)
- minutes = int(m.group('minutes'))
- seconds = m.group('seconds')
- if seconds:
- seconds = int(seconds)
- else:
- seconds = 0
- return hours, minutes, seconds
-
- def __extract_tzd(m):
- '''Return the Time Zone Designator as an offset in seconds from UTC.'''
- if not m:
- return 0
- tzd = m.group('tzd')
- if not tzd:
- return 0
- if tzd == 'Z':
- return 0
- hours = int(m.group('tzdhours'))
- minutes = m.group('tzdminutes')
- if minutes:
- minutes = int(minutes)
- else:
- minutes = 0
- offset = (hours*60 + minutes) * 60
- if tzd[0] == '+':
- return -offset
- return offset
-
- __date_re = ('(?P<year>\d\d\d\d)'
- '(?:(?P<dsep>-|)'
- '(?:(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?'
- '|(?P<julian>\d\d\d)))?')
- __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)'
- __tzd_rx = re.compile(__tzd_re)
- __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
- '(?:(?P=tsep)(?P<seconds>\d\d)(?:[.,]\d+)?)?'
- + __tzd_re)
- __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
- __datetime_rx = re.compile(__datetime_re)
- m = __datetime_rx.match(dateString)
- if (m is None) or (m.group() != dateString): return
- gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
- if gmt[0] == 0: return
- return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
-registerDateHandler(_parse_date_w3dtf)
-
-def _parse_date_rfc822(dateString):
- '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
- data = dateString.split()
- if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
- del data[0]
- if len(data) == 4:
- s = data[3]
- i = s.find('+')
- if i > 0:
- data[3:] = [s[:i], s[i+1:]]
- else:
- data.append('')
- dateString = " ".join(data)
- # Account for the Etc/GMT timezone by stripping 'Etc/'
- elif len(data) == 5 and data[4].lower().startswith('etc/'):
- data[4] = data[4][4:]
- dateString = " ".join(data)
- if len(data) < 5:
- dateString += ' 00:00:00 GMT'
- tm = rfc822.parsedate_tz(dateString)
- if tm:
- return time.gmtime(rfc822.mktime_tz(tm))
-# rfc822.py defines several time zones, but we define some extra ones.
-# 'ET' is equivalent to 'EST', etc.
-_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
-rfc822._timezones.update(_additional_timezones)
-registerDateHandler(_parse_date_rfc822)
-
-def _parse_date_perforce(aDateString):
- """parse a date in yyyy/mm/dd hh:mm:ss TTT format"""
- # Fri, 2006/09/15 08:19:53 EDT
- _my_date_pattern = re.compile( \
- r'(\w{,3}), (\d{,4})/(\d{,2})/(\d{2}) (\d{,2}):(\d{2}):(\d{2}) (\w{,3})')
-
- dow, year, month, day, hour, minute, second, tz = \
- _my_date_pattern.search(aDateString).groups()
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- dateString = "%s, %s %s %s %s:%s:%s %s" % (dow, day, months[int(month) - 1], year, hour, minute, second, tz)
- tm = rfc822.parsedate_tz(dateString)
- if tm:
- return time.gmtime(rfc822.mktime_tz(tm))
-registerDateHandler(_parse_date_perforce)
-
-def _parse_date(dateString):
- '''Parses a variety of date formats into a 9-tuple in GMT'''
- for handler in _date_handlers:
- try:
- date9tuple = handler(dateString)
- if not date9tuple: continue
- if len(date9tuple) != 9:
- if _debug: sys.stderr.write('date handler function must return 9-tuple\n')
- raise ValueError
- map(int, date9tuple)
- return date9tuple
- except Exception, e:
- if _debug: sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e)))
- pass
- return None
-
-def _getCharacterEncoding(http_headers, xml_data):
- '''Get the character encoding of the XML document
-
- http_headers is a dictionary
- xml_data is a raw string (not Unicode)
-
- This is so much trickier than it sounds, it's not even funny.
- According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
- is application/xml, application/*+xml,
- application/xml-external-parsed-entity, or application/xml-dtd,
- the encoding given in the charset parameter of the HTTP Content-Type
- takes precedence over the encoding given in the XML prefix within the
- document, and defaults to 'utf-8' if neither are specified. But, if
- the HTTP Content-Type is text/xml, text/*+xml, or
- text/xml-external-parsed-entity, the encoding given in the XML prefix
- within the document is ALWAYS IGNORED and only the encoding given in
- the charset parameter of the HTTP Content-Type header should be
- respected, and it defaults to 'us-ascii' if not specified.
-
- Furthermore, discussion on the atom-syntax mailing list with the
- author of RFC 3023 leads me to the conclusion that any document
- served with a Content-Type of text/* and no charset parameter
- must be treated as us-ascii. (We now do this.) And also that it
- must always be flagged as non-well-formed. (We now do this too.)
-
- If Content-Type is unspecified (input was local file or non-HTTP source)
- or unrecognized (server just got it totally wrong), then go by the
- encoding given in the XML prefix of the document and default to
- 'iso-8859-1' as per the HTTP specification (RFC 2616).
-
- Then, assuming we didn't find a character encoding in the HTTP headers
- (and the HTTP Content-type allowed us to look in the body), we need
- to sniff the first few bytes of the XML data and try to determine
- whether the encoding is ASCII-compatible. Section F of the XML
- specification shows the way here:
- http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
-
- If the sniffed encoding is not ASCII-compatible, we need to make it
- ASCII compatible so that we can sniff further into the XML declaration
- to find the encoding attribute, which will tell us the true encoding.
-
- Of course, none of this guarantees that we will be able to parse the
- feed in the declared character encoding (assuming it was declared
- correctly, which many are not). CJKCodecs and iconv_codec help a lot;
- you should definitely install them if you can.
- http://cjkpython.i18n.org/
- '''
-
- def _parseHTTPContentType(content_type):
- '''takes HTTP Content-Type header and returns (content type, charset)
-
- If no charset is specified, returns (content type, '')
- If no content type is specified, returns ('', '')
- Both return parameters are guaranteed to be lowercase strings
- '''
- content_type = content_type or ''
- content_type, params = cgi.parse_header(content_type)
- return content_type, params.get('charset', '').replace("'", '')
-
- sniffed_xml_encoding = ''
- xml_encoding = ''
- true_encoding = ''
- http_content_type, http_encoding = _parseHTTPContentType(http_headers.get('content-type', http_headers.get('Content-type')))
- # Must sniff for non-ASCII-compatible character encodings before
- # searching for XML declaration. This heuristic is defined in
- # section F of the XML specification:
- # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
- try:
- if xml_data[:4] == _l2bytes([0x4c, 0x6f, 0xa7, 0x94]):
- # EBCDIC
- xml_data = _ebcdic_to_ascii(xml_data)
- elif xml_data[:4] == _l2bytes([0x00, 0x3c, 0x00, 0x3f]):
- # UTF-16BE
- sniffed_xml_encoding = 'utf-16be'
- xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
- elif (len(xml_data) >= 4) and (xml_data[:2] == _l2bytes([0xfe, 0xff])) and (xml_data[2:4] != _l2bytes([0x00, 0x00])):
- # UTF-16BE with BOM
- sniffed_xml_encoding = 'utf-16be'
- xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
- elif xml_data[:4] == _l2bytes([0x3c, 0x00, 0x3f, 0x00]):
- # UTF-16LE
- sniffed_xml_encoding = 'utf-16le'
- xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
- elif (len(xml_data) >= 4) and (xml_data[:2] == _l2bytes([0xff, 0xfe])) and (xml_data[2:4] != _l2bytes([0x00, 0x00])):
- # UTF-16LE with BOM
- sniffed_xml_encoding = 'utf-16le'
- xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
- elif xml_data[:4] == _l2bytes([0x00, 0x00, 0x00, 0x3c]):
- # UTF-32BE
- sniffed_xml_encoding = 'utf-32be'
- xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
- elif xml_data[:4] == _l2bytes([0x3c, 0x00, 0x00, 0x00]):
- # UTF-32LE
- sniffed_xml_encoding = 'utf-32le'
- xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
- elif xml_data[:4] == _l2bytes([0x00, 0x00, 0xfe, 0xff]):
- # UTF-32BE with BOM
- sniffed_xml_encoding = 'utf-32be'
- xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
- elif xml_data[:4] == _l2bytes([0xff, 0xfe, 0x00, 0x00]):
- # UTF-32LE with BOM
- sniffed_xml_encoding = 'utf-32le'
- xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
- elif xml_data[:3] == _l2bytes([0xef, 0xbb, 0xbf]):
- # UTF-8 with BOM
- sniffed_xml_encoding = 'utf-8'
- xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
- else:
- # ASCII-compatible
- pass
- xml_encoding_match = re.compile(_s2bytes('^<\?.*encoding=[\'"](.*?)[\'"].*\?>')).match(xml_data)
- except:
- xml_encoding_match = None
- if xml_encoding_match:
- xml_encoding = xml_encoding_match.groups()[0].decode('utf-8').lower()
- if sniffed_xml_encoding and (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16')):
- xml_encoding = sniffed_xml_encoding
- acceptable_content_type = 0
- application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
- text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
- if (http_content_type in application_content_types) or \
- (http_content_type.startswith('application/') and http_content_type.endswith('+xml')):
- acceptable_content_type = 1
- true_encoding = http_encoding or xml_encoding or 'utf-8'
- elif (http_content_type in text_content_types) or \
- (http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
- acceptable_content_type = 1
- true_encoding = http_encoding or 'us-ascii'
- elif http_content_type.startswith('text/'):
- true_encoding = http_encoding or 'us-ascii'
- elif http_headers and (not (http_headers.has_key('content-type') or http_headers.has_key('Content-type'))):
- true_encoding = xml_encoding or 'iso-8859-1'
- else:
- true_encoding = xml_encoding or 'utf-8'
- # some feeds claim to be gb2312 but are actually gb18030.
- # apparently MSIE and Firefox both do the following switch:
- if true_encoding.lower() == 'gb2312':
- true_encoding = 'gb18030'
- return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type
-
-def _toUTF8(data, encoding):
- '''Changes an XML data stream on the fly to specify a new encoding
-
- data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already
- encoding is a string recognized by encodings.aliases
- '''
- if _debug: sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding)
- # strip Byte Order Mark (if present)
- if (len(data) >= 4) and (data[:2] == _l2bytes([0xfe, 0xff])) and (data[2:4] != _l2bytes([0x00, 0x00])):
- if _debug:
- sys.stderr.write('stripping BOM\n')
- if encoding != 'utf-16be':
- sys.stderr.write('trying utf-16be instead\n')
- encoding = 'utf-16be'
- data = data[2:]
- elif (len(data) >= 4) and (data[:2] == _l2bytes([0xff, 0xfe])) and (data[2:4] != _l2bytes([0x00, 0x00])):
- if _debug:
- sys.stderr.write('stripping BOM\n')
- if encoding != 'utf-16le':
- sys.stderr.write('trying utf-16le instead\n')
- encoding = 'utf-16le'
- data = data[2:]
- elif data[:3] == _l2bytes([0xef, 0xbb, 0xbf]):
- if _debug:
- sys.stderr.write('stripping BOM\n')
- if encoding != 'utf-8':
- sys.stderr.write('trying utf-8 instead\n')
- encoding = 'utf-8'
- data = data[3:]
- elif data[:4] == _l2bytes([0x00, 0x00, 0xfe, 0xff]):
- if _debug:
- sys.stderr.write('stripping BOM\n')
- if encoding != 'utf-32be':
- sys.stderr.write('trying utf-32be instead\n')
- encoding = 'utf-32be'
- data = data[4:]
- elif data[:4] == _l2bytes([0xff, 0xfe, 0x00, 0x00]):
- if _debug:
- sys.stderr.write('stripping BOM\n')
- if encoding != 'utf-32le':
- sys.stderr.write('trying utf-32le instead\n')
- encoding = 'utf-32le'
- data = data[4:]
- newdata = unicode(data, encoding)
- if _debug: sys.stderr.write('successfully converted %s data to unicode\n' % encoding)
- declmatch = re.compile('^<\?xml[^>]*?>')
- newdecl = '''<?xml version='1.0' encoding='utf-8'?>'''
- if declmatch.search(newdata):
- newdata = declmatch.sub(newdecl, newdata)
- else:
- newdata = newdecl + u'\n' + newdata
- return newdata.encode('utf-8')
-
-def _stripDoctype(data):
- '''Strips DOCTYPE from XML document, returns (rss_version, stripped_data)
-
- rss_version may be 'rss091n' or None
- stripped_data is the same XML document, minus the DOCTYPE
- '''
- start = re.search(_s2bytes('<\w'), data)
- start = start and start.start() or -1
- head,data = data[:start+1], data[start+1:]
-
- entity_pattern = re.compile(_s2bytes(r'^\s*<!ENTITY([^>]*?)>'), re.MULTILINE)
- entity_results=entity_pattern.findall(head)
- head = entity_pattern.sub(_s2bytes(''), head)
- doctype_pattern = re.compile(_s2bytes(r'^\s*<!DOCTYPE([^>]*?)>'), re.MULTILINE)
- doctype_results = doctype_pattern.findall(head)
- doctype = doctype_results and doctype_results[0] or _s2bytes('')
- if doctype.lower().count(_s2bytes('netscape')):
- version = 'rss091n'
- else:
- version = None
-
- # only allow in 'safe' inline entity definitions
- replacement=_s2bytes('')
- if len(doctype_results)==1 and entity_results:
- safe_pattern=re.compile(_s2bytes('\s+(\w+)\s+"(&#\w+;|[^&"]*)"'))
- safe_entities=filter(lambda e: safe_pattern.match(e),entity_results)
- if safe_entities:
- replacement=_s2bytes('<!DOCTYPE feed [\n <!ENTITY') + _s2bytes('>\n <!ENTITY ').join(safe_entities) + _s2bytes('>\n]>')
- data = doctype_pattern.sub(replacement, head) + data
-
- return version, data, dict(replacement and [(k.decode('utf-8'), v.decode('utf-8')) for k, v in safe_pattern.findall(replacement)])
-
-def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[], request_headers={}, response_headers={}):
- '''Parse a feed from a URL, file, stream, or string.
-
- request_headers, if given, is a dict from http header name to value to add
- to the request; this overrides internally generated values.
- '''
- result = FeedParserDict()
- result['feed'] = FeedParserDict()
- result['entries'] = []
- if _XML_AVAILABLE:
- result['bozo'] = 0
- if not isinstance(handlers, list):
- handlers = [handlers]
- try:
- f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers)
- data = f.read()
- except Exception, e:
- result['bozo'] = 1
- result['bozo_exception'] = e
- data = None
- f = None
-
- if hasattr(f, 'headers'):
- result['headers'] = dict(f.headers)
- # overwrite existing headers using response_headers
- if 'headers' in result:
- result['headers'].update(response_headers)
- elif response_headers:
- result['headers'] = copy.deepcopy(response_headers)
-
- # if feed is gzip-compressed, decompress it
- if f and data and 'headers' in result:
- if gzip and result['headers'].get('content-encoding') == 'gzip':
- try:
- data = gzip.GzipFile(fileobj=_StringIO(data)).read()
- except Exception, e:
- # Some feeds claim to be gzipped but they're not, so
- # we get garbage. Ideally, we should re-request the
- # feed without the 'Accept-encoding: gzip' header,
- # but we don't.
- result['bozo'] = 1
- result['bozo_exception'] = e
- data = ''
- elif zlib and result['headers'].get('content-encoding') == 'deflate':
- try:
- data = zlib.decompress(data, -zlib.MAX_WBITS)
- except Exception, e:
- result['bozo'] = 1
- result['bozo_exception'] = e
- data = ''
-
- # save HTTP headers
- if 'headers' in result:
- if 'etag' in result['headers'] or 'ETag' in result['headers']:
- etag = result['headers'].get('etag', result['headers'].get('ETag'))
- if etag:
- result['etag'] = etag
- if 'last-modified' in result['headers'] or 'Last-Modified' in result['headers']:
- modified = result['headers'].get('last-modified', result['headers'].get('Last-Modified'))
- if modified:
- result['modified'] = _parse_date(modified)
- if hasattr(f, 'url'):
- result['href'] = f.url
- result['status'] = 200
- if hasattr(f, 'status'):
- result['status'] = f.status
- if hasattr(f, 'close'):
- f.close()
-
- # there are four encodings to keep track of:
- # - http_encoding is the encoding declared in the Content-Type HTTP header
- # - xml_encoding is the encoding declared in the <?xml declaration
- # - sniffed_encoding is the encoding sniffed from the first 4 bytes of the XML data
- # - result['encoding'] is the actual encoding, as per RFC 3023 and a variety of other conflicting specifications
- http_headers = result.get('headers', {})
- result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type = \
- _getCharacterEncoding(http_headers, data)
- if http_headers and (not acceptable_content_type):
- if http_headers.has_key('content-type') or http_headers.has_key('Content-type'):
- bozo_message = '%s is not an XML media type' % http_headers.get('content-type', http_headers.get('Content-type'))
- else:
- bozo_message = 'no Content-type specified'
- result['bozo'] = 1
- result['bozo_exception'] = NonXMLContentType(bozo_message)
-
- if data is not None:
- result['version'], data, entities = _stripDoctype(data)
-
- # ensure that baseuri is an absolute uri using an acceptable URI scheme
- contentloc = http_headers.get('content-location', http_headers.get('Content-Location', ''))
- href = result.get('href', '')
- baseuri = _makeSafeAbsoluteURI(href, contentloc) or _makeSafeAbsoluteURI(contentloc) or href
-
- baselang = http_headers.get('content-language', http_headers.get('Content-Language', None))
-
- # if server sent 304, we're done
- if result.get('status', 0) == 304:
- result['version'] = ''
- result['debug_message'] = 'The feed has not changed since you last checked, ' + \
- 'so the server sent no data. This is a feature, not a bug!'
- return result
-
- # if there was a problem downloading, we're done
- if data is None:
- return result
-
- # determine character encoding
- use_strict_parser = 0
- known_encoding = 0
- tried_encodings = []
- # try: HTTP encoding, declared XML encoding, encoding sniffed from BOM
- for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding):
- if not proposed_encoding: continue
- if proposed_encoding in tried_encodings: continue
- tried_encodings.append(proposed_encoding)
- try:
- data = _toUTF8(data, proposed_encoding)
- known_encoding = use_strict_parser = 1
- break
- except:
- pass
- # if no luck and we have auto-detection library, try that
- if (not known_encoding) and chardet:
- try:
- proposed_encoding = chardet.detect(data)['encoding']
- if proposed_encoding and (proposed_encoding not in tried_encodings):
- tried_encodings.append(proposed_encoding)
- data = _toUTF8(data, proposed_encoding)
- known_encoding = use_strict_parser = 1
- except:
- pass
- # if still no luck and we haven't tried utf-8 yet, try that
- if (not known_encoding) and ('utf-8' not in tried_encodings):
- try:
- proposed_encoding = 'utf-8'
- tried_encodings.append(proposed_encoding)
- data = _toUTF8(data, proposed_encoding)
- known_encoding = use_strict_parser = 1
- except:
- pass
- # if still no luck and we haven't tried windows-1252 yet, try that
- if (not known_encoding) and ('windows-1252' not in tried_encodings):
- try:
- proposed_encoding = 'windows-1252'
- tried_encodings.append(proposed_encoding)
- data = _toUTF8(data, proposed_encoding)
- known_encoding = use_strict_parser = 1
- except:
- pass
- # if still no luck and we haven't tried iso-8859-2 yet, try that.
- if (not known_encoding) and ('iso-8859-2' not in tried_encodings):
- try:
- proposed_encoding = 'iso-8859-2'
- tried_encodings.append(proposed_encoding)
- data = _toUTF8(data, proposed_encoding)
- known_encoding = use_strict_parser = 1
- except:
- pass
- # if still no luck, give up
- if not known_encoding:
- result['bozo'] = 1
- result['bozo_exception'] = CharacterEncodingUnknown( \
- 'document encoding unknown, I tried ' + \
- '%s, %s, utf-8, windows-1252, and iso-8859-2 but nothing worked' % \
- (result['encoding'], xml_encoding))
- result['encoding'] = ''
- elif proposed_encoding != result['encoding']:
- result['bozo'] = 1
- result['bozo_exception'] = CharacterEncodingOverride( \
- 'document declared as %s, but parsed as %s' % \
- (result['encoding'], proposed_encoding))
- result['encoding'] = proposed_encoding
-
- if not _XML_AVAILABLE:
- use_strict_parser = 0
- if use_strict_parser:
- # initialize the SAX parser
- feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
- saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
- saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
- saxparser.setContentHandler(feedparser)
- saxparser.setErrorHandler(feedparser)
- source = xml.sax.xmlreader.InputSource()
- source.setByteStream(_StringIO(data))
- if hasattr(saxparser, '_ns_stack'):
- # work around bug in built-in SAX parser (doesn't recognize xml: namespace)
- # PyXML doesn't have this problem, and it doesn't have _ns_stack either
- saxparser._ns_stack.append({'http://www.w3.org/XML/1998/namespace':'xml'})
- try:
- saxparser.parse(source)
- except Exception, e:
- if _debug:
- import traceback
- traceback.print_stack()
- traceback.print_exc()
- sys.stderr.write('xml parsing failed\n')
- result['bozo'] = 1
- result['bozo_exception'] = feedparser.exc or e
- use_strict_parser = 0
- if not use_strict_parser:
- feedparser = _LooseFeedParser(baseuri, baselang, 'utf-8', entities)
- feedparser.feed(data.decode('utf-8', 'replace'))
- result['feed'] = feedparser.feeddata
- result['entries'] = feedparser.entries
- result['version'] = result['version'] or feedparser.version
- result['namespaces'] = feedparser.namespacesInUse
- return result
-
-class Serializer:
- def __init__(self, results):
- self.results = results
-
-class TextSerializer(Serializer):
- def write(self, stream=sys.stdout):
- self._writer(stream, self.results, '')
-
- def _writer(self, stream, node, prefix):
- if not node: return
- if hasattr(node, 'keys'):
- keys = node.keys()
- keys.sort()
- for k in keys:
- if k in ('description', 'link'): continue
- if node.has_key(k + '_detail'): continue
- if node.has_key(k + '_parsed'): continue
- self._writer(stream, node[k], prefix + k + '.')
- elif type(node) == types.ListType:
- index = 0
- for n in node:
- self._writer(stream, n, prefix[:-1] + '[' + str(index) + '].')
- index += 1
- else:
- try:
- s = str(node).encode('utf-8')
- s = s.replace('\\', '\\\\')
- s = s.replace('\r', '')
- s = s.replace('\n', r'\n')
- stream.write(prefix[:-1])
- stream.write('=')
- stream.write(s)
- stream.write('\n')
- except:
- pass
-
-class PprintSerializer(Serializer):
- def write(self, stream=sys.stdout):
- if self.results.has_key('href'):
- stream.write(self.results['href'] + '\n\n')
- from pprint import pprint
- pprint(self.results, stream)
- stream.write('\n')
-
-if __name__ == '__main__':
- try:
- from optparse import OptionParser
- except:
- OptionParser = None
-
- if OptionParser:
- optionParser = OptionParser(version=__version__, usage="%prog [options] url_or_filename_or_-")
- optionParser.set_defaults(format="pprint")
- optionParser.add_option("-A", "--user-agent", dest="agent", metavar="AGENT", help="User-Agent for HTTP URLs")
- optionParser.add_option("-e", "--referer", "--referrer", dest="referrer", metavar="URL", help="Referrer for HTTP URLs")
- optionParser.add_option("-t", "--etag", dest="etag", metavar="TAG", help="ETag/If-None-Match for HTTP URLs")
- optionParser.add_option("-m", "--last-modified", dest="modified", metavar="DATE", help="Last-modified/If-Modified-Since for HTTP URLs (any supported date format)")
- optionParser.add_option("-f", "--format", dest="format", metavar="FORMAT", help="output results in FORMAT (text, pprint)")
- optionParser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="write debugging information to stderr")
- (options, urls) = optionParser.parse_args()
- if options.verbose:
- _debug = 1
- if not urls:
- optionParser.print_help()
- sys.exit(0)
- else:
- if not sys.argv[1:]:
- print __doc__
- sys.exit(0)
- class _Options:
- etag = modified = agent = referrer = None
- format = 'pprint'
- options = _Options()
- urls = sys.argv[1:]
-
- zopeCompatibilityHack()
-
- serializer = globals().get(options.format.capitalize() + 'Serializer', Serializer)
- for url in urls:
- results = parse(url, etag=options.etag, modified=options.modified, agent=options.agent, referrer=options.referrer)
- serializer(results).write(sys.stdout)
diff --git a/module/lib/jinja2/__init__.py b/module/lib/jinja2/__init__.py
deleted file mode 100644
index f944e11b6..000000000
--- a/module/lib/jinja2/__init__.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2
- ~~~~~~
-
- Jinja2 is a template engine written in pure Python. It provides a
- Django inspired non-XML syntax but supports inline expressions and
- an optional sandboxed environment.
-
- Nutshell
- --------
-
- Here a small example of a Jinja2 template::
-
- {% extends 'base.html' %}
- {% block title %}Memberlist{% endblock %}
- {% block content %}
- <ul>
- {% for user in users %}
- <li><a href="{{ user.url }}">{{ user.username }}</a></li>
- {% endfor %}
- </ul>
- {% endblock %}
-
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-__docformat__ = 'restructuredtext en'
-try:
- __version__ = __import__('pkg_resources') \
- .get_distribution('Jinja2').version
-except:
- __version__ = 'unknown'
-
-# high level interface
-from jinja2.environment import Environment, Template
-
-# loaders
-from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
- DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
- ModuleLoader
-
-# bytecode caches
-from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
- MemcachedBytecodeCache
-
-# undefined types
-from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
-
-# exceptions
-from jinja2.exceptions import TemplateError, UndefinedError, \
- TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
- TemplateAssertionError
-
-# decorators and public utilities
-from jinja2.filters import environmentfilter, contextfilter, \
- evalcontextfilter
-from jinja2.utils import Markup, escape, clear_caches, \
- environmentfunction, evalcontextfunction, contextfunction, \
- is_undefined
-
-__all__ = [
- 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
- 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
- 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
- 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
- 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
- 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
- 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
- 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
- 'evalcontextfilter', 'evalcontextfunction'
-]
diff --git a/module/lib/jinja2/_markupsafe/__init__.py b/module/lib/jinja2/_markupsafe/__init__.py
deleted file mode 100644
index ec7bd572d..000000000
--- a/module/lib/jinja2/_markupsafe/__init__.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- markupsafe
- ~~~~~~~~~~
-
- Implements a Markup string.
-
- :copyright: (c) 2010 by Armin Ronacher.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-from itertools import imap
-
-
-__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
-
-
-_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
-_entity_re = re.compile(r'&([^;]+);')
-
-
-class Markup(unicode):
- r"""Marks a string as being safe for inclusion in HTML/XML output without
- needing to be escaped. This implements the `__html__` interface a couple
- of frameworks and web applications use. :class:`Markup` is a direct
- subclass of `unicode` and provides all the methods of `unicode` just that
- it escapes arguments passed and always returns `Markup`.
-
- The `escape` function returns markup objects so that double escaping can't
- happen.
-
- The constructor of the :class:`Markup` class can be used for three
- different things: When passed an unicode object it's assumed to be safe,
- when passed an object with an HTML representation (has an `__html__`
- method) that representation is used, otherwise the object passed is
- converted into a unicode string and then assumed to be safe:
-
- >>> Markup("Hello <em>World</em>!")
- Markup(u'Hello <em>World</em>!')
- >>> class Foo(object):
- ... def __html__(self):
- ... return '<a href="#">foo</a>'
- ...
- >>> Markup(Foo())
- Markup(u'<a href="#">foo</a>')
-
- If you want object passed being always treated as unsafe you can use the
- :meth:`escape` classmethod to create a :class:`Markup` object:
-
- >>> Markup.escape("Hello <em>World</em>!")
- Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
-
- Operations on a markup string are markup aware which means that all
- arguments are passed through the :func:`escape` function:
-
- >>> em = Markup("<em>%s</em>")
- >>> em % "foo & bar"
- Markup(u'<em>foo &amp; bar</em>')
- >>> strong = Markup("<strong>%(text)s</strong>")
- >>> strong % {'text': '<blink>hacker here</blink>'}
- Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
- >>> Markup("<em>Hello</em> ") + "<foo>"
- Markup(u'<em>Hello</em> &lt;foo&gt;')
- """
- __slots__ = ()
-
- def __new__(cls, base=u'', encoding=None, errors='strict'):
- if hasattr(base, '__html__'):
- base = base.__html__()
- if encoding is None:
- return unicode.__new__(cls, base)
- return unicode.__new__(cls, base, encoding, errors)
-
- def __html__(self):
- return self
-
- def __add__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(self) + unicode(escape(other)))
- return NotImplemented
-
- def __radd__(self, other):
- if hasattr(other, '__html__') or isinstance(other, basestring):
- return self.__class__(unicode(escape(other)) + unicode(self))
- return NotImplemented
-
- def __mul__(self, num):
- if isinstance(num, (int, long)):
- return self.__class__(unicode.__mul__(self, num))
- return NotImplemented
- __rmul__ = __mul__
-
- def __mod__(self, arg):
- if isinstance(arg, tuple):
- arg = tuple(imap(_MarkupEscapeHelper, arg))
- else:
- arg = _MarkupEscapeHelper(arg)
- return self.__class__(unicode.__mod__(self, arg))
-
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- unicode.__repr__(self)
- )
-
- def join(self, seq):
- return self.__class__(unicode.join(self, imap(escape, seq)))
- join.__doc__ = unicode.join.__doc__
-
- def split(self, *args, **kwargs):
- return map(self.__class__, unicode.split(self, *args, **kwargs))
- split.__doc__ = unicode.split.__doc__
-
- def rsplit(self, *args, **kwargs):
- return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
- rsplit.__doc__ = unicode.rsplit.__doc__
-
- def splitlines(self, *args, **kwargs):
- return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
- splitlines.__doc__ = unicode.splitlines.__doc__
-
- def unescape(self):
- r"""Unescape markup again into an unicode string. This also resolves
- known HTML4 and XHTML entities:
-
- >>> Markup("Main &raquo; <em>About</em>").unescape()
- u'Main \xbb <em>About</em>'
- """
- from jinja2._markupsafe._constants import HTML_ENTITIES
- def handle_match(m):
- name = m.group(1)
- if name in HTML_ENTITIES:
- return unichr(HTML_ENTITIES[name])
- try:
- if name[:2] in ('#x', '#X'):
- return unichr(int(name[2:], 16))
- elif name.startswith('#'):
- return unichr(int(name[1:]))
- except ValueError:
- pass
- return u''
- return _entity_re.sub(handle_match, unicode(self))
-
- def striptags(self):
- r"""Unescape markup into an unicode string and strip all tags. This
- also resolves known HTML4 and XHTML entities. Whitespace is
- normalized to one:
-
- >>> Markup("Main &raquo; <em>About</em>").striptags()
- u'Main \xbb About'
- """
- stripped = u' '.join(_striptags_re.sub('', self).split())
- return Markup(stripped).unescape()
-
- @classmethod
- def escape(cls, s):
- """Escape the string. Works like :func:`escape` with the difference
- that for subclasses of :class:`Markup` this function would return the
- correct subclass.
- """
- rv = escape(s)
- if rv.__class__ is not cls:
- return cls(rv)
- return rv
-
- def make_wrapper(name):
- orig = getattr(unicode, name)
- def func(self, *args, **kwargs):
- args = _escape_argspec(list(args), enumerate(args))
- _escape_argspec(kwargs, kwargs.iteritems())
- return self.__class__(orig(self, *args, **kwargs))
- func.__name__ = orig.__name__
- func.__doc__ = orig.__doc__
- return func
-
- for method in '__getitem__', 'capitalize', \
- 'title', 'lower', 'upper', 'replace', 'ljust', \
- 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
- 'translate', 'expandtabs', 'swapcase', 'zfill':
- locals()[method] = make_wrapper(method)
-
- # new in python 2.5
- if hasattr(unicode, 'partition'):
- partition = make_wrapper('partition'),
- rpartition = make_wrapper('rpartition')
-
- # new in python 2.6
- if hasattr(unicode, 'format'):
- format = make_wrapper('format')
-
- # not in python 3
- if hasattr(unicode, '__getslice__'):
- __getslice__ = make_wrapper('__getslice__')
-
- del method, make_wrapper
-
-
-def _escape_argspec(obj, iterable):
- """Helper for various string-wrapped functions."""
- for key, value in iterable:
- if hasattr(value, '__html__') or isinstance(value, basestring):
- obj[key] = escape(value)
- return obj
-
-
-class _MarkupEscapeHelper(object):
- """Helper for Markup.__mod__"""
-
- def __init__(self, obj):
- self.obj = obj
-
- __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
- __str__ = lambda s: str(escape(s.obj))
- __unicode__ = lambda s: unicode(escape(s.obj))
- __repr__ = lambda s: str(escape(repr(s.obj)))
- __int__ = lambda s: int(s.obj)
- __float__ = lambda s: float(s.obj)
-
-
-# we have to import it down here as the speedups and native
-# modules imports the markup type which is define above.
-try:
- from jinja2._markupsafe._speedups import escape, escape_silent, soft_unicode
-except ImportError:
- from jinja2._markupsafe._native import escape, escape_silent, soft_unicode
diff --git a/module/lib/jinja2/_markupsafe/_bundle.py b/module/lib/jinja2/_markupsafe/_bundle.py
deleted file mode 100644
index e694faf23..000000000
--- a/module/lib/jinja2/_markupsafe/_bundle.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2._markupsafe._bundle
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- This script pulls in markupsafe from a source folder and
- bundles it with Jinja2. It does not pull in the speedups
- module though.
-
- :copyright: Copyright 2010 by the Jinja team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-import sys
-import os
-import re
-
-
-def rewrite_imports(lines):
- for idx, line in enumerate(lines):
- new_line = re.sub(r'(import|from)\s+markupsafe\b',
- r'\1 jinja2._markupsafe', line)
- if new_line != line:
- lines[idx] = new_line
-
-
-def main():
- if len(sys.argv) != 2:
- print 'error: only argument is path to markupsafe'
- sys.exit(1)
- basedir = os.path.dirname(__file__)
- markupdir = sys.argv[1]
- for filename in os.listdir(markupdir):
- if filename.endswith('.py'):
- f = open(os.path.join(markupdir, filename))
- try:
- lines = list(f)
- finally:
- f.close()
- rewrite_imports(lines)
- f = open(os.path.join(basedir, filename), 'w')
- try:
- for line in lines:
- f.write(line)
- finally:
- f.close()
-
-
-if __name__ == '__main__':
- main()
diff --git a/module/lib/jinja2/_markupsafe/_native.py b/module/lib/jinja2/_markupsafe/_native.py
deleted file mode 100644
index 7b95828ec..000000000
--- a/module/lib/jinja2/_markupsafe/_native.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- markupsafe._native
- ~~~~~~~~~~~~~~~~~~
-
- Native Python implementation the C module is not compiled.
-
- :copyright: (c) 2010 by Armin Ronacher.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2._markupsafe import Markup
-
-
-def escape(s):
- """Convert the characters &, <, >, ' and " in string s to HTML-safe
- sequences. Use this if you need to display text that might contain
- such characters in HTML. Marks return value as markup string.
- """
- if hasattr(s, '__html__'):
- return s.__html__()
- return Markup(unicode(s)
- .replace('&', '&amp;')
- .replace('>', '&gt;')
- .replace('<', '&lt;')
- .replace("'", '&#39;')
- .replace('"', '&#34;')
- )
-
-
-def escape_silent(s):
- """Like :func:`escape` but converts `None` into an empty
- markup string.
- """
- if s is None:
- return Markup()
- return escape(s)
-
-
-def soft_unicode(s):
- """Make a string unicode if it isn't already. That way a markup
- string is not converted back to unicode.
- """
- if not isinstance(s, unicode):
- s = unicode(s)
- return s
diff --git a/module/lib/jinja2/_markupsafe/tests.py b/module/lib/jinja2/_markupsafe/tests.py
deleted file mode 100644
index c1ce3943a..000000000
--- a/module/lib/jinja2/_markupsafe/tests.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import gc
-import unittest
-from jinja2._markupsafe import Markup, escape, escape_silent
-
-
-class MarkupTestCase(unittest.TestCase):
-
- def test_markup_operations(self):
- # adding two strings should escape the unsafe one
- unsafe = '<script type="application/x-some-script">alert("foo");</script>'
- safe = Markup('<em>username</em>')
- assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe)
-
- # string interpolations are safe to use too
- assert Markup('<em>%s</em>') % '<bad user>' == \
- '<em>&lt;bad user&gt;</em>'
- assert Markup('<em>%(username)s</em>') % {
- 'username': '<bad user>'
- } == '<em>&lt;bad user&gt;</em>'
-
- # an escaped object is markup too
- assert type(Markup('foo') + 'bar') is Markup
-
- # and it implements __html__ by returning itself
- x = Markup("foo")
- assert x.__html__() is x
-
- # it also knows how to treat __html__ objects
- class Foo(object):
- def __html__(self):
- return '<em>awesome</em>'
- def __unicode__(self):
- return 'awesome'
- assert Markup(Foo()) == '<em>awesome</em>'
- assert Markup('<strong>%s</strong>') % Foo() == \
- '<strong><em>awesome</em></strong>'
-
- # escaping and unescaping
- assert escape('"<>&\'') == '&#34;&lt;&gt;&amp;&#39;'
- assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
- assert Markup("&lt;test&gt;").unescape() == "<test>"
-
- def test_all_set(self):
- import jinja2._markupsafe as markup
- for item in markup.__all__:
- getattr(markup, item)
-
- def test_escape_silent(self):
- assert escape_silent(None) == Markup()
- assert escape(None) == Markup(None)
- assert escape_silent('<foo>') == Markup(u'&lt;foo&gt;')
-
-
-class MarkupLeakTestCase(unittest.TestCase):
-
- def test_markup_leaks(self):
- counts = set()
- for count in xrange(20):
- for item in xrange(1000):
- escape("foo")
- escape("<foo>")
- escape(u"foo")
- escape(u"<foo>")
- counts.add(len(gc.get_objects()))
- assert len(counts) == 1, 'ouch, c extension seems to leak objects'
-
-
-def suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(MarkupTestCase))
-
- # this test only tests the c extension
- if not hasattr(escape, 'func_code'):
- suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
-
- return suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
diff --git a/module/lib/jinja2/_stringdefs.py b/module/lib/jinja2/_stringdefs.py
deleted file mode 100644
index 1161b7f4a..000000000
--- a/module/lib/jinja2/_stringdefs.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2._stringdefs
- ~~~~~~~~~~~~~~~~~~
-
- Strings of all Unicode characters of a certain category.
- Used for matching in Unicode-aware languages. Run to regenerate.
-
- Inspired by chartypes_create.py from the MoinMoin project, original
- implementation from Pygments.
-
- :copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS.
- :license: BSD, see LICENSE for details.
-"""
-
-Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
-
-Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb'
-
-Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a\u0d3b\u0d3c\u0d3d\u0d44\u0d45\u0d49\u0d4e\u0d4f\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d62\u0d63\u0d64\u0d65\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d84\u0d97\u0d98\u0d99\u0db2\u0dbc\u0dbe\u0dbf\u0dc7\u0dc8\u0dc9\u0dcb\u0dcc\u0dcd\u0dce\u0dd5\u0dd7\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e3b\u0e3c\u0e3d\u0e3e\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e83\u0e85\u0e86\u0e89\u0e8b\u0e8c\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8\u0ea9\u0eac\u0eba\u0ebe\u0ebf\u0ec5\u0ec7\u0ece\u0ecf\u0eda\u0edb\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f48\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f8c\u0f8d\u0f8e\u0f8f\u0f98\u0fbd\u0fcd\u0fce\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1022\u1028\u102b\u1033\u1034\u1035\u103a\u103b\u103c\u103d\u103e\u103f\u105a\u105b\u105c\u105d\u105e\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u10c6\u10c7\u10c8\u10c9\u10ca\u10cb\u10cc\u10cd\u10ce\u10cf\u10fd\u10fe\u10ff\u115a\u115b\u115c\u115d\u115e\u11a3\u11a4\u11a5\u11a6\u11a7\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\u1249\u124e\u124f\u1257\u1259\u125e\u125f\u1289\u128e\u128f\u12b1\u12b6\u12b7\u12bf\u12c1\u12c6\u12c7\u12d7\u1311\u1316\u1317\u135b\u135c\u135d\u135e\u137d\u137e\u137f\u139a\u139b\u139c\u139d\u139e\u139f\u13f5\u13f6\u13f7\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u169d\u169e\u169f\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u16f9\u16fa\u16fb\u16fc\u16fd\u16fe\u16ff\u170d\u1715\u1716\u1717\u1718\u1719\u171a\u171b\u171c\u171d\u171e\u171f\u1737\u1738\u1739\u173a\u173b\u173c\u173d\u173e\u173f\u1754\u1755\u1756\u1757\u1758\u1759\u175a\u175b\u175c\u175d\u175e\u175f\u176d\u1771\u1774\u1775\u1776\u1777\u1778\u1779\u177a\u177b\u177c\u177d\u177e\u177f\u17de\u17df\u17ea\u17eb\u17ec\u17ed\u17ee\u17ef\u17fa\u17fb\u17fc\u17fd\u17fe\u17ff\u180f\u181a\u181b\u181c\u181d\u181e\u181f\u1878\u1879\u187a\u187b\u187c\u187d\u187e\u187f\u18aa\u18ab\u18ac\u18ad\u18ae\u18af\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u18f6\u18f7\u18f8\u18f9\u18fa\u18fb\u18fc\u18fd\u18fe\u18ff\u191d\u191e\u191f\u192c\u192d\u192e\u192f\u193c\u193d\u193e\u193f\u1941\u1942\u1943\u196e\u196f\u1975\u1976\u1977\u1978\u1979\u197a\u197b\u197c\u197d\u197e\u197f\u19aa\u19ab\u19ac\u19ad\u19ae\u19af\u19ca\u19cb\u19cc\u19cd\u19ce\u19cf\u19da\u19db\u19dc\u19dd\u1a1c\u1a1d\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a53\u1a54\u1a55\u1a56\u1a57\u1a58\u1a59\u1a5a\u1a5b\u1a5c\u1a5d\u1a5e\u1a5f\u1a60\u1a61\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6b\u1a6c\u1a6d\u1a6e\u1a6f\u1a70\u1a71\u1a72\u1a73\u1a74\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1a7d\u1a7e\u1a7f\u1a80\u1a81\u1a82\u1a83\u1a84\u1a85\u1a86\u1a87\u1a88\u1a89\u1a8a\u1a8b\u1a8c\u1a8d\u1a8e\u1a8f\u1a90\u1a91\u1a92\u1a93\u1a94\u1a95\u1a96\u1a97\u1a98\u1a99\u1a9a\u1a9b\u1a9c\u1a9d\u1a9e\u1a9f\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aa7\u1aa8\u1aa9\u1aaa\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abb\u1abc\u1abd\u1abe\u1abf\u1ac0\u1ac1\u1ac2\u1ac3\u1ac4\u1ac5\u1ac6\u1ac7\u1ac8\u1ac9\u1aca\u1acb\u1acc\u1acd\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u1ade\u1adf\u1ae0\u1ae1\u1ae2\u1ae3\u1ae4\u1ae5\u1ae6\u1ae7\u1ae8\u1ae9\u1aea\u1aeb\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u1af7\u1af8\u1af9\u1afa\u1afb\u1afc\u1afd\u1afe\u1aff\u1b00\u1b01\u1b02\u1b03\u1b04\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b23\u1b24\u1b25\u1b26\u1b27\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b30\u1b31\u1b32\u1b33\u1b34\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u1b4c\u1b4d\u1b4e\u1b4f\u1b50\u1b51\u1b52\u1b53\u1b54\u1b55\u1b56\u1b57\u1b58\u1b59\u1b5a\u1b5b\u1b5c\u1b5d\u1b5e\u1b5f\u1b60\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b6b\u1b6c\u1b6d\u1b6e\u1b6f\u1b70\u1b71\u1b72\u1b73\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u1b7d\u1b7e\u1b7f\u1b80\u1b81\u1b82\u1b83\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1b99\u1b9a\u1b9b\u1b9c\u1b9d\u1b9e\u1b9f\u1ba0\u1ba1\u1ba2\u1ba3\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u1bac\u1bad\u1bae\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u1bb6\u1bb7\u1bb8\u1bb9\u1bba\u1bbb\u1bbc\u1bbd\u1bbe\u1bbf\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be6\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\u1bf4\u1bf5\u1bf6\u1bf7\u1bf8\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c1b\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c24\u1c25\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\u1c36\u1c37\u1c38\u1c39\u1c3a\u1c3b\u1c3c\u1c3d\u1c3e\u1c3f\u1c40\u1c41\u1c42\u1c43\u1c44\u1c45\u1c46\u1c47\u1c48\u1c49\u1c4a\u1c4b\u1c4c\u1c4d\u1c4e\u1c4f\u1c50\u1c51\u1c52\u1c53\u1c54\u1c55\u1c56\u1c57\u1c58\u1c59\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\u1c7e\u1c7f\u1c80\u1c81\u1c82\u1c83\u1c84\u1c85\u1c86\u1c87\u1c88\u1c89\u1c8a\u1c8b\u1c8c\u1c8d\u1c8e\u1c8f\u1c90\u1c91\u1c92\u1c93\u1c94\u1c95\u1c96\u1c97\u1c98\u1c99\u1c9a\u1c9b\u1c9c\u1c9d\u1c9e\u1c9f\u1ca0\u1ca1\u1ca2\u1ca3\u1ca4\u1ca5\u1ca6\u1ca7\u1ca8\u1ca9\u1caa\u1cab\u1cac\u1cad\u1cae\u1caf\u1cb0\u1cb1\u1cb2\u1cb3\u1cb4\u1cb5\u1cb6\u1cb7\u1cb8\u1cb9\u1cba\u1cbb\u1cbc\u1cbd\u1cbe\u1cbf\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u1cc8\u1cc9\u1cca\u1ccb\u1ccc\u1ccd\u1cce\u1ccf\u1cd0\u1cd1\u1cd2\u1cd3\u1cd4\u1cd5\u1cd6\u1cd7\u1cd8\u1cd9\u1cda\u1cdb\u1cdc\u1cdd\u1cde\u1cdf\u1ce0\u1ce1\u1ce2\u1ce3\u1ce4\u1ce5\u1ce6\u1ce7\u1ce8\u1ce9\u1cea\u1ceb\u1cec\u1ced\u1cee\u1cef\u1cf0\u1cf1\u1cf2\u1cf3\u1cf4\u1cf5\u1cf6\u1cf7\u1cf8\u1cf9\u1cfa\u1cfb\u1cfc\u1cfd\u1cfe\u1cff\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dca\u1dcb\u1dcc\u1dcd\u1dce\u1dcf\u1dd0\u1dd1\u1dd2\u1dd3\u1dd4\u1dd5\u1dd6\u1dd7\u1dd8\u1dd9\u1dda\u1ddb\u1ddc\u1ddd\u1dde\u1ddf\u1de0\u1de1\u1de2\u1de3\u1de4\u1de5\u1de6\u1de7\u1de8\u1de9\u1dea\u1deb\u1dec\u1ded\u1dee\u1def\u1df0\u1df1\u1df2\u1df3\u1df4\u1df5\u1df6\u1df7\u1df8\u1df9\u1dfa\u1dfb\u1dfc\u1dfd\u1dfe\u1dff\u1e9c\u1e9d\u1e9e\u1e9f\u1efa\u1efb\u1efc\u1efd\u1efe\u1eff\u1f16\u1f17\u1f1e\u1f1f\u1f46\u1f47\u1f4e\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e\u1f7f\u1fb5\u1fc5\u1fd4\u1fd5\u1fdc\u1ff0\u1ff1\u1ff5\u1fff\u2064\u2065\u2066\u2067\u2068\u2069\u2072\u2073\u208f\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u209d\u209e\u209f\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u20c0\u20c1\u20c2\u20c3\u20c4\u20c5\u20c6\u20c7\u20c8\u20c9\u20ca\u20cb\u20cc\u20cd\u20ce\u20cf\u20ec\u20ed\u20ee\u20ef\u20f0\u20f1\u20f2\u20f3\u20f4\u20f5\u20f6\u20f7\u20f8\u20f9\u20fa\u20fb\u20fc\u20fd\u20fe\u20ff\u214d\u214e\u214f\u2150\u2151\u2152\u2184\u2185\u2186\u2187\u2188\u2189\u218a\u218b\u218c\u218d\u218e\u218f\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2427\u2428\u2429\u242a\u242b\u242c\u242d\u242e\u242f\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u2437\u2438\u2439\u243a\u243b\u243c\u243d\u243e\u243f\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u2453\u2454\u2455\u2456\u2457\u2458\u2459\u245a\u245b\u245c\u245d\u245e\u245f\u269d\u269e\u269f\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2705\u270a\u270b\u2728\u274c\u274e\u2753\u2754\u2755\u2757\u275f\u2760\u2795\u2796\u2797\u27b0\u27bf\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27ec\u27ed\u27ee\u27ef\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2c2f\u2c5f\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf5\u2cf6\u2cf7\u2cf8\u2d26\u2d27\u2d28\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d6b\u2d6c\u2d6d\u2d6e\u2d70\u2d71\u2d72\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2d9c\u2d9d\u2d9e\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de7\u2de8\u2de9\u2dea\u2deb\u2dec\u2ded\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dfe\u2dff\u2e18\u2e19\u2e1a\u2e1b\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u2e2f\u2e30\u2e31\u2e32\u2e33\u2e34\u2e35\u2e36\u2e37\u2e38\u2e39\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e41\u2e42\u2e43\u2e44\u2e45\u2e46\u2e47\u2e48\u2e49\u2e4a\u2e4b\u2e4c\u2e4d\u2e4e\u2e4f\u2e50\u2e51\u2e52\u2e53\u2e54\u2e55\u2e56\u2e57\u2e58\u2e59\u2e5a\u2e5b\u2e5c\u2e5d\u2e5e\u2e5f\u2e60\u2e61\u2e62\u2e63\u2e64\u2e65\u2e66\u2e67\u2e68\u2e69\u2e6a\u2e6b\u2e6c\u2e6d\u2e6e\u2e6f\u2e70\u2e71\u2e72\u2e73\u2e74\u2e75\u2e76\u2e77\u2e78\u2e79\u2e7a\u2e7b\u2e7c\u2e7d\u2e7e\u2e7f\u2e9a\u2ef4\u2ef5\u2ef6\u2ef7\u2ef8\u2ef9\u2efa\u2efb\u2efc\u2efd\u2efe\u2eff\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ffc\u2ffd\u2ffe\u2fff\u3040\u3097\u3098\u3100\u3101\u3102\u3103\u3104\u312d\u312e\u312f\u3130\u318f\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u321f\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u32ff\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dbf\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcc\u9fcd\u9fce\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe4\u9fe5\u9fe6\u9fe7\u9fe8\u9fe9\u9fea\u9feb\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff1\u9ff2\u9ff3\u9ff4\u9ff5\u9ff6\u9ff7\u9ff8\u9ff9\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua48d\ua48e\ua48f\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua630\ua631\ua632\ua633\ua634\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua63c\ua63d\ua63e\ua63f\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua64e\ua64f\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua658\ua659\ua65a\ua65b\ua65c\ua65d\ua65e\ua65f\ua660\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua668\ua669\ua66a\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua676\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68d\ua68e\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua698\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f1\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6f9\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua722\ua723\ua724\ua725\ua726\ua727\ua728\ua729\ua72a\ua72b\ua72c\ua72d\ua72e\ua72f\ua730\ua731\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua740\ua741\ua742\ua743\ua744\ua745\ua746\ua747\ua748\ua749\ua74a\ua74b\ua74c\ua74d\ua74e\ua74f\ua750\ua751\ua752\ua753\ua754\ua755\ua756\ua757\ua758\ua759\ua75a\ua75b\ua75c\ua75d\ua75e\ua75f\ua760\ua761\ua762\ua763\ua764\ua765\ua766\ua767\ua768\ua769\ua76a\ua76b\ua76c\ua76d\ua76e\ua76f\ua770\ua771\ua772\ua773\ua774\ua775\ua776\ua777\ua778\ua779\ua77a\ua77b\ua77c\ua77d\ua77e\ua77f\ua780\ua781\ua782\ua783\ua784\ua785\ua786\ua787\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua78f\ua790\ua791\ua792\ua793\ua794\ua795\ua796\ua797\ua798\ua799\ua79a\ua79b\ua79c\ua79d\ua79e\ua79f\ua7a0\ua7a1\ua7a2\ua7a3\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7ae\ua7af\ua7b0\ua7b1\ua7b2\ua7b3\ua7b4\ua7b5\ua7b6\ua7b7\ua7b8\ua7b9\ua7ba\ua7bb\ua7bc\ua7bd\ua7be\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7c7\ua7c8\ua7c9\ua7ca\ua7cb\ua7cc\ua7cd\ua7ce\ua7cf\ua7d0\ua7d1\ua7d2\ua7d3\ua7d4\ua7d5\ua7d6\ua7d7\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7de\ua7df\ua7e0\ua7e1\ua7e2\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7eb\ua7ec\ua7ed\ua7ee\ua7ef\ua7f0\ua7f1\ua7f2\ua7f3\ua7f4\ua7f5\ua7f6\ua7f7\ua7f8\ua7f9\ua7fa\ua7fb\ua7fc\ua7fd\ua7fe\ua7ff\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua837\ua838\ua839\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua854\ua855\ua856\ua857\ua858\ua859\ua85a\ua85b\ua85c\ua85d\ua85e\ua85f\ua860\ua861\ua862\ua863\ua864\ua865\ua866\ua867\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\ua878\ua879\ua87a\ua87b\ua87c\ua87d\ua87e\ua87f\ua880\ua881\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\ua8c5\ua8c6\ua8c7\ua8c8\ua8c9\ua8ca\ua8cb\ua8cc\ua8cd\ua8ce\ua8cf\ua8d0\ua8d1\ua8d2\ua8d3\ua8d4\ua8d5\ua8d6\ua8d7\ua8d8\ua8d9\ua8da\ua8db\ua8dc\ua8dd\ua8de\ua8df\ua8e0\ua8e1\ua8e2\ua8e3\ua8e4\ua8e5\ua8e6\ua8e7\ua8e8\ua8e9\ua8ea\ua8eb\ua8ec\ua8ed\ua8ee\ua8ef\ua8f0\ua8f1\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8f8\ua8f9\ua8fa\ua8fb\ua8fc\ua8fd\ua8fe\ua8ff\ua900\ua901\ua902\ua903\ua904\ua905\ua906\ua907\ua908\ua909\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\ua92b\ua92c\ua92d\ua92e\ua92f\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua954\ua955\ua956\ua957\ua958\ua959\ua95a\ua95b\ua95c\ua95d\ua95e\ua95f\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\ua97d\ua97e\ua97f\ua980\ua981\ua982\ua983\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9ab\ua9ac\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b3\ua9b4\ua9b5\ua9b6\ua9b7\ua9b8\ua9b9\ua9ba\ua9bb\ua9bc\ua9bd\ua9be\ua9bf\ua9c0\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9c7\ua9c8\ua9c9\ua9ca\ua9cb\ua9cc\ua9cd\ua9ce\ua9cf\ua9d0\ua9d1\ua9d2\ua9d3\ua9d4\ua9d5\ua9d6\ua9d7\ua9d8\ua9d9\ua9da\ua9db\ua9dc\ua9dd\ua9de\ua9df\ua9e0\ua9e1\ua9e2\ua9e3\ua9e4\ua9e5\ua9e6\ua9e7\ua9e8\ua9e9\ua9ea\ua9eb\ua9ec\ua9ed\ua9ee\ua9ef\ua9f0\ua9f1\ua9f2\ua9f3\ua9f4\ua9f5\ua9f6\ua9f7\ua9f8\ua9f9\ua9fa\ua9fb\ua9fc\ua9fd\ua9fe\ua9ff\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa33\uaa34\uaa35\uaa36\uaa37\uaa38\uaa39\uaa3a\uaa3b\uaa3c\uaa3d\uaa3e\uaa3f\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\uaa4e\uaa4f\uaa50\uaa51\uaa52\uaa53\uaa54\uaa55\uaa56\uaa57\uaa58\uaa59\uaa5a\uaa5b\uaa5c\uaa5d\uaa5e\uaa5f\uaa60\uaa61\uaa62\uaa63\uaa64\uaa65\uaa66\uaa67\uaa68\uaa69\uaa6a\uaa6b\uaa6c\uaa6d\uaa6e\uaa6f\uaa70\uaa71\uaa72\uaa73\uaa74\uaa75\uaa76\uaa77\uaa78\uaa79\uaa7a\uaa7b\uaa7c\uaa7d\uaa7e\uaa7f\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaabf\uaac0\uaac1\uaac2\uaac3\uaac4\uaac5\uaac6\uaac7\uaac8\uaac9\uaaca\uaacb\uaacc\uaacd\uaace\uaacf\uaad0\uaad1\uaad2\uaad3\uaad4\uaad5\uaad6\uaad7\uaad8\uaad9\uaada\uaadb\uaadc\uaadd\uaade\uaadf\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf0\uaaf1\uaaf2\uaaf3\uaaf4\uaaf5\uaaf6\uaaf7\uaaf8\uaaf9\uaafa\uaafb\uaafc\uaafd\uaafe\uaaff\uab00\uab01\uab02\uab03\uab04\uab05\uab06\uab07\uab08\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\uab0f\uab10\uab11\uab12\uab13\uab14\uab15\uab16\uab17\uab18\uab19\uab1a\uab1b\uab1c\uab1d\uab1e\uab1f\uab20\uab21\uab22\uab23\uab24\uab25\uab26\uab27\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\uab2f\uab30\uab31\uab32\uab33\uab34\uab35\uab36\uab37\uab38\uab39\uab3a\uab3b\uab3c\uab3d\uab3e\uab3f\uab40\uab41\uab42\uab43\uab44\uab45\uab46\uab47\uab48\uab49\uab4a\uab4b\uab4c\uab4d\uab4e\uab4f\uab50\uab51\uab52\uab53\uab54\uab55\uab56\uab57\uab58\uab59\uab5a\uab5b\uab5c\uab5d\uab5e\uab5f\uab60\uab61\uab62\uab63\uab64\uab65\uab66\uab67\uab68\uab69\uab6a\uab6b\uab6c\uab6d\uab6e\uab6f\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79\uab7a\uab7b\uab7c\uab7d\uab7e\uab7f\uab80\uab81\uab82\uab83\uab84\uab85\uab86\uab87\uab88\uab89\uab8a\uab8b\uab8c\uab8d\uab8e\uab8f\uab90\uab91\uab92\uab93\uab94\uab95\uab96\uab97\uab98\uab99\uab9a\uab9b\uab9c\uab9d\uab9e\uab9f\uaba0\uaba1\uaba2\uaba3\uaba4\uaba5\uaba6\uaba7\uaba8\uaba9\uabaa\uabab\uabac\uabad\uabae\uabaf\uabb0\uabb1\uabb2\uabb3\uabb4\uabb5\uabb6\uabb7\uabb8\uabb9\uabba\uabbb\uabbc\uabbd\uabbe\uabbf\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uabeb\uabec\uabed\uabee\uabef\uabf0\uabf1\uabf2\uabf3\uabf4\uabf5\uabf6\uabf7\uabf8\uabf9\uabfa\uabfb\uabfc\uabfd\uabfe\uabff\ud7a4\ud7a5\ud7a6\ud7a7\ud7a8\ud7a9\ud7aa\ud7ab\ud7ac\ud7ad\ud7ae\ud7af\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\ud7c7\ud7c8\ud7c9\ud7ca\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\ud7fc\ud7fd\ud7fe\ud7ff\ufa2e\ufa2f\ufa6b\ufa6c\ufa6d\ufa6e\ufa6f\ufada\ufadb\ufadc\ufadd\ufade\ufadf\ufae0\ufae1\ufae2\ufae3\ufae4\ufae5\ufae6\ufae7\ufae8\ufae9\ufaea\ufaeb\ufaec\ufaed\ufaee\ufaef\ufaf0\ufaf1\ufaf2\ufaf3\ufaf4\ufaf5\ufaf6\ufaf7\ufaf8\ufaf9\ufafa\ufafb\ufafc\ufafd\ufafe\ufaff\ufb07\ufb08\ufb09\ufb0a\ufb0b\ufb0c\ufb0d\ufb0e\ufb0f\ufb10\ufb11\ufb12\ufb18\ufb19\ufb1a\ufb1b\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\ufbc2\ufbc3\ufbc4\ufbc5\ufbc6\ufbc7\ufbc8\ufbc9\ufbca\ufbcb\ufbcc\ufbcd\ufbce\ufbcf\ufbd0\ufbd1\ufbd2\ufd40\ufd41\ufd42\ufd43\ufd44\ufd45\ufd46\ufd47\ufd48\ufd49\ufd4a\ufd4b\ufd4c\ufd4d\ufd4e\ufd4f\ufd90\ufd91\ufdc8\ufdc9\ufdca\ufdcb\ufdcc\ufdcd\ufdce\ufdcf\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea\ufdeb\ufdec\ufded\ufdee\ufdef\ufdfe\ufdff\ufe1a\ufe1b\ufe1c\ufe1d\ufe1e\ufe1f\ufe24\ufe25\ufe26\ufe27\ufe28\ufe29\ufe2a\ufe2b\ufe2c\ufe2d\ufe2e\ufe2f\ufe53\ufe67\ufe6c\ufe6d\ufe6e\ufe6f\ufe75\ufefd\ufefe\uff00\uffbf\uffc0\uffc1\uffc8\uffc9\uffd0\uffd1\uffd8\uffd9\uffdd\uffde\uffdf\uffe7\uffef\ufff0\ufff1\ufff2\ufff3\ufff4\ufff5\ufff6\ufff7\ufff8\ufffe'
-
-Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33f\ue340\ue341\ue342\ue343\ue344\ue345\ue346\ue347\ue348\ue349\ue34a\ue34b\ue34c\ue34d\ue34e\ue34f\ue350\ue351\ue352\ue353\ue354\ue355\ue356\ue357\ue358\ue359\ue35a\ue35b\ue35c\ue35d\ue35e\ue35f\ue360\ue361\ue362\ue363\ue364\ue365\ue366\ue367\ue368\ue369\ue36a\ue36b\ue36c\ue36d\ue36e\ue36f\ue370\ue371\ue372\ue373\ue374\ue375\ue376\ue377\ue378\ue379\ue37a\ue37b\ue37c\ue37d\ue37e\ue37f\ue380\ue381\ue382\ue383\ue384\ue385\ue386\ue387\ue388\ue389\ue38a\ue38b\ue38c\ue38d\ue38e\ue38f\ue390\ue391\ue392\ue393\ue394\ue395\ue396\ue397\ue398\ue399\ue39a\ue39b\ue39c\ue39d\ue39e\ue39f\ue3a0\ue3a1\ue3a2\ue3a3\ue3a4\ue3a5\ue3a6\ue3a7\ue3a8\ue3a9\ue3aa\ue3ab\ue3ac\ue3ad\ue3ae\ue3af\ue3b0\ue3b1\ue3b2\ue3b3\ue3b4\ue3b5\ue3b6\ue3b7\ue3b8\ue3b9\ue3ba\ue3bb\ue3bc\ue3bd\ue3be\ue3bf\ue3c0\ue3c1\ue3c2\ue3c3\ue3c4\ue3c5\ue3c6\ue3c7\ue3c8\ue3c9\ue3ca\ue3cb\ue3cc\ue3cd\ue3ce\ue3cf\ue3d0\ue3d1\ue3d2\ue3d3\ue3d4\ue3d5\ue3d6\ue3d7\ue3d8\ue3d9\ue3da\ue3db\ue3dc\ue3dd\ue3de\ue3df\ue3e0\ue3e1\ue3e2\ue3e3\ue3e4\ue3e5\ue3e6\ue3e7\ue3e8\ue3e9\ue3ea\ue3eb\ue3ec\ue3ed\ue3ee\ue3ef\ue3f0\ue3f1\ue3f2\ue3f3\ue3f4\ue3f5\ue3f6\ue3f7\ue3f8\ue3f9\ue3fa\ue3fb\ue3fc\ue3fd\ue3fe\ue3ff\ue400\ue401\ue402\ue403\ue404\ue405\ue406\ue407\ue408\ue409\ue40a\ue40b\ue40c\ue40d\ue40e\ue40f\ue410\ue411\ue412\ue413\ue414\ue415\ue416\ue417\ue418\ue419\ue41a\ue41b\ue41c\ue41d\ue41e\ue41f\ue420\ue421\ue422\ue423\ue424\ue425\ue426\ue427\ue428\ue429\ue42a\ue42b\ue42c\ue42d\ue42e\ue42f\ue430\ue431\ue432\ue433\ue434\ue435\ue436\ue437\ue438\ue439\ue43a\ue43b\ue43c\ue43d\ue43e\ue43f\ue440\ue441\ue442\ue443\ue444\ue445\ue446\ue447\ue448\ue449\ue44a\ue44b\ue44c\ue44d\ue44e\ue44f\ue450\ue451\ue452\ue453\ue454\ue455\ue456\ue457\ue458\ue459\ue45a\ue45b\ue45c\ue45d\ue45e\ue45f\ue460\ue461\ue462\ue463\ue464\ue465\ue466\ue467\ue468\ue469\ue46a\ue46b\ue46c\ue46d\ue46e\ue46f\ue470\ue471\ue472\ue473\ue474\ue475\ue476\ue477\ue478\ue479\ue47a\ue47b\ue47c\ue47d\ue47e\ue47f\ue480\ue481\ue482\ue483\ue484\ue485\ue486\ue487\ue488\ue489\ue48a\ue48b\ue48c\ue48d\ue48e\ue48f\ue490\ue491\ue492\ue493\ue494\ue495\ue496\ue497\ue498\ue499\ue49a\ue49b\ue49c\ue49d\ue49e\ue49f\ue4a0\ue4a1\ue4a2\ue4a3\ue4a4\ue4a5\ue4a6\ue4a7\ue4a8\ue4a9\ue4aa\ue4ab\ue4ac\ue4ad\ue4ae\ue4af\ue4b0\ue4b1\ue4b2\ue4b3\ue4b4\ue4b5\ue4b6\ue4b7\ue4b8\ue4b9\ue4ba\ue4bb\ue4bc\ue4bd\ue4be\ue4bf\ue4c0\ue4c1\ue4c2\ue4c3\ue4c4\ue4c5\ue4c6\ue4c7\ue4c8\ue4c9\ue4ca\ue4cb\ue4cc\ue4cd\ue4ce\ue4cf\ue4d0\ue4d1\ue4d2\ue4d3\ue4d4\ue4d5\ue4d6\ue4d7\ue4d8\ue4d9\ue4da\ue4db\ue4dc\ue4dd\ue4de\ue4df\ue4e0\ue4e1\ue4e2\ue4e3\ue4e4\ue4e5\ue4e6\ue4e7\ue4e8\ue4e9\ue4ea\ue4eb\ue4ec\ue4ed\ue4ee\ue4ef\ue4f0\ue4f1\ue4f2\ue4f3\ue4f4\ue4f5\ue4f6\ue4f7\ue4f8\ue4f9\ue4fa\ue4fb\ue4fc\ue4fd\ue4fe\ue4ff\ue500\ue501\ue502\ue503\ue504\ue505\ue506\ue507\ue508\ue509\ue50a\ue50b\ue50c\ue50d\ue50e\ue50f\ue510\ue511\ue512\ue513\ue514\ue515\ue516\ue517\ue518\ue519\ue51a\ue51b\ue51c\ue51d\ue51e\ue51f\ue520\ue521\ue522\ue523\ue524\ue525\ue526\ue527\ue528\ue529\ue52a\ue52b\ue52c\ue52d\ue52e\ue52f\ue530\ue531\ue532\ue533\ue534\ue535\ue536\ue537\ue538\ue539\ue53a\ue53b\ue53c\ue53d\ue53e\ue53f\ue540\ue541\ue542\ue543\ue544\ue545\ue546\ue547\ue548\ue549\ue54a\ue54b\ue54c\ue54d\ue54e\ue54f\ue550\ue551\ue552\ue553\ue554\ue555\ue556\ue557\ue558\ue559\ue55a\ue55b\ue55c\ue55d\ue55e\ue55f\ue560\ue561\ue562\ue563\ue564\ue565\ue566\ue567\ue568\ue569\ue56a\ue56b\ue56c\ue56d\ue56e\ue56f\ue570\ue571\ue572\ue573\ue574\ue575\ue576\ue577\ue578\ue579\ue57a\ue57b\ue57c\ue57d\ue57e\ue57f\ue580\ue581\ue582\ue583\ue584\ue585\ue586\ue587\ue588\ue589\ue58a\ue58b\ue58c\ue58d\ue58e\ue58f\ue590\ue591\ue592\ue593\ue594\ue595\ue596\ue597\ue598\ue599\ue59a\ue59b\ue59c\ue59d\ue59e\ue59f\ue5a0\ue5a1\ue5a2\ue5a3\ue5a4\ue5a5\ue5a6\ue5a7\ue5a8\ue5a9\ue5aa\ue5ab\ue5ac\ue5ad\ue5ae\ue5af\ue5b0\ue5b1\ue5b2\ue5b3\ue5b4\ue5b5\ue5b6\ue5b7\ue5b8\ue5b9\ue5ba\ue5bb\ue5bc\ue5bd\ue5be\ue5bf\ue5c0\ue5c1\ue5c2\ue5c3\ue5c4\ue5c5\ue5c6\ue5c7\ue5c8\ue5c9\ue5ca\ue5cb\ue5cc\ue5cd\ue5ce\ue5cf\ue5d0\ue5d1\ue5d2\ue5d3\ue5d4\ue5d5\ue5d6\ue5d7\ue5d8\ue5d9\ue5da\ue5db\ue5dc\ue5dd\ue5de\ue5df\ue5e0\ue5e1\ue5e2\ue5e3\ue5e4\ue5e5\ue5e6\ue5e7\ue5e8\ue5e9\ue5ea\ue5eb\ue5ec\ue5ed\ue5ee\ue5ef\ue5f0\ue5f1\ue5f2\ue5f3\ue5f4\ue5f5\ue5f6\ue5f7\ue5f8\ue5f9\ue5fa\ue5fb\ue5fc\ue5fd\ue5fe\ue5ff\ue600\ue601\ue602\ue603\ue604\ue605\ue606\ue607\ue608\ue609\ue60a\ue60b\ue60c\ue60d\ue60e\ue60f\ue610\ue611\ue612\ue613\ue614\ue615\ue616\ue617\ue618\ue619\ue61a\ue61b\ue61c\ue61d\ue61e\ue61f\ue620\ue621\ue622\ue623\ue624\ue625\ue626\ue627\ue628\ue629\ue62a\ue62b\ue62c\ue62d\ue62e\ue62f\ue630\ue631\ue632\ue633\ue634\ue635\ue636\ue637\ue638\ue639\ue63a\ue63b\ue63c\ue63d\ue63e\ue63f\ue640\ue641\ue642\ue643\ue644\ue645\ue646\ue647\ue648\ue649\ue64a\ue64b\ue64c\ue64d\ue64e\ue64f\ue650\ue651\ue652\ue653\ue654\ue655\ue656\ue657\ue658\ue659\ue65a\ue65b\ue65c\ue65d\ue65e\ue65f\ue660\ue661\ue662\ue663\ue664\ue665\ue666\ue667\ue668\ue669\ue66a\ue66b\ue66c\ue66d\ue66e\ue66f\ue670\ue671\ue672\ue673\ue674\ue675\ue676\ue677\ue678\ue679\ue67a\ue67b\ue67c\ue67d\ue67e\ue67f\ue680\ue681\ue682\ue683\ue684\ue685\ue686\ue687\ue688\ue689\ue68a\ue68b\ue68c\ue68d\ue68e\ue68f\ue690\ue691\ue692\ue693\ue694\ue695\ue696\ue697\ue698\ue699\ue69a\ue69b\ue69c\ue69d\ue69e\ue69f\ue6a0\ue6a1\ue6a2\ue6a3\ue6a4\ue6a5\ue6a6\ue6a7\ue6a8\ue6a9\ue6aa\ue6ab\ue6ac\ue6ad\ue6ae\ue6af\ue6b0\ue6b1\ue6b2\ue6b3\ue6b4\ue6b5\ue6b6\ue6b7\ue6b8\ue6b9\ue6ba\ue6bb\ue6bc\ue6bd\ue6be\ue6bf\ue6c0\ue6c1\ue6c2\ue6c3\ue6c4\ue6c5\ue6c6\ue6c7\ue6c8\ue6c9\ue6ca\ue6cb\ue6cc\ue6cd\ue6ce\ue6cf\ue6d0\ue6d1\ue6d2\ue6d3\ue6d4\ue6d5\ue6d6\ue6d7\ue6d8\ue6d9\ue6da\ue6db\ue6dc\ue6dd\ue6de\ue6df\ue6e0\ue6e1\ue6e2\ue6e3\ue6e4\ue6e5\ue6e6\ue6e7\ue6e8\ue6e9\ue6ea\ue6eb\ue6ec\ue6ed\ue6ee\ue6ef\ue6f0\ue6f1\ue6f2\ue6f3\ue6f4\ue6f5\ue6f6\ue6f7\ue6f8\ue6f9\ue6fa\ue6fb\ue6fc\ue6fd\ue6fe\ue6ff\ue700\ue701\ue702\ue703\ue704\ue705\ue706\ue707\ue708\ue709\ue70a\ue70b\ue70c\ue70d\ue70e\ue70f\ue710\ue711\ue712\ue713\ue714\ue715\ue716\ue717\ue718\ue719\ue71a\ue71b\ue71c\ue71d\ue71e\ue71f\ue720\ue721\ue722\ue723\ue724\ue725\ue726\ue727\ue728\ue729\ue72a\ue72b\ue72c\ue72d\ue72e\ue72f\ue730\ue731\ue732\ue733\ue734\ue735\ue736\ue737\ue738\ue739\ue73a\ue73b\ue73c\ue73d\ue73e\ue73f\ue740\ue741\ue742\ue743\ue744\ue745\ue746\ue747\ue748\ue749\ue74a\ue74b\ue74c\ue74d\ue74e\ue74f\ue750\ue751\ue752\ue753\ue754\ue755\ue756\ue757\ue758\ue759\ue75a\ue75b\ue75c\ue75d\ue75e\ue75f\ue760\ue761\ue762\ue763\ue764\ue765\ue766\ue767\ue768\ue769\ue76a\ue76b\ue76c\ue76d\ue76e\ue76f\ue770\ue771\ue772\ue773\ue774\ue775\ue776\ue777\ue778\ue779\ue77a\ue77b\ue77c\ue77d\ue77e\ue77f\ue780\ue781\ue782\ue783\ue784\ue785\ue786\ue787\ue788\ue789\ue78a\ue78b\ue78c\ue78d\ue78e\ue78f\ue790\ue791\ue792\ue793\ue794\ue795\ue796\ue797\ue798\ue799\ue79a\ue79b\ue79c\ue79d\ue79e\ue79f\ue7a0\ue7a1\ue7a2\ue7a3\ue7a4\ue7a5\ue7a6\ue7a7\ue7a8\ue7a9\ue7aa\ue7ab\ue7ac\ue7ad\ue7ae\ue7af\ue7b0\ue7b1\ue7b2\ue7b3\ue7b4\ue7b5\ue7b6\ue7b7\ue7b8\ue7b9\ue7ba\ue7bb\ue7bc\ue7bd\ue7be\ue7bf\ue7c0\ue7c1\ue7c2\ue7c3\ue7c4\ue7c5\ue7c6\ue7c7\ue7c8\ue7c9\ue7ca\ue7cb\ue7cc\ue7cd\ue7ce\ue7cf\ue7d0\ue7d1\ue7d2\ue7d3\ue7d4\ue7d5\ue7d6\ue7d7\ue7d8\ue7d9\ue7da\ue7db\ue7dc\ue7dd\ue7de\ue7df\ue7e0\ue7e1\ue7e2\ue7e3\ue7e4\ue7e5\ue7e6\ue7e7\ue7e8\ue7e9\ue7ea\ue7eb\ue7ec\ue7ed\ue7ee\ue7ef\ue7f0\ue7f1\ue7f2\ue7f3\ue7f4\ue7f5\ue7f6\ue7f7\ue7f8\ue7f9\ue7fa\ue7fb\ue7fc\ue7fd\ue7fe\ue7ff\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\ue8ff\ue900\ue901\ue902\ue903\ue904\ue905\ue906\ue907\ue908\ue909\ue90a\ue90b\ue90c\ue90d\ue90e\ue90f\ue910\ue911\ue912\ue913\ue914\ue915\ue916\ue917\ue918\ue919\ue91a\ue91b\ue91c\ue91d\ue91e\ue91f\ue920\ue921\ue922\ue923\ue924\ue925\ue926\ue927\ue928\ue929\ue92a\ue92b\ue92c\ue92d\ue92e\ue92f\ue930\ue931\ue932\ue933\ue934\ue935\ue936\ue937\ue938\ue939\ue93a\ue93b\ue93c\ue93d\ue93e\ue93f\ue940\ue941\ue942\ue943\ue944\ue945\ue946\ue947\ue948\ue949\ue94a\ue94b\ue94c\ue94d\ue94e\ue94f\ue950\ue951\ue952\ue953\ue954\ue955\ue956\ue957\ue958\ue959\ue95a\ue95b\ue95c\ue95d\ue95e\ue95f\ue960\ue961\ue962\ue963\ue964\ue965\ue966\ue967\ue968\ue969\ue96a\ue96b\ue96c\ue96d\ue96e\ue96f\ue970\ue971\ue972\ue973\ue974\ue975\ue976\ue977\ue978\ue979\ue97a\ue97b\ue97c\ue97d\ue97e\ue97f\ue980\ue981\ue982\ue983\ue984\ue985\ue986\ue987\ue988\ue989\ue98a\ue98b\ue98c\ue98d\ue98e\ue98f\ue990\ue991\ue992\ue993\ue994\ue995\ue996\ue997\ue998\ue999\ue99a\ue99b\ue99c\ue99d\ue99e\ue99f\ue9a0\ue9a1\ue9a2\ue9a3\ue9a4\ue9a5\ue9a6\ue9a7\ue9a8\ue9a9\ue9aa\ue9ab\ue9ac\ue9ad\ue9ae\ue9af\ue9b0\ue9b1\ue9b2\ue9b3\ue9b4\ue9b5\ue9b6\ue9b7\ue9b8\ue9b9\ue9ba\ue9bb\ue9bc\ue9bd\ue9be\ue9bf\ue9c0\ue9c1\ue9c2\ue9c3\ue9c4\ue9c5\ue9c6\ue9c7\ue9c8\ue9c9\ue9ca\ue9cb\ue9cc\ue9cd\ue9ce\ue9cf\ue9d0\ue9d1\ue9d2\ue9d3\ue9d4\ue9d5\ue9d6\ue9d7\ue9d8\ue9d9\ue9da\ue9db\ue9dc\ue9dd\ue9de\ue9df\ue9e0\ue9e1\ue9e2\ue9e3\ue9e4\ue9e5\ue9e6\ue9e7\ue9e8\ue9e9\ue9ea\ue9eb\ue9ec\ue9ed\ue9ee\ue9ef\ue9f0\ue9f1\ue9f2\ue9f3\ue9f4\ue9f5\ue9f6\ue9f7\ue9f8\ue9f9\ue9fa\ue9fb\ue9fc\ue9fd\ue9fe\ue9ff\uea00\uea01\uea02\uea03\uea04\uea05\uea06\uea07\uea08\uea09\uea0a\uea0b\uea0c\uea0d\uea0e\uea0f\uea10\uea11\uea12\uea13\uea14\uea15\uea16\uea17\uea18\uea19\uea1a\uea1b\uea1c\uea1d\uea1e\uea1f\uea20\uea21\uea22\uea23\uea24\uea25\uea26\uea27\uea28\uea29\uea2a\uea2b\uea2c\uea2d\uea2e\uea2f\uea30\uea31\uea32\uea33\uea34\uea35\uea36\uea37\uea38\uea39\uea3a\uea3b\uea3c\uea3d\uea3e\uea3f\uea40\uea41\uea42\uea43\uea44\uea45\uea46\uea47\uea48\uea49\uea4a\uea4b\uea4c\uea4d\uea4e\uea4f\uea50\uea51\uea52\uea53\uea54\uea55\uea56\uea57\uea58\uea59\uea5a\uea5b\uea5c\uea5d\uea5e\uea5f\uea60\uea61\uea62\uea63\uea64\uea65\uea66\uea67\uea68\uea69\uea6a\uea6b\uea6c\uea6d\uea6e\uea6f\uea70\uea71\uea72\uea73\uea74\uea75\uea76\uea77\uea78\uea79\uea7a\uea7b\uea7c\uea7d\uea7e\uea7f\uea80\uea81\uea82\uea83\uea84\uea85\uea86\uea87\uea88\uea89\uea8a\uea8b\uea8c\uea8d\uea8e\uea8f\uea90\uea91\uea92\uea93\uea94\uea95\uea96\uea97\uea98\uea99\uea9a\uea9b\uea9c\uea9d\uea9e\uea9f\ueaa0\ueaa1\ueaa2\ueaa3\ueaa4\ueaa5\ueaa6\ueaa7\ueaa8\ueaa9\ueaaa\ueaab\ueaac\ueaad\ueaae\ueaaf\ueab0\ueab1\ueab2\ueab3\ueab4\ueab5\ueab6\ueab7\ueab8\ueab9\ueaba\ueabb\ueabc\ueabd\ueabe\ueabf\ueac0\ueac1\ueac2\ueac3\ueac4\ueac5\ueac6\ueac7\ueac8\ueac9\ueaca\ueacb\ueacc\ueacd\ueace\ueacf\uead0\uead1\uead2\uead3\uead4\uead5\uead6\uead7\uead8\uead9\ueada\ueadb\ueadc\ueadd\ueade\ueadf\ueae0\ueae1\ueae2\ueae3\ueae4\ueae5\ueae6\ueae7\ueae8\ueae9\ueaea\ueaeb\ueaec\ueaed\ueaee\ueaef\ueaf0\ueaf1\ueaf2\ueaf3\ueaf4\ueaf5\ueaf6\ueaf7\ueaf8\ueaf9\ueafa\ueafb\ueafc\ueafd\ueafe\ueaff\ueb00\ueb01\ueb02\ueb03\ueb04\ueb05\ueb06\ueb07\ueb08\ueb09\ueb0a\ueb0b\ueb0c\ueb0d\ueb0e\ueb0f\ueb10\ueb11\ueb12\ueb13\ueb14\ueb15\ueb16\ueb17\ueb18\ueb19\ueb1a\ueb1b\ueb1c\ueb1d\ueb1e\ueb1f\ueb20\ueb21\ueb22\ueb23\ueb24\ueb25\ueb26\ueb27\ueb28\ueb29\ueb2a\ueb2b\ueb2c\ueb2d\ueb2e\ueb2f\ueb30\ueb31\ueb32\ueb33\ueb34\ueb35\ueb36\ueb37\ueb38\ueb39\ueb3a\ueb3b\ueb3c\ueb3d\ueb3e\ueb3f\ueb40\ueb41\ueb42\ueb43\ueb44\ueb45\ueb46\ueb47\ueb48\ueb49\ueb4a\ueb4b\ueb4c\ueb4d\ueb4e\ueb4f\ueb50\ueb51\ueb52\ueb53\ueb54\ueb55\ueb56\ueb57\ueb58\ueb59\ueb5a\ueb5b\ueb5c\ueb5d\ueb5e\ueb5f\ueb60\ueb61\ueb62\ueb63\ueb64\ueb65\ueb66\ueb67\ueb68\ueb69\ueb6a\ueb6b\ueb6c\ueb6d\ueb6e\ueb6f\ueb70\ueb71\ueb72\ueb73\ueb74\ueb75\ueb76\ueb77\ueb78\ueb79\ueb7a\ueb7b\ueb7c\ueb7d\ueb7e\ueb7f\ueb80\ueb81\ueb82\ueb83\ueb84\ueb85\ueb86\ueb87\ueb88\ueb89\ueb8a\ueb8b\ueb8c\ueb8d\ueb8e\ueb8f\ueb90\ueb91\ueb92\ueb93\ueb94\ueb95\ueb96\ueb97\ueb98\ueb99\ueb9a\ueb9b\ueb9c\ueb9d\ueb9e\ueb9f\ueba0\ueba1\ueba2\ueba3\ueba4\ueba5\ueba6\ueba7\ueba8\ueba9\uebaa\uebab\uebac\uebad\uebae\uebaf\uebb0\uebb1\uebb2\uebb3\uebb4\uebb5\uebb6\uebb7\uebb8\uebb9\uebba\uebbb\uebbc\uebbd\uebbe\uebbf\uebc0\uebc1\uebc2\uebc3\uebc4\uebc5\uebc6\uebc7\uebc8\uebc9\uebca\uebcb\uebcc\uebcd\uebce\uebcf\uebd0\uebd1\uebd2\uebd3\uebd4\uebd5\uebd6\uebd7\uebd8\uebd9\uebda\uebdb\uebdc\uebdd\uebde\uebdf\uebe0\uebe1\uebe2\uebe3\uebe4\uebe5\uebe6\uebe7\uebe8\uebe9\uebea\uebeb\uebec\uebed\uebee\uebef\uebf0\uebf1\uebf2\uebf3\uebf4\uebf5\uebf6\uebf7\uebf8\uebf9\uebfa\uebfb\uebfc\uebfd\uebfe\uebff\uec00\uec01\uec02\uec03\uec04\uec05\uec06\uec07\uec08\uec09\uec0a\uec0b\uec0c\uec0d\uec0e\uec0f\uec10\uec11\uec12\uec13\uec14\uec15\uec16\uec17\uec18\uec19\uec1a\uec1b\uec1c\uec1d\uec1e\uec1f\uec20\uec21\uec22\uec23\uec24\uec25\uec26\uec27\uec28\uec29\uec2a\uec2b\uec2c\uec2d\uec2e\uec2f\uec30\uec31\uec32\uec33\uec34\uec35\uec36\uec37\uec38\uec39\uec3a\uec3b\uec3c\uec3d\uec3e\uec3f\uec40\uec41\uec42\uec43\uec44\uec45\uec46\uec47\uec48\uec49\uec4a\uec4b\uec4c\uec4d\uec4e\uec4f\uec50\uec51\uec52\uec53\uec54\uec55\uec56\uec57\uec58\uec59\uec5a\uec5b\uec5c\uec5d\uec5e\uec5f\uec60\uec61\uec62\uec63\uec64\uec65\uec66\uec67\uec68\uec69\uec6a\uec6b\uec6c\uec6d\uec6e\uec6f\uec70\uec71\uec72\uec73\uec74\uec75\uec76\uec77\uec78\uec79\uec7a\uec7b\uec7c\uec7d\uec7e\uec7f\uec80\uec81\uec82\uec83\uec84\uec85\uec86\uec87\uec88\uec89\uec8a\uec8b\uec8c\uec8d\uec8e\uec8f\uec90\uec91\uec92\uec93\uec94\uec95\uec96\uec97\uec98\uec99\uec9a\uec9b\uec9c\uec9d\uec9e\uec9f\ueca0\ueca1\ueca2\ueca3\ueca4\ueca5\ueca6\ueca7\ueca8\ueca9\uecaa\uecab\uecac\uecad\uecae\uecaf\uecb0\uecb1\uecb2\uecb3\uecb4\uecb5\uecb6\uecb7\uecb8\uecb9\uecba\uecbb\uecbc\uecbd\uecbe\uecbf\uecc0\uecc1\uecc2\uecc3\uecc4\uecc5\uecc6\uecc7\uecc8\uecc9\uecca\ueccb\ueccc\ueccd\uecce\ueccf\uecd0\uecd1\uecd2\uecd3\uecd4\uecd5\uecd6\uecd7\uecd8\uecd9\uecda\uecdb\uecdc\uecdd\uecde\uecdf\uece0\uece1\uece2\uece3\uece4\uece5\uece6\uece7\uece8\uece9\uecea\ueceb\uecec\ueced\uecee\uecef\uecf0\uecf1\uecf2\uecf3\uecf4\uecf5\uecf6\uecf7\uecf8\uecf9\uecfa\uecfb\uecfc\uecfd\uecfe\uecff\ued00\ued01\ued02\ued03\ued04\ued05\ued06\ued07\ued08\ued09\ued0a\ued0b\ued0c\ued0d\ued0e\ued0f\ued10\ued11\ued12\ued13\ued14\ued15\ued16\ued17\ued18\ued19\ued1a\ued1b\ued1c\ued1d\ued1e\ued1f\ued20\ued21\ued22\ued23\ued24\ued25\ued26\ued27\ued28\ued29\ued2a\ued2b\ued2c\ued2d\ued2e\ued2f\ued30\ued31\ued32\ued33\ued34\ued35\ued36\ued37\ued38\ued39\ued3a\ued3b\ued3c\ued3d\ued3e\ued3f\ued40\ued41\ued42\ued43\ued44\ued45\ued46\ued47\ued48\ued49\ued4a\ued4b\ued4c\ued4d\ued4e\ued4f\ued50\ued51\ued52\ued53\ued54\ued55\ued56\ued57\ued58\ued59\ued5a\ued5b\ued5c\ued5d\ued5e\ued5f\ued60\ued61\ued62\ued63\ued64\ued65\ued66\ued67\ued68\ued69\ued6a\ued6b\ued6c\ued6d\ued6e\ued6f\ued70\ued71\ued72\ued73\ued74\ued75\ued76\ued77\ued78\ued79\ued7a\ued7b\ued7c\ued7d\ued7e\ued7f\ued80\ued81\ued82\ued83\ued84\ued85\ued86\ued87\ued88\ued89\ued8a\ued8b\ued8c\ued8d\ued8e\ued8f\ued90\ued91\ued92\ued93\ued94\ued95\ued96\ued97\ued98\ued99\ued9a\ued9b\ued9c\ued9d\ued9e\ued9f\ueda0\ueda1\ueda2\ueda3\ueda4\ueda5\ueda6\ueda7\ueda8\ueda9\uedaa\uedab\uedac\uedad\uedae\uedaf\uedb0\uedb1\uedb2\uedb3\uedb4\uedb5\uedb6\uedb7\uedb8\uedb9\uedba\uedbb\uedbc\uedbd\uedbe\uedbf\uedc0\uedc1\uedc2\uedc3\uedc4\uedc5\uedc6\uedc7\uedc8\uedc9\uedca\uedcb\uedcc\uedcd\uedce\uedcf\uedd0\uedd1\uedd2\uedd3\uedd4\uedd5\uedd6\uedd7\uedd8\uedd9\uedda\ueddb\ueddc\ueddd\uedde\ueddf\uede0\uede1\uede2\uede3\uede4\uede5\uede6\uede7\uede8\uede9\uedea\uedeb\uedec\ueded\uedee\uedef\uedf0\uedf1\uedf2\uedf3\uedf4\uedf5\uedf6\uedf7\uedf8\uedf9\uedfa\uedfb\uedfc\uedfd\uedfe\uedff\uee00\uee01\uee02\uee03\uee04\uee05\uee06\uee07\uee08\uee09\uee0a\uee0b\uee0c\uee0d\uee0e\uee0f\uee10\uee11\uee12\uee13\uee14\uee15\uee16\uee17\uee18\uee19\uee1a\uee1b\uee1c\uee1d\uee1e\uee1f\uee20\uee21\uee22\uee23\uee24\uee25\uee26\uee27\uee28\uee29\uee2a\uee2b\uee2c\uee2d\uee2e\uee2f\uee30\uee31\uee32\uee33\uee34\uee35\uee36\uee37\uee38\uee39\uee3a\uee3b\uee3c\uee3d\uee3e\uee3f\uee40\uee41\uee42\uee43\uee44\uee45\uee46\uee47\uee48\uee49\uee4a\uee4b\uee4c\uee4d\uee4e\uee4f\uee50\uee51\uee52\uee53\uee54\uee55\uee56\uee57\uee58\uee59\uee5a\uee5b\uee5c\uee5d\uee5e\uee5f\uee60\uee61\uee62\uee63\uee64\uee65\uee66\uee67\uee68\uee69\uee6a\uee6b\uee6c\uee6d\uee6e\uee6f\uee70\uee71\uee72\uee73\uee74\uee75\uee76\uee77\uee78\uee79\uee7a\uee7b\uee7c\uee7d\uee7e\uee7f\uee80\uee81\uee82\uee83\uee84\uee85\uee86\uee87\uee88\uee89\uee8a\uee8b\uee8c\uee8d\uee8e\uee8f\uee90\uee91\uee92\uee93\uee94\uee95\uee96\uee97\uee98\uee99\uee9a\uee9b\uee9c\uee9d\uee9e\uee9f\ueea0\ueea1\ueea2\ueea3\ueea4\ueea5\ueea6\ueea7\ueea8\ueea9\ueeaa\ueeab\ueeac\ueead\ueeae\ueeaf\ueeb0\ueeb1\ueeb2\ueeb3\ueeb4\ueeb5\ueeb6\ueeb7\ueeb8\ueeb9\ueeba\ueebb\ueebc\ueebd\ueebe\ueebf\ueec0\ueec1\ueec2\ueec3\ueec4\ueec5\ueec6\ueec7\ueec8\ueec9\ueeca\ueecb\ueecc\ueecd\ueece\ueecf\ueed0\ueed1\ueed2\ueed3\ueed4\ueed5\ueed6\ueed7\ueed8\ueed9\ueeda\ueedb\ueedc\ueedd\ueede\ueedf\ueee0\ueee1\ueee2\ueee3\ueee4\ueee5\ueee6\ueee7\ueee8\ueee9\ueeea\ueeeb\ueeec\ueeed\ueeee\ueeef\ueef0\ueef1\ueef2\ueef3\ueef4\ueef5\ueef6\ueef7\ueef8\ueef9\ueefa\ueefb\ueefc\ueefd\ueefe\ueeff\uef00\uef01\uef02\uef03\uef04\uef05\uef06\uef07\uef08\uef09\uef0a\uef0b\uef0c\uef0d\uef0e\uef0f\uef10\uef11\uef12\uef13\uef14\uef15\uef16\uef17\uef18\uef19\uef1a\uef1b\uef1c\uef1d\uef1e\uef1f\uef20\uef21\uef22\uef23\uef24\uef25\uef26\uef27\uef28\uef29\uef2a\uef2b\uef2c\uef2d\uef2e\uef2f\uef30\uef31\uef32\uef33\uef34\uef35\uef36\uef37\uef38\uef39\uef3a\uef3b\uef3c\uef3d\uef3e\uef3f\uef40\uef41\uef42\uef43\uef44\uef45\uef46\uef47\uef48\uef49\uef4a\uef4b\uef4c\uef4d\uef4e\uef4f\uef50\uef51\uef52\uef53\uef54\uef55\uef56\uef57\uef58\uef59\uef5a\uef5b\uef5c\uef5d\uef5e\uef5f\uef60\uef61\uef62\uef63\uef64\uef65\uef66\uef67\uef68\uef69\uef6a\uef6b\uef6c\uef6d\uef6e\uef6f\uef70\uef71\uef72\uef73\uef74\uef75\uef76\uef77\uef78\uef79\uef7a\uef7b\uef7c\uef7d\uef7e\uef7f\uef80\uef81\uef82\uef83\uef84\uef85\uef86\uef87\uef88\uef89\uef8a\uef8b\uef8c\uef8d\uef8e\uef8f\uef90\uef91\uef92\uef93\uef94\uef95\uef96\uef97\uef98\uef99\uef9a\uef9b\uef9c\uef9d\uef9e\uef9f\uefa0\uefa1\uefa2\uefa3\uefa4\uefa5\uefa6\uefa7\uefa8\uefa9\uefaa\uefab\uefac\uefad\uefae\uefaf\uefb0\uefb1\uefb2\uefb3\uefb4\uefb5\uefb6\uefb7\uefb8\uefb9\uefba\uefbb\uefbc\uefbd\uefbe\uefbf\uefc0\uefc1\uefc2\uefc3\uefc4\uefc5\uefc6\uefc7\uefc8\uefc9\uefca\uefcb\uefcc\uefcd\uefce\uefcf\uefd0\uefd1\uefd2\uefd3\uefd4\uefd5\uefd6\uefd7\uefd8\uefd9\uefda\uefdb\uefdc\uefdd\uefde\uefdf\uefe0\uefe1\uefe2\uefe3\uefe4\uefe5\uefe6\uefe7\uefe8\uefe9\uefea\uefeb\uefec\uefed\uefee\uefef\ueff0\ueff1\ueff2\ueff3\ueff4\ueff5\ueff6\ueff7\ueff8\ueff9\ueffa\ueffb\ueffc\ueffd\ueffe\uefff\uf000\uf001\uf002\uf003\uf004\uf005\uf006\uf007\uf008\uf009\uf00a\uf00b\uf00c\uf00d\uf00e\uf00f\uf010\uf011\uf012\uf013\uf014\uf015\uf016\uf017\uf018\uf019\uf01a\uf01b\uf01c\uf01d\uf01e\uf01f\uf020\uf021\uf022\uf023\uf024\uf025\uf026\uf027\uf028\uf029\uf02a\uf02b\uf02c\uf02d\uf02e\uf02f\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf03a\uf03b\uf03c\uf03d\uf03e\uf03f\uf040\uf041\uf042\uf043\uf044\uf045\uf046\uf047\uf048\uf049\uf04a\uf04b\uf04c\uf04d\uf04e\uf04f\uf050\uf051\uf052\uf053\uf054\uf055\uf056\uf057\uf058\uf059\uf05a\uf05b\uf05c\uf05d\uf05e\uf05f\uf060\uf061\uf062\uf063\uf064\uf065\uf066\uf067\uf068\uf069\uf06a\uf06b\uf06c\uf06d\uf06e\uf06f\uf070\uf071\uf072\uf073\uf074\uf075\uf076\uf077\uf078\uf079\uf07a\uf07b\uf07c\uf07d\uf07e\uf07f\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf09e\uf09f\uf0a0\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a6\uf0a7\uf0a8\uf0a9\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0c7\uf0c8\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1\uf0d2\uf0d3\uf0d4\uf0d5\uf0d6\uf0d7\uf0d8\uf0d9\uf0da\uf0db\uf0dc\uf0dd\uf0de\uf0df\uf0e0\uf0e1\uf0e2\uf0e3\uf0e4\uf0e5\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0ec\uf0ed\uf0ee\uf0ef\uf0f0\uf0f1\uf0f2\uf0f3\uf0f4\uf0f5\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb\uf0fc\uf0fd\uf0fe\uf0ff\uf100\uf101\uf102\uf103\uf104\uf105\uf106\uf107\uf108\uf109\uf10a\uf10b\uf10c\uf10d\uf10e\uf10f\uf110\uf111\uf112\uf113\uf114\uf115\uf116\uf117\uf118\uf119\uf11a\uf11b\uf11c\uf11d\uf11e\uf11f\uf120\uf121\uf122\uf123\uf124\uf125\uf126\uf127\uf128\uf129\uf12a\uf12b\uf12c\uf12d\uf12e\uf12f\uf130\uf131\uf132\uf133\uf134\uf135\uf136\uf137\uf138\uf139\uf13a\uf13b\uf13c\uf13d\uf13e\uf13f\uf140\uf141\uf142\uf143\uf144\uf145\uf146\uf147\uf148\uf149\uf14a\uf14b\uf14c\uf14d\uf14e\uf14f\uf150\uf151\uf152\uf153\uf154\uf155\uf156\uf157\uf158\uf159\uf15a\uf15b\uf15c\uf15d\uf15e\uf15f\uf160\uf161\uf162\uf163\uf164\uf165\uf166\uf167\uf168\uf169\uf16a\uf16b\uf16c\uf16d\uf16e\uf16f\uf170\uf171\uf172\uf173\uf174\uf175\uf176\uf177\uf178\uf179\uf17a\uf17b\uf17c\uf17d\uf17e\uf17f\uf180\uf181\uf182\uf183\uf184\uf185\uf186\uf187\uf188\uf189\uf18a\uf18b\uf18c\uf18d\uf18e\uf18f\uf190\uf191\uf192\uf193\uf194\uf195\uf196\uf197\uf198\uf199\uf19a\uf19b\uf19c\uf19d\uf19e\uf19f\uf1a0\uf1a1\uf1a2\uf1a3\uf1a4\uf1a5\uf1a6\uf1a7\uf1a8\uf1a9\uf1aa\uf1ab\uf1ac\uf1ad\uf1ae\uf1af\uf1b0\uf1b1\uf1b2\uf1b3\uf1b4\uf1b5\uf1b6\uf1b7\uf1b8\uf1b9\uf1ba\uf1bb\uf1bc\uf1bd\uf1be\uf1bf\uf1c0\uf1c1\uf1c2\uf1c3\uf1c4\uf1c5\uf1c6\uf1c7\uf1c8\uf1c9\uf1ca\uf1cb\uf1cc\uf1cd\uf1ce\uf1cf\uf1d0\uf1d1\uf1d2\uf1d3\uf1d4\uf1d5\uf1d6\uf1d7\uf1d8\uf1d9\uf1da\uf1db\uf1dc\uf1dd\uf1de\uf1df\uf1e0\uf1e1\uf1e2\uf1e3\uf1e4\uf1e5\uf1e6\uf1e7\uf1e8\uf1e9\uf1ea\uf1eb\uf1ec\uf1ed\uf1ee\uf1ef\uf1f0\uf1f1\uf1f2\uf1f3\uf1f4\uf1f5\uf1f6\uf1f7\uf1f8\uf1f9\uf1fa\uf1fb\uf1fc\uf1fd\uf1fe\uf1ff\uf200\uf201\uf202\uf203\uf204\uf205\uf206\uf207\uf208\uf209\uf20a\uf20b\uf20c\uf20d\uf20e\uf20f\uf210\uf211\uf212\uf213\uf214\uf215\uf216\uf217\uf218\uf219\uf21a\uf21b\uf21c\uf21d\uf21e\uf21f\uf220\uf221\uf222\uf223\uf224\uf225\uf226\uf227\uf228\uf229\uf22a\uf22b\uf22c\uf22d\uf22e\uf22f\uf230\uf231\uf232\uf233\uf234\uf235\uf236\uf237\uf238\uf239\uf23a\uf23b\uf23c\uf23d\uf23e\uf23f\uf240\uf241\uf242\uf243\uf244\uf245\uf246\uf247\uf248\uf249\uf24a\uf24b\uf24c\uf24d\uf24e\uf24f\uf250\uf251\uf252\uf253\uf254\uf255\uf256\uf257\uf258\uf259\uf25a\uf25b\uf25c\uf25d\uf25e\uf25f\uf260\uf261\uf262\uf263\uf264\uf265\uf266\uf267\uf268\uf269\uf26a\uf26b\uf26c\uf26d\uf26e\uf26f\uf270\uf271\uf272\uf273\uf274\uf275\uf276\uf277\uf278\uf279\uf27a\uf27b\uf27c\uf27d\uf27e\uf27f\uf280\uf281\uf282\uf283\uf284\uf285\uf286\uf287\uf288\uf289\uf28a\uf28b\uf28c\uf28d\uf28e\uf28f\uf290\uf291\uf292\uf293\uf294\uf295\uf296\uf297\uf298\uf299\uf29a\uf29b\uf29c\uf29d\uf29e\uf29f\uf2a0\uf2a1\uf2a2\uf2a3\uf2a4\uf2a5\uf2a6\uf2a7\uf2a8\uf2a9\uf2aa\uf2ab\uf2ac\uf2ad\uf2ae\uf2af\uf2b0\uf2b1\uf2b2\uf2b3\uf2b4\uf2b5\uf2b6\uf2b7\uf2b8\uf2b9\uf2ba\uf2bb\uf2bc\uf2bd\uf2be\uf2bf\uf2c0\uf2c1\uf2c2\uf2c3\uf2c4\uf2c5\uf2c6\uf2c7\uf2c8\uf2c9\uf2ca\uf2cb\uf2cc\uf2cd\uf2ce\uf2cf\uf2d0\uf2d1\uf2d2\uf2d3\uf2d4\uf2d5\uf2d6\uf2d7\uf2d8\uf2d9\uf2da\uf2db\uf2dc\uf2dd\uf2de\uf2df\uf2e0\uf2e1\uf2e2\uf2e3\uf2e4\uf2e5\uf2e6\uf2e7\uf2e8\uf2e9\uf2ea\uf2eb\uf2ec\uf2ed\uf2ee\uf2ef\uf2f0\uf2f1\uf2f2\uf2f3\uf2f4\uf2f5\uf2f6\uf2f7\uf2f8\uf2f9\uf2fa\uf2fb\uf2fc\uf2fd\uf2fe\uf2ff\uf300\uf301\uf302\uf303\uf304\uf305\uf306\uf307\uf308\uf309\uf30a\uf30b\uf30c\uf30d\uf30e\uf30f\uf310\uf311\uf312\uf313\uf314\uf315\uf316\uf317\uf318\uf319\uf31a\uf31b\uf31c\uf31d\uf31e\uf31f\uf320\uf321\uf322\uf323\uf324\uf325\uf326\uf327\uf328\uf329\uf32a\uf32b\uf32c\uf32d\uf32e\uf32f\uf330\uf331\uf332\uf333\uf334\uf335\uf336\uf337\uf338\uf339\uf33a\uf33b\uf33c\uf33d\uf33e\uf33f\uf340\uf341\uf342\uf343\uf344\uf345\uf346\uf347\uf348\uf349\uf34a\uf34b\uf34c\uf34d\uf34e\uf34f\uf350\uf351\uf352\uf353\uf354\uf355\uf356\uf357\uf358\uf359\uf35a\uf35b\uf35c\uf35d\uf35e\uf35f\uf360\uf361\uf362\uf363\uf364\uf365\uf366\uf367\uf368\uf369\uf36a\uf36b\uf36c\uf36d\uf36e\uf36f\uf370\uf371\uf372\uf373\uf374\uf375\uf376\uf377\uf378\uf379\uf37a\uf37b\uf37c\uf37d\uf37e\uf37f\uf380\uf381\uf382\uf383\uf384\uf385\uf386\uf387\uf388\uf389\uf38a\uf38b\uf38c\uf38d\uf38e\uf38f\uf390\uf391\uf392\uf393\uf394\uf395\uf396\uf397\uf398\uf399\uf39a\uf39b\uf39c\uf39d\uf39e\uf39f\uf3a0\uf3a1\uf3a2\uf3a3\uf3a4\uf3a5\uf3a6\uf3a7\uf3a8\uf3a9\uf3aa\uf3ab\uf3ac\uf3ad\uf3ae\uf3af\uf3b0\uf3b1\uf3b2\uf3b3\uf3b4\uf3b5\uf3b6\uf3b7\uf3b8\uf3b9\uf3ba\uf3bb\uf3bc\uf3bd\uf3be\uf3bf\uf3c0\uf3c1\uf3c2\uf3c3\uf3c4\uf3c5\uf3c6\uf3c7\uf3c8\uf3c9\uf3ca\uf3cb\uf3cc\uf3cd\uf3ce\uf3cf\uf3d0\uf3d1\uf3d2\uf3d3\uf3d4\uf3d5\uf3d6\uf3d7\uf3d8\uf3d9\uf3da\uf3db\uf3dc\uf3dd\uf3de\uf3df\uf3e0\uf3e1\uf3e2\uf3e3\uf3e4\uf3e5\uf3e6\uf3e7\uf3e8\uf3e9\uf3ea\uf3eb\uf3ec\uf3ed\uf3ee\uf3ef\uf3f0\uf3f1\uf3f2\uf3f3\uf3f4\uf3f5\uf3f6\uf3f7\uf3f8\uf3f9\uf3fa\uf3fb\uf3fc\uf3fd\uf3fe\uf3ff\uf400\uf401\uf402\uf403\uf404\uf405\uf406\uf407\uf408\uf409\uf40a\uf40b\uf40c\uf40d\uf40e\uf40f\uf410\uf411\uf412\uf413\uf414\uf415\uf416\uf417\uf418\uf419\uf41a\uf41b\uf41c\uf41d\uf41e\uf41f\uf420\uf421\uf422\uf423\uf424\uf425\uf426\uf427\uf428\uf429\uf42a\uf42b\uf42c\uf42d\uf42e\uf42f\uf430\uf431\uf432\uf433\uf434\uf435\uf436\uf437\uf438\uf439\uf43a\uf43b\uf43c\uf43d\uf43e\uf43f\uf440\uf441\uf442\uf443\uf444\uf445\uf446\uf447\uf448\uf449\uf44a\uf44b\uf44c\uf44d\uf44e\uf44f\uf450\uf451\uf452\uf453\uf454\uf455\uf456\uf457\uf458\uf459\uf45a\uf45b\uf45c\uf45d\uf45e\uf45f\uf460\uf461\uf462\uf463\uf464\uf465\uf466\uf467\uf468\uf469\uf46a\uf46b\uf46c\uf46d\uf46e\uf46f\uf470\uf471\uf472\uf473\uf474\uf475\uf476\uf477\uf478\uf479\uf47a\uf47b\uf47c\uf47d\uf47e\uf47f\uf480\uf481\uf482\uf483\uf484\uf485\uf486\uf487\uf488\uf489\uf48a\uf48b\uf48c\uf48d\uf48e\uf48f\uf490\uf491\uf492\uf493\uf494\uf495\uf496\uf497\uf498\uf499\uf49a\uf49b\uf49c\uf49d\uf49e\uf49f\uf4a0\uf4a1\uf4a2\uf4a3\uf4a4\uf4a5\uf4a6\uf4a7\uf4a8\uf4a9\uf4aa\uf4ab\uf4ac\uf4ad\uf4ae\uf4af\uf4b0\uf4b1\uf4b2\uf4b3\uf4b4\uf4b5\uf4b6\uf4b7\uf4b8\uf4b9\uf4ba\uf4bb\uf4bc\uf4bd\uf4be\uf4bf\uf4c0\uf4c1\uf4c2\uf4c3\uf4c4\uf4c5\uf4c6\uf4c7\uf4c8\uf4c9\uf4ca\uf4cb\uf4cc\uf4cd\uf4ce\uf4cf\uf4d0\uf4d1\uf4d2\uf4d3\uf4d4\uf4d5\uf4d6\uf4d7\uf4d8\uf4d9\uf4da\uf4db\uf4dc\uf4dd\uf4de\uf4df\uf4e0\uf4e1\uf4e2\uf4e3\uf4e4\uf4e5\uf4e6\uf4e7\uf4e8\uf4e9\uf4ea\uf4eb\uf4ec\uf4ed\uf4ee\uf4ef\uf4f0\uf4f1\uf4f2\uf4f3\uf4f4\uf4f5\uf4f6\uf4f7\uf4f8\uf4f9\uf4fa\uf4fb\uf4fc\uf4fd\uf4fe\uf4ff\uf500\uf501\uf502\uf503\uf504\uf505\uf506\uf507\uf508\uf509\uf50a\uf50b\uf50c\uf50d\uf50e\uf50f\uf510\uf511\uf512\uf513\uf514\uf515\uf516\uf517\uf518\uf519\uf51a\uf51b\uf51c\uf51d\uf51e\uf51f\uf520\uf521\uf522\uf523\uf524\uf525\uf526\uf527\uf528\uf529\uf52a\uf52b\uf52c\uf52d\uf52e\uf52f\uf530\uf531\uf532\uf533\uf534\uf535\uf536\uf537\uf538\uf539\uf53a\uf53b\uf53c\uf53d\uf53e\uf53f\uf540\uf541\uf542\uf543\uf544\uf545\uf546\uf547\uf548\uf549\uf54a\uf54b\uf54c\uf54d\uf54e\uf54f\uf550\uf551\uf552\uf553\uf554\uf555\uf556\uf557\uf558\uf559\uf55a\uf55b\uf55c\uf55d\uf55e\uf55f\uf560\uf561\uf562\uf563\uf564\uf565\uf566\uf567\uf568\uf569\uf56a\uf56b\uf56c\uf56d\uf56e\uf56f\uf570\uf571\uf572\uf573\uf574\uf575\uf576\uf577\uf578\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf582\uf583\uf584\uf585\uf586\uf587\uf588\uf589\uf58a\uf58b\uf58c\uf58d\uf58e\uf58f\uf590\uf591\uf592\uf593\uf594\uf595\uf596\uf597\uf598\uf599\uf59a\uf59b\uf59c\uf59d\uf59e\uf59f\uf5a0\uf5a1\uf5a2\uf5a3\uf5a4\uf5a5\uf5a6\uf5a7\uf5a8\uf5a9\uf5aa\uf5ab\uf5ac\uf5ad\uf5ae\uf5af\uf5b0\uf5b1\uf5b2\uf5b3\uf5b4\uf5b5\uf5b6\uf5b7\uf5b8\uf5b9\uf5ba\uf5bb\uf5bc\uf5bd\uf5be\uf5bf\uf5c0\uf5c1\uf5c2\uf5c3\uf5c4\uf5c5\uf5c6\uf5c7\uf5c8\uf5c9\uf5ca\uf5cb\uf5cc\uf5cd\uf5ce\uf5cf\uf5d0\uf5d1\uf5d2\uf5d3\uf5d4\uf5d5\uf5d6\uf5d7\uf5d8\uf5d9\uf5da\uf5db\uf5dc\uf5dd\uf5de\uf5df\uf5e0\uf5e1\uf5e2\uf5e3\uf5e4\uf5e5\uf5e6\uf5e7\uf5e8\uf5e9\uf5ea\uf5eb\uf5ec\uf5ed\uf5ee\uf5ef\uf5f0\uf5f1\uf5f2\uf5f3\uf5f4\uf5f5\uf5f6\uf5f7\uf5f8\uf5f9\uf5fa\uf5fb\uf5fc\uf5fd\uf5fe\uf5ff\uf600\uf601\uf602\uf603\uf604\uf605\uf606\uf607\uf608\uf609\uf60a\uf60b\uf60c\uf60d\uf60e\uf60f\uf610\uf611\uf612\uf613\uf614\uf615\uf616\uf617\uf618\uf619\uf61a\uf61b\uf61c\uf61d\uf61e\uf61f\uf620\uf621\uf622\uf623\uf624\uf625\uf626\uf627\uf628\uf629\uf62a\uf62b\uf62c\uf62d\uf62e\uf62f\uf630\uf631\uf632\uf633\uf634\uf635\uf636\uf637\uf638\uf639\uf63a\uf63b\uf63c\uf63d\uf63e\uf63f\uf640\uf641\uf642\uf643\uf644\uf645\uf646\uf647\uf648\uf649\uf64a\uf64b\uf64c\uf64d\uf64e\uf64f\uf650\uf651\uf652\uf653\uf654\uf655\uf656\uf657\uf658\uf659\uf65a\uf65b\uf65c\uf65d\uf65e\uf65f\uf660\uf661\uf662\uf663\uf664\uf665\uf666\uf667\uf668\uf669\uf66a\uf66b\uf66c\uf66d\uf66e\uf66f\uf670\uf671\uf672\uf673\uf674\uf675\uf676\uf677\uf678\uf679\uf67a\uf67b\uf67c\uf67d\uf67e\uf67f\uf680\uf681\uf682\uf683\uf684\uf685\uf686\uf687\uf688\uf689\uf68a\uf68b\uf68c\uf68d\uf68e\uf68f\uf690\uf691\uf692\uf693\uf694\uf695\uf696\uf697\uf698\uf699\uf69a\uf69b\uf69c\uf69d\uf69e\uf69f\uf6a0\uf6a1\uf6a2\uf6a3\uf6a4\uf6a5\uf6a6\uf6a7\uf6a8\uf6a9\uf6aa\uf6ab\uf6ac\uf6ad\uf6ae\uf6af\uf6b0\uf6b1\uf6b2\uf6b3\uf6b4\uf6b5\uf6b6\uf6b7\uf6b8\uf6b9\uf6ba\uf6bb\uf6bc\uf6bd\uf6be\uf6bf\uf6c0\uf6c1\uf6c2\uf6c3\uf6c4\uf6c5\uf6c6\uf6c7\uf6c8\uf6c9\uf6ca\uf6cb\uf6cc\uf6cd\uf6ce\uf6cf\uf6d0\uf6d1\uf6d2\uf6d3\uf6d4\uf6d5\uf6d6\uf6d7\uf6d8\uf6d9\uf6da\uf6db\uf6dc\uf6dd\uf6de\uf6df\uf6e0\uf6e1\uf6e2\uf6e3\uf6e4\uf6e5\uf6e6\uf6e7\uf6e8\uf6e9\uf6ea\uf6eb\uf6ec\uf6ed\uf6ee\uf6ef\uf6f0\uf6f1\uf6f2\uf6f3\uf6f4\uf6f5\uf6f6\uf6f7\uf6f8\uf6f9\uf6fa\uf6fb\uf6fc\uf6fd\uf6fe\uf6ff\uf700\uf701\uf702\uf703\uf704\uf705\uf706\uf707\uf708\uf709\uf70a\uf70b\uf70c\uf70d\uf70e\uf70f\uf710\uf711\uf712\uf713\uf714\uf715\uf716\uf717\uf718\uf719\uf71a\uf71b\uf71c\uf71d\uf71e\uf71f\uf720\uf721\uf722\uf723\uf724\uf725\uf726\uf727\uf728\uf729\uf72a\uf72b\uf72c\uf72d\uf72e\uf72f\uf730\uf731\uf732\uf733\uf734\uf735\uf736\uf737\uf738\uf739\uf73a\uf73b\uf73c\uf73d\uf73e\uf73f\uf740\uf741\uf742\uf743\uf744\uf745\uf746\uf747\uf748\uf749\uf74a\uf74b\uf74c\uf74d\uf74e\uf74f\uf750\uf751\uf752\uf753\uf754\uf755\uf756\uf757\uf758\uf759\uf75a\uf75b\uf75c\uf75d\uf75e\uf75f\uf760\uf761\uf762\uf763\uf764\uf765\uf766\uf767\uf768\uf769\uf76a\uf76b\uf76c\uf76d\uf76e\uf76f\uf770\uf771\uf772\uf773\uf774\uf775\uf776\uf777\uf778\uf779\uf77a\uf77b\uf77c\uf77d\uf77e\uf77f\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff\uf800\uf801\uf802\uf803\uf804\uf805\uf806\uf807\uf808\uf809\uf80a\uf80b\uf80c\uf80d\uf80e\uf80f\uf810\uf811\uf812\uf813\uf814\uf815\uf816\uf817\uf818\uf819\uf81a\uf81b\uf81c\uf81d\uf81e\uf81f\uf820\uf821\uf822\uf823\uf824\uf825\uf826\uf827\uf828\uf829\uf82a\uf82b\uf82c\uf82d\uf82e\uf82f\uf830\uf831\uf832\uf833\uf834\uf835\uf836\uf837\uf838\uf839\uf83a\uf83b\uf83c\uf83d\uf83e\uf83f\uf840\uf841\uf842\uf843\uf844\uf845\uf846\uf847\uf848\uf849\uf84a\uf84b\uf84c\uf84d\uf84e\uf84f\uf850\uf851\uf852\uf853\uf854\uf855\uf856\uf857\uf858\uf859\uf85a\uf85b\uf85c\uf85d\uf85e\uf85f\uf860\uf861\uf862\uf863\uf864\uf865\uf866\uf867\uf868\uf869\uf86a\uf86b\uf86c\uf86d\uf86e\uf86f\uf870\uf871\uf872\uf873\uf874\uf875\uf876\uf877\uf878\uf879\uf87a\uf87b\uf87c\uf87d\uf87e\uf87f\uf880\uf881\uf882\uf883\uf884\uf885\uf886\uf887\uf888\uf889\uf88a\uf88b\uf88c\uf88d\uf88e\uf88f\uf890\uf891\uf892\uf893\uf894\uf895\uf896\uf897\uf898\uf899\uf89a\uf89b\uf89c\uf89d\uf89e\uf89f\uf8a0\uf8a1\uf8a2\uf8a3\uf8a4\uf8a5\uf8a6\uf8a7\uf8a8\uf8a9\uf8aa\uf8ab\uf8ac\uf8ad\uf8ae\uf8af\uf8b0\uf8b1\uf8b2\uf8b3\uf8b4\uf8b5\uf8b6\uf8b7\uf8b8\uf8b9\uf8ba\uf8bb\uf8bc\uf8bd\uf8be\uf8bf\uf8c0\uf8c1\uf8c2\uf8c3\uf8c4\uf8c5\uf8c6\uf8c7\uf8c8\uf8c9\uf8ca\uf8cb\uf8cc\uf8cd\uf8ce\uf8cf\uf8d0\uf8d1\uf8d2\uf8d3\uf8d4\uf8d5\uf8d6\uf8d7\uf8d8\uf8d9\uf8da\uf8db\uf8dc\uf8dd\uf8de\uf8df\uf8e0\uf8e1\uf8e2\uf8e3\uf8e4\uf8e5\uf8e6\uf8e7\uf8e8\uf8e9\uf8ea\uf8eb\uf8ec\uf8ed\uf8ee\uf8ef\uf8f0\uf8f1\uf8f2\uf8f3\uf8f4\uf8f5\uf8f6\uf8f7\uf8f8\uf8f9\uf8fa\uf8fb\uf8fc\uf8fd\uf8fe\uf8ff'
-
-try:
- Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\udb3e\udb3f\udb40\udb41\udb42\udb43\udb44\udb45\udb46\udb47\udb48\udb49\udb4a\udb4b\udb4c\udb4d\udb4e\udb4f\udb50\udb51\udb52\udb53\udb54\udb55\udb56\udb57\udb58\udb59\udb5a\udb5b\udb5c\udb5d\udb5e\udb5f\udb60\udb61\udb62\udb63\udb64\udb65\udb66\udb67\udb68\udb69\udb6a\udb6b\udb6c\udb6d\udb6e\udb6f\udb70\udb71\udb72\udb73\udb74\udb75\udb76\udb77\udb78\udb79\udb7a\udb7b\udb7c\udb7d\udb7e\udb7f\udb80\udb81\udb82\udb83\udb84\udb85\udb86\udb87\udb88\udb89\udb8a\udb8b\udb8c\udb8d\udb8e\udb8f\udb90\udb91\udb92\udb93\udb94\udb95\udb96\udb97\udb98\udb99\udb9a\udb9b\udb9c\udb9d\udb9e\udb9f\udba0\udba1\udba2\udba3\udba4\udba5\udba6\udba7\udba8\udba9\udbaa\udbab\udbac\udbad\udbae\udbaf\udbb0\udbb1\udbb2\udbb3\udbb4\udbb5\udbb6\udbb7\udbb8\udbb9\udbba\udbbb\udbbc\udbbd\udbbe\udbbf\udbc0\udbc1\udbc2\udbc3\udbc4\udbc5\udbc6\udbc7\udbc8\udbc9\udbca\udbcb\udbcc\udbcd\udbce\udbcf\udbd0\udbd1\udbd2\udbd3\udbd4\udbd5\udbd6\udbd7\udbd8\udbd9\udbda\udbdb\udbdc\udbdd\udbde\udbdf\udbe0\udbe1\udbe2\udbe3\udbe4\udbe5\udbe6\udbe7\udbe8\udbe9\udbea\udbeb\udbec\udbed\udbee\udbef\udbf0\udbf1\udbf2\udbf3\udbf4\udbf5\udbf6\udbf7\udbf8\udbf9\udbfa\udbfb\udbfc\udbfd\udbfe\U0010fc00\udc01\udc02\udc03\udc04\udc05\udc06\udc07\udc08\udc09\udc0a\udc0b\udc0c\udc0d\udc0e\udc0f\udc10\udc11\udc12\udc13\udc14\udc15\udc16\udc17\udc18\udc19\udc1a\udc1b\udc1c\udc1d\udc1e\udc1f\udc20\udc21\udc22\udc23\udc24\udc25\udc26\udc27\udc28\udc29\udc2a\udc2b\udc2c\udc2d\udc2e\udc2f\udc30\udc31\udc32\udc33\udc34\udc35\udc36\udc37\udc38\udc39\udc3a\udc3b\udc3c\udc3d\udc3e\udc3f\udc40\udc41\udc42\udc43\udc44\udc45\udc46\udc47\udc48\udc49\udc4a\udc4b\udc4c\udc4d\udc4e\udc4f\udc50\udc51\udc52\udc53\udc54\udc55\udc56\udc57\udc58\udc59\udc5a\udc5b\udc5c\udc5d\udc5e\udc5f\udc60\udc61\udc62\udc63\udc64\udc65\udc66\udc67\udc68\udc69\udc6a\udc6b\udc6c\udc6d\udc6e\udc6f\udc70\udc71\udc72\udc73\udc74\udc75\udc76\udc77\udc78\udc79\udc7a\udc7b\udc7c\udc7d\udc7e\udc7f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff\udd00\udd01\udd02\udd03\udd04\udd05\udd06\udd07\udd08\udd09\udd0a\udd0b\udd0c\udd0d\udd0e\udd0f\udd10\udd11\udd12\udd13\udd14\udd15\udd16\udd17\udd18\udd19\udd1a\udd1b\udd1c\udd1d\udd1e\udd1f\udd20\udd21\udd22\udd23\udd24\udd25\udd26\udd27\udd28\udd29\udd2a\udd2b\udd2c\udd2d\udd2e\udd2f\udd30\udd31\udd32\udd33\udd34\udd35\udd36\udd37\udd38\udd39\udd3a\udd3b\udd3c\udd3d\udd3e\udd3f\udd40\udd41\udd42\udd43\udd44\udd45\udd46\udd47\udd48\udd49\udd4a\udd4b\udd4c\udd4d\udd4e\udd4f\udd50\udd51\udd52\udd53\udd54\udd55\udd56\udd57\udd58\udd59\udd5a\udd5b\udd5c\udd5d\udd5e\udd5f\udd60\udd61\udd62\udd63\udd64\udd65\udd66\udd67\udd68\udd69\udd6a\udd6b\udd6c\udd6d\udd6e\udd6f\udd70\udd71\udd72\udd73\udd74\udd75\udd76\udd77\udd78\udd79\udd7a\udd7b\udd7c\udd7d\udd7e\udd7f\udd80\udd81\udd82\udd83\udd84\udd85\udd86\udd87\udd88\udd89\udd8a\udd8b\udd8c\udd8d\udd8e\udd8f\udd90\udd91\udd92\udd93\udd94\udd95\udd96\udd97\udd98\udd99\udd9a\udd9b\udd9c\udd9d\udd9e\udd9f\udda0\udda1\udda2\udda3\udda4\udda5\udda6\udda7\udda8\udda9\uddaa\uddab\uddac\uddad\uddae\uddaf\uddb0\uddb1\uddb2\uddb3\uddb4\uddb5\uddb6\uddb7\uddb8\uddb9\uddba\uddbb\uddbc\uddbd\uddbe\uddbf\uddc0\uddc1\uddc2\uddc3\uddc4\uddc5\uddc6\uddc7\uddc8\uddc9\uddca\uddcb\uddcc\uddcd\uddce\uddcf\uddd0\uddd1\uddd2\uddd3\uddd4\uddd5\uddd6\uddd7\uddd8\uddd9\uddda\udddb\udddc\udddd\uddde\udddf\udde0\udde1\udde2\udde3\udde4\udde5\udde6\udde7\udde8\udde9\uddea\uddeb\uddec\udded\uddee\uddef\uddf0\uddf1\uddf2\uddf3\uddf4\uddf5\uddf6\uddf7\uddf8\uddf9\uddfa\uddfb\uddfc\uddfd\uddfe\uddff\ude00\ude01\ude02\ude03\ude04\ude05\ude06\ude07\ude08\ude09\ude0a\ude0b\ude0c\ude0d\ude0e\ude0f\ude10\ude11\ude12\ude13\ude14\ude15\ude16\ude17\ude18\ude19\ude1a\ude1b\ude1c\ude1d\ude1e\ude1f\ude20\ude21\ude22\ude23\ude24\ude25\ude26\ude27\ude28\ude29\ude2a\ude2b\ude2c\ude2d\ude2e\ude2f\ude30\ude31\ude32\ude33\ude34\ude35\ude36\ude37\ude38\ude39\ude3a\ude3b\ude3c\ude3d\ude3e\ude3f\ude40\ude41\ude42\ude43\ude44\ude45\ude46\ude47\ude48\ude49\ude4a\ude4b\ude4c\ude4d\ude4e\ude4f\ude50\ude51\ude52\ude53\ude54\ude55\ude56\ude57\ude58\ude59\ude5a\ude5b\ude5c\ude5d\ude5e\ude5f\ude60\ude61\ude62\ude63\ude64\ude65\ude66\ude67\ude68\ude69\ude6a\ude6b\ude6c\ude6d\ude6e\ude6f\ude70\ude71\ude72\ude73\ude74\ude75\ude76\ude77\ude78\ude79\ude7a\ude7b\ude7c\ude7d\ude7e\ude7f\ude80\ude81\ude82\ude83\ude84\ude85\ude86\ude87\ude88\ude89\ude8a\ude8b\ude8c\ude8d\ude8e\ude8f\ude90\ude91\ude92\ude93\ude94\ude95\ude96\ude97\ude98\ude99\ude9a\ude9b\ude9c\ude9d\ude9e\ude9f\udea0\udea1\udea2\udea3\udea4\udea5\udea6\udea7\udea8\udea9\udeaa\udeab\udeac\udead\udeae\udeaf\udeb0\udeb1\udeb2\udeb3\udeb4\udeb5\udeb6\udeb7\udeb8\udeb9\udeba\udebb\udebc\udebd\udebe\udebf\udec0\udec1\udec2\udec3\udec4\udec5\udec6\udec7\udec8\udec9\udeca\udecb\udecc\udecd\udece\udecf\uded0\uded1\uded2\uded3\uded4\uded5\uded6\uded7\uded8\uded9\udeda\udedb\udedc\udedd\udede\udedf\udee0\udee1\udee2\udee3\udee4\udee5\udee6\udee7\udee8\udee9\udeea\udeeb\udeec\udeed\udeee\udeef\udef0\udef1\udef2\udef3\udef4\udef5\udef6\udef7\udef8\udef9\udefa\udefb\udefc\udefd\udefe\udeff\udf00\udf01\udf02\udf03\udf04\udf05\udf06\udf07\udf08\udf09\udf0a\udf0b\udf0c\udf0d\udf0e\udf0f\udf10\udf11\udf12\udf13\udf14\udf15\udf16\udf17\udf18\udf19\udf1a\udf1b\udf1c\udf1d\udf1e\udf1f\udf20\udf21\udf22\udf23\udf24\udf25\udf26\udf27\udf28\udf29\udf2a\udf2b\udf2c\udf2d\udf2e\udf2f\udf30\udf31\udf32\udf33\udf34\udf35\udf36\udf37\udf38\udf39\udf3a\udf3b\udf3c\udf3d\udf3e\udf3f\udf40\udf41\udf42\udf43\udf44\udf45\udf46\udf47\udf48\udf49\udf4a\udf4b\udf4c\udf4d\udf4e\udf4f\udf50\udf51\udf52\udf53\udf54\udf55\udf56\udf57\udf58\udf59\udf5a\udf5b\udf5c\udf5d\udf5e\udf5f\udf60\udf61\udf62\udf63\udf64\udf65\udf66\udf67\udf68\udf69\udf6a\udf6b\udf6c\udf6d\udf6e\udf6f\udf70\udf71\udf72\udf73\udf74\udf75\udf76\udf77\udf78\udf79\udf7a\udf7b\udf7c\udf7d\udf7e\udf7f\udf80\udf81\udf82\udf83\udf84\udf85\udf86\udf87\udf88\udf89\udf8a\udf8b\udf8c\udf8d\udf8e\udf8f\udf90\udf91\udf92\udf93\udf94\udf95\udf96\udf97\udf98\udf99\udf9a\udf9b\udf9c\udf9d\udf9e\udf9f\udfa0\udfa1\udfa2\udfa3\udfa4\udfa5\udfa6\udfa7\udfa8\udfa9\udfaa\udfab\udfac\udfad\udfae\udfaf\udfb0\udfb1\udfb2\udfb3\udfb4\udfb5\udfb6\udfb7\udfb8\udfb9\udfba\udfbb\udfbc\udfbd\udfbe\udfbf\udfc0\udfc1\udfc2\udfc3\udfc4\udfc5\udfc6\udfc7\udfc8\udfc9\udfca\udfcb\udfcc\udfcd\udfce\udfcf\udfd0\udfd1\udfd2\udfd3\udfd4\udfd5\udfd6\udfd7\udfd8\udfd9\udfda\udfdb\udfdc\udfdd\udfde\udfdf\udfe0\udfe1\udfe2\udfe3\udfe4\udfe5\udfe6\udfe7\udfe8\udfe9\udfea\udfeb\udfec\udfed\udfee\udfef\udff0\udff1\udff2\udff3\udff4\udff5\udff6\udff7\udff8\udff9\udffa\udffb\udffc\udffd\udffe\udfff'")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates
-
-Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213c\u213d\u2146\u2147\u2148\u2149\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3\u2ce4\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a'
-
-Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f'
-
-Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e32\u0e33\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94\u0e95\u0e96\u0e97\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead\u0eae\u0eaf\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0edc\u0edd\u0f00\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f88\u0f89\u0f8a\u0f8b\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1023\u1024\u1025\u1026\u1027\u1029\u102a\u1050\u1051\u1052\u1053\u1054\u1055\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6\u10f7\u10f8\u10f9\u10fa\u1100\u1101\u1102\u1103\u1104\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110d\u110e\u110f\u1110\u1111\u1112\u1113\u1114\u1115\u1116\u1117\u1118\u1119\u111a\u111b\u111c\u111d\u111e\u111f\u1120\u1121\u1122\u1123\u1124\u1125\u1126\u1127\u1128\u1129\u112a\u112b\u112c\u112d\u112e\u112f\u1130\u1131\u1132\u1133\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u1148\u1149\u114a\u114b\u114c\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u1158\u1159\u115f\u1160\u1161\u1162\u1163\u1164\u1165\u1166\u1167\u1168\u1169\u116a\u116b\u116c\u116d\u116e\u116f\u1170\u1171\u1172\u1173\u1174\u1175\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u1185\u1186\u1187\u1188\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u119f\u11a0\u11a1\u11a2\u11a8\u11a9\u11aa\u11ab\u11ac\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u11c8\u11c9\u11ca\u11cb\u11cc\u11cd\u11ce\u11cf\u11d0\u11d1\u11d2\u11d3\u11d4\u11d5\u11d6\u11d7\u11d8\u11d9\u11da\u11db\u11dc\u11dd\u11de\u11df\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u11f2\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u1358\u1359\u135a\u1380\u1381\u1382\u1383\u1384\u1385\u1386\u1387\u1388\u1389\u138a\u138b\u138c\u138d\u138e\u138f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u13a6\u13a7\u13a8\u13a9\u13aa\u13ab\u13ac\u13ad\u13ae\u13af\u13b0\u13b1\u13b2\u13b3\u13b4\u13b5\u13b6\u13b7\u13b8\u13b9\u13ba\u13bb\u13bc\u13bd\u13be\u13bf\u13c0\u13c1\u13c2\u13c3\u13c4\u13c5\u13c6\u13c7\u13c8\u13c9\u13ca\u13cb\u13cc\u13cd\u13ce\u13cf\u13d0\u13d1\u13d2\u13d3\u13d4\u13d5\u13d6\u13d7\u13d8\u13d9\u13da\u13db\u13dc\u13dd\u13de\u13df\u13e0\u13e1\u13e2\u13e3\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u13ee\u13ef\u13f0\u13f1\u13f2\u13f3\u13f4\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157c\u157d\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u166f\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17dc\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182a\u182b\u182c\u182d\u182e\u182f\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183a\u183b\u183c\u183d\u183e\u183f\u1840\u1841\u1842\u1844\u1845\u1846\u1847\u1848\u1849\u184a\u184b\u184c\u184d\u184e\u184f\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185a\u185b\u185c\u185d\u185e\u185f\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186a\u186b\u186c\u186d\u186e\u186f\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188a\u188b\u188c\u188d\u188e\u188f\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189a\u189b\u189c\u189d\u189e\u189f\u18a0\u18a1\u18a2\u18a3\u18a4\u18a5\u18a6\u18a7\u18a8\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190a\u190b\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u2135\u2136\u2137\u2138\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u3006\u303c\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u309f\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30ff\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312a\u312b\u312c\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313b\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u3463\u3464\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u346e\u346f\u3470\u3471\u3472\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u3499\u349a\u349b\u349c\u349d\u349e\u349f\u34a0\u34a1\u34a2\u34a3\u34a4\u34a5\u34a6\u34a7\u34a8\u34a9\u34aa\u34ab\u34ac\u34ad\u34ae\u34af\u34b0\u34b1\u34b2\u34b3\u34b4\u34b5\u34b6\u34b7\u34b8\u34b9\u34ba\u34bb\u34bc\u34bd\u34be\u34bf\u34c0\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cb\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d3\u34d4\u34d5\u34d6\u34d7\u34d8\u34d9\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u34e7\u34e8\u34e9\u34ea\u34eb\u34ec\u34ed\u34ee\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u350c\u350d\u350e\u350f\u3510\u3511\u3512\u3513\u3514\u3515\u3516\u3517\u3518\u3519\u351a\u351b\u351c\u351d\u351e\u351f\u3520\u3521\u3522\u3523\u3524\u3525\u3526\u3527\u3528\u3529\u352a\u352b\u352c\u352d\u352e\u352f\u3530\u3531\u3532\u3533\u3534\u3535\u3536\u3537\u3538\u3539\u353a\u353b\u353c\u353d\u353e\u353f\u3540\u3541\u3542\u3543\u3544\u3545\u3546\u3547\u3548\u3549\u354a\u354b\u354c\u354d\u354e\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558\u3559\u355a\u355b\u355c\u355d\u355e\u355f\u3560\u3561\u3562\u3563\u3564\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3605\u3606\u3607\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u3677\u3678\u3679\u367a\u367b\u367c\u367d\u367e\u367f\u3680\u3681\u3682\u3683\u3684\u3685\u3686\u3687\u3688\u3689\u368a\u368b\u368c\u368d\u368e\u368f\u3690\u3691\u3692\u3693\u3694\u3695\u3696\u3697\u3698\u3699\u369a\u369b\u369c\u369d\u369e\u369f\u36a0\u36a1\u36a2\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e0\u36e1\u36e2\u36e3\u36e4\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u3723\u3724\u3725\u3726\u3727\u3728\u3729\u372a\u372b\u372c\u372d\u372e\u372f\u3730\u3731\u3732\u3733\u3734\u3735\u3736\u3737\u3738\u3739\u373a\u373b\u373c\u373d\u373e\u373f\u3740\u3741\u3742\u3743\u3744\u3745\u3746\u3747\u3748\u3749\u374a\u374b\u374c\u374d\u374e\u374f\u3750\u3751\u3752\u3753\u3754\u3755\u3756\u3757\u3758\u3759\u375a\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u3762\u3763\u3764\u3765\u3766\u3767\u3768\u3769\u376a\u376b\u376c\u376d\u376e\u376f\u3770\u3771\u3772\u3773\u3774\u3775\u3776\u3777\u3778\u3779\u377a\u377b\u377c\u377d\u377e\u377f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u3787\u3788\u3789\u378a\u378b\u378c\u378d\u378e\u378f\u3790\u3791\u3792\u3793\u3794\u3795\u3796\u3797\u3798\u3799\u379a\u379b\u379c\u379d\u379e\u379f\u37a0\u37a1\u37a2\u37a3\u37a4\u37a5\u37a6\u37a7\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b7\u37b8\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u37c4\u37c5\u37c6\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9a\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da3\u3da4\u3da5\u3da6\u3da7\u3da8\u3da9\u3daa\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e36\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f75\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3fef\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u408c\u408d\u408e\u408f\u4090\u4091\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u409e\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40df\u40e0\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u40f0\u40f1\u40f2\u40f3\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4100\u4101\u4102\u4103\u4104\u4105\u4106\u4107\u4108\u4109\u410a\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4122\u4123\u4124\u4125\u4126\u4127\u4128\u4129\u412a\u412b\u412c\u412d\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u417e\u417f\u4180\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u4188\u4189\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4191\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a3\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c2\u41c3\u41c4\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d3\u41d4\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4274\u4275\u4276\u4277\u4278\u4279\u427a\u427b\u427c\u427d\u427e\u427f\u4280\u4281\u4282\u4283\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4290\u4291\u4292\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u429a\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b4\u42b5\u42b6\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u4316\u4317\u4318\u4319\u431a\u431b\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u432e\u432f\u4330\u4331\u4332\u4333\u4334\u4335\u4336\u4337\u4338\u4339\u433a\u433b\u433c\u433d\u433e\u433f\u4340\u4341\u4342\u4343\u4344\u4345\u4346\u4347\u4348\u4349\u434a\u434b\u434c\u434d\u434e\u434f\u4350\u4351\u4352\u4353\u4354\u4355\u4356\u4357\u4358\u4359\u435a\u435b\u435c\u435d\u435e\u435f\u4360\u4361\u4362\u4363\u4364\u4365\u4366\u4367\u4368\u4369\u436a\u436b\u436c\u436d\u436e\u436f\u4370\u4371\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u437f\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4390\u4391\u4392\u4393\u4394\u4395\u4396\u4397\u4398\u4399\u439a\u439b\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43aa\u43ab\u43ac\u43ad\u43ae\u43af\u43b0\u43b1\u43b2\u43b3\u43b4\u43b5\u43b6\u43b7\u43b8\u43b9\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c0\u43c1\u43c2\u43c3\u43c4\u43c5\u43c6\u43c7\u43c8\u43c9\u43ca\u43cb\u43cc\u43cd\u43ce\u43cf\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u443e\u443f\u4440\u4441\u4442\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445b\u445c\u445d\u445e\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u45d6\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u460b\u460c\u460d\u460e\u460f\u4610\u4611\u4612\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u4651\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u466e\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u4721\u4722\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u472b\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u4754\u4755\u4756\u4757\u4758\u4759\u475a\u475b\u475c\u475d\u475e\u475f\u4760\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u4775\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u47b9\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u47c7\u47c8\u47c9\u47ca\u47cb\u47cc\u47cd\u47ce\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d6\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fc\u47fd\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u481e\u481f\u4820\u4821\u4822\u4823\u4824\u4825\u4826\u4827\u4828\u4829\u482a\u482b\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u4899\u489a\u489b\u489c\u489d\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u496f\u4970\u4971\u4972\u4973\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u497f\u4980\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a1b\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u4a30\u4a31\u4a32\u4a33\u4a34\u4a35\u4a36\u4a37\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4a3f\u4a40\u4a41\u4a42\u4a43\u4a44\u4a45\u4a46\u4a47\u4a48\u4a49\u4a4a\u4a4b\u4a4c\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u4a5f\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u4a85\u4a86\u4a87\u4a88\u4a89\u4a8a\u4a8b\u4a8c\u4a8d\u4a8e\u4a8f\u4a90\u4a91\u4a92\u4a93\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aab\u4aac\u4aad\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab4\u4ab5\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u4abf\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u4ae8\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af8\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4be0\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u4bf5\u4bf6\u4bf7\u4bf8\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d2a\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d41\u4d42\u4d43\u4d44\u4d45\u4d46\u4d47\u4d48\u4d49\u4d4a\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u4d51\u4d52\u4d53\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5b\u4d5c\u4d5d\u4d5e\u4d5f\u4d60\u4d61\u4d62\u4d63\u4d64\u4d65\u4d66\u4d67\u4d68\u4d69\u4d6a\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d75\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7b\u4d7c\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d86\u4d87\u4d88\u4d89\u4d8a\u4d8b\u4d8c\u4d8d\u4d8e\u4d8f\u4d90\u4d91\u4d92\u4d93\u4d94\u4d95\u4d96\u4d97\u4d98\u4d99\u4d9a\u4d9b\u4d9c\u4d9d\u4d9e\u4d9f\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5\u4da6\u4da7\u4da8\u4da9\u4daa\u4dab\u4dac\u4dad\u4dae\u4daf\u4db0\u4db1\u4db2\u4db3\u4db4\u4db5\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e21\u4e22\u4e23\u4e24\u4e25\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e39\u4e3a\u4e3b\u4e3c\u4e3d\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u4e45\u4e46\u4e47\u4e48\u4e49\u4e4a\u4e4b\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e62\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u4e79\u4e7a\u4e7b\u4e7c\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e83\u4e84\u4e85\u4e86\u4e87\u4e88\u4e89\u4e8a\u4e8b\u4e8c\u4e8d\u4e8e\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u4ea8\u4ea9\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4eb8\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512b\u512c\u512d\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513a\u513b\u513c\u513d\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u5159\u515a\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u5166\u5167\u5168\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u5177\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5180\u5181\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u5195\u5196\u5197\u5198\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u51a9\u51aa\u51ab\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d6\u51d7\u51d8\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u5268\u5269\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5272\u5273\u5274\u5275\u5276\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u5280\u5281\u5282\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u5290\u5291\u5292\u5293\u5294\u5295\u5296\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d6\u52d7\u52d8\u52d9\u52da\u52db\u52dc\u52dd\u52de\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u5311\u5312\u5313\u5314\u5315\u5316\u5317\u5318\u5319\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u5326\u5327\u5328\u5329\u532a\u532b\u532c\u532d\u532e\u532f\u5330\u5331\u5332\u5333\u5334\u5335\u5336\u5337\u5338\u5339\u533a\u533b\u533c\u533d\u533e\u533f\u5340\u5341\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u534b\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u5354\u5355\u5356\u5357\u5358\u5359\u535a\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u536b\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u5379\u537a\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u538e\u538f\u5390\u5391\u5392\u5393\u5394\u5395\u5396\u5397\u5398\u5399\u539a\u539b\u539c\u539d\u539e\u539f\u53a0\u53a1\u53a2\u53a3\u53a4\u53a5\u53a6\u53a7\u53a8\u53a9\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5651\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u56ba\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56cc\u56cd\u56ce\u56cf\u56d0\u56d1\u56d2\u56d3\u56d4\u56d5\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u56ff\u5700\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u5725\u5726\u5727\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5900\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a90\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b34\u5b35\u5b36\u5b37\u5b38\u5b39\u5b3a\u5b3b\u5b3c\u5b3d\u5b3e\u5b3f\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b4f\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u5b68\u5b69\u5b6a\u5b6b\u5b6c\u5b6d\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b74\u5b75\u5b76\u5b77\u5b78\u5b79\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b81\u5b82\u5b83\u5b84\u5b85\u5b86\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba7\u5ba8\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c2d\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u5c5e\u5c5f\u5c60\u5c61\u5c62\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d\u5c6e\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4a\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u61f6\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u6203\u6204\u6205\u6206\u6207\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u6214\u6215\u6216\u6217\u6218\u6219\u621a\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6226\u6227\u6228\u6229\u622a\u622b\u622c\u622d\u622e\u622f\u6230\u6231\u6232\u6233\u6234\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u623d\u623e\u623f\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u625f\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6278\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b2\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u6526\u6527\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u658b\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u66ee\u66ef\u66f0\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u66fd\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6715\u6716\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6994\u6995\u6996\u6997\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6aeb\u6aec\u6aed\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0c\u6b0d\u6b0e\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b23\u6b24\u6b25\u6b26\u6b27\u6b28\u6b29\u6b2a\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b5e\u6b5f\u6b60\u6b61\u6b62\u6b63\u6b64\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b6f\u6b70\u6b71\u6b72\u6b73\u6b74\u6b75\u6b76\u6b77\u6b78\u6b79\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u6bac\u6bad\u6bae\u6baf\u6bb0\u6bb1\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bba\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc5\u6bc6\u6bc7\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u6bd9\u6bda\u6bdb\u6bdc\u6bdd\u6bde\u6bdf\u6be0\u6be1\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u6beb\u6bec\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0d\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d16\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecb\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edc\u6edd\u6ede\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3d\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd2\u6fd3\u6fd4\u6fd5\u6fd6\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u7179\u717a\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a6\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u7245\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7260\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u727a\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7345\u7346\u7347\u7348\u7349\u734a\u734b\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u7375\u7376\u7377\u7378\u7379\u737a\u737b\u737c\u737d\u737e\u737f\u7380\u7381\u7382\u7383\u7384\u7385\u7386\u7387\u7388\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74e3\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u750f\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751b\u751c\u751d\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u7527\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7568\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7572\u7573\u7574\u7575\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u757f\u7580\u7581\u7582\u7583\u7584\u7585\u7586\u7587\u7588\u7589\u758a\u758b\u758c\u758d\u758e\u758f\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u7629\u762a\u762b\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u764d\u764e\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76bf\u76c0\u76c1\u76c2\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e0\u76e1\u76e2\u76e3\u76e4\u76e5\u76e6\u76e7\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7\u77d8\u77d9\u77da\u77db\u77dc\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u7878\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u79a9\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79b7\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a76\u7a77\u7a78\u7a79\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac5\u7ac6\u7ac7\u7ac8\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc0\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc0\u7cc1\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u7ce8\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd0\u7dd1\u7dd2\u7dd3\u7dd4\u7dd5\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4b\u7e4c\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e71\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e93\u7e94\u7e95\u7e96\u7e97\u7e98\u7e99\u7e9a\u7e9b\u7e9c\u7e9d\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u7f35\u7f36\u7f37\u7f38\u7f39\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f3f\u7f40\u7f41\u7f42\u7f43\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u7f50\u7f51\u7f52\u7f53\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f65\u7f66\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8061\u8062\u8063\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8073\u8074\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a4\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81dc\u81dd\u81de\u81df\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u81e6\u81e7\u81e8\u81e9\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u81f5\u81f6\u81f7\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u8211\u8212\u8213\u8214\u8215\u8216\u8217\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u821f\u8220\u8221\u8222\u8223\u8224\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u823d\u823e\u823f\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8279\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u842a\u842b\u842c\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u8613\u8614\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ab\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d6\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u8862\u8863\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u8963\u8964\u8965\u8966\u8967\u8968\u8969\u896a\u896b\u896c\u896d\u896e\u896f\u8970\u8971\u8972\u8973\u8974\u8975\u8976\u8977\u8978\u8979\u897a\u897b\u897c\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8990\u8991\u8992\u8993\u8994\u8995\u8996\u8997\u8998\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a0\u89a1\u89a2\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89be\u89bf\u89c0\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89e8\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u89ef\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8ae9\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c76\u8c77\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u8d19\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8db6\u8db7\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6e\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u8e80\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u8e9d\u8e9e\u8e9f\u8ea0\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8ea9\u8eaa\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u8eb1\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ebe\u8ebf\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8faf\u8fb0\u8fb1\u8fb2\u8fb3\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u908c\u908d\u908e\u908f\u9090\u9091\u9092\u9093\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9144\u9145\u9146\u9147\u9148\u9149\u914a\u914b\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf\u91c0\u91c1\u91c2\u91c3\u91c4\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93f8\u93f9\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u9467\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u9577\u9578\u9579\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u95de\u95df\u95e0\u95e1\u95e2\u95e3\u95e4\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u974b\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u9860\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u986d\u986e\u986f\u9870\u9871\u9872\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98aa\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u9a5d\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u9ab8\u9ab9\u9aba\u9abb\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac1\u9ac2\u9ac3\u9ac4\u9ac5\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add\u9ade\u9adf\u9ae0\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u9b03\u9b04\u9b05\u9b06\u9b07\u9b08\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b33\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u9b43\u9b44\u9b45\u9b46\u9b47\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b61\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfb\u9bfc\u9bfd\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c1c\u9c1d\u9c1e\u9c1f\u9c20\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d64\u9d65\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d90\u9d91\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcb\u9dcc\u9dcd\u9dce\u9dcf\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9dfc\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e16\u9e17\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u9e77\u9e78\u9e79\u9e7a\u9e7b\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e87\u9e88\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ecd\u9ece\u9ecf\u9ed0\u9ed1\u9ed2\u9ed3\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9edc\u9edd\u9ede\u9edf\u9ee0\u9ee1\u9ee2\u9ee3\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eed\u9eee\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef5\u9ef6\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f05\u9f06\u9f07\u9f08\u9f09\u9f0a\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f14\u9f15\u9f16\u9f17\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u9f22\u9f23\u9f24\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f33\u9f34\u9f35\u9f36\u9f37\u9f38\u9f39\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u9f56\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fab\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fb4\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua800\ua801\ua803\ua804\ua805\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\uac00\uac01\uac02\uac03\uac04\uac05\uac06\uac07\uac08\uac09\uac0a\uac0b\uac0c\uac0d\uac0e\uac0f\uac10\uac11\uac12\uac13\uac14\uac15\uac16\uac17\uac18\uac19\uac1a\uac1b\uac1c\uac1d\uac1e\uac1f\uac20\uac21\uac22\uac23\uac24\uac25\uac26\uac27\uac28\uac29\uac2a\uac2b\uac2c\uac2d\uac2e\uac2f\uac30\uac31\uac32\uac33\uac34\uac35\uac36\uac37\uac38\uac39\uac3a\uac3b\uac3c\uac3d\uac3e\uac3f\uac40\uac41\uac42\uac43\uac44\uac45\uac46\uac47\uac48\uac49\uac4a\uac4b\uac4c\uac4d\uac4e\uac4f\uac50\uac51\uac52\uac53\uac54\uac55\uac56\uac57\uac58\uac59\uac5a\uac5b\uac5c\uac5d\uac5e\uac5f\uac60\uac61\uac62\uac63\uac64\uac65\uac66\uac67\uac68\uac69\uac6a\uac6b\uac6c\uac6d\uac6e\uac6f\uac70\uac71\uac72\uac73\uac74\uac75\uac76\uac77\uac78\uac79\uac7a\uac7b\uac7c\uac7d\uac7e\uac7f\uac80\uac81\uac82\uac83\uac84\uac85\uac86\uac87\uac88\uac89\uac8a\uac8b\uac8c\uac8d\uac8e\uac8f\uac90\uac91\uac92\uac93\uac94\uac95\uac96\uac97\uac98\uac99\uac9a\uac9b\uac9c\uac9d\uac9e\uac9f\uaca0\uaca1\uaca2\uaca3\uaca4\uaca5\uaca6\uaca7\uaca8\uaca9\uacaa\uacab\uacac\uacad\uacae\uacaf\uacb0\uacb1\uacb2\uacb3\uacb4\uacb5\uacb6\uacb7\uacb8\uacb9\uacba\uacbb\uacbc\uacbd\uacbe\uacbf\uacc0\uacc1\uacc2\uacc3\uacc4\uacc5\uacc6\uacc7\uacc8\uacc9\uacca\uaccb\uaccc\uaccd\uacce\uaccf\uacd0\uacd1\uacd2\uacd3\uacd4\uacd5\uacd6\uacd7\uacd8\uacd9\uacda\uacdb\uacdc\uacdd\uacde\uacdf\uace0\uace1\uace2\uace3\uace4\uace5\uace6\uace7\uace8\uace9\uacea\uaceb\uacec\uaced\uacee\uacef\uacf0\uacf1\uacf2\uacf3\uacf4\uacf5\uacf6\uacf7\uacf8\uacf9\uacfa\uacfb\uacfc\uacfd\uacfe\uacff\uad00\uad01\uad02\uad03\uad04\uad05\uad06\uad07\uad08\uad09\uad0a\uad0b\uad0c\uad0d\uad0e\uad0f\uad10\uad11\uad12\uad13\uad14\uad15\uad16\uad17\uad18\uad19\uad1a\uad1b\uad1c\uad1d\uad1e\uad1f\uad20\uad21\uad22\uad23\uad24\uad25\uad26\uad27\uad28\uad29\uad2a\uad2b\uad2c\uad2d\uad2e\uad2f\uad30\uad31\uad32\uad33\uad34\uad35\uad36\uad37\uad38\uad39\uad3a\uad3b\uad3c\uad3d\uad3e\uad3f\uad40\uad41\uad42\uad43\uad44\uad45\uad46\uad47\uad48\uad49\uad4a\uad4b\uad4c\uad4d\uad4e\uad4f\uad50\uad51\uad52\uad53\uad54\uad55\uad56\uad57\uad58\uad59\uad5a\uad5b\uad5c\uad5d\uad5e\uad5f\uad60\uad61\uad62\uad63\uad64\uad65\uad66\uad67\uad68\uad69\uad6a\uad6b\uad6c\uad6d\uad6e\uad6f\uad70\uad71\uad72\uad73\uad74\uad75\uad76\uad77\uad78\uad79\uad7a\uad7b\uad7c\uad7d\uad7e\uad7f\uad80\uad81\uad82\uad83\uad84\uad85\uad86\uad87\uad88\uad89\uad8a\uad8b\uad8c\uad8d\uad8e\uad8f\uad90\uad91\uad92\uad93\uad94\uad95\uad96\uad97\uad98\uad99\uad9a\uad9b\uad9c\uad9d\uad9e\uad9f\uada0\uada1\uada2\uada3\uada4\uada5\uada6\uada7\uada8\uada9\uadaa\uadab\uadac\uadad\uadae\uadaf\uadb0\uadb1\uadb2\uadb3\uadb4\uadb5\uadb6\uadb7\uadb8\uadb9\uadba\uadbb\uadbc\uadbd\uadbe\uadbf\uadc0\uadc1\uadc2\uadc3\uadc4\uadc5\uadc6\uadc7\uadc8\uadc9\uadca\uadcb\uadcc\uadcd\uadce\uadcf\uadd0\uadd1\uadd2\uadd3\uadd4\uadd5\uadd6\uadd7\uadd8\uadd9\uadda\uaddb\uaddc\uaddd\uadde\uaddf\uade0\uade1\uade2\uade3\uade4\uade5\uade6\uade7\uade8\uade9\uadea\uadeb\uadec\uaded\uadee\uadef\uadf0\uadf1\uadf2\uadf3\uadf4\uadf5\uadf6\uadf7\uadf8\uadf9\uadfa\uadfb\uadfc\uadfd\uadfe\uadff\uae00\uae01\uae02\uae03\uae04\uae05\uae06\uae07\uae08\uae09\uae0a\uae0b\uae0c\uae0d\uae0e\uae0f\uae10\uae11\uae12\uae13\uae14\uae15\uae16\uae17\uae18\uae19\uae1a\uae1b\uae1c\uae1d\uae1e\uae1f\uae20\uae21\uae22\uae23\uae24\uae25\uae26\uae27\uae28\uae29\uae2a\uae2b\uae2c\uae2d\uae2e\uae2f\uae30\uae31\uae32\uae33\uae34\uae35\uae36\uae37\uae38\uae39\uae3a\uae3b\uae3c\uae3d\uae3e\uae3f\uae40\uae41\uae42\uae43\uae44\uae45\uae46\uae47\uae48\uae49\uae4a\uae4b\uae4c\uae4d\uae4e\uae4f\uae50\uae51\uae52\uae53\uae54\uae55\uae56\uae57\uae58\uae59\uae5a\uae5b\uae5c\uae5d\uae5e\uae5f\uae60\uae61\uae62\uae63\uae64\uae65\uae66\uae67\uae68\uae69\uae6a\uae6b\uae6c\uae6d\uae6e\uae6f\uae70\uae71\uae72\uae73\uae74\uae75\uae76\uae77\uae78\uae79\uae7a\uae7b\uae7c\uae7d\uae7e\uae7f\uae80\uae81\uae82\uae83\uae84\uae85\uae86\uae87\uae88\uae89\uae8a\uae8b\uae8c\uae8d\uae8e\uae8f\uae90\uae91\uae92\uae93\uae94\uae95\uae96\uae97\uae98\uae99\uae9a\uae9b\uae9c\uae9d\uae9e\uae9f\uaea0\uaea1\uaea2\uaea3\uaea4\uaea5\uaea6\uaea7\uaea8\uaea9\uaeaa\uaeab\uaeac\uaead\uaeae\uaeaf\uaeb0\uaeb1\uaeb2\uaeb3\uaeb4\uaeb5\uaeb6\uaeb7\uaeb8\uaeb9\uaeba\uaebb\uaebc\uaebd\uaebe\uaebf\uaec0\uaec1\uaec2\uaec3\uaec4\uaec5\uaec6\uaec7\uaec8\uaec9\uaeca\uaecb\uaecc\uaecd\uaece\uaecf\uaed0\uaed1\uaed2\uaed3\uaed4\uaed5\uaed6\uaed7\uaed8\uaed9\uaeda\uaedb\uaedc\uaedd\uaede\uaedf\uaee0\uaee1\uaee2\uaee3\uaee4\uaee5\uaee6\uaee7\uaee8\uaee9\uaeea\uaeeb\uaeec\uaeed\uaeee\uaeef\uaef0\uaef1\uaef2\uaef3\uaef4\uaef5\uaef6\uaef7\uaef8\uaef9\uaefa\uaefb\uaefc\uaefd\uaefe\uaeff\uaf00\uaf01\uaf02\uaf03\uaf04\uaf05\uaf06\uaf07\uaf08\uaf09\uaf0a\uaf0b\uaf0c\uaf0d\uaf0e\uaf0f\uaf10\uaf11\uaf12\uaf13\uaf14\uaf15\uaf16\uaf17\uaf18\uaf19\uaf1a\uaf1b\uaf1c\uaf1d\uaf1e\uaf1f\uaf20\uaf21\uaf22\uaf23\uaf24\uaf25\uaf26\uaf27\uaf28\uaf29\uaf2a\uaf2b\uaf2c\uaf2d\uaf2e\uaf2f\uaf30\uaf31\uaf32\uaf33\uaf34\uaf35\uaf36\uaf37\uaf38\uaf39\uaf3a\uaf3b\uaf3c\uaf3d\uaf3e\uaf3f\uaf40\uaf41\uaf42\uaf43\uaf44\uaf45\uaf46\uaf47\uaf48\uaf49\uaf4a\uaf4b\uaf4c\uaf4d\uaf4e\uaf4f\uaf50\uaf51\uaf52\uaf53\uaf54\uaf55\uaf56\uaf57\uaf58\uaf59\uaf5a\uaf5b\uaf5c\uaf5d\uaf5e\uaf5f\uaf60\uaf61\uaf62\uaf63\uaf64\uaf65\uaf66\uaf67\uaf68\uaf69\uaf6a\uaf6b\uaf6c\uaf6d\uaf6e\uaf6f\uaf70\uaf71\uaf72\uaf73\uaf74\uaf75\uaf76\uaf77\uaf78\uaf79\uaf7a\uaf7b\uaf7c\uaf7d\uaf7e\uaf7f\uaf80\uaf81\uaf82\uaf83\uaf84\uaf85\uaf86\uaf87\uaf88\uaf89\uaf8a\uaf8b\uaf8c\uaf8d\uaf8e\uaf8f\uaf90\uaf91\uaf92\uaf93\uaf94\uaf95\uaf96\uaf97\uaf98\uaf99\uaf9a\uaf9b\uaf9c\uaf9d\uaf9e\uaf9f\uafa0\uafa1\uafa2\uafa3\uafa4\uafa5\uafa6\uafa7\uafa8\uafa9\uafaa\uafab\uafac\uafad\uafae\uafaf\uafb0\uafb1\uafb2\uafb3\uafb4\uafb5\uafb6\uafb7\uafb8\uafb9\uafba\uafbb\uafbc\uafbd\uafbe\uafbf\uafc0\uafc1\uafc2\uafc3\uafc4\uafc5\uafc6\uafc7\uafc8\uafc9\uafca\uafcb\uafcc\uafcd\uafce\uafcf\uafd0\uafd1\uafd2\uafd3\uafd4\uafd5\uafd6\uafd7\uafd8\uafd9\uafda\uafdb\uafdc\uafdd\uafde\uafdf\uafe0\uafe1\uafe2\uafe3\uafe4\uafe5\uafe6\uafe7\uafe8\uafe9\uafea\uafeb\uafec\uafed\uafee\uafef\uaff0\uaff1\uaff2\uaff3\uaff4\uaff5\uaff6\uaff7\uaff8\uaff9\uaffa\uaffb\uaffc\uaffd\uaffe\uafff\ub000\ub001\ub002\ub003\ub004\ub005\ub006\ub007\ub008\ub009\ub00a\ub00b\ub00c\ub00d\ub00e\ub00f\ub010\ub011\ub012\ub013\ub014\ub015\ub016\ub017\ub018\ub019\ub01a\ub01b\ub01c\ub01d\ub01e\ub01f\ub020\ub021\ub022\ub023\ub024\ub025\ub026\ub027\ub028\ub029\ub02a\ub02b\ub02c\ub02d\ub02e\ub02f\ub030\ub031\ub032\ub033\ub034\ub035\ub036\ub037\ub038\ub039\ub03a\ub03b\ub03c\ub03d\ub03e\ub03f\ub040\ub041\ub042\ub043\ub044\ub045\ub046\ub047\ub048\ub049\ub04a\ub04b\ub04c\ub04d\ub04e\ub04f\ub050\ub051\ub052\ub053\ub054\ub055\ub056\ub057\ub058\ub059\ub05a\ub05b\ub05c\ub05d\ub05e\ub05f\ub060\ub061\ub062\ub063\ub064\ub065\ub066\ub067\ub068\ub069\ub06a\ub06b\ub06c\ub06d\ub06e\ub06f\ub070\ub071\ub072\ub073\ub074\ub075\ub076\ub077\ub078\ub079\ub07a\ub07b\ub07c\ub07d\ub07e\ub07f\ub080\ub081\ub082\ub083\ub084\ub085\ub086\ub087\ub088\ub089\ub08a\ub08b\ub08c\ub08d\ub08e\ub08f\ub090\ub091\ub092\ub093\ub094\ub095\ub096\ub097\ub098\ub099\ub09a\ub09b\ub09c\ub09d\ub09e\ub09f\ub0a0\ub0a1\ub0a2\ub0a3\ub0a4\ub0a5\ub0a6\ub0a7\ub0a8\ub0a9\ub0aa\ub0ab\ub0ac\ub0ad\ub0ae\ub0af\ub0b0\ub0b1\ub0b2\ub0b3\ub0b4\ub0b5\ub0b6\ub0b7\ub0b8\ub0b9\ub0ba\ub0bb\ub0bc\ub0bd\ub0be\ub0bf\ub0c0\ub0c1\ub0c2\ub0c3\ub0c4\ub0c5\ub0c6\ub0c7\ub0c8\ub0c9\ub0ca\ub0cb\ub0cc\ub0cd\ub0ce\ub0cf\ub0d0\ub0d1\ub0d2\ub0d3\ub0d4\ub0d5\ub0d6\ub0d7\ub0d8\ub0d9\ub0da\ub0db\ub0dc\ub0dd\ub0de\ub0df\ub0e0\ub0e1\ub0e2\ub0e3\ub0e4\ub0e5\ub0e6\ub0e7\ub0e8\ub0e9\ub0ea\ub0eb\ub0ec\ub0ed\ub0ee\ub0ef\ub0f0\ub0f1\ub0f2\ub0f3\ub0f4\ub0f5\ub0f6\ub0f7\ub0f8\ub0f9\ub0fa\ub0fb\ub0fc\ub0fd\ub0fe\ub0ff\ub100\ub101\ub102\ub103\ub104\ub105\ub106\ub107\ub108\ub109\ub10a\ub10b\ub10c\ub10d\ub10e\ub10f\ub110\ub111\ub112\ub113\ub114\ub115\ub116\ub117\ub118\ub119\ub11a\ub11b\ub11c\ub11d\ub11e\ub11f\ub120\ub121\ub122\ub123\ub124\ub125\ub126\ub127\ub128\ub129\ub12a\ub12b\ub12c\ub12d\ub12e\ub12f\ub130\ub131\ub132\ub133\ub134\ub135\ub136\ub137\ub138\ub139\ub13a\ub13b\ub13c\ub13d\ub13e\ub13f\ub140\ub141\ub142\ub143\ub144\ub145\ub146\ub147\ub148\ub149\ub14a\ub14b\ub14c\ub14d\ub14e\ub14f\ub150\ub151\ub152\ub153\ub154\ub155\ub156\ub157\ub158\ub159\ub15a\ub15b\ub15c\ub15d\ub15e\ub15f\ub160\ub161\ub162\ub163\ub164\ub165\ub166\ub167\ub168\ub169\ub16a\ub16b\ub16c\ub16d\ub16e\ub16f\ub170\ub171\ub172\ub173\ub174\ub175\ub176\ub177\ub178\ub179\ub17a\ub17b\ub17c\ub17d\ub17e\ub17f\ub180\ub181\ub182\ub183\ub184\ub185\ub186\ub187\ub188\ub189\ub18a\ub18b\ub18c\ub18d\ub18e\ub18f\ub190\ub191\ub192\ub193\ub194\ub195\ub196\ub197\ub198\ub199\ub19a\ub19b\ub19c\ub19d\ub19e\ub19f\ub1a0\ub1a1\ub1a2\ub1a3\ub1a4\ub1a5\ub1a6\ub1a7\ub1a8\ub1a9\ub1aa\ub1ab\ub1ac\ub1ad\ub1ae\ub1af\ub1b0\ub1b1\ub1b2\ub1b3\ub1b4\ub1b5\ub1b6\ub1b7\ub1b8\ub1b9\ub1ba\ub1bb\ub1bc\ub1bd\ub1be\ub1bf\ub1c0\ub1c1\ub1c2\ub1c3\ub1c4\ub1c5\ub1c6\ub1c7\ub1c8\ub1c9\ub1ca\ub1cb\ub1cc\ub1cd\ub1ce\ub1cf\ub1d0\ub1d1\ub1d2\ub1d3\ub1d4\ub1d5\ub1d6\ub1d7\ub1d8\ub1d9\ub1da\ub1db\ub1dc\ub1dd\ub1de\ub1df\ub1e0\ub1e1\ub1e2\ub1e3\ub1e4\ub1e5\ub1e6\ub1e7\ub1e8\ub1e9\ub1ea\ub1eb\ub1ec\ub1ed\ub1ee\ub1ef\ub1f0\ub1f1\ub1f2\ub1f3\ub1f4\ub1f5\ub1f6\ub1f7\ub1f8\ub1f9\ub1fa\ub1fb\ub1fc\ub1fd\ub1fe\ub1ff\ub200\ub201\ub202\ub203\ub204\ub205\ub206\ub207\ub208\ub209\ub20a\ub20b\ub20c\ub20d\ub20e\ub20f\ub210\ub211\ub212\ub213\ub214\ub215\ub216\ub217\ub218\ub219\ub21a\ub21b\ub21c\ub21d\ub21e\ub21f\ub220\ub221\ub222\ub223\ub224\ub225\ub226\ub227\ub228\ub229\ub22a\ub22b\ub22c\ub22d\ub22e\ub22f\ub230\ub231\ub232\ub233\ub234\ub235\ub236\ub237\ub238\ub239\ub23a\ub23b\ub23c\ub23d\ub23e\ub23f\ub240\ub241\ub242\ub243\ub244\ub245\ub246\ub247\ub248\ub249\ub24a\ub24b\ub24c\ub24d\ub24e\ub24f\ub250\ub251\ub252\ub253\ub254\ub255\ub256\ub257\ub258\ub259\ub25a\ub25b\ub25c\ub25d\ub25e\ub25f\ub260\ub261\ub262\ub263\ub264\ub265\ub266\ub267\ub268\ub269\ub26a\ub26b\ub26c\ub26d\ub26e\ub26f\ub270\ub271\ub272\ub273\ub274\ub275\ub276\ub277\ub278\ub279\ub27a\ub27b\ub27c\ub27d\ub27e\ub27f\ub280\ub281\ub282\ub283\ub284\ub285\ub286\ub287\ub288\ub289\ub28a\ub28b\ub28c\ub28d\ub28e\ub28f\ub290\ub291\ub292\ub293\ub294\ub295\ub296\ub297\ub298\ub299\ub29a\ub29b\ub29c\ub29d\ub29e\ub29f\ub2a0\ub2a1\ub2a2\ub2a3\ub2a4\ub2a5\ub2a6\ub2a7\ub2a8\ub2a9\ub2aa\ub2ab\ub2ac\ub2ad\ub2ae\ub2af\ub2b0\ub2b1\ub2b2\ub2b3\ub2b4\ub2b5\ub2b6\ub2b7\ub2b8\ub2b9\ub2ba\ub2bb\ub2bc\ub2bd\ub2be\ub2bf\ub2c0\ub2c1\ub2c2\ub2c3\ub2c4\ub2c5\ub2c6\ub2c7\ub2c8\ub2c9\ub2ca\ub2cb\ub2cc\ub2cd\ub2ce\ub2cf\ub2d0\ub2d1\ub2d2\ub2d3\ub2d4\ub2d5\ub2d6\ub2d7\ub2d8\ub2d9\ub2da\ub2db\ub2dc\ub2dd\ub2de\ub2df\ub2e0\ub2e1\ub2e2\ub2e3\ub2e4\ub2e5\ub2e6\ub2e7\ub2e8\ub2e9\ub2ea\ub2eb\ub2ec\ub2ed\ub2ee\ub2ef\ub2f0\ub2f1\ub2f2\ub2f3\ub2f4\ub2f5\ub2f6\ub2f7\ub2f8\ub2f9\ub2fa\ub2fb\ub2fc\ub2fd\ub2fe\ub2ff\ub300\ub301\ub302\ub303\ub304\ub305\ub306\ub307\ub308\ub309\ub30a\ub30b\ub30c\ub30d\ub30e\ub30f\ub310\ub311\ub312\ub313\ub314\ub315\ub316\ub317\ub318\ub319\ub31a\ub31b\ub31c\ub31d\ub31e\ub31f\ub320\ub321\ub322\ub323\ub324\ub325\ub326\ub327\ub328\ub329\ub32a\ub32b\ub32c\ub32d\ub32e\ub32f\ub330\ub331\ub332\ub333\ub334\ub335\ub336\ub337\ub338\ub339\ub33a\ub33b\ub33c\ub33d\ub33e\ub33f\ub340\ub341\ub342\ub343\ub344\ub345\ub346\ub347\ub348\ub349\ub34a\ub34b\ub34c\ub34d\ub34e\ub34f\ub350\ub351\ub352\ub353\ub354\ub355\ub356\ub357\ub358\ub359\ub35a\ub35b\ub35c\ub35d\ub35e\ub35f\ub360\ub361\ub362\ub363\ub364\ub365\ub366\ub367\ub368\ub369\ub36a\ub36b\ub36c\ub36d\ub36e\ub36f\ub370\ub371\ub372\ub373\ub374\ub375\ub376\ub377\ub378\ub379\ub37a\ub37b\ub37c\ub37d\ub37e\ub37f\ub380\ub381\ub382\ub383\ub384\ub385\ub386\ub387\ub388\ub389\ub38a\ub38b\ub38c\ub38d\ub38e\ub38f\ub390\ub391\ub392\ub393\ub394\ub395\ub396\ub397\ub398\ub399\ub39a\ub39b\ub39c\ub39d\ub39e\ub39f\ub3a0\ub3a1\ub3a2\ub3a3\ub3a4\ub3a5\ub3a6\ub3a7\ub3a8\ub3a9\ub3aa\ub3ab\ub3ac\ub3ad\ub3ae\ub3af\ub3b0\ub3b1\ub3b2\ub3b3\ub3b4\ub3b5\ub3b6\ub3b7\ub3b8\ub3b9\ub3ba\ub3bb\ub3bc\ub3bd\ub3be\ub3bf\ub3c0\ub3c1\ub3c2\ub3c3\ub3c4\ub3c5\ub3c6\ub3c7\ub3c8\ub3c9\ub3ca\ub3cb\ub3cc\ub3cd\ub3ce\ub3cf\ub3d0\ub3d1\ub3d2\ub3d3\ub3d4\ub3d5\ub3d6\ub3d7\ub3d8\ub3d9\ub3da\ub3db\ub3dc\ub3dd\ub3de\ub3df\ub3e0\ub3e1\ub3e2\ub3e3\ub3e4\ub3e5\ub3e6\ub3e7\ub3e8\ub3e9\ub3ea\ub3eb\ub3ec\ub3ed\ub3ee\ub3ef\ub3f0\ub3f1\ub3f2\ub3f3\ub3f4\ub3f5\ub3f6\ub3f7\ub3f8\ub3f9\ub3fa\ub3fb\ub3fc\ub3fd\ub3fe\ub3ff\ub400\ub401\ub402\ub403\ub404\ub405\ub406\ub407\ub408\ub409\ub40a\ub40b\ub40c\ub40d\ub40e\ub40f\ub410\ub411\ub412\ub413\ub414\ub415\ub416\ub417\ub418\ub419\ub41a\ub41b\ub41c\ub41d\ub41e\ub41f\ub420\ub421\ub422\ub423\ub424\ub425\ub426\ub427\ub428\ub429\ub42a\ub42b\ub42c\ub42d\ub42e\ub42f\ub430\ub431\ub432\ub433\ub434\ub435\ub436\ub437\ub438\ub439\ub43a\ub43b\ub43c\ub43d\ub43e\ub43f\ub440\ub441\ub442\ub443\ub444\ub445\ub446\ub447\ub448\ub449\ub44a\ub44b\ub44c\ub44d\ub44e\ub44f\ub450\ub451\ub452\ub453\ub454\ub455\ub456\ub457\ub458\ub459\ub45a\ub45b\ub45c\ub45d\ub45e\ub45f\ub460\ub461\ub462\ub463\ub464\ub465\ub466\ub467\ub468\ub469\ub46a\ub46b\ub46c\ub46d\ub46e\ub46f\ub470\ub471\ub472\ub473\ub474\ub475\ub476\ub477\ub478\ub479\ub47a\ub47b\ub47c\ub47d\ub47e\ub47f\ub480\ub481\ub482\ub483\ub484\ub485\ub486\ub487\ub488\ub489\ub48a\ub48b\ub48c\ub48d\ub48e\ub48f\ub490\ub491\ub492\ub493\ub494\ub495\ub496\ub497\ub498\ub499\ub49a\ub49b\ub49c\ub49d\ub49e\ub49f\ub4a0\ub4a1\ub4a2\ub4a3\ub4a4\ub4a5\ub4a6\ub4a7\ub4a8\ub4a9\ub4aa\ub4ab\ub4ac\ub4ad\ub4ae\ub4af\ub4b0\ub4b1\ub4b2\ub4b3\ub4b4\ub4b5\ub4b6\ub4b7\ub4b8\ub4b9\ub4ba\ub4bb\ub4bc\ub4bd\ub4be\ub4bf\ub4c0\ub4c1\ub4c2\ub4c3\ub4c4\ub4c5\ub4c6\ub4c7\ub4c8\ub4c9\ub4ca\ub4cb\ub4cc\ub4cd\ub4ce\ub4cf\ub4d0\ub4d1\ub4d2\ub4d3\ub4d4\ub4d5\ub4d6\ub4d7\ub4d8\ub4d9\ub4da\ub4db\ub4dc\ub4dd\ub4de\ub4df\ub4e0\ub4e1\ub4e2\ub4e3\ub4e4\ub4e5\ub4e6\ub4e7\ub4e8\ub4e9\ub4ea\ub4eb\ub4ec\ub4ed\ub4ee\ub4ef\ub4f0\ub4f1\ub4f2\ub4f3\ub4f4\ub4f5\ub4f6\ub4f7\ub4f8\ub4f9\ub4fa\ub4fb\ub4fc\ub4fd\ub4fe\ub4ff\ub500\ub501\ub502\ub503\ub504\ub505\ub506\ub507\ub508\ub509\ub50a\ub50b\ub50c\ub50d\ub50e\ub50f\ub510\ub511\ub512\ub513\ub514\ub515\ub516\ub517\ub518\ub519\ub51a\ub51b\ub51c\ub51d\ub51e\ub51f\ub520\ub521\ub522\ub523\ub524\ub525\ub526\ub527\ub528\ub529\ub52a\ub52b\ub52c\ub52d\ub52e\ub52f\ub530\ub531\ub532\ub533\ub534\ub535\ub536\ub537\ub538\ub539\ub53a\ub53b\ub53c\ub53d\ub53e\ub53f\ub540\ub541\ub542\ub543\ub544\ub545\ub546\ub547\ub548\ub549\ub54a\ub54b\ub54c\ub54d\ub54e\ub54f\ub550\ub551\ub552\ub553\ub554\ub555\ub556\ub557\ub558\ub559\ub55a\ub55b\ub55c\ub55d\ub55e\ub55f\ub560\ub561\ub562\ub563\ub564\ub565\ub566\ub567\ub568\ub569\ub56a\ub56b\ub56c\ub56d\ub56e\ub56f\ub570\ub571\ub572\ub573\ub574\ub575\ub576\ub577\ub578\ub579\ub57a\ub57b\ub57c\ub57d\ub57e\ub57f\ub580\ub581\ub582\ub583\ub584\ub585\ub586\ub587\ub588\ub589\ub58a\ub58b\ub58c\ub58d\ub58e\ub58f\ub590\ub591\ub592\ub593\ub594\ub595\ub596\ub597\ub598\ub599\ub59a\ub59b\ub59c\ub59d\ub59e\ub59f\ub5a0\ub5a1\ub5a2\ub5a3\ub5a4\ub5a5\ub5a6\ub5a7\ub5a8\ub5a9\ub5aa\ub5ab\ub5ac\ub5ad\ub5ae\ub5af\ub5b0\ub5b1\ub5b2\ub5b3\ub5b4\ub5b5\ub5b6\ub5b7\ub5b8\ub5b9\ub5ba\ub5bb\ub5bc\ub5bd\ub5be\ub5bf\ub5c0\ub5c1\ub5c2\ub5c3\ub5c4\ub5c5\ub5c6\ub5c7\ub5c8\ub5c9\ub5ca\ub5cb\ub5cc\ub5cd\ub5ce\ub5cf\ub5d0\ub5d1\ub5d2\ub5d3\ub5d4\ub5d5\ub5d6\ub5d7\ub5d8\ub5d9\ub5da\ub5db\ub5dc\ub5dd\ub5de\ub5df\ub5e0\ub5e1\ub5e2\ub5e3\ub5e4\ub5e5\ub5e6\ub5e7\ub5e8\ub5e9\ub5ea\ub5eb\ub5ec\ub5ed\ub5ee\ub5ef\ub5f0\ub5f1\ub5f2\ub5f3\ub5f4\ub5f5\ub5f6\ub5f7\ub5f8\ub5f9\ub5fa\ub5fb\ub5fc\ub5fd\ub5fe\ub5ff\ub600\ub601\ub602\ub603\ub604\ub605\ub606\ub607\ub608\ub609\ub60a\ub60b\ub60c\ub60d\ub60e\ub60f\ub610\ub611\ub612\ub613\ub614\ub615\ub616\ub617\ub618\ub619\ub61a\ub61b\ub61c\ub61d\ub61e\ub61f\ub620\ub621\ub622\ub623\ub624\ub625\ub626\ub627\ub628\ub629\ub62a\ub62b\ub62c\ub62d\ub62e\ub62f\ub630\ub631\ub632\ub633\ub634\ub635\ub636\ub637\ub638\ub639\ub63a\ub63b\ub63c\ub63d\ub63e\ub63f\ub640\ub641\ub642\ub643\ub644\ub645\ub646\ub647\ub648\ub649\ub64a\ub64b\ub64c\ub64d\ub64e\ub64f\ub650\ub651\ub652\ub653\ub654\ub655\ub656\ub657\ub658\ub659\ub65a\ub65b\ub65c\ub65d\ub65e\ub65f\ub660\ub661\ub662\ub663\ub664\ub665\ub666\ub667\ub668\ub669\ub66a\ub66b\ub66c\ub66d\ub66e\ub66f\ub670\ub671\ub672\ub673\ub674\ub675\ub676\ub677\ub678\ub679\ub67a\ub67b\ub67c\ub67d\ub67e\ub67f\ub680\ub681\ub682\ub683\ub684\ub685\ub686\ub687\ub688\ub689\ub68a\ub68b\ub68c\ub68d\ub68e\ub68f\ub690\ub691\ub692\ub693\ub694\ub695\ub696\ub697\ub698\ub699\ub69a\ub69b\ub69c\ub69d\ub69e\ub69f\ub6a0\ub6a1\ub6a2\ub6a3\ub6a4\ub6a5\ub6a6\ub6a7\ub6a8\ub6a9\ub6aa\ub6ab\ub6ac\ub6ad\ub6ae\ub6af\ub6b0\ub6b1\ub6b2\ub6b3\ub6b4\ub6b5\ub6b6\ub6b7\ub6b8\ub6b9\ub6ba\ub6bb\ub6bc\ub6bd\ub6be\ub6bf\ub6c0\ub6c1\ub6c2\ub6c3\ub6c4\ub6c5\ub6c6\ub6c7\ub6c8\ub6c9\ub6ca\ub6cb\ub6cc\ub6cd\ub6ce\ub6cf\ub6d0\ub6d1\ub6d2\ub6d3\ub6d4\ub6d5\ub6d6\ub6d7\ub6d8\ub6d9\ub6da\ub6db\ub6dc\ub6dd\ub6de\ub6df\ub6e0\ub6e1\ub6e2\ub6e3\ub6e4\ub6e5\ub6e6\ub6e7\ub6e8\ub6e9\ub6ea\ub6eb\ub6ec\ub6ed\ub6ee\ub6ef\ub6f0\ub6f1\ub6f2\ub6f3\ub6f4\ub6f5\ub6f6\ub6f7\ub6f8\ub6f9\ub6fa\ub6fb\ub6fc\ub6fd\ub6fe\ub6ff\ub700\ub701\ub702\ub703\ub704\ub705\ub706\ub707\ub708\ub709\ub70a\ub70b\ub70c\ub70d\ub70e\ub70f\ub710\ub711\ub712\ub713\ub714\ub715\ub716\ub717\ub718\ub719\ub71a\ub71b\ub71c\ub71d\ub71e\ub71f\ub720\ub721\ub722\ub723\ub724\ub725\ub726\ub727\ub728\ub729\ub72a\ub72b\ub72c\ub72d\ub72e\ub72f\ub730\ub731\ub732\ub733\ub734\ub735\ub736\ub737\ub738\ub739\ub73a\ub73b\ub73c\ub73d\ub73e\ub73f\ub740\ub741\ub742\ub743\ub744\ub745\ub746\ub747\ub748\ub749\ub74a\ub74b\ub74c\ub74d\ub74e\ub74f\ub750\ub751\ub752\ub753\ub754\ub755\ub756\ub757\ub758\ub759\ub75a\ub75b\ub75c\ub75d\ub75e\ub75f\ub760\ub761\ub762\ub763\ub764\ub765\ub766\ub767\ub768\ub769\ub76a\ub76b\ub76c\ub76d\ub76e\ub76f\ub770\ub771\ub772\ub773\ub774\ub775\ub776\ub777\ub778\ub779\ub77a\ub77b\ub77c\ub77d\ub77e\ub77f\ub780\ub781\ub782\ub783\ub784\ub785\ub786\ub787\ub788\ub789\ub78a\ub78b\ub78c\ub78d\ub78e\ub78f\ub790\ub791\ub792\ub793\ub794\ub795\ub796\ub797\ub798\ub799\ub79a\ub79b\ub79c\ub79d\ub79e\ub79f\ub7a0\ub7a1\ub7a2\ub7a3\ub7a4\ub7a5\ub7a6\ub7a7\ub7a8\ub7a9\ub7aa\ub7ab\ub7ac\ub7ad\ub7ae\ub7af\ub7b0\ub7b1\ub7b2\ub7b3\ub7b4\ub7b5\ub7b6\ub7b7\ub7b8\ub7b9\ub7ba\ub7bb\ub7bc\ub7bd\ub7be\ub7bf\ub7c0\ub7c1\ub7c2\ub7c3\ub7c4\ub7c5\ub7c6\ub7c7\ub7c8\ub7c9\ub7ca\ub7cb\ub7cc\ub7cd\ub7ce\ub7cf\ub7d0\ub7d1\ub7d2\ub7d3\ub7d4\ub7d5\ub7d6\ub7d7\ub7d8\ub7d9\ub7da\ub7db\ub7dc\ub7dd\ub7de\ub7df\ub7e0\ub7e1\ub7e2\ub7e3\ub7e4\ub7e5\ub7e6\ub7e7\ub7e8\ub7e9\ub7ea\ub7eb\ub7ec\ub7ed\ub7ee\ub7ef\ub7f0\ub7f1\ub7f2\ub7f3\ub7f4\ub7f5\ub7f6\ub7f7\ub7f8\ub7f9\ub7fa\ub7fb\ub7fc\ub7fd\ub7fe\ub7ff\ub800\ub801\ub802\ub803\ub804\ub805\ub806\ub807\ub808\ub809\ub80a\ub80b\ub80c\ub80d\ub80e\ub80f\ub810\ub811\ub812\ub813\ub814\ub815\ub816\ub817\ub818\ub819\ub81a\ub81b\ub81c\ub81d\ub81e\ub81f\ub820\ub821\ub822\ub823\ub824\ub825\ub826\ub827\ub828\ub829\ub82a\ub82b\ub82c\ub82d\ub82e\ub82f\ub830\ub831\ub832\ub833\ub834\ub835\ub836\ub837\ub838\ub839\ub83a\ub83b\ub83c\ub83d\ub83e\ub83f\ub840\ub841\ub842\ub843\ub844\ub845\ub846\ub847\ub848\ub849\ub84a\ub84b\ub84c\ub84d\ub84e\ub84f\ub850\ub851\ub852\ub853\ub854\ub855\ub856\ub857\ub858\ub859\ub85a\ub85b\ub85c\ub85d\ub85e\ub85f\ub860\ub861\ub862\ub863\ub864\ub865\ub866\ub867\ub868\ub869\ub86a\ub86b\ub86c\ub86d\ub86e\ub86f\ub870\ub871\ub872\ub873\ub874\ub875\ub876\ub877\ub878\ub879\ub87a\ub87b\ub87c\ub87d\ub87e\ub87f\ub880\ub881\ub882\ub883\ub884\ub885\ub886\ub887\ub888\ub889\ub88a\ub88b\ub88c\ub88d\ub88e\ub88f\ub890\ub891\ub892\ub893\ub894\ub895\ub896\ub897\ub898\ub899\ub89a\ub89b\ub89c\ub89d\ub89e\ub89f\ub8a0\ub8a1\ub8a2\ub8a3\ub8a4\ub8a5\ub8a6\ub8a7\ub8a8\ub8a9\ub8aa\ub8ab\ub8ac\ub8ad\ub8ae\ub8af\ub8b0\ub8b1\ub8b2\ub8b3\ub8b4\ub8b5\ub8b6\ub8b7\ub8b8\ub8b9\ub8ba\ub8bb\ub8bc\ub8bd\ub8be\ub8bf\ub8c0\ub8c1\ub8c2\ub8c3\ub8c4\ub8c5\ub8c6\ub8c7\ub8c8\ub8c9\ub8ca\ub8cb\ub8cc\ub8cd\ub8ce\ub8cf\ub8d0\ub8d1\ub8d2\ub8d3\ub8d4\ub8d5\ub8d6\ub8d7\ub8d8\ub8d9\ub8da\ub8db\ub8dc\ub8dd\ub8de\ub8df\ub8e0\ub8e1\ub8e2\ub8e3\ub8e4\ub8e5\ub8e6\ub8e7\ub8e8\ub8e9\ub8ea\ub8eb\ub8ec\ub8ed\ub8ee\ub8ef\ub8f0\ub8f1\ub8f2\ub8f3\ub8f4\ub8f5\ub8f6\ub8f7\ub8f8\ub8f9\ub8fa\ub8fb\ub8fc\ub8fd\ub8fe\ub8ff\ub900\ub901\ub902\ub903\ub904\ub905\ub906\ub907\ub908\ub909\ub90a\ub90b\ub90c\ub90d\ub90e\ub90f\ub910\ub911\ub912\ub913\ub914\ub915\ub916\ub917\ub918\ub919\ub91a\ub91b\ub91c\ub91d\ub91e\ub91f\ub920\ub921\ub922\ub923\ub924\ub925\ub926\ub927\ub928\ub929\ub92a\ub92b\ub92c\ub92d\ub92e\ub92f\ub930\ub931\ub932\ub933\ub934\ub935\ub936\ub937\ub938\ub939\ub93a\ub93b\ub93c\ub93d\ub93e\ub93f\ub940\ub941\ub942\ub943\ub944\ub945\ub946\ub947\ub948\ub949\ub94a\ub94b\ub94c\ub94d\ub94e\ub94f\ub950\ub951\ub952\ub953\ub954\ub955\ub956\ub957\ub958\ub959\ub95a\ub95b\ub95c\ub95d\ub95e\ub95f\ub960\ub961\ub962\ub963\ub964\ub965\ub966\ub967\ub968\ub969\ub96a\ub96b\ub96c\ub96d\ub96e\ub96f\ub970\ub971\ub972\ub973\ub974\ub975\ub976\ub977\ub978\ub979\ub97a\ub97b\ub97c\ub97d\ub97e\ub97f\ub980\ub981\ub982\ub983\ub984\ub985\ub986\ub987\ub988\ub989\ub98a\ub98b\ub98c\ub98d\ub98e\ub98f\ub990\ub991\ub992\ub993\ub994\ub995\ub996\ub997\ub998\ub999\ub99a\ub99b\ub99c\ub99d\ub99e\ub99f\ub9a0\ub9a1\ub9a2\ub9a3\ub9a4\ub9a5\ub9a6\ub9a7\ub9a8\ub9a9\ub9aa\ub9ab\ub9ac\ub9ad\ub9ae\ub9af\ub9b0\ub9b1\ub9b2\ub9b3\ub9b4\ub9b5\ub9b6\ub9b7\ub9b8\ub9b9\ub9ba\ub9bb\ub9bc\ub9bd\ub9be\ub9bf\ub9c0\ub9c1\ub9c2\ub9c3\ub9c4\ub9c5\ub9c6\ub9c7\ub9c8\ub9c9\ub9ca\ub9cb\ub9cc\ub9cd\ub9ce\ub9cf\ub9d0\ub9d1\ub9d2\ub9d3\ub9d4\ub9d5\ub9d6\ub9d7\ub9d8\ub9d9\ub9da\ub9db\ub9dc\ub9dd\ub9de\ub9df\ub9e0\ub9e1\ub9e2\ub9e3\ub9e4\ub9e5\ub9e6\ub9e7\ub9e8\ub9e9\ub9ea\ub9eb\ub9ec\ub9ed\ub9ee\ub9ef\ub9f0\ub9f1\ub9f2\ub9f3\ub9f4\ub9f5\ub9f6\ub9f7\ub9f8\ub9f9\ub9fa\ub9fb\ub9fc\ub9fd\ub9fe\ub9ff\uba00\uba01\uba02\uba03\uba04\uba05\uba06\uba07\uba08\uba09\uba0a\uba0b\uba0c\uba0d\uba0e\uba0f\uba10\uba11\uba12\uba13\uba14\uba15\uba16\uba17\uba18\uba19\uba1a\uba1b\uba1c\uba1d\uba1e\uba1f\uba20\uba21\uba22\uba23\uba24\uba25\uba26\uba27\uba28\uba29\uba2a\uba2b\uba2c\uba2d\uba2e\uba2f\uba30\uba31\uba32\uba33\uba34\uba35\uba36\uba37\uba38\uba39\uba3a\uba3b\uba3c\uba3d\uba3e\uba3f\uba40\uba41\uba42\uba43\uba44\uba45\uba46\uba47\uba48\uba49\uba4a\uba4b\uba4c\uba4d\uba4e\uba4f\uba50\uba51\uba52\uba53\uba54\uba55\uba56\uba57\uba58\uba59\uba5a\uba5b\uba5c\uba5d\uba5e\uba5f\uba60\uba61\uba62\uba63\uba64\uba65\uba66\uba67\uba68\uba69\uba6a\uba6b\uba6c\uba6d\uba6e\uba6f\uba70\uba71\uba72\uba73\uba74\uba75\uba76\uba77\uba78\uba79\uba7a\uba7b\uba7c\uba7d\uba7e\uba7f\uba80\uba81\uba82\uba83\uba84\uba85\uba86\uba87\uba88\uba89\uba8a\uba8b\uba8c\uba8d\uba8e\uba8f\uba90\uba91\uba92\uba93\uba94\uba95\uba96\uba97\uba98\uba99\uba9a\uba9b\uba9c\uba9d\uba9e\uba9f\ubaa0\ubaa1\ubaa2\ubaa3\ubaa4\ubaa5\ubaa6\ubaa7\ubaa8\ubaa9\ubaaa\ubaab\ubaac\ubaad\ubaae\ubaaf\ubab0\ubab1\ubab2\ubab3\ubab4\ubab5\ubab6\ubab7\ubab8\ubab9\ubaba\ubabb\ubabc\ubabd\ubabe\ubabf\ubac0\ubac1\ubac2\ubac3\ubac4\ubac5\ubac6\ubac7\ubac8\ubac9\ubaca\ubacb\ubacc\ubacd\ubace\ubacf\ubad0\ubad1\ubad2\ubad3\ubad4\ubad5\ubad6\ubad7\ubad8\ubad9\ubada\ubadb\ubadc\ubadd\ubade\ubadf\ubae0\ubae1\ubae2\ubae3\ubae4\ubae5\ubae6\ubae7\ubae8\ubae9\ubaea\ubaeb\ubaec\ubaed\ubaee\ubaef\ubaf0\ubaf1\ubaf2\ubaf3\ubaf4\ubaf5\ubaf6\ubaf7\ubaf8\ubaf9\ubafa\ubafb\ubafc\ubafd\ubafe\ubaff\ubb00\ubb01\ubb02\ubb03\ubb04\ubb05\ubb06\ubb07\ubb08\ubb09\ubb0a\ubb0b\ubb0c\ubb0d\ubb0e\ubb0f\ubb10\ubb11\ubb12\ubb13\ubb14\ubb15\ubb16\ubb17\ubb18\ubb19\ubb1a\ubb1b\ubb1c\ubb1d\ubb1e\ubb1f\ubb20\ubb21\ubb22\ubb23\ubb24\ubb25\ubb26\ubb27\ubb28\ubb29\ubb2a\ubb2b\ubb2c\ubb2d\ubb2e\ubb2f\ubb30\ubb31\ubb32\ubb33\ubb34\ubb35\ubb36\ubb37\ubb38\ubb39\ubb3a\ubb3b\ubb3c\ubb3d\ubb3e\ubb3f\ubb40\ubb41\ubb42\ubb43\ubb44\ubb45\ubb46\ubb47\ubb48\ubb49\ubb4a\ubb4b\ubb4c\ubb4d\ubb4e\ubb4f\ubb50\ubb51\ubb52\ubb53\ubb54\ubb55\ubb56\ubb57\ubb58\ubb59\ubb5a\ubb5b\ubb5c\ubb5d\ubb5e\ubb5f\ubb60\ubb61\ubb62\ubb63\ubb64\ubb65\ubb66\ubb67\ubb68\ubb69\ubb6a\ubb6b\ubb6c\ubb6d\ubb6e\ubb6f\ubb70\ubb71\ubb72\ubb73\ubb74\ubb75\ubb76\ubb77\ubb78\ubb79\ubb7a\ubb7b\ubb7c\ubb7d\ubb7e\ubb7f\ubb80\ubb81\ubb82\ubb83\ubb84\ubb85\ubb86\ubb87\ubb88\ubb89\ubb8a\ubb8b\ubb8c\ubb8d\ubb8e\ubb8f\ubb90\ubb91\ubb92\ubb93\ubb94\ubb95\ubb96\ubb97\ubb98\ubb99\ubb9a\ubb9b\ubb9c\ubb9d\ubb9e\ubb9f\ubba0\ubba1\ubba2\ubba3\ubba4\ubba5\ubba6\ubba7\ubba8\ubba9\ubbaa\ubbab\ubbac\ubbad\ubbae\ubbaf\ubbb0\ubbb1\ubbb2\ubbb3\ubbb4\ubbb5\ubbb6\ubbb7\ubbb8\ubbb9\ubbba\ubbbb\ubbbc\ubbbd\ubbbe\ubbbf\ubbc0\ubbc1\ubbc2\ubbc3\ubbc4\ubbc5\ubbc6\ubbc7\ubbc8\ubbc9\ubbca\ubbcb\ubbcc\ubbcd\ubbce\ubbcf\ubbd0\ubbd1\ubbd2\ubbd3\ubbd4\ubbd5\ubbd6\ubbd7\ubbd8\ubbd9\ubbda\ubbdb\ubbdc\ubbdd\ubbde\ubbdf\ubbe0\ubbe1\ubbe2\ubbe3\ubbe4\ubbe5\ubbe6\ubbe7\ubbe8\ubbe9\ubbea\ubbeb\ubbec\ubbed\ubbee\ubbef\ubbf0\ubbf1\ubbf2\ubbf3\ubbf4\ubbf5\ubbf6\ubbf7\ubbf8\ubbf9\ubbfa\ubbfb\ubbfc\ubbfd\ubbfe\ubbff\ubc00\ubc01\ubc02\ubc03\ubc04\ubc05\ubc06\ubc07\ubc08\ubc09\ubc0a\ubc0b\ubc0c\ubc0d\ubc0e\ubc0f\ubc10\ubc11\ubc12\ubc13\ubc14\ubc15\ubc16\ubc17\ubc18\ubc19\ubc1a\ubc1b\ubc1c\ubc1d\ubc1e\ubc1f\ubc20\ubc21\ubc22\ubc23\ubc24\ubc25\ubc26\ubc27\ubc28\ubc29\ubc2a\ubc2b\ubc2c\ubc2d\ubc2e\ubc2f\ubc30\ubc31\ubc32\ubc33\ubc34\ubc35\ubc36\ubc37\ubc38\ubc39\ubc3a\ubc3b\ubc3c\ubc3d\ubc3e\ubc3f\ubc40\ubc41\ubc42\ubc43\ubc44\ubc45\ubc46\ubc47\ubc48\ubc49\ubc4a\ubc4b\ubc4c\ubc4d\ubc4e\ubc4f\ubc50\ubc51\ubc52\ubc53\ubc54\ubc55\ubc56\ubc57\ubc58\ubc59\ubc5a\ubc5b\ubc5c\ubc5d\ubc5e\ubc5f\ubc60\ubc61\ubc62\ubc63\ubc64\ubc65\ubc66\ubc67\ubc68\ubc69\ubc6a\ubc6b\ubc6c\ubc6d\ubc6e\ubc6f\ubc70\ubc71\ubc72\ubc73\ubc74\ubc75\ubc76\ubc77\ubc78\ubc79\ubc7a\ubc7b\ubc7c\ubc7d\ubc7e\ubc7f\ubc80\ubc81\ubc82\ubc83\ubc84\ubc85\ubc86\ubc87\ubc88\ubc89\ubc8a\ubc8b\ubc8c\ubc8d\ubc8e\ubc8f\ubc90\ubc91\ubc92\ubc93\ubc94\ubc95\ubc96\ubc97\ubc98\ubc99\ubc9a\ubc9b\ubc9c\ubc9d\ubc9e\ubc9f\ubca0\ubca1\ubca2\ubca3\ubca4\ubca5\ubca6\ubca7\ubca8\ubca9\ubcaa\ubcab\ubcac\ubcad\ubcae\ubcaf\ubcb0\ubcb1\ubcb2\ubcb3\ubcb4\ubcb5\ubcb6\ubcb7\ubcb8\ubcb9\ubcba\ubcbb\ubcbc\ubcbd\ubcbe\ubcbf\ubcc0\ubcc1\ubcc2\ubcc3\ubcc4\ubcc5\ubcc6\ubcc7\ubcc8\ubcc9\ubcca\ubccb\ubccc\ubccd\ubcce\ubccf\ubcd0\ubcd1\ubcd2\ubcd3\ubcd4\ubcd5\ubcd6\ubcd7\ubcd8\ubcd9\ubcda\ubcdb\ubcdc\ubcdd\ubcde\ubcdf\ubce0\ubce1\ubce2\ubce3\ubce4\ubce5\ubce6\ubce7\ubce8\ubce9\ubcea\ubceb\ubcec\ubced\ubcee\ubcef\ubcf0\ubcf1\ubcf2\ubcf3\ubcf4\ubcf5\ubcf6\ubcf7\ubcf8\ubcf9\ubcfa\ubcfb\ubcfc\ubcfd\ubcfe\ubcff\ubd00\ubd01\ubd02\ubd03\ubd04\ubd05\ubd06\ubd07\ubd08\ubd09\ubd0a\ubd0b\ubd0c\ubd0d\ubd0e\ubd0f\ubd10\ubd11\ubd12\ubd13\ubd14\ubd15\ubd16\ubd17\ubd18\ubd19\ubd1a\ubd1b\ubd1c\ubd1d\ubd1e\ubd1f\ubd20\ubd21\ubd22\ubd23\ubd24\ubd25\ubd26\ubd27\ubd28\ubd29\ubd2a\ubd2b\ubd2c\ubd2d\ubd2e\ubd2f\ubd30\ubd31\ubd32\ubd33\ubd34\ubd35\ubd36\ubd37\ubd38\ubd39\ubd3a\ubd3b\ubd3c\ubd3d\ubd3e\ubd3f\ubd40\ubd41\ubd42\ubd43\ubd44\ubd45\ubd46\ubd47\ubd48\ubd49\ubd4a\ubd4b\ubd4c\ubd4d\ubd4e\ubd4f\ubd50\ubd51\ubd52\ubd53\ubd54\ubd55\ubd56\ubd57\ubd58\ubd59\ubd5a\ubd5b\ubd5c\ubd5d\ubd5e\ubd5f\ubd60\ubd61\ubd62\ubd63\ubd64\ubd65\ubd66\ubd67\ubd68\ubd69\ubd6a\ubd6b\ubd6c\ubd6d\ubd6e\ubd6f\ubd70\ubd71\ubd72\ubd73\ubd74\ubd75\ubd76\ubd77\ubd78\ubd79\ubd7a\ubd7b\ubd7c\ubd7d\ubd7e\ubd7f\ubd80\ubd81\ubd82\ubd83\ubd84\ubd85\ubd86\ubd87\ubd88\ubd89\ubd8a\ubd8b\ubd8c\ubd8d\ubd8e\ubd8f\ubd90\ubd91\ubd92\ubd93\ubd94\ubd95\ubd96\ubd97\ubd98\ubd99\ubd9a\ubd9b\ubd9c\ubd9d\ubd9e\ubd9f\ubda0\ubda1\ubda2\ubda3\ubda4\ubda5\ubda6\ubda7\ubda8\ubda9\ubdaa\ubdab\ubdac\ubdad\ubdae\ubdaf\ubdb0\ubdb1\ubdb2\ubdb3\ubdb4\ubdb5\ubdb6\ubdb7\ubdb8\ubdb9\ubdba\ubdbb\ubdbc\ubdbd\ubdbe\ubdbf\ubdc0\ubdc1\ubdc2\ubdc3\ubdc4\ubdc5\ubdc6\ubdc7\ubdc8\ubdc9\ubdca\ubdcb\ubdcc\ubdcd\ubdce\ubdcf\ubdd0\ubdd1\ubdd2\ubdd3\ubdd4\ubdd5\ubdd6\ubdd7\ubdd8\ubdd9\ubdda\ubddb\ubddc\ubddd\ubdde\ubddf\ubde0\ubde1\ubde2\ubde3\ubde4\ubde5\ubde6\ubde7\ubde8\ubde9\ubdea\ubdeb\ubdec\ubded\ubdee\ubdef\ubdf0\ubdf1\ubdf2\ubdf3\ubdf4\ubdf5\ubdf6\ubdf7\ubdf8\ubdf9\ubdfa\ubdfb\ubdfc\ubdfd\ubdfe\ubdff\ube00\ube01\ube02\ube03\ube04\ube05\ube06\ube07\ube08\ube09\ube0a\ube0b\ube0c\ube0d\ube0e\ube0f\ube10\ube11\ube12\ube13\ube14\ube15\ube16\ube17\ube18\ube19\ube1a\ube1b\ube1c\ube1d\ube1e\ube1f\ube20\ube21\ube22\ube23\ube24\ube25\ube26\ube27\ube28\ube29\ube2a\ube2b\ube2c\ube2d\ube2e\ube2f\ube30\ube31\ube32\ube33\ube34\ube35\ube36\ube37\ube38\ube39\ube3a\ube3b\ube3c\ube3d\ube3e\ube3f\ube40\ube41\ube42\ube43\ube44\ube45\ube46\ube47\ube48\ube49\ube4a\ube4b\ube4c\ube4d\ube4e\ube4f\ube50\ube51\ube52\ube53\ube54\ube55\ube56\ube57\ube58\ube59\ube5a\ube5b\ube5c\ube5d\ube5e\ube5f\ube60\ube61\ube62\ube63\ube64\ube65\ube66\ube67\ube68\ube69\ube6a\ube6b\ube6c\ube6d\ube6e\ube6f\ube70\ube71\ube72\ube73\ube74\ube75\ube76\ube77\ube78\ube79\ube7a\ube7b\ube7c\ube7d\ube7e\ube7f\ube80\ube81\ube82\ube83\ube84\ube85\ube86\ube87\ube88\ube89\ube8a\ube8b\ube8c\ube8d\ube8e\ube8f\ube90\ube91\ube92\ube93\ube94\ube95\ube96\ube97\ube98\ube99\ube9a\ube9b\ube9c\ube9d\ube9e\ube9f\ubea0\ubea1\ubea2\ubea3\ubea4\ubea5\ubea6\ubea7\ubea8\ubea9\ubeaa\ubeab\ubeac\ubead\ubeae\ubeaf\ubeb0\ubeb1\ubeb2\ubeb3\ubeb4\ubeb5\ubeb6\ubeb7\ubeb8\ubeb9\ubeba\ubebb\ubebc\ubebd\ubebe\ubebf\ubec0\ubec1\ubec2\ubec3\ubec4\ubec5\ubec6\ubec7\ubec8\ubec9\ubeca\ubecb\ubecc\ubecd\ubece\ubecf\ubed0\ubed1\ubed2\ubed3\ubed4\ubed5\ubed6\ubed7\ubed8\ubed9\ubeda\ubedb\ubedc\ubedd\ubede\ubedf\ubee0\ubee1\ubee2\ubee3\ubee4\ubee5\ubee6\ubee7\ubee8\ubee9\ubeea\ubeeb\ubeec\ubeed\ubeee\ubeef\ubef0\ubef1\ubef2\ubef3\ubef4\ubef5\ubef6\ubef7\ubef8\ubef9\ubefa\ubefb\ubefc\ubefd\ubefe\ubeff\ubf00\ubf01\ubf02\ubf03\ubf04\ubf05\ubf06\ubf07\ubf08\ubf09\ubf0a\ubf0b\ubf0c\ubf0d\ubf0e\ubf0f\ubf10\ubf11\ubf12\ubf13\ubf14\ubf15\ubf16\ubf17\ubf18\ubf19\ubf1a\ubf1b\ubf1c\ubf1d\ubf1e\ubf1f\ubf20\ubf21\ubf22\ubf23\ubf24\ubf25\ubf26\ubf27\ubf28\ubf29\ubf2a\ubf2b\ubf2c\ubf2d\ubf2e\ubf2f\ubf30\ubf31\ubf32\ubf33\ubf34\ubf35\ubf36\ubf37\ubf38\ubf39\ubf3a\ubf3b\ubf3c\ubf3d\ubf3e\ubf3f\ubf40\ubf41\ubf42\ubf43\ubf44\ubf45\ubf46\ubf47\ubf48\ubf49\ubf4a\ubf4b\ubf4c\ubf4d\ubf4e\ubf4f\ubf50\ubf51\ubf52\ubf53\ubf54\ubf55\ubf56\ubf57\ubf58\ubf59\ubf5a\ubf5b\ubf5c\ubf5d\ubf5e\ubf5f\ubf60\ubf61\ubf62\ubf63\ubf64\ubf65\ubf66\ubf67\ubf68\ubf69\ubf6a\ubf6b\ubf6c\ubf6d\ubf6e\ubf6f\ubf70\ubf71\ubf72\ubf73\ubf74\ubf75\ubf76\ubf77\ubf78\ubf79\ubf7a\ubf7b\ubf7c\ubf7d\ubf7e\ubf7f\ubf80\ubf81\ubf82\ubf83\ubf84\ubf85\ubf86\ubf87\ubf88\ubf89\ubf8a\ubf8b\ubf8c\ubf8d\ubf8e\ubf8f\ubf90\ubf91\ubf92\ubf93\ubf94\ubf95\ubf96\ubf97\ubf98\ubf99\ubf9a\ubf9b\ubf9c\ubf9d\ubf9e\ubf9f\ubfa0\ubfa1\ubfa2\ubfa3\ubfa4\ubfa5\ubfa6\ubfa7\ubfa8\ubfa9\ubfaa\ubfab\ubfac\ubfad\ubfae\ubfaf\ubfb0\ubfb1\ubfb2\ubfb3\ubfb4\ubfb5\ubfb6\ubfb7\ubfb8\ubfb9\ubfba\ubfbb\ubfbc\ubfbd\ubfbe\ubfbf\ubfc0\ubfc1\ubfc2\ubfc3\ubfc4\ubfc5\ubfc6\ubfc7\ubfc8\ubfc9\ubfca\ubfcb\ubfcc\ubfcd\ubfce\ubfcf\ubfd0\ubfd1\ubfd2\ubfd3\ubfd4\ubfd5\ubfd6\ubfd7\ubfd8\ubfd9\ubfda\ubfdb\ubfdc\ubfdd\ubfde\ubfdf\ubfe0\ubfe1\ubfe2\ubfe3\ubfe4\ubfe5\ubfe6\ubfe7\ubfe8\ubfe9\ubfea\ubfeb\ubfec\ubfed\ubfee\ubfef\ubff0\ubff1\ubff2\ubff3\ubff4\ubff5\ubff6\ubff7\ubff8\ubff9\ubffa\ubffb\ubffc\ubffd\ubffe\ubfff\uc000\uc001\uc002\uc003\uc004\uc005\uc006\uc007\uc008\uc009\uc00a\uc00b\uc00c\uc00d\uc00e\uc00f\uc010\uc011\uc012\uc013\uc014\uc015\uc016\uc017\uc018\uc019\uc01a\uc01b\uc01c\uc01d\uc01e\uc01f\uc020\uc021\uc022\uc023\uc024\uc025\uc026\uc027\uc028\uc029\uc02a\uc02b\uc02c\uc02d\uc02e\uc02f\uc030\uc031\uc032\uc033\uc034\uc035\uc036\uc037\uc038\uc039\uc03a\uc03b\uc03c\uc03d\uc03e\uc03f\uc040\uc041\uc042\uc043\uc044\uc045\uc046\uc047\uc048\uc049\uc04a\uc04b\uc04c\uc04d\uc04e\uc04f\uc050\uc051\uc052\uc053\uc054\uc055\uc056\uc057\uc058\uc059\uc05a\uc05b\uc05c\uc05d\uc05e\uc05f\uc060\uc061\uc062\uc063\uc064\uc065\uc066\uc067\uc068\uc069\uc06a\uc06b\uc06c\uc06d\uc06e\uc06f\uc070\uc071\uc072\uc073\uc074\uc075\uc076\uc077\uc078\uc079\uc07a\uc07b\uc07c\uc07d\uc07e\uc07f\uc080\uc081\uc082\uc083\uc084\uc085\uc086\uc087\uc088\uc089\uc08a\uc08b\uc08c\uc08d\uc08e\uc08f\uc090\uc091\uc092\uc093\uc094\uc095\uc096\uc097\uc098\uc099\uc09a\uc09b\uc09c\uc09d\uc09e\uc09f\uc0a0\uc0a1\uc0a2\uc0a3\uc0a4\uc0a5\uc0a6\uc0a7\uc0a8\uc0a9\uc0aa\uc0ab\uc0ac\uc0ad\uc0ae\uc0af\uc0b0\uc0b1\uc0b2\uc0b3\uc0b4\uc0b5\uc0b6\uc0b7\uc0b8\uc0b9\uc0ba\uc0bb\uc0bc\uc0bd\uc0be\uc0bf\uc0c0\uc0c1\uc0c2\uc0c3\uc0c4\uc0c5\uc0c6\uc0c7\uc0c8\uc0c9\uc0ca\uc0cb\uc0cc\uc0cd\uc0ce\uc0cf\uc0d0\uc0d1\uc0d2\uc0d3\uc0d4\uc0d5\uc0d6\uc0d7\uc0d8\uc0d9\uc0da\uc0db\uc0dc\uc0dd\uc0de\uc0df\uc0e0\uc0e1\uc0e2\uc0e3\uc0e4\uc0e5\uc0e6\uc0e7\uc0e8\uc0e9\uc0ea\uc0eb\uc0ec\uc0ed\uc0ee\uc0ef\uc0f0\uc0f1\uc0f2\uc0f3\uc0f4\uc0f5\uc0f6\uc0f7\uc0f8\uc0f9\uc0fa\uc0fb\uc0fc\uc0fd\uc0fe\uc0ff\uc100\uc101\uc102\uc103\uc104\uc105\uc106\uc107\uc108\uc109\uc10a\uc10b\uc10c\uc10d\uc10e\uc10f\uc110\uc111\uc112\uc113\uc114\uc115\uc116\uc117\uc118\uc119\uc11a\uc11b\uc11c\uc11d\uc11e\uc11f\uc120\uc121\uc122\uc123\uc124\uc125\uc126\uc127\uc128\uc129\uc12a\uc12b\uc12c\uc12d\uc12e\uc12f\uc130\uc131\uc132\uc133\uc134\uc135\uc136\uc137\uc138\uc139\uc13a\uc13b\uc13c\uc13d\uc13e\uc13f\uc140\uc141\uc142\uc143\uc144\uc145\uc146\uc147\uc148\uc149\uc14a\uc14b\uc14c\uc14d\uc14e\uc14f\uc150\uc151\uc152\uc153\uc154\uc155\uc156\uc157\uc158\uc159\uc15a\uc15b\uc15c\uc15d\uc15e\uc15f\uc160\uc161\uc162\uc163\uc164\uc165\uc166\uc167\uc168\uc169\uc16a\uc16b\uc16c\uc16d\uc16e\uc16f\uc170\uc171\uc172\uc173\uc174\uc175\uc176\uc177\uc178\uc179\uc17a\uc17b\uc17c\uc17d\uc17e\uc17f\uc180\uc181\uc182\uc183\uc184\uc185\uc186\uc187\uc188\uc189\uc18a\uc18b\uc18c\uc18d\uc18e\uc18f\uc190\uc191\uc192\uc193\uc194\uc195\uc196\uc197\uc198\uc199\uc19a\uc19b\uc19c\uc19d\uc19e\uc19f\uc1a0\uc1a1\uc1a2\uc1a3\uc1a4\uc1a5\uc1a6\uc1a7\uc1a8\uc1a9\uc1aa\uc1ab\uc1ac\uc1ad\uc1ae\uc1af\uc1b0\uc1b1\uc1b2\uc1b3\uc1b4\uc1b5\uc1b6\uc1b7\uc1b8\uc1b9\uc1ba\uc1bb\uc1bc\uc1bd\uc1be\uc1bf\uc1c0\uc1c1\uc1c2\uc1c3\uc1c4\uc1c5\uc1c6\uc1c7\uc1c8\uc1c9\uc1ca\uc1cb\uc1cc\uc1cd\uc1ce\uc1cf\uc1d0\uc1d1\uc1d2\uc1d3\uc1d4\uc1d5\uc1d6\uc1d7\uc1d8\uc1d9\uc1da\uc1db\uc1dc\uc1dd\uc1de\uc1df\uc1e0\uc1e1\uc1e2\uc1e3\uc1e4\uc1e5\uc1e6\uc1e7\uc1e8\uc1e9\uc1ea\uc1eb\uc1ec\uc1ed\uc1ee\uc1ef\uc1f0\uc1f1\uc1f2\uc1f3\uc1f4\uc1f5\uc1f6\uc1f7\uc1f8\uc1f9\uc1fa\uc1fb\uc1fc\uc1fd\uc1fe\uc1ff\uc200\uc201\uc202\uc203\uc204\uc205\uc206\uc207\uc208\uc209\uc20a\uc20b\uc20c\uc20d\uc20e\uc20f\uc210\uc211\uc212\uc213\uc214\uc215\uc216\uc217\uc218\uc219\uc21a\uc21b\uc21c\uc21d\uc21e\uc21f\uc220\uc221\uc222\uc223\uc224\uc225\uc226\uc227\uc228\uc229\uc22a\uc22b\uc22c\uc22d\uc22e\uc22f\uc230\uc231\uc232\uc233\uc234\uc235\uc236\uc237\uc238\uc239\uc23a\uc23b\uc23c\uc23d\uc23e\uc23f\uc240\uc241\uc242\uc243\uc244\uc245\uc246\uc247\uc248\uc249\uc24a\uc24b\uc24c\uc24d\uc24e\uc24f\uc250\uc251\uc252\uc253\uc254\uc255\uc256\uc257\uc258\uc259\uc25a\uc25b\uc25c\uc25d\uc25e\uc25f\uc260\uc261\uc262\uc263\uc264\uc265\uc266\uc267\uc268\uc269\uc26a\uc26b\uc26c\uc26d\uc26e\uc26f\uc270\uc271\uc272\uc273\uc274\uc275\uc276\uc277\uc278\uc279\uc27a\uc27b\uc27c\uc27d\uc27e\uc27f\uc280\uc281\uc282\uc283\uc284\uc285\uc286\uc287\uc288\uc289\uc28a\uc28b\uc28c\uc28d\uc28e\uc28f\uc290\uc291\uc292\uc293\uc294\uc295\uc296\uc297\uc298\uc299\uc29a\uc29b\uc29c\uc29d\uc29e\uc29f\uc2a0\uc2a1\uc2a2\uc2a3\uc2a4\uc2a5\uc2a6\uc2a7\uc2a8\uc2a9\uc2aa\uc2ab\uc2ac\uc2ad\uc2ae\uc2af\uc2b0\uc2b1\uc2b2\uc2b3\uc2b4\uc2b5\uc2b6\uc2b7\uc2b8\uc2b9\uc2ba\uc2bb\uc2bc\uc2bd\uc2be\uc2bf\uc2c0\uc2c1\uc2c2\uc2c3\uc2c4\uc2c5\uc2c6\uc2c7\uc2c8\uc2c9\uc2ca\uc2cb\uc2cc\uc2cd\uc2ce\uc2cf\uc2d0\uc2d1\uc2d2\uc2d3\uc2d4\uc2d5\uc2d6\uc2d7\uc2d8\uc2d9\uc2da\uc2db\uc2dc\uc2dd\uc2de\uc2df\uc2e0\uc2e1\uc2e2\uc2e3\uc2e4\uc2e5\uc2e6\uc2e7\uc2e8\uc2e9\uc2ea\uc2eb\uc2ec\uc2ed\uc2ee\uc2ef\uc2f0\uc2f1\uc2f2\uc2f3\uc2f4\uc2f5\uc2f6\uc2f7\uc2f8\uc2f9\uc2fa\uc2fb\uc2fc\uc2fd\uc2fe\uc2ff\uc300\uc301\uc302\uc303\uc304\uc305\uc306\uc307\uc308\uc309\uc30a\uc30b\uc30c\uc30d\uc30e\uc30f\uc310\uc311\uc312\uc313\uc314\uc315\uc316\uc317\uc318\uc319\uc31a\uc31b\uc31c\uc31d\uc31e\uc31f\uc320\uc321\uc322\uc323\uc324\uc325\uc326\uc327\uc328\uc329\uc32a\uc32b\uc32c\uc32d\uc32e\uc32f\uc330\uc331\uc332\uc333\uc334\uc335\uc336\uc337\uc338\uc339\uc33a\uc33b\uc33c\uc33d\uc33e\uc33f\uc340\uc341\uc342\uc343\uc344\uc345\uc346\uc347\uc348\uc349\uc34a\uc34b\uc34c\uc34d\uc34e\uc34f\uc350\uc351\uc352\uc353\uc354\uc355\uc356\uc357\uc358\uc359\uc35a\uc35b\uc35c\uc35d\uc35e\uc35f\uc360\uc361\uc362\uc363\uc364\uc365\uc366\uc367\uc368\uc369\uc36a\uc36b\uc36c\uc36d\uc36e\uc36f\uc370\uc371\uc372\uc373\uc374\uc375\uc376\uc377\uc378\uc379\uc37a\uc37b\uc37c\uc37d\uc37e\uc37f\uc380\uc381\uc382\uc383\uc384\uc385\uc386\uc387\uc388\uc389\uc38a\uc38b\uc38c\uc38d\uc38e\uc38f\uc390\uc391\uc392\uc393\uc394\uc395\uc396\uc397\uc398\uc399\uc39a\uc39b\uc39c\uc39d\uc39e\uc39f\uc3a0\uc3a1\uc3a2\uc3a3\uc3a4\uc3a5\uc3a6\uc3a7\uc3a8\uc3a9\uc3aa\uc3ab\uc3ac\uc3ad\uc3ae\uc3af\uc3b0\uc3b1\uc3b2\uc3b3\uc3b4\uc3b5\uc3b6\uc3b7\uc3b8\uc3b9\uc3ba\uc3bb\uc3bc\uc3bd\uc3be\uc3bf\uc3c0\uc3c1\uc3c2\uc3c3\uc3c4\uc3c5\uc3c6\uc3c7\uc3c8\uc3c9\uc3ca\uc3cb\uc3cc\uc3cd\uc3ce\uc3cf\uc3d0\uc3d1\uc3d2\uc3d3\uc3d4\uc3d5\uc3d6\uc3d7\uc3d8\uc3d9\uc3da\uc3db\uc3dc\uc3dd\uc3de\uc3df\uc3e0\uc3e1\uc3e2\uc3e3\uc3e4\uc3e5\uc3e6\uc3e7\uc3e8\uc3e9\uc3ea\uc3eb\uc3ec\uc3ed\uc3ee\uc3ef\uc3f0\uc3f1\uc3f2\uc3f3\uc3f4\uc3f5\uc3f6\uc3f7\uc3f8\uc3f9\uc3fa\uc3fb\uc3fc\uc3fd\uc3fe\uc3ff\uc400\uc401\uc402\uc403\uc404\uc405\uc406\uc407\uc408\uc409\uc40a\uc40b\uc40c\uc40d\uc40e\uc40f\uc410\uc411\uc412\uc413\uc414\uc415\uc416\uc417\uc418\uc419\uc41a\uc41b\uc41c\uc41d\uc41e\uc41f\uc420\uc421\uc422\uc423\uc424\uc425\uc426\uc427\uc428\uc429\uc42a\uc42b\uc42c\uc42d\uc42e\uc42f\uc430\uc431\uc432\uc433\uc434\uc435\uc436\uc437\uc438\uc439\uc43a\uc43b\uc43c\uc43d\uc43e\uc43f\uc440\uc441\uc442\uc443\uc444\uc445\uc446\uc447\uc448\uc449\uc44a\uc44b\uc44c\uc44d\uc44e\uc44f\uc450\uc451\uc452\uc453\uc454\uc455\uc456\uc457\uc458\uc459\uc45a\uc45b\uc45c\uc45d\uc45e\uc45f\uc460\uc461\uc462\uc463\uc464\uc465\uc466\uc467\uc468\uc469\uc46a\uc46b\uc46c\uc46d\uc46e\uc46f\uc470\uc471\uc472\uc473\uc474\uc475\uc476\uc477\uc478\uc479\uc47a\uc47b\uc47c\uc47d\uc47e\uc47f\uc480\uc481\uc482\uc483\uc484\uc485\uc486\uc487\uc488\uc489\uc48a\uc48b\uc48c\uc48d\uc48e\uc48f\uc490\uc491\uc492\uc493\uc494\uc495\uc496\uc497\uc498\uc499\uc49a\uc49b\uc49c\uc49d\uc49e\uc49f\uc4a0\uc4a1\uc4a2\uc4a3\uc4a4\uc4a5\uc4a6\uc4a7\uc4a8\uc4a9\uc4aa\uc4ab\uc4ac\uc4ad\uc4ae\uc4af\uc4b0\uc4b1\uc4b2\uc4b3\uc4b4\uc4b5\uc4b6\uc4b7\uc4b8\uc4b9\uc4ba\uc4bb\uc4bc\uc4bd\uc4be\uc4bf\uc4c0\uc4c1\uc4c2\uc4c3\uc4c4\uc4c5\uc4c6\uc4c7\uc4c8\uc4c9\uc4ca\uc4cb\uc4cc\uc4cd\uc4ce\uc4cf\uc4d0\uc4d1\uc4d2\uc4d3\uc4d4\uc4d5\uc4d6\uc4d7\uc4d8\uc4d9\uc4da\uc4db\uc4dc\uc4dd\uc4de\uc4df\uc4e0\uc4e1\uc4e2\uc4e3\uc4e4\uc4e5\uc4e6\uc4e7\uc4e8\uc4e9\uc4ea\uc4eb\uc4ec\uc4ed\uc4ee\uc4ef\uc4f0\uc4f1\uc4f2\uc4f3\uc4f4\uc4f5\uc4f6\uc4f7\uc4f8\uc4f9\uc4fa\uc4fb\uc4fc\uc4fd\uc4fe\uc4ff\uc500\uc501\uc502\uc503\uc504\uc505\uc506\uc507\uc508\uc509\uc50a\uc50b\uc50c\uc50d\uc50e\uc50f\uc510\uc511\uc512\uc513\uc514\uc515\uc516\uc517\uc518\uc519\uc51a\uc51b\uc51c\uc51d\uc51e\uc51f\uc520\uc521\uc522\uc523\uc524\uc525\uc526\uc527\uc528\uc529\uc52a\uc52b\uc52c\uc52d\uc52e\uc52f\uc530\uc531\uc532\uc533\uc534\uc535\uc536\uc537\uc538\uc539\uc53a\uc53b\uc53c\uc53d\uc53e\uc53f\uc540\uc541\uc542\uc543\uc544\uc545\uc546\uc547\uc548\uc549\uc54a\uc54b\uc54c\uc54d\uc54e\uc54f\uc550\uc551\uc552\uc553\uc554\uc555\uc556\uc557\uc558\uc559\uc55a\uc55b\uc55c\uc55d\uc55e\uc55f\uc560\uc561\uc562\uc563\uc564\uc565\uc566\uc567\uc568\uc569\uc56a\uc56b\uc56c\uc56d\uc56e\uc56f\uc570\uc571\uc572\uc573\uc574\uc575\uc576\uc577\uc578\uc579\uc57a\uc57b\uc57c\uc57d\uc57e\uc57f\uc580\uc581\uc582\uc583\uc584\uc585\uc586\uc587\uc588\uc589\uc58a\uc58b\uc58c\uc58d\uc58e\uc58f\uc590\uc591\uc592\uc593\uc594\uc595\uc596\uc597\uc598\uc599\uc59a\uc59b\uc59c\uc59d\uc59e\uc59f\uc5a0\uc5a1\uc5a2\uc5a3\uc5a4\uc5a5\uc5a6\uc5a7\uc5a8\uc5a9\uc5aa\uc5ab\uc5ac\uc5ad\uc5ae\uc5af\uc5b0\uc5b1\uc5b2\uc5b3\uc5b4\uc5b5\uc5b6\uc5b7\uc5b8\uc5b9\uc5ba\uc5bb\uc5bc\uc5bd\uc5be\uc5bf\uc5c0\uc5c1\uc5c2\uc5c3\uc5c4\uc5c5\uc5c6\uc5c7\uc5c8\uc5c9\uc5ca\uc5cb\uc5cc\uc5cd\uc5ce\uc5cf\uc5d0\uc5d1\uc5d2\uc5d3\uc5d4\uc5d5\uc5d6\uc5d7\uc5d8\uc5d9\uc5da\uc5db\uc5dc\uc5dd\uc5de\uc5df\uc5e0\uc5e1\uc5e2\uc5e3\uc5e4\uc5e5\uc5e6\uc5e7\uc5e8\uc5e9\uc5ea\uc5eb\uc5ec\uc5ed\uc5ee\uc5ef\uc5f0\uc5f1\uc5f2\uc5f3\uc5f4\uc5f5\uc5f6\uc5f7\uc5f8\uc5f9\uc5fa\uc5fb\uc5fc\uc5fd\uc5fe\uc5ff\uc600\uc601\uc602\uc603\uc604\uc605\uc606\uc607\uc608\uc609\uc60a\uc60b\uc60c\uc60d\uc60e\uc60f\uc610\uc611\uc612\uc613\uc614\uc615\uc616\uc617\uc618\uc619\uc61a\uc61b\uc61c\uc61d\uc61e\uc61f\uc620\uc621\uc622\uc623\uc624\uc625\uc626\uc627\uc628\uc629\uc62a\uc62b\uc62c\uc62d\uc62e\uc62f\uc630\uc631\uc632\uc633\uc634\uc635\uc636\uc637\uc638\uc639\uc63a\uc63b\uc63c\uc63d\uc63e\uc63f\uc640\uc641\uc642\uc643\uc644\uc645\uc646\uc647\uc648\uc649\uc64a\uc64b\uc64c\uc64d\uc64e\uc64f\uc650\uc651\uc652\uc653\uc654\uc655\uc656\uc657\uc658\uc659\uc65a\uc65b\uc65c\uc65d\uc65e\uc65f\uc660\uc661\uc662\uc663\uc664\uc665\uc666\uc667\uc668\uc669\uc66a\uc66b\uc66c\uc66d\uc66e\uc66f\uc670\uc671\uc672\uc673\uc674\uc675\uc676\uc677\uc678\uc679\uc67a\uc67b\uc67c\uc67d\uc67e\uc67f\uc680\uc681\uc682\uc683\uc684\uc685\uc686\uc687\uc688\uc689\uc68a\uc68b\uc68c\uc68d\uc68e\uc68f\uc690\uc691\uc692\uc693\uc694\uc695\uc696\uc697\uc698\uc699\uc69a\uc69b\uc69c\uc69d\uc69e\uc69f\uc6a0\uc6a1\uc6a2\uc6a3\uc6a4\uc6a5\uc6a6\uc6a7\uc6a8\uc6a9\uc6aa\uc6ab\uc6ac\uc6ad\uc6ae\uc6af\uc6b0\uc6b1\uc6b2\uc6b3\uc6b4\uc6b5\uc6b6\uc6b7\uc6b8\uc6b9\uc6ba\uc6bb\uc6bc\uc6bd\uc6be\uc6bf\uc6c0\uc6c1\uc6c2\uc6c3\uc6c4\uc6c5\uc6c6\uc6c7\uc6c8\uc6c9\uc6ca\uc6cb\uc6cc\uc6cd\uc6ce\uc6cf\uc6d0\uc6d1\uc6d2\uc6d3\uc6d4\uc6d5\uc6d6\uc6d7\uc6d8\uc6d9\uc6da\uc6db\uc6dc\uc6dd\uc6de\uc6df\uc6e0\uc6e1\uc6e2\uc6e3\uc6e4\uc6e5\uc6e6\uc6e7\uc6e8\uc6e9\uc6ea\uc6eb\uc6ec\uc6ed\uc6ee\uc6ef\uc6f0\uc6f1\uc6f2\uc6f3\uc6f4\uc6f5\uc6f6\uc6f7\uc6f8\uc6f9\uc6fa\uc6fb\uc6fc\uc6fd\uc6fe\uc6ff\uc700\uc701\uc702\uc703\uc704\uc705\uc706\uc707\uc708\uc709\uc70a\uc70b\uc70c\uc70d\uc70e\uc70f\uc710\uc711\uc712\uc713\uc714\uc715\uc716\uc717\uc718\uc719\uc71a\uc71b\uc71c\uc71d\uc71e\uc71f\uc720\uc721\uc722\uc723\uc724\uc725\uc726\uc727\uc728\uc729\uc72a\uc72b\uc72c\uc72d\uc72e\uc72f\uc730\uc731\uc732\uc733\uc734\uc735\uc736\uc737\uc738\uc739\uc73a\uc73b\uc73c\uc73d\uc73e\uc73f\uc740\uc741\uc742\uc743\uc744\uc745\uc746\uc747\uc748\uc749\uc74a\uc74b\uc74c\uc74d\uc74e\uc74f\uc750\uc751\uc752\uc753\uc754\uc755\uc756\uc757\uc758\uc759\uc75a\uc75b\uc75c\uc75d\uc75e\uc75f\uc760\uc761\uc762\uc763\uc764\uc765\uc766\uc767\uc768\uc769\uc76a\uc76b\uc76c\uc76d\uc76e\uc76f\uc770\uc771\uc772\uc773\uc774\uc775\uc776\uc777\uc778\uc779\uc77a\uc77b\uc77c\uc77d\uc77e\uc77f\uc780\uc781\uc782\uc783\uc784\uc785\uc786\uc787\uc788\uc789\uc78a\uc78b\uc78c\uc78d\uc78e\uc78f\uc790\uc791\uc792\uc793\uc794\uc795\uc796\uc797\uc798\uc799\uc79a\uc79b\uc79c\uc79d\uc79e\uc79f\uc7a0\uc7a1\uc7a2\uc7a3\uc7a4\uc7a5\uc7a6\uc7a7\uc7a8\uc7a9\uc7aa\uc7ab\uc7ac\uc7ad\uc7ae\uc7af\uc7b0\uc7b1\uc7b2\uc7b3\uc7b4\uc7b5\uc7b6\uc7b7\uc7b8\uc7b9\uc7ba\uc7bb\uc7bc\uc7bd\uc7be\uc7bf\uc7c0\uc7c1\uc7c2\uc7c3\uc7c4\uc7c5\uc7c6\uc7c7\uc7c8\uc7c9\uc7ca\uc7cb\uc7cc\uc7cd\uc7ce\uc7cf\uc7d0\uc7d1\uc7d2\uc7d3\uc7d4\uc7d5\uc7d6\uc7d7\uc7d8\uc7d9\uc7da\uc7db\uc7dc\uc7dd\uc7de\uc7df\uc7e0\uc7e1\uc7e2\uc7e3\uc7e4\uc7e5\uc7e6\uc7e7\uc7e8\uc7e9\uc7ea\uc7eb\uc7ec\uc7ed\uc7ee\uc7ef\uc7f0\uc7f1\uc7f2\uc7f3\uc7f4\uc7f5\uc7f6\uc7f7\uc7f8\uc7f9\uc7fa\uc7fb\uc7fc\uc7fd\uc7fe\uc7ff\uc800\uc801\uc802\uc803\uc804\uc805\uc806\uc807\uc808\uc809\uc80a\uc80b\uc80c\uc80d\uc80e\uc80f\uc810\uc811\uc812\uc813\uc814\uc815\uc816\uc817\uc818\uc819\uc81a\uc81b\uc81c\uc81d\uc81e\uc81f\uc820\uc821\uc822\uc823\uc824\uc825\uc826\uc827\uc828\uc829\uc82a\uc82b\uc82c\uc82d\uc82e\uc82f\uc830\uc831\uc832\uc833\uc834\uc835\uc836\uc837\uc838\uc839\uc83a\uc83b\uc83c\uc83d\uc83e\uc83f\uc840\uc841\uc842\uc843\uc844\uc845\uc846\uc847\uc848\uc849\uc84a\uc84b\uc84c\uc84d\uc84e\uc84f\uc850\uc851\uc852\uc853\uc854\uc855\uc856\uc857\uc858\uc859\uc85a\uc85b\uc85c\uc85d\uc85e\uc85f\uc860\uc861\uc862\uc863\uc864\uc865\uc866\uc867\uc868\uc869\uc86a\uc86b\uc86c\uc86d\uc86e\uc86f\uc870\uc871\uc872\uc873\uc874\uc875\uc876\uc877\uc878\uc879\uc87a\uc87b\uc87c\uc87d\uc87e\uc87f\uc880\uc881\uc882\uc883\uc884\uc885\uc886\uc887\uc888\uc889\uc88a\uc88b\uc88c\uc88d\uc88e\uc88f\uc890\uc891\uc892\uc893\uc894\uc895\uc896\uc897\uc898\uc899\uc89a\uc89b\uc89c\uc89d\uc89e\uc89f\uc8a0\uc8a1\uc8a2\uc8a3\uc8a4\uc8a5\uc8a6\uc8a7\uc8a8\uc8a9\uc8aa\uc8ab\uc8ac\uc8ad\uc8ae\uc8af\uc8b0\uc8b1\uc8b2\uc8b3\uc8b4\uc8b5\uc8b6\uc8b7\uc8b8\uc8b9\uc8ba\uc8bb\uc8bc\uc8bd\uc8be\uc8bf\uc8c0\uc8c1\uc8c2\uc8c3\uc8c4\uc8c5\uc8c6\uc8c7\uc8c8\uc8c9\uc8ca\uc8cb\uc8cc\uc8cd\uc8ce\uc8cf\uc8d0\uc8d1\uc8d2\uc8d3\uc8d4\uc8d5\uc8d6\uc8d7\uc8d8\uc8d9\uc8da\uc8db\uc8dc\uc8dd\uc8de\uc8df\uc8e0\uc8e1\uc8e2\uc8e3\uc8e4\uc8e5\uc8e6\uc8e7\uc8e8\uc8e9\uc8ea\uc8eb\uc8ec\uc8ed\uc8ee\uc8ef\uc8f0\uc8f1\uc8f2\uc8f3\uc8f4\uc8f5\uc8f6\uc8f7\uc8f8\uc8f9\uc8fa\uc8fb\uc8fc\uc8fd\uc8fe\uc8ff\uc900\uc901\uc902\uc903\uc904\uc905\uc906\uc907\uc908\uc909\uc90a\uc90b\uc90c\uc90d\uc90e\uc90f\uc910\uc911\uc912\uc913\uc914\uc915\uc916\uc917\uc918\uc919\uc91a\uc91b\uc91c\uc91d\uc91e\uc91f\uc920\uc921\uc922\uc923\uc924\uc925\uc926\uc927\uc928\uc929\uc92a\uc92b\uc92c\uc92d\uc92e\uc92f\uc930\uc931\uc932\uc933\uc934\uc935\uc936\uc937\uc938\uc939\uc93a\uc93b\uc93c\uc93d\uc93e\uc93f\uc940\uc941\uc942\uc943\uc944\uc945\uc946\uc947\uc948\uc949\uc94a\uc94b\uc94c\uc94d\uc94e\uc94f\uc950\uc951\uc952\uc953\uc954\uc955\uc956\uc957\uc958\uc959\uc95a\uc95b\uc95c\uc95d\uc95e\uc95f\uc960\uc961\uc962\uc963\uc964\uc965\uc966\uc967\uc968\uc969\uc96a\uc96b\uc96c\uc96d\uc96e\uc96f\uc970\uc971\uc972\uc973\uc974\uc975\uc976\uc977\uc978\uc979\uc97a\uc97b\uc97c\uc97d\uc97e\uc97f\uc980\uc981\uc982\uc983\uc984\uc985\uc986\uc987\uc988\uc989\uc98a\uc98b\uc98c\uc98d\uc98e\uc98f\uc990\uc991\uc992\uc993\uc994\uc995\uc996\uc997\uc998\uc999\uc99a\uc99b\uc99c\uc99d\uc99e\uc99f\uc9a0\uc9a1\uc9a2\uc9a3\uc9a4\uc9a5\uc9a6\uc9a7\uc9a8\uc9a9\uc9aa\uc9ab\uc9ac\uc9ad\uc9ae\uc9af\uc9b0\uc9b1\uc9b2\uc9b3\uc9b4\uc9b5\uc9b6\uc9b7\uc9b8\uc9b9\uc9ba\uc9bb\uc9bc\uc9bd\uc9be\uc9bf\uc9c0\uc9c1\uc9c2\uc9c3\uc9c4\uc9c5\uc9c6\uc9c7\uc9c8\uc9c9\uc9ca\uc9cb\uc9cc\uc9cd\uc9ce\uc9cf\uc9d0\uc9d1\uc9d2\uc9d3\uc9d4\uc9d5\uc9d6\uc9d7\uc9d8\uc9d9\uc9da\uc9db\uc9dc\uc9dd\uc9de\uc9df\uc9e0\uc9e1\uc9e2\uc9e3\uc9e4\uc9e5\uc9e6\uc9e7\uc9e8\uc9e9\uc9ea\uc9eb\uc9ec\uc9ed\uc9ee\uc9ef\uc9f0\uc9f1\uc9f2\uc9f3\uc9f4\uc9f5\uc9f6\uc9f7\uc9f8\uc9f9\uc9fa\uc9fb\uc9fc\uc9fd\uc9fe\uc9ff\uca00\uca01\uca02\uca03\uca04\uca05\uca06\uca07\uca08\uca09\uca0a\uca0b\uca0c\uca0d\uca0e\uca0f\uca10\uca11\uca12\uca13\uca14\uca15\uca16\uca17\uca18\uca19\uca1a\uca1b\uca1c\uca1d\uca1e\uca1f\uca20\uca21\uca22\uca23\uca24\uca25\uca26\uca27\uca28\uca29\uca2a\uca2b\uca2c\uca2d\uca2e\uca2f\uca30\uca31\uca32\uca33\uca34\uca35\uca36\uca37\uca38\uca39\uca3a\uca3b\uca3c\uca3d\uca3e\uca3f\uca40\uca41\uca42\uca43\uca44\uca45\uca46\uca47\uca48\uca49\uca4a\uca4b\uca4c\uca4d\uca4e\uca4f\uca50\uca51\uca52\uca53\uca54\uca55\uca56\uca57\uca58\uca59\uca5a\uca5b\uca5c\uca5d\uca5e\uca5f\uca60\uca61\uca62\uca63\uca64\uca65\uca66\uca67\uca68\uca69\uca6a\uca6b\uca6c\uca6d\uca6e\uca6f\uca70\uca71\uca72\uca73\uca74\uca75\uca76\uca77\uca78\uca79\uca7a\uca7b\uca7c\uca7d\uca7e\uca7f\uca80\uca81\uca82\uca83\uca84\uca85\uca86\uca87\uca88\uca89\uca8a\uca8b\uca8c\uca8d\uca8e\uca8f\uca90\uca91\uca92\uca93\uca94\uca95\uca96\uca97\uca98\uca99\uca9a\uca9b\uca9c\uca9d\uca9e\uca9f\ucaa0\ucaa1\ucaa2\ucaa3\ucaa4\ucaa5\ucaa6\ucaa7\ucaa8\ucaa9\ucaaa\ucaab\ucaac\ucaad\ucaae\ucaaf\ucab0\ucab1\ucab2\ucab3\ucab4\ucab5\ucab6\ucab7\ucab8\ucab9\ucaba\ucabb\ucabc\ucabd\ucabe\ucabf\ucac0\ucac1\ucac2\ucac3\ucac4\ucac5\ucac6\ucac7\ucac8\ucac9\ucaca\ucacb\ucacc\ucacd\ucace\ucacf\ucad0\ucad1\ucad2\ucad3\ucad4\ucad5\ucad6\ucad7\ucad8\ucad9\ucada\ucadb\ucadc\ucadd\ucade\ucadf\ucae0\ucae1\ucae2\ucae3\ucae4\ucae5\ucae6\ucae7\ucae8\ucae9\ucaea\ucaeb\ucaec\ucaed\ucaee\ucaef\ucaf0\ucaf1\ucaf2\ucaf3\ucaf4\ucaf5\ucaf6\ucaf7\ucaf8\ucaf9\ucafa\ucafb\ucafc\ucafd\ucafe\ucaff\ucb00\ucb01\ucb02\ucb03\ucb04\ucb05\ucb06\ucb07\ucb08\ucb09\ucb0a\ucb0b\ucb0c\ucb0d\ucb0e\ucb0f\ucb10\ucb11\ucb12\ucb13\ucb14\ucb15\ucb16\ucb17\ucb18\ucb19\ucb1a\ucb1b\ucb1c\ucb1d\ucb1e\ucb1f\ucb20\ucb21\ucb22\ucb23\ucb24\ucb25\ucb26\ucb27\ucb28\ucb29\ucb2a\ucb2b\ucb2c\ucb2d\ucb2e\ucb2f\ucb30\ucb31\ucb32\ucb33\ucb34\ucb35\ucb36\ucb37\ucb38\ucb39\ucb3a\ucb3b\ucb3c\ucb3d\ucb3e\ucb3f\ucb40\ucb41\ucb42\ucb43\ucb44\ucb45\ucb46\ucb47\ucb48\ucb49\ucb4a\ucb4b\ucb4c\ucb4d\ucb4e\ucb4f\ucb50\ucb51\ucb52\ucb53\ucb54\ucb55\ucb56\ucb57\ucb58\ucb59\ucb5a\ucb5b\ucb5c\ucb5d\ucb5e\ucb5f\ucb60\ucb61\ucb62\ucb63\ucb64\ucb65\ucb66\ucb67\ucb68\ucb69\ucb6a\ucb6b\ucb6c\ucb6d\ucb6e\ucb6f\ucb70\ucb71\ucb72\ucb73\ucb74\ucb75\ucb76\ucb77\ucb78\ucb79\ucb7a\ucb7b\ucb7c\ucb7d\ucb7e\ucb7f\ucb80\ucb81\ucb82\ucb83\ucb84\ucb85\ucb86\ucb87\ucb88\ucb89\ucb8a\ucb8b\ucb8c\ucb8d\ucb8e\ucb8f\ucb90\ucb91\ucb92\ucb93\ucb94\ucb95\ucb96\ucb97\ucb98\ucb99\ucb9a\ucb9b\ucb9c\ucb9d\ucb9e\ucb9f\ucba0\ucba1\ucba2\ucba3\ucba4\ucba5\ucba6\ucba7\ucba8\ucba9\ucbaa\ucbab\ucbac\ucbad\ucbae\ucbaf\ucbb0\ucbb1\ucbb2\ucbb3\ucbb4\ucbb5\ucbb6\ucbb7\ucbb8\ucbb9\ucbba\ucbbb\ucbbc\ucbbd\ucbbe\ucbbf\ucbc0\ucbc1\ucbc2\ucbc3\ucbc4\ucbc5\ucbc6\ucbc7\ucbc8\ucbc9\ucbca\ucbcb\ucbcc\ucbcd\ucbce\ucbcf\ucbd0\ucbd1\ucbd2\ucbd3\ucbd4\ucbd5\ucbd6\ucbd7\ucbd8\ucbd9\ucbda\ucbdb\ucbdc\ucbdd\ucbde\ucbdf\ucbe0\ucbe1\ucbe2\ucbe3\ucbe4\ucbe5\ucbe6\ucbe7\ucbe8\ucbe9\ucbea\ucbeb\ucbec\ucbed\ucbee\ucbef\ucbf0\ucbf1\ucbf2\ucbf3\ucbf4\ucbf5\ucbf6\ucbf7\ucbf8\ucbf9\ucbfa\ucbfb\ucbfc\ucbfd\ucbfe\ucbff\ucc00\ucc01\ucc02\ucc03\ucc04\ucc05\ucc06\ucc07\ucc08\ucc09\ucc0a\ucc0b\ucc0c\ucc0d\ucc0e\ucc0f\ucc10\ucc11\ucc12\ucc13\ucc14\ucc15\ucc16\ucc17\ucc18\ucc19\ucc1a\ucc1b\ucc1c\ucc1d\ucc1e\ucc1f\ucc20\ucc21\ucc22\ucc23\ucc24\ucc25\ucc26\ucc27\ucc28\ucc29\ucc2a\ucc2b\ucc2c\ucc2d\ucc2e\ucc2f\ucc30\ucc31\ucc32\ucc33\ucc34\ucc35\ucc36\ucc37\ucc38\ucc39\ucc3a\ucc3b\ucc3c\ucc3d\ucc3e\ucc3f\ucc40\ucc41\ucc42\ucc43\ucc44\ucc45\ucc46\ucc47\ucc48\ucc49\ucc4a\ucc4b\ucc4c\ucc4d\ucc4e\ucc4f\ucc50\ucc51\ucc52\ucc53\ucc54\ucc55\ucc56\ucc57\ucc58\ucc59\ucc5a\ucc5b\ucc5c\ucc5d\ucc5e\ucc5f\ucc60\ucc61\ucc62\ucc63\ucc64\ucc65\ucc66\ucc67\ucc68\ucc69\ucc6a\ucc6b\ucc6c\ucc6d\ucc6e\ucc6f\ucc70\ucc71\ucc72\ucc73\ucc74\ucc75\ucc76\ucc77\ucc78\ucc79\ucc7a\ucc7b\ucc7c\ucc7d\ucc7e\ucc7f\ucc80\ucc81\ucc82\ucc83\ucc84\ucc85\ucc86\ucc87\ucc88\ucc89\ucc8a\ucc8b\ucc8c\ucc8d\ucc8e\ucc8f\ucc90\ucc91\ucc92\ucc93\ucc94\ucc95\ucc96\ucc97\ucc98\ucc99\ucc9a\ucc9b\ucc9c\ucc9d\ucc9e\ucc9f\ucca0\ucca1\ucca2\ucca3\ucca4\ucca5\ucca6\ucca7\ucca8\ucca9\uccaa\uccab\uccac\uccad\uccae\uccaf\uccb0\uccb1\uccb2\uccb3\uccb4\uccb5\uccb6\uccb7\uccb8\uccb9\uccba\uccbb\uccbc\uccbd\uccbe\uccbf\uccc0\uccc1\uccc2\uccc3\uccc4\uccc5\uccc6\uccc7\uccc8\uccc9\uccca\ucccb\ucccc\ucccd\uccce\ucccf\uccd0\uccd1\uccd2\uccd3\uccd4\uccd5\uccd6\uccd7\uccd8\uccd9\uccda\uccdb\uccdc\uccdd\uccde\uccdf\ucce0\ucce1\ucce2\ucce3\ucce4\ucce5\ucce6\ucce7\ucce8\ucce9\uccea\ucceb\uccec\ucced\uccee\uccef\uccf0\uccf1\uccf2\uccf3\uccf4\uccf5\uccf6\uccf7\uccf8\uccf9\uccfa\uccfb\uccfc\uccfd\uccfe\uccff\ucd00\ucd01\ucd02\ucd03\ucd04\ucd05\ucd06\ucd07\ucd08\ucd09\ucd0a\ucd0b\ucd0c\ucd0d\ucd0e\ucd0f\ucd10\ucd11\ucd12\ucd13\ucd14\ucd15\ucd16\ucd17\ucd18\ucd19\ucd1a\ucd1b\ucd1c\ucd1d\ucd1e\ucd1f\ucd20\ucd21\ucd22\ucd23\ucd24\ucd25\ucd26\ucd27\ucd28\ucd29\ucd2a\ucd2b\ucd2c\ucd2d\ucd2e\ucd2f\ucd30\ucd31\ucd32\ucd33\ucd34\ucd35\ucd36\ucd37\ucd38\ucd39\ucd3a\ucd3b\ucd3c\ucd3d\ucd3e\ucd3f\ucd40\ucd41\ucd42\ucd43\ucd44\ucd45\ucd46\ucd47\ucd48\ucd49\ucd4a\ucd4b\ucd4c\ucd4d\ucd4e\ucd4f\ucd50\ucd51\ucd52\ucd53\ucd54\ucd55\ucd56\ucd57\ucd58\ucd59\ucd5a\ucd5b\ucd5c\ucd5d\ucd5e\ucd5f\ucd60\ucd61\ucd62\ucd63\ucd64\ucd65\ucd66\ucd67\ucd68\ucd69\ucd6a\ucd6b\ucd6c\ucd6d\ucd6e\ucd6f\ucd70\ucd71\ucd72\ucd73\ucd74\ucd75\ucd76\ucd77\ucd78\ucd79\ucd7a\ucd7b\ucd7c\ucd7d\ucd7e\ucd7f\ucd80\ucd81\ucd82\ucd83\ucd84\ucd85\ucd86\ucd87\ucd88\ucd89\ucd8a\ucd8b\ucd8c\ucd8d\ucd8e\ucd8f\ucd90\ucd91\ucd92\ucd93\ucd94\ucd95\ucd96\ucd97\ucd98\ucd99\ucd9a\ucd9b\ucd9c\ucd9d\ucd9e\ucd9f\ucda0\ucda1\ucda2\ucda3\ucda4\ucda5\ucda6\ucda7\ucda8\ucda9\ucdaa\ucdab\ucdac\ucdad\ucdae\ucdaf\ucdb0\ucdb1\ucdb2\ucdb3\ucdb4\ucdb5\ucdb6\ucdb7\ucdb8\ucdb9\ucdba\ucdbb\ucdbc\ucdbd\ucdbe\ucdbf\ucdc0\ucdc1\ucdc2\ucdc3\ucdc4\ucdc5\ucdc6\ucdc7\ucdc8\ucdc9\ucdca\ucdcb\ucdcc\ucdcd\ucdce\ucdcf\ucdd0\ucdd1\ucdd2\ucdd3\ucdd4\ucdd5\ucdd6\ucdd7\ucdd8\ucdd9\ucdda\ucddb\ucddc\ucddd\ucdde\ucddf\ucde0\ucde1\ucde2\ucde3\ucde4\ucde5\ucde6\ucde7\ucde8\ucde9\ucdea\ucdeb\ucdec\ucded\ucdee\ucdef\ucdf0\ucdf1\ucdf2\ucdf3\ucdf4\ucdf5\ucdf6\ucdf7\ucdf8\ucdf9\ucdfa\ucdfb\ucdfc\ucdfd\ucdfe\ucdff\uce00\uce01\uce02\uce03\uce04\uce05\uce06\uce07\uce08\uce09\uce0a\uce0b\uce0c\uce0d\uce0e\uce0f\uce10\uce11\uce12\uce13\uce14\uce15\uce16\uce17\uce18\uce19\uce1a\uce1b\uce1c\uce1d\uce1e\uce1f\uce20\uce21\uce22\uce23\uce24\uce25\uce26\uce27\uce28\uce29\uce2a\uce2b\uce2c\uce2d\uce2e\uce2f\uce30\uce31\uce32\uce33\uce34\uce35\uce36\uce37\uce38\uce39\uce3a\uce3b\uce3c\uce3d\uce3e\uce3f\uce40\uce41\uce42\uce43\uce44\uce45\uce46\uce47\uce48\uce49\uce4a\uce4b\uce4c\uce4d\uce4e\uce4f\uce50\uce51\uce52\uce53\uce54\uce55\uce56\uce57\uce58\uce59\uce5a\uce5b\uce5c\uce5d\uce5e\uce5f\uce60\uce61\uce62\uce63\uce64\uce65\uce66\uce67\uce68\uce69\uce6a\uce6b\uce6c\uce6d\uce6e\uce6f\uce70\uce71\uce72\uce73\uce74\uce75\uce76\uce77\uce78\uce79\uce7a\uce7b\uce7c\uce7d\uce7e\uce7f\uce80\uce81\uce82\uce83\uce84\uce85\uce86\uce87\uce88\uce89\uce8a\uce8b\uce8c\uce8d\uce8e\uce8f\uce90\uce91\uce92\uce93\uce94\uce95\uce96\uce97\uce98\uce99\uce9a\uce9b\uce9c\uce9d\uce9e\uce9f\ucea0\ucea1\ucea2\ucea3\ucea4\ucea5\ucea6\ucea7\ucea8\ucea9\uceaa\uceab\uceac\ucead\uceae\uceaf\uceb0\uceb1\uceb2\uceb3\uceb4\uceb5\uceb6\uceb7\uceb8\uceb9\uceba\ucebb\ucebc\ucebd\ucebe\ucebf\ucec0\ucec1\ucec2\ucec3\ucec4\ucec5\ucec6\ucec7\ucec8\ucec9\uceca\ucecb\ucecc\ucecd\ucece\ucecf\uced0\uced1\uced2\uced3\uced4\uced5\uced6\uced7\uced8\uced9\uceda\ucedb\ucedc\ucedd\ucede\ucedf\ucee0\ucee1\ucee2\ucee3\ucee4\ucee5\ucee6\ucee7\ucee8\ucee9\uceea\uceeb\uceec\uceed\uceee\uceef\ucef0\ucef1\ucef2\ucef3\ucef4\ucef5\ucef6\ucef7\ucef8\ucef9\ucefa\ucefb\ucefc\ucefd\ucefe\uceff\ucf00\ucf01\ucf02\ucf03\ucf04\ucf05\ucf06\ucf07\ucf08\ucf09\ucf0a\ucf0b\ucf0c\ucf0d\ucf0e\ucf0f\ucf10\ucf11\ucf12\ucf13\ucf14\ucf15\ucf16\ucf17\ucf18\ucf19\ucf1a\ucf1b\ucf1c\ucf1d\ucf1e\ucf1f\ucf20\ucf21\ucf22\ucf23\ucf24\ucf25\ucf26\ucf27\ucf28\ucf29\ucf2a\ucf2b\ucf2c\ucf2d\ucf2e\ucf2f\ucf30\ucf31\ucf32\ucf33\ucf34\ucf35\ucf36\ucf37\ucf38\ucf39\ucf3a\ucf3b\ucf3c\ucf3d\ucf3e\ucf3f\ucf40\ucf41\ucf42\ucf43\ucf44\ucf45\ucf46\ucf47\ucf48\ucf49\ucf4a\ucf4b\ucf4c\ucf4d\ucf4e\ucf4f\ucf50\ucf51\ucf52\ucf53\ucf54\ucf55\ucf56\ucf57\ucf58\ucf59\ucf5a\ucf5b\ucf5c\ucf5d\ucf5e\ucf5f\ucf60\ucf61\ucf62\ucf63\ucf64\ucf65\ucf66\ucf67\ucf68\ucf69\ucf6a\ucf6b\ucf6c\ucf6d\ucf6e\ucf6f\ucf70\ucf71\ucf72\ucf73\ucf74\ucf75\ucf76\ucf77\ucf78\ucf79\ucf7a\ucf7b\ucf7c\ucf7d\ucf7e\ucf7f\ucf80\ucf81\ucf82\ucf83\ucf84\ucf85\ucf86\ucf87\ucf88\ucf89\ucf8a\ucf8b\ucf8c\ucf8d\ucf8e\ucf8f\ucf90\ucf91\ucf92\ucf93\ucf94\ucf95\ucf96\ucf97\ucf98\ucf99\ucf9a\ucf9b\ucf9c\ucf9d\ucf9e\ucf9f\ucfa0\ucfa1\ucfa2\ucfa3\ucfa4\ucfa5\ucfa6\ucfa7\ucfa8\ucfa9\ucfaa\ucfab\ucfac\ucfad\ucfae\ucfaf\ucfb0\ucfb1\ucfb2\ucfb3\ucfb4\ucfb5\ucfb6\ucfb7\ucfb8\ucfb9\ucfba\ucfbb\ucfbc\ucfbd\ucfbe\ucfbf\ucfc0\ucfc1\ucfc2\ucfc3\ucfc4\ucfc5\ucfc6\ucfc7\ucfc8\ucfc9\ucfca\ucfcb\ucfcc\ucfcd\ucfce\ucfcf\ucfd0\ucfd1\ucfd2\ucfd3\ucfd4\ucfd5\ucfd6\ucfd7\ucfd8\ucfd9\ucfda\ucfdb\ucfdc\ucfdd\ucfde\ucfdf\ucfe0\ucfe1\ucfe2\ucfe3\ucfe4\ucfe5\ucfe6\ucfe7\ucfe8\ucfe9\ucfea\ucfeb\ucfec\ucfed\ucfee\ucfef\ucff0\ucff1\ucff2\ucff3\ucff4\ucff5\ucff6\ucff7\ucff8\ucff9\ucffa\ucffb\ucffc\ucffd\ucffe\ucfff\ud000\ud001\ud002\ud003\ud004\ud005\ud006\ud007\ud008\ud009\ud00a\ud00b\ud00c\ud00d\ud00e\ud00f\ud010\ud011\ud012\ud013\ud014\ud015\ud016\ud017\ud018\ud019\ud01a\ud01b\ud01c\ud01d\ud01e\ud01f\ud020\ud021\ud022\ud023\ud024\ud025\ud026\ud027\ud028\ud029\ud02a\ud02b\ud02c\ud02d\ud02e\ud02f\ud030\ud031\ud032\ud033\ud034\ud035\ud036\ud037\ud038\ud039\ud03a\ud03b\ud03c\ud03d\ud03e\ud03f\ud040\ud041\ud042\ud043\ud044\ud045\ud046\ud047\ud048\ud049\ud04a\ud04b\ud04c\ud04d\ud04e\ud04f\ud050\ud051\ud052\ud053\ud054\ud055\ud056\ud057\ud058\ud059\ud05a\ud05b\ud05c\ud05d\ud05e\ud05f\ud060\ud061\ud062\ud063\ud064\ud065\ud066\ud067\ud068\ud069\ud06a\ud06b\ud06c\ud06d\ud06e\ud06f\ud070\ud071\ud072\ud073\ud074\ud075\ud076\ud077\ud078\ud079\ud07a\ud07b\ud07c\ud07d\ud07e\ud07f\ud080\ud081\ud082\ud083\ud084\ud085\ud086\ud087\ud088\ud089\ud08a\ud08b\ud08c\ud08d\ud08e\ud08f\ud090\ud091\ud092\ud093\ud094\ud095\ud096\ud097\ud098\ud099\ud09a\ud09b\ud09c\ud09d\ud09e\ud09f\ud0a0\ud0a1\ud0a2\ud0a3\ud0a4\ud0a5\ud0a6\ud0a7\ud0a8\ud0a9\ud0aa\ud0ab\ud0ac\ud0ad\ud0ae\ud0af\ud0b0\ud0b1\ud0b2\ud0b3\ud0b4\ud0b5\ud0b6\ud0b7\ud0b8\ud0b9\ud0ba\ud0bb\ud0bc\ud0bd\ud0be\ud0bf\ud0c0\ud0c1\ud0c2\ud0c3\ud0c4\ud0c5\ud0c6\ud0c7\ud0c8\ud0c9\ud0ca\ud0cb\ud0cc\ud0cd\ud0ce\ud0cf\ud0d0\ud0d1\ud0d2\ud0d3\ud0d4\ud0d5\ud0d6\ud0d7\ud0d8\ud0d9\ud0da\ud0db\ud0dc\ud0dd\ud0de\ud0df\ud0e0\ud0e1\ud0e2\ud0e3\ud0e4\ud0e5\ud0e6\ud0e7\ud0e8\ud0e9\ud0ea\ud0eb\ud0ec\ud0ed\ud0ee\ud0ef\ud0f0\ud0f1\ud0f2\ud0f3\ud0f4\ud0f5\ud0f6\ud0f7\ud0f8\ud0f9\ud0fa\ud0fb\ud0fc\ud0fd\ud0fe\ud0ff\ud100\ud101\ud102\ud103\ud104\ud105\ud106\ud107\ud108\ud109\ud10a\ud10b\ud10c\ud10d\ud10e\ud10f\ud110\ud111\ud112\ud113\ud114\ud115\ud116\ud117\ud118\ud119\ud11a\ud11b\ud11c\ud11d\ud11e\ud11f\ud120\ud121\ud122\ud123\ud124\ud125\ud126\ud127\ud128\ud129\ud12a\ud12b\ud12c\ud12d\ud12e\ud12f\ud130\ud131\ud132\ud133\ud134\ud135\ud136\ud137\ud138\ud139\ud13a\ud13b\ud13c\ud13d\ud13e\ud13f\ud140\ud141\ud142\ud143\ud144\ud145\ud146\ud147\ud148\ud149\ud14a\ud14b\ud14c\ud14d\ud14e\ud14f\ud150\ud151\ud152\ud153\ud154\ud155\ud156\ud157\ud158\ud159\ud15a\ud15b\ud15c\ud15d\ud15e\ud15f\ud160\ud161\ud162\ud163\ud164\ud165\ud166\ud167\ud168\ud169\ud16a\ud16b\ud16c\ud16d\ud16e\ud16f\ud170\ud171\ud172\ud173\ud174\ud175\ud176\ud177\ud178\ud179\ud17a\ud17b\ud17c\ud17d\ud17e\ud17f\ud180\ud181\ud182\ud183\ud184\ud185\ud186\ud187\ud188\ud189\ud18a\ud18b\ud18c\ud18d\ud18e\ud18f\ud190\ud191\ud192\ud193\ud194\ud195\ud196\ud197\ud198\ud199\ud19a\ud19b\ud19c\ud19d\ud19e\ud19f\ud1a0\ud1a1\ud1a2\ud1a3\ud1a4\ud1a5\ud1a6\ud1a7\ud1a8\ud1a9\ud1aa\ud1ab\ud1ac\ud1ad\ud1ae\ud1af\ud1b0\ud1b1\ud1b2\ud1b3\ud1b4\ud1b5\ud1b6\ud1b7\ud1b8\ud1b9\ud1ba\ud1bb\ud1bc\ud1bd\ud1be\ud1bf\ud1c0\ud1c1\ud1c2\ud1c3\ud1c4\ud1c5\ud1c6\ud1c7\ud1c8\ud1c9\ud1ca\ud1cb\ud1cc\ud1cd\ud1ce\ud1cf\ud1d0\ud1d1\ud1d2\ud1d3\ud1d4\ud1d5\ud1d6\ud1d7\ud1d8\ud1d9\ud1da\ud1db\ud1dc\ud1dd\ud1de\ud1df\ud1e0\ud1e1\ud1e2\ud1e3\ud1e4\ud1e5\ud1e6\ud1e7\ud1e8\ud1e9\ud1ea\ud1eb\ud1ec\ud1ed\ud1ee\ud1ef\ud1f0\ud1f1\ud1f2\ud1f3\ud1f4\ud1f5\ud1f6\ud1f7\ud1f8\ud1f9\ud1fa\ud1fb\ud1fc\ud1fd\ud1fe\ud1ff\ud200\ud201\ud202\ud203\ud204\ud205\ud206\ud207\ud208\ud209\ud20a\ud20b\ud20c\ud20d\ud20e\ud20f\ud210\ud211\ud212\ud213\ud214\ud215\ud216\ud217\ud218\ud219\ud21a\ud21b\ud21c\ud21d\ud21e\ud21f\ud220\ud221\ud222\ud223\ud224\ud225\ud226\ud227\ud228\ud229\ud22a\ud22b\ud22c\ud22d\ud22e\ud22f\ud230\ud231\ud232\ud233\ud234\ud235\ud236\ud237\ud238\ud239\ud23a\ud23b\ud23c\ud23d\ud23e\ud23f\ud240\ud241\ud242\ud243\ud244\ud245\ud246\ud247\ud248\ud249\ud24a\ud24b\ud24c\ud24d\ud24e\ud24f\ud250\ud251\ud252\ud253\ud254\ud255\ud256\ud257\ud258\ud259\ud25a\ud25b\ud25c\ud25d\ud25e\ud25f\ud260\ud261\ud262\ud263\ud264\ud265\ud266\ud267\ud268\ud269\ud26a\ud26b\ud26c\ud26d\ud26e\ud26f\ud270\ud271\ud272\ud273\ud274\ud275\ud276\ud277\ud278\ud279\ud27a\ud27b\ud27c\ud27d\ud27e\ud27f\ud280\ud281\ud282\ud283\ud284\ud285\ud286\ud287\ud288\ud289\ud28a\ud28b\ud28c\ud28d\ud28e\ud28f\ud290\ud291\ud292\ud293\ud294\ud295\ud296\ud297\ud298\ud299\ud29a\ud29b\ud29c\ud29d\ud29e\ud29f\ud2a0\ud2a1\ud2a2\ud2a3\ud2a4\ud2a5\ud2a6\ud2a7\ud2a8\ud2a9\ud2aa\ud2ab\ud2ac\ud2ad\ud2ae\ud2af\ud2b0\ud2b1\ud2b2\ud2b3\ud2b4\ud2b5\ud2b6\ud2b7\ud2b8\ud2b9\ud2ba\ud2bb\ud2bc\ud2bd\ud2be\ud2bf\ud2c0\ud2c1\ud2c2\ud2c3\ud2c4\ud2c5\ud2c6\ud2c7\ud2c8\ud2c9\ud2ca\ud2cb\ud2cc\ud2cd\ud2ce\ud2cf\ud2d0\ud2d1\ud2d2\ud2d3\ud2d4\ud2d5\ud2d6\ud2d7\ud2d8\ud2d9\ud2da\ud2db\ud2dc\ud2dd\ud2de\ud2df\ud2e0\ud2e1\ud2e2\ud2e3\ud2e4\ud2e5\ud2e6\ud2e7\ud2e8\ud2e9\ud2ea\ud2eb\ud2ec\ud2ed\ud2ee\ud2ef\ud2f0\ud2f1\ud2f2\ud2f3\ud2f4\ud2f5\ud2f6\ud2f7\ud2f8\ud2f9\ud2fa\ud2fb\ud2fc\ud2fd\ud2fe\ud2ff\ud300\ud301\ud302\ud303\ud304\ud305\ud306\ud307\ud308\ud309\ud30a\ud30b\ud30c\ud30d\ud30e\ud30f\ud310\ud311\ud312\ud313\ud314\ud315\ud316\ud317\ud318\ud319\ud31a\ud31b\ud31c\ud31d\ud31e\ud31f\ud320\ud321\ud322\ud323\ud324\ud325\ud326\ud327\ud328\ud329\ud32a\ud32b\ud32c\ud32d\ud32e\ud32f\ud330\ud331\ud332\ud333\ud334\ud335\ud336\ud337\ud338\ud339\ud33a\ud33b\ud33c\ud33d\ud33e\ud33f\ud340\ud341\ud342\ud343\ud344\ud345\ud346\ud347\ud348\ud349\ud34a\ud34b\ud34c\ud34d\ud34e\ud34f\ud350\ud351\ud352\ud353\ud354\ud355\ud356\ud357\ud358\ud359\ud35a\ud35b\ud35c\ud35d\ud35e\ud35f\ud360\ud361\ud362\ud363\ud364\ud365\ud366\ud367\ud368\ud369\ud36a\ud36b\ud36c\ud36d\ud36e\ud36f\ud370\ud371\ud372\ud373\ud374\ud375\ud376\ud377\ud378\ud379\ud37a\ud37b\ud37c\ud37d\ud37e\ud37f\ud380\ud381\ud382\ud383\ud384\ud385\ud386\ud387\ud388\ud389\ud38a\ud38b\ud38c\ud38d\ud38e\ud38f\ud390\ud391\ud392\ud393\ud394\ud395\ud396\ud397\ud398\ud399\ud39a\ud39b\ud39c\ud39d\ud39e\ud39f\ud3a0\ud3a1\ud3a2\ud3a3\ud3a4\ud3a5\ud3a6\ud3a7\ud3a8\ud3a9\ud3aa\ud3ab\ud3ac\ud3ad\ud3ae\ud3af\ud3b0\ud3b1\ud3b2\ud3b3\ud3b4\ud3b5\ud3b6\ud3b7\ud3b8\ud3b9\ud3ba\ud3bb\ud3bc\ud3bd\ud3be\ud3bf\ud3c0\ud3c1\ud3c2\ud3c3\ud3c4\ud3c5\ud3c6\ud3c7\ud3c8\ud3c9\ud3ca\ud3cb\ud3cc\ud3cd\ud3ce\ud3cf\ud3d0\ud3d1\ud3d2\ud3d3\ud3d4\ud3d5\ud3d6\ud3d7\ud3d8\ud3d9\ud3da\ud3db\ud3dc\ud3dd\ud3de\ud3df\ud3e0\ud3e1\ud3e2\ud3e3\ud3e4\ud3e5\ud3e6\ud3e7\ud3e8\ud3e9\ud3ea\ud3eb\ud3ec\ud3ed\ud3ee\ud3ef\ud3f0\ud3f1\ud3f2\ud3f3\ud3f4\ud3f5\ud3f6\ud3f7\ud3f8\ud3f9\ud3fa\ud3fb\ud3fc\ud3fd\ud3fe\ud3ff\ud400\ud401\ud402\ud403\ud404\ud405\ud406\ud407\ud408\ud409\ud40a\ud40b\ud40c\ud40d\ud40e\ud40f\ud410\ud411\ud412\ud413\ud414\ud415\ud416\ud417\ud418\ud419\ud41a\ud41b\ud41c\ud41d\ud41e\ud41f\ud420\ud421\ud422\ud423\ud424\ud425\ud426\ud427\ud428\ud429\ud42a\ud42b\ud42c\ud42d\ud42e\ud42f\ud430\ud431\ud432\ud433\ud434\ud435\ud436\ud437\ud438\ud439\ud43a\ud43b\ud43c\ud43d\ud43e\ud43f\ud440\ud441\ud442\ud443\ud444\ud445\ud446\ud447\ud448\ud449\ud44a\ud44b\ud44c\ud44d\ud44e\ud44f\ud450\ud451\ud452\ud453\ud454\ud455\ud456\ud457\ud458\ud459\ud45a\ud45b\ud45c\ud45d\ud45e\ud45f\ud460\ud461\ud462\ud463\ud464\ud465\ud466\ud467\ud468\ud469\ud46a\ud46b\ud46c\ud46d\ud46e\ud46f\ud470\ud471\ud472\ud473\ud474\ud475\ud476\ud477\ud478\ud479\ud47a\ud47b\ud47c\ud47d\ud47e\ud47f\ud480\ud481\ud482\ud483\ud484\ud485\ud486\ud487\ud488\ud489\ud48a\ud48b\ud48c\ud48d\ud48e\ud48f\ud490\ud491\ud492\ud493\ud494\ud495\ud496\ud497\ud498\ud499\ud49a\ud49b\ud49c\ud49d\ud49e\ud49f\ud4a0\ud4a1\ud4a2\ud4a3\ud4a4\ud4a5\ud4a6\ud4a7\ud4a8\ud4a9\ud4aa\ud4ab\ud4ac\ud4ad\ud4ae\ud4af\ud4b0\ud4b1\ud4b2\ud4b3\ud4b4\ud4b5\ud4b6\ud4b7\ud4b8\ud4b9\ud4ba\ud4bb\ud4bc\ud4bd\ud4be\ud4bf\ud4c0\ud4c1\ud4c2\ud4c3\ud4c4\ud4c5\ud4c6\ud4c7\ud4c8\ud4c9\ud4ca\ud4cb\ud4cc\ud4cd\ud4ce\ud4cf\ud4d0\ud4d1\ud4d2\ud4d3\ud4d4\ud4d5\ud4d6\ud4d7\ud4d8\ud4d9\ud4da\ud4db\ud4dc\ud4dd\ud4de\ud4df\ud4e0\ud4e1\ud4e2\ud4e3\ud4e4\ud4e5\ud4e6\ud4e7\ud4e8\ud4e9\ud4ea\ud4eb\ud4ec\ud4ed\ud4ee\ud4ef\ud4f0\ud4f1\ud4f2\ud4f3\ud4f4\ud4f5\ud4f6\ud4f7\ud4f8\ud4f9\ud4fa\ud4fb\ud4fc\ud4fd\ud4fe\ud4ff\ud500\ud501\ud502\ud503\ud504\ud505\ud506\ud507\ud508\ud509\ud50a\ud50b\ud50c\ud50d\ud50e\ud50f\ud510\ud511\ud512\ud513\ud514\ud515\ud516\ud517\ud518\ud519\ud51a\ud51b\ud51c\ud51d\ud51e\ud51f\ud520\ud521\ud522\ud523\ud524\ud525\ud526\ud527\ud528\ud529\ud52a\ud52b\ud52c\ud52d\ud52e\ud52f\ud530\ud531\ud532\ud533\ud534\ud535\ud536\ud537\ud538\ud539\ud53a\ud53b\ud53c\ud53d\ud53e\ud53f\ud540\ud541\ud542\ud543\ud544\ud545\ud546\ud547\ud548\ud549\ud54a\ud54b\ud54c\ud54d\ud54e\ud54f\ud550\ud551\ud552\ud553\ud554\ud555\ud556\ud557\ud558\ud559\ud55a\ud55b\ud55c\ud55d\ud55e\ud55f\ud560\ud561\ud562\ud563\ud564\ud565\ud566\ud567\ud568\ud569\ud56a\ud56b\ud56c\ud56d\ud56e\ud56f\ud570\ud571\ud572\ud573\ud574\ud575\ud576\ud577\ud578\ud579\ud57a\ud57b\ud57c\ud57d\ud57e\ud57f\ud580\ud581\ud582\ud583\ud584\ud585\ud586\ud587\ud588\ud589\ud58a\ud58b\ud58c\ud58d\ud58e\ud58f\ud590\ud591\ud592\ud593\ud594\ud595\ud596\ud597\ud598\ud599\ud59a\ud59b\ud59c\ud59d\ud59e\ud59f\ud5a0\ud5a1\ud5a2\ud5a3\ud5a4\ud5a5\ud5a6\ud5a7\ud5a8\ud5a9\ud5aa\ud5ab\ud5ac\ud5ad\ud5ae\ud5af\ud5b0\ud5b1\ud5b2\ud5b3\ud5b4\ud5b5\ud5b6\ud5b7\ud5b8\ud5b9\ud5ba\ud5bb\ud5bc\ud5bd\ud5be\ud5bf\ud5c0\ud5c1\ud5c2\ud5c3\ud5c4\ud5c5\ud5c6\ud5c7\ud5c8\ud5c9\ud5ca\ud5cb\ud5cc\ud5cd\ud5ce\ud5cf\ud5d0\ud5d1\ud5d2\ud5d3\ud5d4\ud5d5\ud5d6\ud5d7\ud5d8\ud5d9\ud5da\ud5db\ud5dc\ud5dd\ud5de\ud5df\ud5e0\ud5e1\ud5e2\ud5e3\ud5e4\ud5e5\ud5e6\ud5e7\ud5e8\ud5e9\ud5ea\ud5eb\ud5ec\ud5ed\ud5ee\ud5ef\ud5f0\ud5f1\ud5f2\ud5f3\ud5f4\ud5f5\ud5f6\ud5f7\ud5f8\ud5f9\ud5fa\ud5fb\ud5fc\ud5fd\ud5fe\ud5ff\ud600\ud601\ud602\ud603\ud604\ud605\ud606\ud607\ud608\ud609\ud60a\ud60b\ud60c\ud60d\ud60e\ud60f\ud610\ud611\ud612\ud613\ud614\ud615\ud616\ud617\ud618\ud619\ud61a\ud61b\ud61c\ud61d\ud61e\ud61f\ud620\ud621\ud622\ud623\ud624\ud625\ud626\ud627\ud628\ud629\ud62a\ud62b\ud62c\ud62d\ud62e\ud62f\ud630\ud631\ud632\ud633\ud634\ud635\ud636\ud637\ud638\ud639\ud63a\ud63b\ud63c\ud63d\ud63e\ud63f\ud640\ud641\ud642\ud643\ud644\ud645\ud646\ud647\ud648\ud649\ud64a\ud64b\ud64c\ud64d\ud64e\ud64f\ud650\ud651\ud652\ud653\ud654\ud655\ud656\ud657\ud658\ud659\ud65a\ud65b\ud65c\ud65d\ud65e\ud65f\ud660\ud661\ud662\ud663\ud664\ud665\ud666\ud667\ud668\ud669\ud66a\ud66b\ud66c\ud66d\ud66e\ud66f\ud670\ud671\ud672\ud673\ud674\ud675\ud676\ud677\ud678\ud679\ud67a\ud67b\ud67c\ud67d\ud67e\ud67f\ud680\ud681\ud682\ud683\ud684\ud685\ud686\ud687\ud688\ud689\ud68a\ud68b\ud68c\ud68d\ud68e\ud68f\ud690\ud691\ud692\ud693\ud694\ud695\ud696\ud697\ud698\ud699\ud69a\ud69b\ud69c\ud69d\ud69e\ud69f\ud6a0\ud6a1\ud6a2\ud6a3\ud6a4\ud6a5\ud6a6\ud6a7\ud6a8\ud6a9\ud6aa\ud6ab\ud6ac\ud6ad\ud6ae\ud6af\ud6b0\ud6b1\ud6b2\ud6b3\ud6b4\ud6b5\ud6b6\ud6b7\ud6b8\ud6b9\ud6ba\ud6bb\ud6bc\ud6bd\ud6be\ud6bf\ud6c0\ud6c1\ud6c2\ud6c3\ud6c4\ud6c5\ud6c6\ud6c7\ud6c8\ud6c9\ud6ca\ud6cb\ud6cc\ud6cd\ud6ce\ud6cf\ud6d0\ud6d1\ud6d2\ud6d3\ud6d4\ud6d5\ud6d6\ud6d7\ud6d8\ud6d9\ud6da\ud6db\ud6dc\ud6dd\ud6de\ud6df\ud6e0\ud6e1\ud6e2\ud6e3\ud6e4\ud6e5\ud6e6\ud6e7\ud6e8\ud6e9\ud6ea\ud6eb\ud6ec\ud6ed\ud6ee\ud6ef\ud6f0\ud6f1\ud6f2\ud6f3\ud6f4\ud6f5\ud6f6\ud6f7\ud6f8\ud6f9\ud6fa\ud6fb\ud6fc\ud6fd\ud6fe\ud6ff\ud700\ud701\ud702\ud703\ud704\ud705\ud706\ud707\ud708\ud709\ud70a\ud70b\ud70c\ud70d\ud70e\ud70f\ud710\ud711\ud712\ud713\ud714\ud715\ud716\ud717\ud718\ud719\ud71a\ud71b\ud71c\ud71d\ud71e\ud71f\ud720\ud721\ud722\ud723\ud724\ud725\ud726\ud727\ud728\ud729\ud72a\ud72b\ud72c\ud72d\ud72e\ud72f\ud730\ud731\ud732\ud733\ud734\ud735\ud736\ud737\ud738\ud739\ud73a\ud73b\ud73c\ud73d\ud73e\ud73f\ud740\ud741\ud742\ud743\ud744\ud745\ud746\ud747\ud748\ud749\ud74a\ud74b\ud74c\ud74d\ud74e\ud74f\ud750\ud751\ud752\ud753\ud754\ud755\ud756\ud757\ud758\ud759\ud75a\ud75b\ud75c\ud75d\ud75e\ud75f\ud760\ud761\ud762\ud763\ud764\ud765\ud766\ud767\ud768\ud769\ud76a\ud76b\ud76c\ud76d\ud76e\ud76f\ud770\ud771\ud772\ud773\ud774\ud775\ud776\ud777\ud778\ud779\ud77a\ud77b\ud77c\ud77d\ud77e\ud77f\ud780\ud781\ud782\ud783\ud784\ud785\ud786\ud787\ud788\ud789\ud78a\ud78b\ud78c\ud78d\ud78e\ud78f\ud790\ud791\ud792\ud793\ud794\ud795\ud796\ud797\ud798\ud799\ud79a\ud79b\ud79c\ud79d\ud79e\ud79f\ud7a0\ud7a1\ud7a2\ud7a3\uf900\uf901\uf902\uf903\uf904\uf905\uf906\uf907\uf908\uf909\uf90a\uf90b\uf90c\uf90d\uf90e\uf90f\uf910\uf911\uf912\uf913\uf914\uf915\uf916\uf917\uf918\uf919\uf91a\uf91b\uf91c\uf91d\uf91e\uf91f\uf920\uf921\uf922\uf923\uf924\uf925\uf926\uf927\uf928\uf929\uf92a\uf92b\uf92c\uf92d\uf92e\uf92f\uf930\uf931\uf932\uf933\uf934\uf935\uf936\uf937\uf938\uf939\uf93a\uf93b\uf93c\uf93d\uf93e\uf93f\uf940\uf941\uf942\uf943\uf944\uf945\uf946\uf947\uf948\uf949\uf94a\uf94b\uf94c\uf94d\uf94e\uf94f\uf950\uf951\uf952\uf953\uf954\uf955\uf956\uf957\uf958\uf959\uf95a\uf95b\uf95c\uf95d\uf95e\uf95f\uf960\uf961\uf962\uf963\uf964\uf965\uf966\uf967\uf968\uf969\uf96a\uf96b\uf96c\uf96d\uf96e\uf96f\uf970\uf971\uf972\uf973\uf974\uf975\uf976\uf977\uf978\uf979\uf97a\uf97b\uf97c\uf97d\uf97e\uf97f\uf980\uf981\uf982\uf983\uf984\uf985\uf986\uf987\uf988\uf989\uf98a\uf98b\uf98c\uf98d\uf98e\uf98f\uf990\uf991\uf992\uf993\uf994\uf995\uf996\uf997\uf998\uf999\uf99a\uf99b\uf99c\uf99d\uf99e\uf99f\uf9a0\uf9a1\uf9a2\uf9a3\uf9a4\uf9a5\uf9a6\uf9a7\uf9a8\uf9a9\uf9aa\uf9ab\uf9ac\uf9ad\uf9ae\uf9af\uf9b0\uf9b1\uf9b2\uf9b3\uf9b4\uf9b5\uf9b6\uf9b7\uf9b8\uf9b9\uf9ba\uf9bb\uf9bc\uf9bd\uf9be\uf9bf\uf9c0\uf9c1\uf9c2\uf9c3\uf9c4\uf9c5\uf9c6\uf9c7\uf9c8\uf9c9\uf9ca\uf9cb\uf9cc\uf9cd\uf9ce\uf9cf\uf9d0\uf9d1\uf9d2\uf9d3\uf9d4\uf9d5\uf9d6\uf9d7\uf9d8\uf9d9\uf9da\uf9db\uf9dc\uf9dd\uf9de\uf9df\uf9e0\uf9e1\uf9e2\uf9e3\uf9e4\uf9e5\uf9e6\uf9e7\uf9e8\uf9e9\uf9ea\uf9eb\uf9ec\uf9ed\uf9ee\uf9ef\uf9f0\uf9f1\uf9f2\uf9f3\uf9f4\uf9f5\uf9f6\uf9f7\uf9f8\uf9f9\uf9fa\uf9fb\uf9fc\uf9fd\uf9fe\uf9ff\ufa00\ufa01\ufa02\ufa03\ufa04\ufa05\ufa06\ufa07\ufa08\ufa09\ufa0a\ufa0b\ufa0c\ufa0d\ufa0e\ufa0f\ufa10\ufa11\ufa12\ufa13\ufa14\ufa15\ufa16\ufa17\ufa18\ufa19\ufa1a\ufa1b\ufa1c\ufa1d\ufa1e\ufa1f\ufa20\ufa21\ufa22\ufa23\ufa24\ufa25\ufa26\ufa27\ufa28\ufa29\ufa2a\ufa2b\ufa2c\ufa2d\ufa30\ufa31\ufa32\ufa33\ufa34\ufa35\ufa36\ufa37\ufa38\ufa39\ufa3a\ufa3b\ufa3c\ufa3d\ufa3e\ufa3f\ufa40\ufa41\ufa42\ufa43\ufa44\ufa45\ufa46\ufa47\ufa48\ufa49\ufa4a\ufa4b\ufa4c\ufa4d\ufa4e\ufa4f\ufa50\ufa51\ufa52\ufa53\ufa54\ufa55\ufa56\ufa57\ufa58\ufa59\ufa5a\ufa5b\ufa5c\ufa5d\ufa5e\ufa5f\ufa60\ufa61\ufa62\ufa63\ufa64\ufa65\ufa66\ufa67\ufa68\ufa69\ufa6a\ufa70\ufa71\ufa72\ufa73\ufa74\ufa75\ufa76\ufa77\ufa78\ufa79\ufa7a\ufa7b\ufa7c\ufa7d\ufa7e\ufa7f\ufa80\ufa81\ufa82\ufa83\ufa84\ufa85\ufa86\ufa87\ufa88\ufa89\ufa8a\ufa8b\ufa8c\ufa8d\ufa8e\ufa8f\ufa90\ufa91\ufa92\ufa93\ufa94\ufa95\ufa96\ufa97\ufa98\ufa99\ufa9a\ufa9b\ufa9c\ufa9d\ufa9e\ufa9f\ufaa0\ufaa1\ufaa2\ufaa3\ufaa4\ufaa5\ufaa6\ufaa7\ufaa8\ufaa9\ufaaa\ufaab\ufaac\ufaad\ufaae\ufaaf\ufab0\ufab1\ufab2\ufab3\ufab4\ufab5\ufab6\ufab7\ufab8\ufab9\ufaba\ufabb\ufabc\ufabd\ufabe\ufabf\ufac0\ufac1\ufac2\ufac3\ufac4\ufac5\ufac6\ufac7\ufac8\ufac9\ufaca\ufacb\ufacc\ufacd\uface\ufacf\ufad0\ufad1\ufad2\ufad3\ufad4\ufad5\ufad6\ufad7\ufad8\ufad9\ufb1d\ufb1f\ufb20\ufb21\ufb22\ufb23\ufb24\ufb25\ufb26\ufb27\ufb28\ufb2a\ufb2b\ufb2c\ufb2d\ufb2e\ufb2f\ufb30\ufb31\ufb32\ufb33\ufb34\ufb35\ufb36\ufb38\ufb39\ufb3a\ufb3b\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46\ufb47\ufb48\ufb49\ufb4a\ufb4b\ufb4c\ufb4d\ufb4e\ufb4f\ufb50\ufb51\ufb52\ufb53\ufb54\ufb55\ufb56\ufb57\ufb58\ufb59\ufb5a\ufb5b\ufb5c\ufb5d\ufb5e\ufb5f\ufb60\ufb61\ufb62\ufb63\ufb64\ufb65\ufb66\ufb67\ufb68\ufb69\ufb6a\ufb6b\ufb6c\ufb6d\ufb6e\ufb6f\ufb70\ufb71\ufb72\ufb73\ufb74\ufb75\ufb76\ufb77\ufb78\ufb79\ufb7a\ufb7b\ufb7c\ufb7d\ufb7e\ufb7f\ufb80\ufb81\ufb82\ufb83\ufb84\ufb85\ufb86\ufb87\ufb88\ufb89\ufb8a\ufb8b\ufb8c\ufb8d\ufb8e\ufb8f\ufb90\ufb91\ufb92\ufb93\ufb94\ufb95\ufb96\ufb97\ufb98\ufb99\ufb9a\ufb9b\ufb9c\ufb9d\ufb9e\ufb9f\ufba0\ufba1\ufba2\ufba3\ufba4\ufba5\ufba6\ufba7\ufba8\ufba9\ufbaa\ufbab\ufbac\ufbad\ufbae\ufbaf\ufbb0\ufbb1\ufbd3\ufbd4\ufbd5\ufbd6\ufbd7\ufbd8\ufbd9\ufbda\ufbdb\ufbdc\ufbdd\ufbde\ufbdf\ufbe0\ufbe1\ufbe2\ufbe3\ufbe4\ufbe5\ufbe6\ufbe7\ufbe8\ufbe9\ufbea\ufbeb\ufbec\ufbed\ufbee\ufbef\ufbf0\ufbf1\ufbf2\ufbf3\ufbf4\ufbf5\ufbf6\ufbf7\ufbf8\ufbf9\ufbfa\ufbfb\ufbfc\ufbfd\ufbfe\ufbff\ufc00\ufc01\ufc02\ufc03\ufc04\ufc05\ufc06\ufc07\ufc08\ufc09\ufc0a\ufc0b\ufc0c\ufc0d\ufc0e\ufc0f\ufc10\ufc11\ufc12\ufc13\ufc14\ufc15\ufc16\ufc17\ufc18\ufc19\ufc1a\ufc1b\ufc1c\ufc1d\ufc1e\ufc1f\ufc20\ufc21\ufc22\ufc23\ufc24\ufc25\ufc26\ufc27\ufc28\ufc29\ufc2a\ufc2b\ufc2c\ufc2d\ufc2e\ufc2f\ufc30\ufc31\ufc32\ufc33\ufc34\ufc35\ufc36\ufc37\ufc38\ufc39\ufc3a\ufc3b\ufc3c\ufc3d\ufc3e\ufc3f\ufc40\ufc41\ufc42\ufc43\ufc44\ufc45\ufc46\ufc47\ufc48\ufc49\ufc4a\ufc4b\ufc4c\ufc4d\ufc4e\ufc4f\ufc50\ufc51\ufc52\ufc53\ufc54\ufc55\ufc56\ufc57\ufc58\ufc59\ufc5a\ufc5b\ufc5c\ufc5d\ufc5e\ufc5f\ufc60\ufc61\ufc62\ufc63\ufc64\ufc65\ufc66\ufc67\ufc68\ufc69\ufc6a\ufc6b\ufc6c\ufc6d\ufc6e\ufc6f\ufc70\ufc71\ufc72\ufc73\ufc74\ufc75\ufc76\ufc77\ufc78\ufc79\ufc7a\ufc7b\ufc7c\ufc7d\ufc7e\ufc7f\ufc80\ufc81\ufc82\ufc83\ufc84\ufc85\ufc86\ufc87\ufc88\ufc89\ufc8a\ufc8b\ufc8c\ufc8d\ufc8e\ufc8f\ufc90\ufc91\ufc92\ufc93\ufc94\ufc95\ufc96\ufc97\ufc98\ufc99\ufc9a\ufc9b\ufc9c\ufc9d\ufc9e\ufc9f\ufca0\ufca1\ufca2\ufca3\ufca4\ufca5\ufca6\ufca7\ufca8\ufca9\ufcaa\ufcab\ufcac\ufcad\ufcae\ufcaf\ufcb0\ufcb1\ufcb2\ufcb3\ufcb4\ufcb5\ufcb6\ufcb7\ufcb8\ufcb9\ufcba\ufcbb\ufcbc\ufcbd\ufcbe\ufcbf\ufcc0\ufcc1\ufcc2\ufcc3\ufcc4\ufcc5\ufcc6\ufcc7\ufcc8\ufcc9\ufcca\ufccb\ufccc\ufccd\ufcce\ufccf\ufcd0\ufcd1\ufcd2\ufcd3\ufcd4\ufcd5\ufcd6\ufcd7\ufcd8\ufcd9\ufcda\ufcdb\ufcdc\ufcdd\ufcde\ufcdf\ufce0\ufce1\ufce2\ufce3\ufce4\ufce5\ufce6\ufce7\ufce8\ufce9\ufcea\ufceb\ufcec\ufced\ufcee\ufcef\ufcf0\ufcf1\ufcf2\ufcf3\ufcf4\ufcf5\ufcf6\ufcf7\ufcf8\ufcf9\ufcfa\ufcfb\ufcfc\ufcfd\ufcfe\ufcff\ufd00\ufd01\ufd02\ufd03\ufd04\ufd05\ufd06\ufd07\ufd08\ufd09\ufd0a\ufd0b\ufd0c\ufd0d\ufd0e\ufd0f\ufd10\ufd11\ufd12\ufd13\ufd14\ufd15\ufd16\ufd17\ufd18\ufd19\ufd1a\ufd1b\ufd1c\ufd1d\ufd1e\ufd1f\ufd20\ufd21\ufd22\ufd23\ufd24\ufd25\ufd26\ufd27\ufd28\ufd29\ufd2a\ufd2b\ufd2c\ufd2d\ufd2e\ufd2f\ufd30\ufd31\ufd32\ufd33\ufd34\ufd35\ufd36\ufd37\ufd38\ufd39\ufd3a\ufd3b\ufd3c\ufd3d\ufd50\ufd51\ufd52\ufd53\ufd54\ufd55\ufd56\ufd57\ufd58\ufd59\ufd5a\ufd5b\ufd5c\ufd5d\ufd5e\ufd5f\ufd60\ufd61\ufd62\ufd63\ufd64\ufd65\ufd66\ufd67\ufd68\ufd69\ufd6a\ufd6b\ufd6c\ufd6d\ufd6e\ufd6f\ufd70\ufd71\ufd72\ufd73\ufd74\ufd75\ufd76\ufd77\ufd78\ufd79\ufd7a\ufd7b\ufd7c\ufd7d\ufd7e\ufd7f\ufd80\ufd81\ufd82\ufd83\ufd84\ufd85\ufd86\ufd87\ufd88\ufd89\ufd8a\ufd8b\ufd8c\ufd8d\ufd8e\ufd8f\ufd92\ufd93\ufd94\ufd95\ufd96\ufd97\ufd98\ufd99\ufd9a\ufd9b\ufd9c\ufd9d\ufd9e\ufd9f\ufda0\ufda1\ufda2\ufda3\ufda4\ufda5\ufda6\ufda7\ufda8\ufda9\ufdaa\ufdab\ufdac\ufdad\ufdae\ufdaf\ufdb0\ufdb1\ufdb2\ufdb3\ufdb4\ufdb5\ufdb6\ufdb7\ufdb8\ufdb9\ufdba\ufdbb\ufdbc\ufdbd\ufdbe\ufdbf\ufdc0\ufdc1\ufdc2\ufdc3\ufdc4\ufdc5\ufdc6\ufdc7\ufdf0\ufdf1\ufdf2\ufdf3\ufdf4\ufdf5\ufdf6\ufdf7\ufdf8\ufdf9\ufdfa\ufdfb\ufe70\ufe71\ufe72\ufe73\ufe74\ufe76\ufe77\ufe78\ufe79\ufe7a\ufe7b\ufe7c\ufe7d\ufe7e\ufe7f\ufe80\ufe81\ufe82\ufe83\ufe84\ufe85\ufe86\ufe87\ufe88\ufe89\ufe8a\ufe8b\ufe8c\ufe8d\ufe8e\ufe8f\ufe90\ufe91\ufe92\ufe93\ufe94\ufe95\ufe96\ufe97\ufe98\ufe99\ufe9a\ufe9b\ufe9c\ufe9d\ufe9e\ufe9f\ufea0\ufea1\ufea2\ufea3\ufea4\ufea5\ufea6\ufea7\ufea8\ufea9\ufeaa\ufeab\ufeac\ufead\ufeae\ufeaf\ufeb0\ufeb1\ufeb2\ufeb3\ufeb4\ufeb5\ufeb6\ufeb7\ufeb8\ufeb9\ufeba\ufebb\ufebc\ufebd\ufebe\ufebf\ufec0\ufec1\ufec2\ufec3\ufec4\ufec5\ufec6\ufec7\ufec8\ufec9\ufeca\ufecb\ufecc\ufecd\ufece\ufecf\ufed0\ufed1\ufed2\ufed3\ufed4\ufed5\ufed6\ufed7\ufed8\ufed9\ufeda\ufedb\ufedc\ufedd\ufede\ufedf\ufee0\ufee1\ufee2\ufee3\ufee4\ufee5\ufee6\ufee7\ufee8\ufee9\ufeea\ufeeb\ufeec\ufeed\ufeee\ufeef\ufef0\ufef1\ufef2\ufef3\ufef4\ufef5\ufef6\ufef7\ufef8\ufef9\ufefa\ufefb\ufefc\uff66\uff67\uff68\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\uff8a\uff8b\uff8c\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d\uffa0\uffa1\uffa2\uffa3\uffa4\uffa5\uffa6\uffa7\uffa8\uffa9\uffaa\uffab\uffac\uffad\uffae\uffaf\uffb0\uffb1\uffb2\uffb3\uffb4\uffb5\uffb6\uffb7\uffb8\uffb9\uffba\uffbb\uffbc\uffbd\uffbe\uffc2\uffc3\uffc4\uffc5\uffc6\uffc7\uffca\uffcb\uffcc\uffcd\uffce\uffcf\uffd2\uffd3\uffd4\uffd5\uffd6\uffd7\uffda\uffdb\uffdc'
-
-Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc'
-
-Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a'
-
-Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827'
-
-Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4'
-
-Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23'
-
-Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'
-
-Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a'
-
-No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf'
-
-Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f'
-
-Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d'
-
-Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
-
-Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d'
-
-Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c'
-
-Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65'
-
-Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
-
-Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6'
-
-Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3'
-
-Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\ufb29\ufe62\ufe64\ufe65\ufe66\uff0b\uff1c\uff1d\uff1e\uff5c\uff5e\uffe2\uffe9\uffea\uffeb\uffec'
-
-So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u2701\u2702\u2703\u2704\u2706\u2707\u2708\u2709\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274d\u274f\u2750\u2751\u2752\u2756\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2e80\u2e81\u2e82\u2e83\u2e84\u2e85\u2e86\u2e87\u2e88\u2e89\u2e8a\u2e8b\u2e8c\u2e8d\u2e8e\u2e8f\u2e90\u2e91\u2e92\u2e93\u2e94\u2e95\u2e96\u2e97\u2e98\u2e99\u2e9b\u2e9c\u2e9d\u2e9e\u2e9f\u2ea0\u2ea1\u2ea2\u2ea3\u2ea4\u2ea5\u2ea6\u2ea7\u2ea8\u2ea9\u2eaa\u2eab\u2eac\u2ead\u2eae\u2eaf\u2eb0\u2eb1\u2eb2\u2eb3\u2eb4\u2eb5\u2eb6\u2eb7\u2eb8\u2eb9\u2eba\u2ebb\u2ebc\u2ebd\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u2ee2\u2ee3\u2ee4\u2ee5\u2ee6\u2ee7\u2ee8\u2ee9\u2eea\u2eeb\u2eec\u2eed\u2eee\u2eef\u2ef0\u2ef1\u2ef2\u2ef3\u2f00\u2f01\u2f02\u2f03\u2f04\u2f05\u2f06\u2f07\u2f08\u2f09\u2f0a\u2f0b\u2f0c\u2f0d\u2f0e\u2f0f\u2f10\u2f11\u2f12\u2f13\u2f14\u2f15\u2f16\u2f17\u2f18\u2f19\u2f1a\u2f1b\u2f1c\u2f1d\u2f1e\u2f1f\u2f20\u2f21\u2f22\u2f23\u2f24\u2f25\u2f26\u2f27\u2f28\u2f29\u2f2a\u2f2b\u2f2c\u2f2d\u2f2e\u2f2f\u2f30\u2f31\u2f32\u2f33\u2f34\u2f35\u2f36\u2f37\u2f38\u2f39\u2f3a\u2f3b\u2f3c\u2f3d\u2f3e\u2f3f\u2f40\u2f41\u2f42\u2f43\u2f44\u2f45\u2f46\u2f47\u2f48\u2f49\u2f4a\u2f4b\u2f4c\u2f4d\u2f4e\u2f4f\u2f50\u2f51\u2f52\u2f53\u2f54\u2f55\u2f56\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2f5d\u2f5e\u2f5f\u2f60\u2f61\u2f62\u2f63\u2f64\u2f65\u2f66\u2f67\u2f68\u2f69\u2f6a\u2f6b\u2f6c\u2f6d\u2f6e\u2f6f\u2f70\u2f71\u2f72\u2f73\u2f74\u2f75\u2f76\u2f77\u2f78\u2f79\u2f7a\u2f7b\u2f7c\u2f7d\u2f7e\u2f7f\u2f80\u2f81\u2f82\u2f83\u2f84\u2f85\u2f86\u2f87\u2f88\u2f89\u2f8a\u2f8b\u2f8c\u2f8d\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3250\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u326b\u326c\u326d\u326e\u326f\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u328a\u328b\u328c\u328d\u328e\u328f\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329a\u329b\u329c\u329d\u329e\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u32a5\u32a6\u32a7\u32a8\u32a9\u32aa\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32c0\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32ce\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e1\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f7\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330c\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331a\u331b\u331c\u331d\u331e\u331f\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332a\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333a\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b\u334c\u334d\u334e\u334f\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339b\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a2\u33a3\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ab\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua828\ua829\ua82a\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd'
-
-Zl = u'\u2028'
-
-Zp = u'\u2029'
-
-Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
-
-cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
-
-def combine(*args):
- return u''.join([globals()[cat] for cat in args])
-
-xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u302A-\u302F\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA802\uA803-\uA805\uA806\uA807-\uA80A\uA80B\uA80C-\uA822\uA823-\uA824\uA825-\uA826\uA827\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE00-\uFE0F\uFE20-\uFE23\uFE33-\uFE34\uFE4D-\uFE4F\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFF9E-\uFF9F\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
-
-def allexcept(*args):
- newcats = cats[:]
- for arg in args:
- newcats.remove(arg)
- return u''.join([globals()[cat] for cat in newcats])
-
-if __name__ == '__main__':
- import unicodedata
-
- categories = {}
-
- f = open(__file__.rstrip('co'))
- try:
- content = f.read()
- finally:
- f.close()
-
- header = content[:content.find('Cc =')]
- footer = content[content.find("def combine("):]
-
- for code in range(65535):
- c = unichr(code)
- cat = unicodedata.category(c)
- categories.setdefault(cat, []).append(c)
-
- f = open(__file__, 'w')
- f.write(header)
-
- for cat in sorted(categories):
- val = u''.join(categories[cat])
- if cat == 'Cs':
- # Jython can't handle isolated surrogates
- f.write("""\
-try:
- Cs = eval(r"%r")
-except UnicodeDecodeError:
- Cs = '' # Jython can't handle isolated surrogates\n\n""" % val)
- else:
- f.write('%s = %r\n\n' % (cat, val))
- f.write('cats = %r\n\n' % sorted(categories.keys()))
-
- f.write(footer)
- f.close()
diff --git a/module/lib/jinja2/bccache.py b/module/lib/jinja2/bccache.py
deleted file mode 100644
index 1e2236c3a..000000000
--- a/module/lib/jinja2/bccache.py
+++ /dev/null
@@ -1,280 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.bccache
- ~~~~~~~~~~~~~~
-
- This module implements the bytecode cache system Jinja is optionally
- using. This is useful if you have very complex template situations and
- the compiliation of all those templates slow down your application too
- much.
-
- Situations where this is useful are often forking web applications that
- are initialized on the first request.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from os import path, listdir
-import marshal
-import tempfile
-import cPickle as pickle
-import fnmatch
-from cStringIO import StringIO
-try:
- from hashlib import sha1
-except ImportError:
- from sha import new as sha1
-from jinja2.utils import open_if_exists
-
-
-bc_version = 1
-bc_magic = 'j2'.encode('ascii') + pickle.dumps(bc_version, 2)
-
-
-class Bucket(object):
- """Buckets are used to store the bytecode for one template. It's created
- and initialized by the bytecode cache and passed to the loading functions.
-
- The buckets get an internal checksum from the cache assigned and use this
- to automatically reject outdated cache material. Individual bytecode
- cache subclasses don't have to care about cache invalidation.
- """
-
- def __init__(self, environment, key, checksum):
- self.environment = environment
- self.key = key
- self.checksum = checksum
- self.reset()
-
- def reset(self):
- """Resets the bucket (unloads the bytecode)."""
- self.code = None
-
- def load_bytecode(self, f):
- """Loads bytecode from a file or file like object."""
- # make sure the magic header is correct
- magic = f.read(len(bc_magic))
- if magic != bc_magic:
- self.reset()
- return
- # the source code of the file changed, we need to reload
- checksum = pickle.load(f)
- if self.checksum != checksum:
- self.reset()
- return
- # now load the code. Because marshal is not able to load
- # from arbitrary streams we have to work around that
- if isinstance(f, file):
- self.code = marshal.load(f)
- else:
- self.code = marshal.loads(f.read())
-
- def write_bytecode(self, f):
- """Dump the bytecode into the file or file like object passed."""
- if self.code is None:
- raise TypeError('can\'t write empty bucket')
- f.write(bc_magic)
- pickle.dump(self.checksum, f, 2)
- if isinstance(f, file):
- marshal.dump(self.code, f)
- else:
- f.write(marshal.dumps(self.code))
-
- def bytecode_from_string(self, string):
- """Load bytecode from a string."""
- self.load_bytecode(StringIO(string))
-
- def bytecode_to_string(self):
- """Return the bytecode as string."""
- out = StringIO()
- self.write_bytecode(out)
- return out.getvalue()
-
-
-class BytecodeCache(object):
- """To implement your own bytecode cache you have to subclass this class
- and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
- these methods are passed a :class:`~jinja2.bccache.Bucket`.
-
- A very basic bytecode cache that saves the bytecode on the file system::
-
- from os import path
-
- class MyCache(BytecodeCache):
-
- def __init__(self, directory):
- self.directory = directory
-
- def load_bytecode(self, bucket):
- filename = path.join(self.directory, bucket.key)
- if path.exists(filename):
- with open(filename, 'rb') as f:
- bucket.load_bytecode(f)
-
- def dump_bytecode(self, bucket):
- filename = path.join(self.directory, bucket.key)
- with open(filename, 'wb') as f:
- bucket.write_bytecode(f)
-
- A more advanced version of a filesystem based bytecode cache is part of
- Jinja2.
- """
-
- def load_bytecode(self, bucket):
- """Subclasses have to override this method to load bytecode into a
- bucket. If they are not able to find code in the cache for the
- bucket, it must not do anything.
- """
- raise NotImplementedError()
-
- def dump_bytecode(self, bucket):
- """Subclasses have to override this method to write the bytecode
- from a bucket back to the cache. If it unable to do so it must not
- fail silently but raise an exception.
- """
- raise NotImplementedError()
-
- def clear(self):
- """Clears the cache. This method is not used by Jinja2 but should be
- implemented to allow applications to clear the bytecode cache used
- by a particular environment.
- """
-
- def get_cache_key(self, name, filename=None):
- """Returns the unique hash key for this template name."""
- hash = sha1(name.encode('utf-8'))
- if filename is not None:
- if isinstance(filename, unicode):
- filename = filename.encode('utf-8')
- hash.update('|' + filename)
- return hash.hexdigest()
-
- def get_source_checksum(self, source):
- """Returns a checksum for the source."""
- return sha1(source.encode('utf-8')).hexdigest()
-
- def get_bucket(self, environment, name, filename, source):
- """Return a cache bucket for the given template. All arguments are
- mandatory but filename may be `None`.
- """
- key = self.get_cache_key(name, filename)
- checksum = self.get_source_checksum(source)
- bucket = Bucket(environment, key, checksum)
- self.load_bytecode(bucket)
- return bucket
-
- def set_bucket(self, bucket):
- """Put the bucket into the cache."""
- self.dump_bytecode(bucket)
-
-
-class FileSystemBytecodeCache(BytecodeCache):
- """A bytecode cache that stores bytecode on the filesystem. It accepts
- two arguments: The directory where the cache items are stored and a
- pattern string that is used to build the filename.
-
- If no directory is specified the system temporary items folder is used.
-
- The pattern can be used to have multiple separate caches operate on the
- same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
- is replaced with the cache key.
-
- >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
-
- This bytecode cache supports clearing of the cache using the clear method.
- """
-
- def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
- if directory is None:
- directory = tempfile.gettempdir()
- self.directory = directory
- self.pattern = pattern
-
- def _get_cache_filename(self, bucket):
- return path.join(self.directory, self.pattern % bucket.key)
-
- def load_bytecode(self, bucket):
- f = open_if_exists(self._get_cache_filename(bucket), 'rb')
- if f is not None:
- try:
- bucket.load_bytecode(f)
- finally:
- f.close()
-
- def dump_bytecode(self, bucket):
- f = open(self._get_cache_filename(bucket), 'wb')
- try:
- bucket.write_bytecode(f)
- finally:
- f.close()
-
- def clear(self):
- # imported lazily here because google app-engine doesn't support
- # write access on the file system and the function does not exist
- # normally.
- from os import remove
- files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
- for filename in files:
- try:
- remove(path.join(self.directory, filename))
- except OSError:
- pass
-
-
-class MemcachedBytecodeCache(BytecodeCache):
- """This class implements a bytecode cache that uses a memcache cache for
- storing the information. It does not enforce a specific memcache library
- (tummy's memcache or cmemcache) but will accept any class that provides
- the minimal interface required.
-
- Libraries compatible with this class:
-
- - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
- - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
- - `cmemcache <http://gijsbert.org/cmemcache/>`_
-
- (Unfortunately the django cache interface is not compatible because it
- does not support storing binary data, only unicode. You can however pass
- the underlying cache client to the bytecode cache which is available
- as `django.core.cache.cache._client`.)
-
- The minimal interface for the client passed to the constructor is this:
-
- .. class:: MinimalClientInterface
-
- .. method:: set(key, value[, timeout])
-
- Stores the bytecode in the cache. `value` is a string and
- `timeout` the timeout of the key. If timeout is not provided
- a default timeout or no timeout should be assumed, if it's
- provided it's an integer with the number of seconds the cache
- item should exist.
-
- .. method:: get(key)
-
- Returns the value for the cache key. If the item does not
- exist in the cache the return value must be `None`.
-
- The other arguments to the constructor are the prefix for all keys that
- is added before the actual cache key and the timeout for the bytecode in
- the cache system. We recommend a high (or no) timeout.
-
- This bytecode cache does not support clearing of used items in the cache.
- The clear method is a no-operation function.
- """
-
- def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
- self.client = client
- self.prefix = prefix
- self.timeout = timeout
-
- def load_bytecode(self, bucket):
- code = self.client.get(self.prefix + bucket.key)
- if code is not None:
- bucket.bytecode_from_string(code)
-
- def dump_bytecode(self, bucket):
- args = (self.prefix + bucket.key, bucket.bytecode_to_string())
- if self.timeout is not None:
- args += (self.timeout,)
- self.client.set(*args)
diff --git a/module/lib/jinja2/compiler.py b/module/lib/jinja2/compiler.py
deleted file mode 100644
index 57641596a..000000000
--- a/module/lib/jinja2/compiler.py
+++ /dev/null
@@ -1,1640 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.compiler
- ~~~~~~~~~~~~~~~
-
- Compiles nodes into python code.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from cStringIO import StringIO
-from itertools import chain
-from copy import deepcopy
-from jinja2 import nodes
-from jinja2.nodes import EvalContext
-from jinja2.visitor import NodeVisitor, NodeTransformer
-from jinja2.exceptions import TemplateAssertionError
-from jinja2.utils import Markup, concat, escape, is_python_keyword, next
-
-
-operators = {
- 'eq': '==',
- 'ne': '!=',
- 'gt': '>',
- 'gteq': '>=',
- 'lt': '<',
- 'lteq': '<=',
- 'in': 'in',
- 'notin': 'not in'
-}
-
-try:
- exec '(0 if 0 else 0)'
-except SyntaxError:
- have_condexpr = False
-else:
- have_condexpr = True
-
-
-# what method to iterate over items do we want to use for dict iteration
-# in generated code? on 2.x let's go with iteritems, on 3.x with items
-if hasattr(dict, 'iteritems'):
- dict_item_iter = 'iteritems'
-else:
- dict_item_iter = 'items'
-
-
-# does if 0: dummy(x) get us x into the scope?
-def unoptimize_before_dead_code():
- x = 42
- def f():
- if 0: dummy(x)
- return f
-unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure)
-
-
-def generate(node, environment, name, filename, stream=None,
- defer_init=False):
- """Generate the python source for a node tree."""
- if not isinstance(node, nodes.Template):
- raise TypeError('Can\'t compile non template nodes')
- generator = CodeGenerator(environment, name, filename, stream, defer_init)
- generator.visit(node)
- if stream is None:
- return generator.stream.getvalue()
-
-
-def has_safe_repr(value):
- """Does the node have a safe representation?"""
- if value is None or value is NotImplemented or value is Ellipsis:
- return True
- if isinstance(value, (bool, int, long, float, complex, basestring,
- xrange, Markup)):
- return True
- if isinstance(value, (tuple, list, set, frozenset)):
- for item in value:
- if not has_safe_repr(item):
- return False
- return True
- elif isinstance(value, dict):
- for key, value in value.iteritems():
- if not has_safe_repr(key):
- return False
- if not has_safe_repr(value):
- return False
- return True
- return False
-
-
-def find_undeclared(nodes, names):
- """Check if the names passed are accessed undeclared. The return value
- is a set of all the undeclared names from the sequence of names found.
- """
- visitor = UndeclaredNameVisitor(names)
- try:
- for node in nodes:
- visitor.visit(node)
- except VisitorExit:
- pass
- return visitor.undeclared
-
-
-class Identifiers(object):
- """Tracks the status of identifiers in frames."""
-
- def __init__(self):
- # variables that are known to be declared (probably from outer
- # frames or because they are special for the frame)
- self.declared = set()
-
- # undeclared variables from outer scopes
- self.outer_undeclared = set()
-
- # names that are accessed without being explicitly declared by
- # this one or any of the outer scopes. Names can appear both in
- # declared and undeclared.
- self.undeclared = set()
-
- # names that are declared locally
- self.declared_locally = set()
-
- # names that are declared by parameters
- self.declared_parameter = set()
-
- def add_special(self, name):
- """Register a special name like `loop`."""
- self.undeclared.discard(name)
- self.declared.add(name)
-
- def is_declared(self, name, local_only=False):
- """Check if a name is declared in this or an outer scope."""
- if name in self.declared_locally or name in self.declared_parameter:
- return True
- if local_only:
- return False
- return name in self.declared
-
- def copy(self):
- return deepcopy(self)
-
-
-class Frame(object):
- """Holds compile time information for us."""
-
- def __init__(self, eval_ctx, parent=None):
- self.eval_ctx = eval_ctx
- self.identifiers = Identifiers()
-
- # a toplevel frame is the root + soft frames such as if conditions.
- self.toplevel = False
-
- # the root frame is basically just the outermost frame, so no if
- # conditions. This information is used to optimize inheritance
- # situations.
- self.rootlevel = False
-
- # in some dynamic inheritance situations the compiler needs to add
- # write tests around output statements.
- self.require_output_check = parent and parent.require_output_check
-
- # inside some tags we are using a buffer rather than yield statements.
- # this for example affects {% filter %} or {% macro %}. If a frame
- # is buffered this variable points to the name of the list used as
- # buffer.
- self.buffer = None
-
- # the name of the block we're in, otherwise None.
- self.block = parent and parent.block or None
-
- # a set of actually assigned names
- self.assigned_names = set()
-
- # the parent of this frame
- self.parent = parent
-
- if parent is not None:
- self.identifiers.declared.update(
- parent.identifiers.declared |
- parent.identifiers.declared_parameter |
- parent.assigned_names
- )
- self.identifiers.outer_undeclared.update(
- parent.identifiers.undeclared -
- self.identifiers.declared
- )
- self.buffer = parent.buffer
-
- def copy(self):
- """Create a copy of the current one."""
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.identifiers = object.__new__(self.identifiers.__class__)
- rv.identifiers.__dict__.update(self.identifiers.__dict__)
- return rv
-
- def inspect(self, nodes, hard_scope=False):
- """Walk the node and check for identifiers. If the scope is hard (eg:
- enforce on a python level) overrides from outer scopes are tracked
- differently.
- """
- visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
- for node in nodes:
- visitor.visit(node)
-
- def find_shadowed(self, extra=()):
- """Find all the shadowed names. extra is an iterable of variables
- that may be defined with `add_special` which may occour scoped.
- """
- i = self.identifiers
- return (i.declared | i.outer_undeclared) & \
- (i.declared_locally | i.declared_parameter) | \
- set(x for x in extra if i.is_declared(x))
-
- def inner(self):
- """Return an inner frame."""
- return Frame(self.eval_ctx, self)
-
- def soft(self):
- """Return a soft frame. A soft frame may not be modified as
- standalone thing as it shares the resources with the frame it
- was created of, but it's not a rootlevel frame any longer.
- """
- rv = self.copy()
- rv.rootlevel = False
- return rv
-
- __copy__ = copy
-
-
-class VisitorExit(RuntimeError):
- """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
-
-
-class DependencyFinderVisitor(NodeVisitor):
- """A visitor that collects filter and test calls."""
-
- def __init__(self):
- self.filters = set()
- self.tests = set()
-
- def visit_Filter(self, node):
- self.generic_visit(node)
- self.filters.add(node.name)
-
- def visit_Test(self, node):
- self.generic_visit(node)
- self.tests.add(node.name)
-
- def visit_Block(self, node):
- """Stop visiting at blocks."""
-
-
-class UndeclaredNameVisitor(NodeVisitor):
- """A visitor that checks if a name is accessed without being
- declared. This is different from the frame visitor as it will
- not stop at closure frames.
- """
-
- def __init__(self, names):
- self.names = set(names)
- self.undeclared = set()
-
- def visit_Name(self, node):
- if node.ctx == 'load' and node.name in self.names:
- self.undeclared.add(node.name)
- if self.undeclared == self.names:
- raise VisitorExit()
- else:
- self.names.discard(node.name)
-
- def visit_Block(self, node):
- """Stop visiting a blocks."""
-
-
-class FrameIdentifierVisitor(NodeVisitor):
- """A visitor for `Frame.inspect`."""
-
- def __init__(self, identifiers, hard_scope):
- self.identifiers = identifiers
- self.hard_scope = hard_scope
-
- def visit_Name(self, node):
- """All assignments to names go through this function."""
- if node.ctx == 'store':
- self.identifiers.declared_locally.add(node.name)
- elif node.ctx == 'param':
- self.identifiers.declared_parameter.add(node.name)
- elif node.ctx == 'load' and not \
- self.identifiers.is_declared(node.name, self.hard_scope):
- self.identifiers.undeclared.add(node.name)
-
- def visit_If(self, node):
- self.visit(node.test)
- real_identifiers = self.identifiers
-
- old_names = real_identifiers.declared_locally | \
- real_identifiers.declared_parameter
-
- def inner_visit(nodes):
- if not nodes:
- return set()
- self.identifiers = real_identifiers.copy()
- for subnode in nodes:
- self.visit(subnode)
- rv = self.identifiers.declared_locally - old_names
- # we have to remember the undeclared variables of this branch
- # because we will have to pull them.
- real_identifiers.undeclared.update(self.identifiers.undeclared)
- self.identifiers = real_identifiers
- return rv
-
- body = inner_visit(node.body)
- else_ = inner_visit(node.else_ or ())
-
- # the differences between the two branches are also pulled as
- # undeclared variables
- real_identifiers.undeclared.update(body.symmetric_difference(else_) -
- real_identifiers.declared)
-
- # remember those that are declared.
- real_identifiers.declared_locally.update(body | else_)
-
- def visit_Macro(self, node):
- self.identifiers.declared_locally.add(node.name)
-
- def visit_Import(self, node):
- self.generic_visit(node)
- self.identifiers.declared_locally.add(node.target)
-
- def visit_FromImport(self, node):
- self.generic_visit(node)
- for name in node.names:
- if isinstance(name, tuple):
- self.identifiers.declared_locally.add(name[1])
- else:
- self.identifiers.declared_locally.add(name)
-
- def visit_Assign(self, node):
- """Visit assignments in the correct order."""
- self.visit(node.node)
- self.visit(node.target)
-
- def visit_For(self, node):
- """Visiting stops at for blocks. However the block sequence
- is visited as part of the outer scope.
- """
- self.visit(node.iter)
-
- def visit_CallBlock(self, node):
- self.visit(node.call)
-
- def visit_FilterBlock(self, node):
- self.visit(node.filter)
-
- def visit_Scope(self, node):
- """Stop visiting at scopes."""
-
- def visit_Block(self, node):
- """Stop visiting at blocks."""
-
-
-class CompilerExit(Exception):
- """Raised if the compiler encountered a situation where it just
- doesn't make sense to further process the code. Any block that
- raises such an exception is not further processed.
- """
-
-
-class CodeGenerator(NodeVisitor):
-
- def __init__(self, environment, name, filename, stream=None,
- defer_init=False):
- if stream is None:
- stream = StringIO()
- self.environment = environment
- self.name = name
- self.filename = filename
- self.stream = stream
- self.created_block_context = False
- self.defer_init = defer_init
-
- # aliases for imports
- self.import_aliases = {}
-
- # a registry for all blocks. Because blocks are moved out
- # into the global python scope they are registered here
- self.blocks = {}
-
- # the number of extends statements so far
- self.extends_so_far = 0
-
- # some templates have a rootlevel extends. In this case we
- # can safely assume that we're a child template and do some
- # more optimizations.
- self.has_known_extends = False
-
- # the current line number
- self.code_lineno = 1
-
- # registry of all filters and tests (global, not block local)
- self.tests = {}
- self.filters = {}
-
- # the debug information
- self.debug_info = []
- self._write_debug_info = None
-
- # the number of new lines before the next write()
- self._new_lines = 0
-
- # the line number of the last written statement
- self._last_line = 0
-
- # true if nothing was written so far.
- self._first_write = True
-
- # used by the `temporary_identifier` method to get new
- # unique, temporary identifier
- self._last_identifier = 0
-
- # the current indentation
- self._indentation = 0
-
- # -- Various compilation helpers
-
- def fail(self, msg, lineno):
- """Fail with a :exc:`TemplateAssertionError`."""
- raise TemplateAssertionError(msg, lineno, self.name, self.filename)
-
- def temporary_identifier(self):
- """Get a new unique identifier."""
- self._last_identifier += 1
- return 't_%d' % self._last_identifier
-
- def buffer(self, frame):
- """Enable buffering for the frame from that point onwards."""
- frame.buffer = self.temporary_identifier()
- self.writeline('%s = []' % frame.buffer)
-
- def return_buffer_contents(self, frame):
- """Return the buffer contents of the frame."""
- if frame.eval_ctx.volatile:
- self.writeline('if context.eval_ctx.autoescape:')
- self.indent()
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- self.outdent()
- self.writeline('else:')
- self.indent()
- self.writeline('return concat(%s)' % frame.buffer)
- self.outdent()
- elif frame.eval_ctx.autoescape:
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- else:
- self.writeline('return concat(%s)' % frame.buffer)
-
- def indent(self):
- """Indent by one."""
- self._indentation += 1
-
- def outdent(self, step=1):
- """Outdent by step."""
- self._indentation -= step
-
- def start_write(self, frame, node=None):
- """Yield or write into the frame buffer."""
- if frame.buffer is None:
- self.writeline('yield ', node)
- else:
- self.writeline('%s.append(' % frame.buffer, node)
-
- def end_write(self, frame):
- """End the writing process started by `start_write`."""
- if frame.buffer is not None:
- self.write(')')
-
- def simple_write(self, s, frame, node=None):
- """Simple shortcut for start_write + write + end_write."""
- self.start_write(frame, node)
- self.write(s)
- self.end_write(frame)
-
- def blockvisit(self, nodes, frame):
- """Visit a list of nodes as block in a frame. If the current frame
- is no buffer a dummy ``if 0: yield None`` is written automatically
- unless the force_generator parameter is set to False.
- """
- if frame.buffer is None:
- self.writeline('if 0: yield None')
- else:
- self.writeline('pass')
- try:
- for node in nodes:
- self.visit(node, frame)
- except CompilerExit:
- pass
-
- def write(self, x):
- """Write a string into the output stream."""
- if self._new_lines:
- if not self._first_write:
- self.stream.write('\n' * self._new_lines)
- self.code_lineno += self._new_lines
- if self._write_debug_info is not None:
- self.debug_info.append((self._write_debug_info,
- self.code_lineno))
- self._write_debug_info = None
- self._first_write = False
- self.stream.write(' ' * self._indentation)
- self._new_lines = 0
- self.stream.write(x)
-
- def writeline(self, x, node=None, extra=0):
- """Combination of newline and write."""
- self.newline(node, extra)
- self.write(x)
-
- def newline(self, node=None, extra=0):
- """Add one or more newlines before the next write."""
- self._new_lines = max(self._new_lines, 1 + extra)
- if node is not None and node.lineno != self._last_line:
- self._write_debug_info = node.lineno
- self._last_line = node.lineno
-
- def signature(self, node, frame, extra_kwargs=None):
- """Writes a function call to the stream for the current node.
- A leading comma is added automatically. The extra keyword
- arguments may not include python keywords otherwise a syntax
- error could occour. The extra keyword arguments should be given
- as python dict.
- """
- # if any of the given keyword arguments is a python keyword
- # we have to make sure that no invalid call is created.
- kwarg_workaround = False
- for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
- if is_python_keyword(kwarg):
- kwarg_workaround = True
- break
-
- for arg in node.args:
- self.write(', ')
- self.visit(arg, frame)
-
- if not kwarg_workaround:
- for kwarg in node.kwargs:
- self.write(', ')
- self.visit(kwarg, frame)
- if extra_kwargs is not None:
- for key, value in extra_kwargs.iteritems():
- self.write(', %s=%s' % (key, value))
- if node.dyn_args:
- self.write(', *')
- self.visit(node.dyn_args, frame)
-
- if kwarg_workaround:
- if node.dyn_kwargs is not None:
- self.write(', **dict({')
- else:
- self.write(', **{')
- for kwarg in node.kwargs:
- self.write('%r: ' % kwarg.key)
- self.visit(kwarg.value, frame)
- self.write(', ')
- if extra_kwargs is not None:
- for key, value in extra_kwargs.iteritems():
- self.write('%r: %s, ' % (key, value))
- if node.dyn_kwargs is not None:
- self.write('}, **')
- self.visit(node.dyn_kwargs, frame)
- self.write(')')
- else:
- self.write('}')
-
- elif node.dyn_kwargs is not None:
- self.write(', **')
- self.visit(node.dyn_kwargs, frame)
-
- def pull_locals(self, frame):
- """Pull all the references identifiers into the local scope."""
- for name in frame.identifiers.undeclared:
- self.writeline('l_%s = context.resolve(%r)' % (name, name))
-
- def pull_dependencies(self, nodes):
- """Pull all the dependencies."""
- visitor = DependencyFinderVisitor()
- for node in nodes:
- visitor.visit(node)
- for dependency in 'filters', 'tests':
- mapping = getattr(self, dependency)
- for name in getattr(visitor, dependency):
- if name not in mapping:
- mapping[name] = self.temporary_identifier()
- self.writeline('%s = environment.%s[%r]' %
- (mapping[name], dependency, name))
-
- def unoptimize_scope(self, frame):
- """Disable Python optimizations for the frame."""
- # XXX: this is not that nice but it has no real overhead. It
- # mainly works because python finds the locals before dead code
- # is removed. If that breaks we have to add a dummy function
- # that just accepts the arguments and does nothing.
- if frame.identifiers.declared:
- self.writeline('%sdummy(%s)' % (
- unoptimize_before_dead_code and 'if 0: ' or '',
- ', '.join('l_' + name for name in frame.identifiers.declared)
- ))
-
- def push_scope(self, frame, extra_vars=()):
- """This function returns all the shadowed variables in a dict
- in the form name: alias and will write the required assignments
- into the current scope. No indentation takes place.
-
- This also predefines locally declared variables from the loop
- body because under some circumstances it may be the case that
-
- `extra_vars` is passed to `Frame.find_shadowed`.
- """
- aliases = {}
- for name in frame.find_shadowed(extra_vars):
- aliases[name] = ident = self.temporary_identifier()
- self.writeline('%s = l_%s' % (ident, name))
- to_declare = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_declare.add('l_' + name)
- if to_declare:
- self.writeline(' = '.join(to_declare) + ' = missing')
- return aliases
-
- def pop_scope(self, aliases, frame):
- """Restore all aliases and delete unused variables."""
- for name, alias in aliases.iteritems():
- self.writeline('l_%s = %s' % (name, alias))
- to_delete = set()
- for name in frame.identifiers.declared_locally:
- if name not in aliases:
- to_delete.add('l_' + name)
- if to_delete:
- # we cannot use the del statement here because enclosed
- # scopes can trigger a SyntaxError:
- # a = 42; b = lambda: a; del a
- self.writeline(' = '.join(to_delete) + ' = missing')
-
- def function_scoping(self, node, frame, children=None,
- find_special=True):
- """In Jinja a few statements require the help of anonymous
- functions. Those are currently macros and call blocks and in
- the future also recursive loops. As there is currently
- technical limitation that doesn't allow reading and writing a
- variable in a scope where the initial value is coming from an
- outer scope, this function tries to fall back with a common
- error message. Additionally the frame passed is modified so
- that the argumetns are collected and callers are looked up.
-
- This will return the modified frame.
- """
- # we have to iterate twice over it, make sure that works
- if children is None:
- children = node.iter_child_nodes()
- children = list(children)
- func_frame = frame.inner()
- func_frame.inspect(children, hard_scope=True)
-
- # variables that are undeclared (accessed before declaration) and
- # declared locally *and* part of an outside scope raise a template
- # assertion error. Reason: we can't generate reasonable code from
- # it without aliasing all the variables.
- # this could be fixed in Python 3 where we have the nonlocal
- # keyword or if we switch to bytecode generation
- overriden_closure_vars = (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared &
- (func_frame.identifiers.declared_locally |
- func_frame.identifiers.declared_parameter)
- )
- if overriden_closure_vars:
- self.fail('It\'s not possible to set and access variables '
- 'derived from an outer scope! (affects: %s)' %
- ', '.join(sorted(overriden_closure_vars)), node.lineno)
-
- # remove variables from a closure from the frame's undeclared
- # identifiers.
- func_frame.identifiers.undeclared -= (
- func_frame.identifiers.undeclared &
- func_frame.identifiers.declared
- )
-
- # no special variables for this scope, abort early
- if not find_special:
- return func_frame
-
- func_frame.accesses_kwargs = False
- func_frame.accesses_varargs = False
- func_frame.accesses_caller = False
- func_frame.arguments = args = ['l_' + x.name for x in node.args]
-
- undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
-
- if 'caller' in undeclared:
- func_frame.accesses_caller = True
- func_frame.identifiers.add_special('caller')
- args.append('l_caller')
- if 'kwargs' in undeclared:
- func_frame.accesses_kwargs = True
- func_frame.identifiers.add_special('kwargs')
- args.append('l_kwargs')
- if 'varargs' in undeclared:
- func_frame.accesses_varargs = True
- func_frame.identifiers.add_special('varargs')
- args.append('l_varargs')
- return func_frame
-
- def macro_body(self, node, frame, children=None):
- """Dump the function def of a macro or call block."""
- frame = self.function_scoping(node, frame, children)
- # macros are delayed, they never require output checks
- frame.require_output_check = False
- args = frame.arguments
- # XXX: this is an ugly fix for the loop nesting bug
- # (tests.test_old_bugs.test_loop_call_bug). This works around
- # a identifier nesting problem we have in general. It's just more
- # likely to happen in loops which is why we work around it. The
- # real solution would be "nonlocal" all the identifiers that are
- # leaking into a new python frame and might be used both unassigned
- # and assigned.
- if 'loop' in frame.identifiers.declared:
- args = args + ['l_loop=l_loop']
- self.writeline('def macro(%s):' % ', '.join(args), node)
- self.indent()
- self.buffer(frame)
- self.pull_locals(frame)
- self.blockvisit(node.body, frame)
- self.return_buffer_contents(frame)
- self.outdent()
- return frame
-
- def macro_def(self, node, frame):
- """Dump the macro definition for the def created by macro_body."""
- arg_tuple = ', '.join(repr(x.name) for x in node.args)
- name = getattr(node, 'name', None)
- if len(node.args) == 1:
- arg_tuple += ','
- self.write('Macro(environment, macro, %r, (%s), (' %
- (name, arg_tuple))
- for arg in node.defaults:
- self.visit(arg, frame)
- self.write(', ')
- self.write('), %r, %r, %r)' % (
- bool(frame.accesses_kwargs),
- bool(frame.accesses_varargs),
- bool(frame.accesses_caller)
- ))
-
- def position(self, node):
- """Return a human readable position for the node."""
- rv = 'line %d' % node.lineno
- if self.name is not None:
- rv += ' in ' + repr(self.name)
- return rv
-
- # -- Statement Visitors
-
- def visit_Template(self, node, frame=None):
- assert frame is None, 'no root frame allowed'
- eval_ctx = EvalContext(self.environment, self.name)
-
- from jinja2.runtime import __all__ as exported
- self.writeline('from __future__ import division')
- self.writeline('from jinja2.runtime import ' + ', '.join(exported))
- if not unoptimize_before_dead_code:
- self.writeline('dummy = lambda *x: None')
-
- # if we want a deferred initialization we cannot move the
- # environment into a local name
- envenv = not self.defer_init and ', environment=environment' or ''
-
- # do we have an extends tag at all? If not, we can save some
- # overhead by just not processing any inheritance code.
- have_extends = node.find(nodes.Extends) is not None
-
- # find all blocks
- for block in node.find_all(nodes.Block):
- if block.name in self.blocks:
- self.fail('block %r defined twice' % block.name, block.lineno)
- self.blocks[block.name] = block
-
- # find all imports and import them
- for import_ in node.find_all(nodes.ImportedName):
- if import_.importname not in self.import_aliases:
- imp = import_.importname
- self.import_aliases[imp] = alias = self.temporary_identifier()
- if '.' in imp:
- module, obj = imp.rsplit('.', 1)
- self.writeline('from %s import %s as %s' %
- (module, obj, alias))
- else:
- self.writeline('import %s as %s' % (imp, alias))
-
- # add the load name
- self.writeline('name = %r' % self.name)
-
- # generate the root render function.
- self.writeline('def root(context%s):' % envenv, extra=1)
-
- # process the root
- frame = Frame(eval_ctx)
- frame.inspect(node.body)
- frame.toplevel = frame.rootlevel = True
- frame.require_output_check = have_extends and not self.has_known_extends
- self.indent()
- if have_extends:
- self.writeline('parent_template = None')
- if 'self' in find_undeclared(node.body, ('self',)):
- frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
- self.pull_locals(frame)
- self.pull_dependencies(node.body)
- self.blockvisit(node.body, frame)
- self.outdent()
-
- # make sure that the parent root is called.
- if have_extends:
- if not self.has_known_extends:
- self.indent()
- self.writeline('if parent_template is not None:')
- self.indent()
- self.writeline('for event in parent_template.'
- 'root_render_func(context):')
- self.indent()
- self.writeline('yield event')
- self.outdent(2 + (not self.has_known_extends))
-
- # at this point we now have the blocks collected and can visit them too.
- for name, block in self.blocks.iteritems():
- block_frame = Frame(eval_ctx)
- block_frame.inspect(block.body)
- block_frame.block = name
- self.writeline('def block_%s(context%s):' % (name, envenv),
- block, 1)
- self.indent()
- undeclared = find_undeclared(block.body, ('self', 'super'))
- if 'self' in undeclared:
- block_frame.identifiers.add_special('self')
- self.writeline('l_self = TemplateReference(context)')
- if 'super' in undeclared:
- block_frame.identifiers.add_special('super')
- self.writeline('l_super = context.super(%r, '
- 'block_%s)' % (name, name))
- self.pull_locals(block_frame)
- self.pull_dependencies(block.body)
- self.blockvisit(block.body, block_frame)
- self.outdent()
-
- self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
- for x in self.blocks),
- extra=1)
-
- # add a function that returns the debug info
- self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
- in self.debug_info))
-
- def visit_Block(self, node, frame):
- """Call a block and register it for the template."""
- level = 1
- if frame.toplevel:
- # if we know that we are a child template, there is no need to
- # check if we are one
- if self.has_known_extends:
- return
- if self.extends_so_far > 0:
- self.writeline('if parent_template is None:')
- self.indent()
- level += 1
- context = node.scoped and 'context.derived(locals())' or 'context'
- self.writeline('for event in context.blocks[%r][0](%s):' % (
- node.name, context), node)
- self.indent()
- self.simple_write('event', frame)
- self.outdent(level)
-
- def visit_Extends(self, node, frame):
- """Calls the extender."""
- if not frame.toplevel:
- self.fail('cannot use extend from a non top-level scope',
- node.lineno)
-
- # if the number of extends statements in general is zero so
- # far, we don't have to add a check if something extended
- # the template before this one.
- if self.extends_so_far > 0:
-
- # if we have a known extends we just add a template runtime
- # error into the generated code. We could catch that at compile
- # time too, but i welcome it not to confuse users by throwing the
- # same error at different times just "because we can".
- if not self.has_known_extends:
- self.writeline('if parent_template is not None:')
- self.indent()
- self.writeline('raise TemplateRuntimeError(%r)' %
- 'extended multiple times')
- self.outdent()
-
- # if we have a known extends already we don't need that code here
- # as we know that the template execution will end here.
- if self.has_known_extends:
- raise CompilerExit()
-
- self.writeline('parent_template = environment.get_template(', node)
- self.visit(node.template, frame)
- self.write(', %r)' % self.name)
- self.writeline('for name, parent_block in parent_template.'
- 'blocks.%s():' % dict_item_iter)
- self.indent()
- self.writeline('context.blocks.setdefault(name, []).'
- 'append(parent_block)')
- self.outdent()
-
- # if this extends statement was in the root level we can take
- # advantage of that information and simplify the generated code
- # in the top level from this point onwards
- if frame.rootlevel:
- self.has_known_extends = True
-
- # and now we have one more
- self.extends_so_far += 1
-
- def visit_Include(self, node, frame):
- """Handles includes."""
- if node.with_context:
- self.unoptimize_scope(frame)
- if node.ignore_missing:
- self.writeline('try:')
- self.indent()
-
- func_name = 'get_or_select_template'
- if isinstance(node.template, nodes.Const):
- if isinstance(node.template.value, basestring):
- func_name = 'get_template'
- elif isinstance(node.template.value, (tuple, list)):
- func_name = 'select_template'
- elif isinstance(node.template, (nodes.Tuple, nodes.List)):
- func_name = 'select_template'
-
- self.writeline('template = environment.%s(' % func_name, node)
- self.visit(node.template, frame)
- self.write(', %r)' % self.name)
- if node.ignore_missing:
- self.outdent()
- self.writeline('except TemplateNotFound:')
- self.indent()
- self.writeline('pass')
- self.outdent()
- self.writeline('else:')
- self.indent()
-
- if node.with_context:
- self.writeline('for event in template.root_render_func('
- 'template.new_context(context.parent, True, '
- 'locals())):')
- else:
- self.writeline('for event in template.module._body_stream:')
-
- self.indent()
- self.simple_write('event', frame)
- self.outdent()
-
- if node.ignore_missing:
- self.outdent()
-
- def visit_Import(self, node, frame):
- """Visit regular imports."""
- if node.with_context:
- self.unoptimize_scope(frame)
- self.writeline('l_%s = ' % node.target, node)
- if frame.toplevel:
- self.write('context.vars[%r] = ' % node.target)
- self.write('environment.get_template(')
- self.visit(node.template, frame)
- self.write(', %r).' % self.name)
- if node.with_context:
- self.write('make_module(context.parent, True, locals())')
- else:
- self.write('module')
- if frame.toplevel and not node.target.startswith('_'):
- self.writeline('context.exported_vars.discard(%r)' % node.target)
- frame.assigned_names.add(node.target)
-
- def visit_FromImport(self, node, frame):
- """Visit named imports."""
- self.newline(node)
- self.write('included_template = environment.get_template(')
- self.visit(node.template, frame)
- self.write(', %r).' % self.name)
- if node.with_context:
- self.write('make_module(context.parent, True)')
- else:
- self.write('module')
-
- var_names = []
- discarded_names = []
- for name in node.names:
- if isinstance(name, tuple):
- name, alias = name
- else:
- alias = name
- self.writeline('l_%s = getattr(included_template, '
- '%r, missing)' % (alias, name))
- self.writeline('if l_%s is missing:' % alias)
- self.indent()
- self.writeline('l_%s = environment.undefined(%r %% '
- 'included_template.__name__, '
- 'name=%r)' %
- (alias, 'the template %%r (imported on %s) does '
- 'not export the requested name %s' % (
- self.position(node),
- repr(name)
- ), name))
- self.outdent()
- if frame.toplevel:
- var_names.append(alias)
- if not alias.startswith('_'):
- discarded_names.append(alias)
- frame.assigned_names.add(alias)
-
- if var_names:
- if len(var_names) == 1:
- name = var_names[0]
- self.writeline('context.vars[%r] = l_%s' % (name, name))
- else:
- self.writeline('context.vars.update({%s})' % ', '.join(
- '%r: l_%s' % (name, name) for name in var_names
- ))
- if discarded_names:
- if len(discarded_names) == 1:
- self.writeline('context.exported_vars.discard(%r)' %
- discarded_names[0])
- else:
- self.writeline('context.exported_vars.difference_'
- 'update((%s))' % ', '.join(map(repr, discarded_names)))
-
- def visit_For(self, node, frame):
- # when calculating the nodes for the inner frame we have to exclude
- # the iterator contents from it
- children = node.iter_child_nodes(exclude=('iter',))
- if node.recursive:
- loop_frame = self.function_scoping(node, frame, children,
- find_special=False)
- else:
- loop_frame = frame.inner()
- loop_frame.inspect(children)
-
- # try to figure out if we have an extended loop. An extended loop
- # is necessary if the loop is in recursive mode if the special loop
- # variable is accessed in the body.
- extended_loop = node.recursive or 'loop' in \
- find_undeclared(node.iter_child_nodes(
- only=('body',)), ('loop',))
-
- # if we don't have an recursive loop we have to find the shadowed
- # variables at that point. Because loops can be nested but the loop
- # variable is a special one we have to enforce aliasing for it.
- if not node.recursive:
- aliases = self.push_scope(loop_frame, ('loop',))
-
- # otherwise we set up a buffer and add a function def
- else:
- self.writeline('def loop(reciter, loop_render_func):', node)
- self.indent()
- self.buffer(loop_frame)
- aliases = {}
-
- # make sure the loop variable is a special one and raise a template
- # assertion error if a loop tries to write to loop
- if extended_loop:
- loop_frame.identifiers.add_special('loop')
- for name in node.find_all(nodes.Name):
- if name.ctx == 'store' and name.name == 'loop':
- self.fail('Can\'t assign to special loop variable '
- 'in for-loop target', name.lineno)
-
- self.pull_locals(loop_frame)
- if node.else_:
- iteration_indicator = self.temporary_identifier()
- self.writeline('%s = 1' % iteration_indicator)
-
- # Create a fake parent loop if the else or test section of a
- # loop is accessing the special loop variable and no parent loop
- # exists.
- if 'loop' not in aliases and 'loop' in find_undeclared(
- node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
- self.writeline("l_loop = environment.undefined(%r, name='loop')" %
- ("'loop' is undefined. the filter section of a loop as well "
- "as the else block doesn't have access to the special 'loop'"
- " variable of the current loop. Because there is no parent "
- "loop it's undefined. Happened in loop on %s" %
- self.position(node)))
-
- self.writeline('for ', node)
- self.visit(node.target, loop_frame)
- self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
-
- # if we have an extened loop and a node test, we filter in the
- # "outer frame".
- if extended_loop and node.test is not None:
- self.write('(')
- self.visit(node.target, loop_frame)
- self.write(' for ')
- self.visit(node.target, loop_frame)
- self.write(' in ')
- if node.recursive:
- self.write('reciter')
- else:
- self.visit(node.iter, loop_frame)
- self.write(' if (')
- test_frame = loop_frame.copy()
- self.visit(node.test, test_frame)
- self.write('))')
-
- elif node.recursive:
- self.write('reciter')
- else:
- self.visit(node.iter, loop_frame)
-
- if node.recursive:
- self.write(', recurse=loop_render_func):')
- else:
- self.write(extended_loop and '):' or ':')
-
- # tests in not extended loops become a continue
- if not extended_loop and node.test is not None:
- self.indent()
- self.writeline('if not ')
- self.visit(node.test, loop_frame)
- self.write(':')
- self.indent()
- self.writeline('continue')
- self.outdent(2)
-
- self.indent()
- self.blockvisit(node.body, loop_frame)
- if node.else_:
- self.writeline('%s = 0' % iteration_indicator)
- self.outdent()
-
- if node.else_:
- self.writeline('if %s:' % iteration_indicator)
- self.indent()
- self.blockvisit(node.else_, loop_frame)
- self.outdent()
-
- # reset the aliases if there are any.
- if not node.recursive:
- self.pop_scope(aliases, loop_frame)
-
- # if the node was recursive we have to return the buffer contents
- # and start the iteration code
- if node.recursive:
- self.return_buffer_contents(loop_frame)
- self.outdent()
- self.start_write(frame, node)
- self.write('loop(')
- self.visit(node.iter, frame)
- self.write(', loop)')
- self.end_write(frame)
-
- def visit_If(self, node, frame):
- if_frame = frame.soft()
- self.writeline('if ', node)
- self.visit(node.test, if_frame)
- self.write(':')
- self.indent()
- self.blockvisit(node.body, if_frame)
- self.outdent()
- if node.else_:
- self.writeline('else:')
- self.indent()
- self.blockvisit(node.else_, if_frame)
- self.outdent()
-
- def visit_Macro(self, node, frame):
- macro_frame = self.macro_body(node, frame)
- self.newline()
- if frame.toplevel:
- if not node.name.startswith('_'):
- self.write('context.exported_vars.add(%r)' % node.name)
- self.writeline('context.vars[%r] = ' % node.name)
- self.write('l_%s = ' % node.name)
- self.macro_def(node, macro_frame)
- frame.assigned_names.add(node.name)
-
- def visit_CallBlock(self, node, frame):
- children = node.iter_child_nodes(exclude=('call',))
- call_frame = self.macro_body(node, frame, children)
- self.writeline('caller = ')
- self.macro_def(node, call_frame)
- self.start_write(frame, node)
- self.visit_Call(node.call, call_frame, forward_caller=True)
- self.end_write(frame)
-
- def visit_FilterBlock(self, node, frame):
- filter_frame = frame.inner()
- filter_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(filter_frame)
- self.pull_locals(filter_frame)
- self.buffer(filter_frame)
- self.blockvisit(node.body, filter_frame)
- self.start_write(frame, node)
- self.visit_Filter(node.filter, filter_frame)
- self.end_write(frame)
- self.pop_scope(aliases, filter_frame)
-
- def visit_ExprStmt(self, node, frame):
- self.newline(node)
- self.visit(node.node, frame)
-
- def visit_Output(self, node, frame):
- # if we have a known extends statement, we don't output anything
- # if we are in a require_output_check section
- if self.has_known_extends and frame.require_output_check:
- return
-
- if self.environment.finalize:
- finalize = lambda x: unicode(self.environment.finalize(x))
- else:
- finalize = unicode
-
- # if we are inside a frame that requires output checking, we do so
- outdent_later = False
- if frame.require_output_check:
- self.writeline('if parent_template is None:')
- self.indent()
- outdent_later = True
-
- # try to evaluate as many chunks as possible into a static
- # string at compile time.
- body = []
- for child in node.nodes:
- try:
- const = child.as_const(frame.eval_ctx)
- except nodes.Impossible:
- body.append(child)
- continue
- # the frame can't be volatile here, becaus otherwise the
- # as_const() function would raise an Impossible exception
- # at that point.
- try:
- if frame.eval_ctx.autoescape:
- if hasattr(const, '__html__'):
- const = const.__html__()
- else:
- const = escape(const)
- const = finalize(const)
- except:
- # if something goes wrong here we evaluate the node
- # at runtime for easier debugging
- body.append(child)
- continue
- if body and isinstance(body[-1], list):
- body[-1].append(const)
- else:
- body.append([const])
-
- # if we have less than 3 nodes or a buffer we yield or extend/append
- if len(body) < 3 or frame.buffer is not None:
- if frame.buffer is not None:
- # for one item we append, for more we extend
- if len(body) == 1:
- self.writeline('%s.append(' % frame.buffer)
- else:
- self.writeline('%s.extend((' % frame.buffer)
- self.indent()
- for item in body:
- if isinstance(item, list):
- val = repr(concat(item))
- if frame.buffer is None:
- self.writeline('yield ' + val)
- else:
- self.writeline(val + ', ')
- else:
- if frame.buffer is None:
- self.writeline('yield ', item)
- else:
- self.newline(item)
- close = 1
- if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
- elif frame.eval_ctx.autoescape:
- self.write('escape(')
- else:
- self.write('to_string(')
- if self.environment.finalize is not None:
- self.write('environment.finalize(')
- close += 1
- self.visit(item, frame)
- self.write(')' * close)
- if frame.buffer is not None:
- self.write(', ')
- if frame.buffer is not None:
- # close the open parentheses
- self.outdent()
- self.writeline(len(body) == 1 and ')' or '))')
-
- # otherwise we create a format string as this is faster in that case
- else:
- format = []
- arguments = []
- for item in body:
- if isinstance(item, list):
- format.append(concat(item).replace('%', '%%'))
- else:
- format.append('%s')
- arguments.append(item)
- self.writeline('yield ')
- self.write(repr(concat(format)) + ' % (')
- idx = -1
- self.indent()
- for argument in arguments:
- self.newline(argument)
- close = 0
- if frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' escape or to_string)(')
- close += 1
- elif frame.eval_ctx.autoescape:
- self.write('escape(')
- close += 1
- if self.environment.finalize is not None:
- self.write('environment.finalize(')
- close += 1
- self.visit(argument, frame)
- self.write(')' * close + ', ')
- self.outdent()
- self.writeline(')')
-
- if outdent_later:
- self.outdent()
-
- def visit_Assign(self, node, frame):
- self.newline(node)
- # toplevel assignments however go into the local namespace and
- # the current template's context. We create a copy of the frame
- # here and add a set so that the Name visitor can add the assigned
- # names here.
- if frame.toplevel:
- assignment_frame = frame.copy()
- assignment_frame.toplevel_assignments = set()
- else:
- assignment_frame = frame
- self.visit(node.target, assignment_frame)
- self.write(' = ')
- self.visit(node.node, frame)
-
- # make sure toplevel assignments are added to the context.
- if frame.toplevel:
- public_names = [x for x in assignment_frame.toplevel_assignments
- if not x.startswith('_')]
- if len(assignment_frame.toplevel_assignments) == 1:
- name = next(iter(assignment_frame.toplevel_assignments))
- self.writeline('context.vars[%r] = l_%s' % (name, name))
- else:
- self.writeline('context.vars.update({')
- for idx, name in enumerate(assignment_frame.toplevel_assignments):
- if idx:
- self.write(', ')
- self.write('%r: l_%s' % (name, name))
- self.write('})')
- if public_names:
- if len(public_names) == 1:
- self.writeline('context.exported_vars.add(%r)' %
- public_names[0])
- else:
- self.writeline('context.exported_vars.update((%s))' %
- ', '.join(map(repr, public_names)))
-
- # -- Expression Visitors
-
- def visit_Name(self, node, frame):
- if node.ctx == 'store' and frame.toplevel:
- frame.toplevel_assignments.add(node.name)
- self.write('l_' + node.name)
- frame.assigned_names.add(node.name)
-
- def visit_Const(self, node, frame):
- val = node.value
- if isinstance(val, float):
- self.write(str(val))
- else:
- self.write(repr(val))
-
- def visit_TemplateData(self, node, frame):
- try:
- self.write(repr(node.as_const(frame.eval_ctx)))
- except nodes.Impossible:
- self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
- % node.data)
-
- def visit_Tuple(self, node, frame):
- self.write('(')
- idx = -1
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item, frame)
- self.write(idx == 0 and ',)' or ')')
-
- def visit_List(self, node, frame):
- self.write('[')
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item, frame)
- self.write(']')
-
- def visit_Dict(self, node, frame):
- self.write('{')
- for idx, item in enumerate(node.items):
- if idx:
- self.write(', ')
- self.visit(item.key, frame)
- self.write(': ')
- self.visit(item.value, frame)
- self.write('}')
-
- def binop(operator):
- def visitor(self, node, frame):
- self.write('(')
- self.visit(node.left, frame)
- self.write(' %s ' % operator)
- self.visit(node.right, frame)
- self.write(')')
- return visitor
-
- def uaop(operator):
- def visitor(self, node, frame):
- self.write('(' + operator)
- self.visit(node.node, frame)
- self.write(')')
- return visitor
-
- visit_Add = binop('+')
- visit_Sub = binop('-')
- visit_Mul = binop('*')
- visit_Div = binop('/')
- visit_FloorDiv = binop('//')
- visit_Pow = binop('**')
- visit_Mod = binop('%')
- visit_And = binop('and')
- visit_Or = binop('or')
- visit_Pos = uaop('+')
- visit_Neg = uaop('-')
- visit_Not = uaop('not ')
- del binop, uaop
-
- def visit_Concat(self, node, frame):
- if frame.eval_ctx.volatile:
- func_name = '(context.eval_ctx.volatile and' \
- ' markup_join or unicode_join)'
- elif frame.eval_ctx.autoescape:
- func_name = 'markup_join'
- else:
- func_name = 'unicode_join'
- self.write('%s((' % func_name)
- for arg in node.nodes:
- self.visit(arg, frame)
- self.write(', ')
- self.write('))')
-
- def visit_Compare(self, node, frame):
- self.visit(node.expr, frame)
- for op in node.ops:
- self.visit(op, frame)
-
- def visit_Operand(self, node, frame):
- self.write(' %s ' % operators[node.op])
- self.visit(node.expr, frame)
-
- def visit_Getattr(self, node, frame):
- self.write('environment.getattr(')
- self.visit(node.node, frame)
- self.write(', %r)' % node.attr)
-
- def visit_Getitem(self, node, frame):
- # slices bypass the environment getitem method.
- if isinstance(node.arg, nodes.Slice):
- self.visit(node.node, frame)
- self.write('[')
- self.visit(node.arg, frame)
- self.write(']')
- else:
- self.write('environment.getitem(')
- self.visit(node.node, frame)
- self.write(', ')
- self.visit(node.arg, frame)
- self.write(')')
-
- def visit_Slice(self, node, frame):
- if node.start is not None:
- self.visit(node.start, frame)
- self.write(':')
- if node.stop is not None:
- self.visit(node.stop, frame)
- if node.step is not None:
- self.write(':')
- self.visit(node.step, frame)
-
- def visit_Filter(self, node, frame):
- self.write(self.filters[node.name] + '(')
- func = self.environment.filters.get(node.name)
- if func is None:
- self.fail('no filter named %r' % node.name, node.lineno)
- if getattr(func, 'contextfilter', False):
- self.write('context, ')
- elif getattr(func, 'evalcontextfilter', False):
- self.write('context.eval_ctx, ')
- elif getattr(func, 'environmentfilter', False):
- self.write('environment, ')
-
- # if the filter node is None we are inside a filter block
- # and want to write to the current buffer
- if node.node is not None:
- self.visit(node.node, frame)
- elif frame.eval_ctx.volatile:
- self.write('(context.eval_ctx.autoescape and'
- ' Markup(concat(%s)) or concat(%s))' %
- (frame.buffer, frame.buffer))
- elif frame.eval_ctx.autoescape:
- self.write('Markup(concat(%s))' % frame.buffer)
- else:
- self.write('concat(%s)' % frame.buffer)
- self.signature(node, frame)
- self.write(')')
-
- def visit_Test(self, node, frame):
- self.write(self.tests[node.name] + '(')
- if node.name not in self.environment.tests:
- self.fail('no test named %r' % node.name, node.lineno)
- self.visit(node.node, frame)
- self.signature(node, frame)
- self.write(')')
-
- def visit_CondExpr(self, node, frame):
- def write_expr2():
- if node.expr2 is not None:
- return self.visit(node.expr2, frame)
- self.write('environment.undefined(%r)' % ('the inline if-'
- 'expression on %s evaluated to false and '
- 'no else section was defined.' % self.position(node)))
-
- if not have_condexpr:
- self.write('((')
- self.visit(node.test, frame)
- self.write(') and (')
- self.visit(node.expr1, frame)
- self.write(',) or (')
- write_expr2()
- self.write(',))[0]')
- else:
- self.write('(')
- self.visit(node.expr1, frame)
- self.write(' if ')
- self.visit(node.test, frame)
- self.write(' else ')
- write_expr2()
- self.write(')')
-
- def visit_Call(self, node, frame, forward_caller=False):
- if self.environment.sandboxed:
- self.write('environment.call(context, ')
- else:
- self.write('context.call(')
- self.visit(node.node, frame)
- extra_kwargs = forward_caller and {'caller': 'caller'} or None
- self.signature(node, frame, extra_kwargs)
- self.write(')')
-
- def visit_Keyword(self, node, frame):
- self.write(node.key + '=')
- self.visit(node.value, frame)
-
- # -- Unused nodes for extensions
-
- def visit_MarkSafe(self, node, frame):
- self.write('Markup(')
- self.visit(node.expr, frame)
- self.write(')')
-
- def visit_MarkSafeIfAutoescape(self, node, frame):
- self.write('(context.eval_ctx.autoescape and Markup or identity)(')
- self.visit(node.expr, frame)
- self.write(')')
-
- def visit_EnvironmentAttribute(self, node, frame):
- self.write('environment.' + node.name)
-
- def visit_ExtensionAttribute(self, node, frame):
- self.write('environment.extensions[%r].%s' % (node.identifier, node.name))
-
- def visit_ImportedName(self, node, frame):
- self.write(self.import_aliases[node.importname])
-
- def visit_InternalName(self, node, frame):
- self.write(node.name)
-
- def visit_ContextReference(self, node, frame):
- self.write('context')
-
- def visit_Continue(self, node, frame):
- self.writeline('continue', node)
-
- def visit_Break(self, node, frame):
- self.writeline('break', node)
-
- def visit_Scope(self, node, frame):
- scope_frame = frame.inner()
- scope_frame.inspect(node.iter_child_nodes())
- aliases = self.push_scope(scope_frame)
- self.pull_locals(scope_frame)
- self.blockvisit(node.body, scope_frame)
- self.pop_scope(aliases, scope_frame)
-
- def visit_EvalContextModifier(self, node, frame):
- for keyword in node.options:
- self.writeline('context.eval_ctx.%s = ' % keyword.key)
- self.visit(keyword.value, frame)
- try:
- val = keyword.value.as_const(frame.eval_ctx)
- except nodes.Impossible:
- frame.eval_ctx.volatile = True
- else:
- setattr(frame.eval_ctx, keyword.key, val)
-
- def visit_ScopedEvalContextModifier(self, node, frame):
- old_ctx_name = self.temporary_identifier()
- safed_ctx = frame.eval_ctx.save()
- self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
- self.visit_EvalContextModifier(node, frame)
- for child in node.body:
- self.visit(child, frame)
- frame.eval_ctx.revert(safed_ctx)
- self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
diff --git a/module/lib/jinja2/debug.py b/module/lib/jinja2/debug.py
deleted file mode 100644
index eb15456d1..000000000
--- a/module/lib/jinja2/debug.py
+++ /dev/null
@@ -1,308 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.debug
- ~~~~~~~~~~~~
-
- Implements the debug interface for Jinja. This module does some pretty
- ugly stuff with the Python traceback system in order to achieve tracebacks
- with correct line numbers, locals and contents.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import sys
-import traceback
-from jinja2.utils import CodeType, missing, internal_code
-from jinja2.exceptions import TemplateSyntaxError
-
-
-# how does the raise helper look like?
-try:
- exec "raise TypeError, 'foo'"
-except SyntaxError:
- raise_helper = 'raise __jinja_exception__[1]'
-except TypeError:
- raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
-
-
-class TracebackFrameProxy(object):
- """Proxies a traceback frame."""
-
- def __init__(self, tb):
- self.tb = tb
-
- def _set_tb_next(self, next):
- if tb_set_next is not None:
- tb_set_next(self.tb, next and next.tb or None)
- self._tb_next = next
-
- def _get_tb_next(self):
- return self._tb_next
-
- tb_next = property(_get_tb_next, _set_tb_next)
- del _get_tb_next, _set_tb_next
-
- @property
- def is_jinja_frame(self):
- return '__jinja_template__' in self.tb.tb_frame.f_globals
-
- def __getattr__(self, name):
- return getattr(self.tb, name)
-
-
-class ProcessedTraceback(object):
- """Holds a Jinja preprocessed traceback for priting or reraising."""
-
- def __init__(self, exc_type, exc_value, frames):
- assert frames, 'no frames for this traceback?'
- self.exc_type = exc_type
- self.exc_value = exc_value
- self.frames = frames
-
- def chain_frames(self):
- """Chains the frames. Requires ctypes or the debugsupport extension."""
- prev_tb = None
- for tb in self.frames:
- if prev_tb is not None:
- prev_tb.tb_next = tb
- prev_tb = tb
- prev_tb.tb_next = None
-
- def render_as_text(self, limit=None):
- """Return a string with the traceback."""
- lines = traceback.format_exception(self.exc_type, self.exc_value,
- self.frames[0], limit=limit)
- return ''.join(lines).rstrip()
-
- def render_as_html(self, full=False):
- """Return a unicode string with the traceback as rendered HTML."""
- from jinja2.debugrenderer import render_traceback
- return u'%s\n\n<!--\n%s\n-->' % (
- render_traceback(self, full=full),
- self.render_as_text().decode('utf-8', 'replace')
- )
-
- @property
- def is_template_syntax_error(self):
- """`True` if this is a template syntax error."""
- return isinstance(self.exc_value, TemplateSyntaxError)
-
- @property
- def exc_info(self):
- """Exception info tuple with a proxy around the frame objects."""
- return self.exc_type, self.exc_value, self.frames[0]
-
- @property
- def standard_exc_info(self):
- """Standard python exc_info for re-raising"""
- return self.exc_type, self.exc_value, self.frames[0].tb
-
-
-def make_traceback(exc_info, source_hint=None):
- """Creates a processed traceback object from the exc_info."""
- exc_type, exc_value, tb = exc_info
- if isinstance(exc_value, TemplateSyntaxError):
- exc_info = translate_syntax_error(exc_value, source_hint)
- initial_skip = 0
- else:
- initial_skip = 1
- return translate_exception(exc_info, initial_skip)
-
-
-def translate_syntax_error(error, source=None):
- """Rewrites a syntax error to please traceback systems."""
- error.source = source
- error.translated = True
- exc_info = (error.__class__, error, None)
- filename = error.filename
- if filename is None:
- filename = '<unknown>'
- return fake_exc_info(exc_info, filename, error.lineno)
-
-
-def translate_exception(exc_info, initial_skip=0):
- """If passed an exc_info it will automatically rewrite the exceptions
- all the way down to the correct line numbers and frames.
- """
- tb = exc_info[2]
- frames = []
-
- # skip some internal frames if wanted
- for x in xrange(initial_skip):
- if tb is not None:
- tb = tb.tb_next
- initial_tb = tb
-
- while tb is not None:
- # skip frames decorated with @internalcode. These are internal
- # calls we can't avoid and that are useless in template debugging
- # output.
- if tb.tb_frame.f_code in internal_code:
- tb = tb.tb_next
- continue
-
- # save a reference to the next frame if we override the current
- # one with a faked one.
- next = tb.tb_next
-
- # fake template exceptions
- template = tb.tb_frame.f_globals.get('__jinja_template__')
- if template is not None:
- lineno = template.get_corresponding_lineno(tb.tb_lineno)
- tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
- lineno)[2]
-
- frames.append(TracebackFrameProxy(tb))
- tb = next
-
- # if we don't have any exceptions in the frames left, we have to
- # reraise it unchanged.
- # XXX: can we backup here? when could this happen?
- if not frames:
- raise exc_info[0], exc_info[1], exc_info[2]
-
- traceback = ProcessedTraceback(exc_info[0], exc_info[1], frames)
- if tb_set_next is not None:
- traceback.chain_frames()
- return traceback
-
-
-def fake_exc_info(exc_info, filename, lineno):
- """Helper for `translate_exception`."""
- exc_type, exc_value, tb = exc_info
-
- # figure the real context out
- if tb is not None:
- real_locals = tb.tb_frame.f_locals.copy()
- ctx = real_locals.get('context')
- if ctx:
- locals = ctx.get_all()
- else:
- locals = {}
- for name, value in real_locals.iteritems():
- if name.startswith('l_') and value is not missing:
- locals[name[2:]] = value
-
- # if there is a local called __jinja_exception__, we get
- # rid of it to not break the debug functionality.
- locals.pop('__jinja_exception__', None)
- else:
- locals = {}
-
- # assamble fake globals we need
- globals = {
- '__name__': filename,
- '__file__': filename,
- '__jinja_exception__': exc_info[:2],
-
- # we don't want to keep the reference to the template around
- # to not cause circular dependencies, but we mark it as Jinja
- # frame for the ProcessedTraceback
- '__jinja_template__': None
- }
-
- # and fake the exception
- code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
-
- # if it's possible, change the name of the code. This won't work
- # on some python environments such as google appengine
- try:
- if tb is None:
- location = 'template'
- else:
- function = tb.tb_frame.f_code.co_name
- if function == 'root':
- location = 'top-level template code'
- elif function.startswith('block_'):
- location = 'block "%s"' % function[6:]
- else:
- location = 'template'
- code = CodeType(0, code.co_nlocals, code.co_stacksize,
- code.co_flags, code.co_code, code.co_consts,
- code.co_names, code.co_varnames, filename,
- location, code.co_firstlineno,
- code.co_lnotab, (), ())
- except:
- pass
-
- # execute the code and catch the new traceback
- try:
- exec code in globals, locals
- except:
- exc_info = sys.exc_info()
- new_tb = exc_info[2].tb_next
-
- # return without this frame
- return exc_info[:2] + (new_tb,)
-
-
-def _init_ugly_crap():
- """This function implements a few ugly things so that we can patch the
- traceback objects. The function returned allows resetting `tb_next` on
- any python traceback object.
- """
- import ctypes
- from types import TracebackType
-
- # figure out side of _Py_ssize_t
- if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
- _Py_ssize_t = ctypes.c_int64
- else:
- _Py_ssize_t = ctypes.c_int
-
- # regular python
- class _PyObject(ctypes.Structure):
- pass
- _PyObject._fields_ = [
- ('ob_refcnt', _Py_ssize_t),
- ('ob_type', ctypes.POINTER(_PyObject))
- ]
-
- # python with trace
- if hasattr(sys, 'getobjects'):
- class _PyObject(ctypes.Structure):
- pass
- _PyObject._fields_ = [
- ('_ob_next', ctypes.POINTER(_PyObject)),
- ('_ob_prev', ctypes.POINTER(_PyObject)),
- ('ob_refcnt', _Py_ssize_t),
- ('ob_type', ctypes.POINTER(_PyObject))
- ]
-
- class _Traceback(_PyObject):
- pass
- _Traceback._fields_ = [
- ('tb_next', ctypes.POINTER(_Traceback)),
- ('tb_frame', ctypes.POINTER(_PyObject)),
- ('tb_lasti', ctypes.c_int),
- ('tb_lineno', ctypes.c_int)
- ]
-
- def tb_set_next(tb, next):
- """Set the tb_next attribute of a traceback object."""
- if not (isinstance(tb, TracebackType) and
- (next is None or isinstance(next, TracebackType))):
- raise TypeError('tb_set_next arguments must be traceback objects')
- obj = _Traceback.from_address(id(tb))
- if tb.tb_next is not None:
- old = _Traceback.from_address(id(tb.tb_next))
- old.ob_refcnt -= 1
- if next is None:
- obj.tb_next = ctypes.POINTER(_Traceback)()
- else:
- next = _Traceback.from_address(id(next))
- next.ob_refcnt += 1
- obj.tb_next = ctypes.pointer(next)
-
- return tb_set_next
-
-
-# try to get a tb_set_next implementation
-try:
- from jinja2._debugsupport import tb_set_next
-except ImportError:
- try:
- tb_set_next = _init_ugly_crap()
- except:
- tb_set_next = None
-del _init_ugly_crap
diff --git a/module/lib/jinja2/defaults.py b/module/lib/jinja2/defaults.py
deleted file mode 100644
index d2d45443a..000000000
--- a/module/lib/jinja2/defaults.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.defaults
- ~~~~~~~~~~~~~~~
-
- Jinja default filters and tags.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
-
-
-# defaults for the parser / lexer
-BLOCK_START_STRING = '{%'
-BLOCK_END_STRING = '%}'
-VARIABLE_START_STRING = '{{'
-VARIABLE_END_STRING = '}}'
-COMMENT_START_STRING = '{#'
-COMMENT_END_STRING = '#}'
-LINE_STATEMENT_PREFIX = None
-LINE_COMMENT_PREFIX = None
-TRIM_BLOCKS = False
-NEWLINE_SEQUENCE = '\n'
-
-
-# default filters, tests and namespace
-from jinja2.filters import FILTERS as DEFAULT_FILTERS
-from jinja2.tests import TESTS as DEFAULT_TESTS
-DEFAULT_NAMESPACE = {
- 'range': xrange,
- 'dict': lambda **kw: kw,
- 'lipsum': generate_lorem_ipsum,
- 'cycler': Cycler,
- 'joiner': Joiner
-}
-
-
-# export all constants
-__all__ = tuple(x for x in locals().keys() if x.isupper())
diff --git a/module/lib/jinja2/environment.py b/module/lib/jinja2/environment.py
deleted file mode 100644
index ac74a5c68..000000000
--- a/module/lib/jinja2/environment.py
+++ /dev/null
@@ -1,1118 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.environment
- ~~~~~~~~~~~~~~~~~~
-
- Provides a class that holds runtime and parsing time options.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import os
-import sys
-from jinja2 import nodes
-from jinja2.defaults import *
-from jinja2.lexer import get_lexer, TokenStream
-from jinja2.parser import Parser
-from jinja2.optimizer import optimize
-from jinja2.compiler import generate
-from jinja2.runtime import Undefined, new_context
-from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
- TemplatesNotFound
-from jinja2.utils import import_string, LRUCache, Markup, missing, \
- concat, consume, internalcode, _encode_filename
-
-
-# for direct template usage we have up to ten living environments
-_spontaneous_environments = LRUCache(10)
-
-# the function to create jinja traceback objects. This is dynamically
-# imported on the first exception in the exception handler.
-_make_traceback = None
-
-
-def get_spontaneous_environment(*args):
- """Return a new spontaneous environment. A spontaneous environment is an
- unnamed and unaccessible (in theory) environment that is used for
- templates generated from a string and not from the file system.
- """
- try:
- env = _spontaneous_environments.get(args)
- except TypeError:
- return Environment(*args)
- if env is not None:
- return env
- _spontaneous_environments[args] = env = Environment(*args)
- env.shared = True
- return env
-
-
-def create_cache(size):
- """Return the cache class for the given size."""
- if size == 0:
- return None
- if size < 0:
- return {}
- return LRUCache(size)
-
-
-def copy_cache(cache):
- """Create an empty copy of the given cache."""
- if cache is None:
- return None
- elif type(cache) is dict:
- return {}
- return LRUCache(cache.capacity)
-
-
-def load_extensions(environment, extensions):
- """Load the extensions from the list and bind it to the environment.
- Returns a dict of instanciated environments.
- """
- result = {}
- for extension in extensions:
- if isinstance(extension, basestring):
- extension = import_string(extension)
- result[extension.identifier] = extension(environment)
- return result
-
-
-def _environment_sanity_check(environment):
- """Perform a sanity check on the environment."""
- assert issubclass(environment.undefined, Undefined), 'undefined must ' \
- 'be a subclass of undefined because filters depend on it.'
- assert environment.block_start_string != \
- environment.variable_start_string != \
- environment.comment_start_string, 'block, variable and comment ' \
- 'start strings must be different'
- assert environment.newline_sequence in ('\r', '\r\n', '\n'), \
- 'newline_sequence set to unknown line ending string.'
- return environment
-
-
-class Environment(object):
- r"""The core component of Jinja is the `Environment`. It contains
- important shared variables like configuration, filters, tests,
- globals and others. Instances of this class may be modified if
- they are not shared and if no template was loaded so far.
- Modifications on environments after the first template was loaded
- will lead to surprising effects and undefined behavior.
-
- Here the possible initialization parameters:
-
- `block_start_string`
- The string marking the begin of a block. Defaults to ``'{%'``.
-
- `block_end_string`
- The string marking the end of a block. Defaults to ``'%}'``.
-
- `variable_start_string`
- The string marking the begin of a print statement.
- Defaults to ``'{{'``.
-
- `variable_end_string`
- The string marking the end of a print statement. Defaults to
- ``'}}'``.
-
- `comment_start_string`
- The string marking the begin of a comment. Defaults to ``'{#'``.
-
- `comment_end_string`
- The string marking the end of a comment. Defaults to ``'#}'``.
-
- `line_statement_prefix`
- If given and a string, this will be used as prefix for line based
- statements. See also :ref:`line-statements`.
-
- `line_comment_prefix`
- If given and a string, this will be used as prefix for line based
- based comments. See also :ref:`line-statements`.
-
- .. versionadded:: 2.2
-
- `trim_blocks`
- If this is set to ``True`` the first newline after a block is
- removed (block, not variable tag!). Defaults to `False`.
-
- `newline_sequence`
- The sequence that starts a newline. Must be one of ``'\r'``,
- ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
- useful default for Linux and OS X systems as well as web
- applications.
-
- `extensions`
- List of Jinja extensions to use. This can either be import paths
- as strings or extension classes. For more information have a
- look at :ref:`the extensions documentation <jinja-extensions>`.
-
- `optimized`
- should the optimizer be enabled? Default is `True`.
-
- `undefined`
- :class:`Undefined` or a subclass of it that is used to represent
- undefined values in the template.
-
- `finalize`
- A callable that can be used to process the result of a variable
- expression before it is output. For example one can convert
- `None` implicitly into an empty string here.
-
- `autoescape`
- If set to true the XML/HTML autoescaping feature is enabled by
- default. For more details about auto escaping see
- :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also
- be a callable that is passed the template name and has to
- return `True` or `False` depending on autoescape should be
- enabled by default.
-
- .. versionchanged:: 2.4
- `autoescape` can now be a function
-
- `loader`
- The template loader for this environment.
-
- `cache_size`
- The size of the cache. Per default this is ``50`` which means
- that if more than 50 templates are loaded the loader will clean
- out the least recently used template. If the cache size is set to
- ``0`` templates are recompiled all the time, if the cache size is
- ``-1`` the cache will not be cleaned.
-
- `auto_reload`
- Some loaders load templates from locations where the template
- sources may change (ie: file system or database). If
- `auto_reload` is set to `True` (default) every time a template is
- requested the loader checks if the source changed and if yes, it
- will reload the template. For higher performance it's possible to
- disable that.
-
- `bytecode_cache`
- If set to a bytecode cache object, this object will provide a
- cache for the internal Jinja bytecode so that templates don't
- have to be parsed if they were not changed.
-
- See :ref:`bytecode-cache` for more information.
- """
-
- #: if this environment is sandboxed. Modifying this variable won't make
- #: the environment sandboxed though. For a real sandboxed environment
- #: have a look at jinja2.sandbox
- sandboxed = False
-
- #: True if the environment is just an overlay
- overlayed = False
-
- #: the environment this environment is linked to if it is an overlay
- linked_to = None
-
- #: shared environments have this set to `True`. A shared environment
- #: must not be modified
- shared = False
-
- #: these are currently EXPERIMENTAL undocumented features.
- exception_handler = None
- exception_formatter = None
-
- def __init__(self,
- block_start_string=BLOCK_START_STRING,
- block_end_string=BLOCK_END_STRING,
- variable_start_string=VARIABLE_START_STRING,
- variable_end_string=VARIABLE_END_STRING,
- comment_start_string=COMMENT_START_STRING,
- comment_end_string=COMMENT_END_STRING,
- line_statement_prefix=LINE_STATEMENT_PREFIX,
- line_comment_prefix=LINE_COMMENT_PREFIX,
- trim_blocks=TRIM_BLOCKS,
- newline_sequence=NEWLINE_SEQUENCE,
- extensions=(),
- optimized=True,
- undefined=Undefined,
- finalize=None,
- autoescape=False,
- loader=None,
- cache_size=50,
- auto_reload=True,
- bytecode_cache=None):
- # !!Important notice!!
- # The constructor accepts quite a few arguments that should be
- # passed by keyword rather than position. However it's important to
- # not change the order of arguments because it's used at least
- # internally in those cases:
- # - spontaneus environments (i18n extension and Template)
- # - unittests
- # If parameter changes are required only add parameters at the end
- # and don't change the arguments (or the defaults!) of the arguments
- # existing already.
-
- # lexer / parser information
- self.block_start_string = block_start_string
- self.block_end_string = block_end_string
- self.variable_start_string = variable_start_string
- self.variable_end_string = variable_end_string
- self.comment_start_string = comment_start_string
- self.comment_end_string = comment_end_string
- self.line_statement_prefix = line_statement_prefix
- self.line_comment_prefix = line_comment_prefix
- self.trim_blocks = trim_blocks
- self.newline_sequence = newline_sequence
-
- # runtime information
- self.undefined = undefined
- self.optimized = optimized
- self.finalize = finalize
- self.autoescape = autoescape
-
- # defaults
- self.filters = DEFAULT_FILTERS.copy()
- self.tests = DEFAULT_TESTS.copy()
- self.globals = DEFAULT_NAMESPACE.copy()
-
- # set the loader provided
- self.loader = loader
- self.bytecode_cache = None
- self.cache = create_cache(cache_size)
- self.bytecode_cache = bytecode_cache
- self.auto_reload = auto_reload
-
- # load extensions
- self.extensions = load_extensions(self, extensions)
-
- _environment_sanity_check(self)
-
- def add_extension(self, extension):
- """Adds an extension after the environment was created.
-
- .. versionadded:: 2.5
- """
- self.extensions.update(load_extensions(self, [extension]))
-
- def extend(self, **attributes):
- """Add the items to the instance of the environment if they do not exist
- yet. This is used by :ref:`extensions <writing-extensions>` to register
- callbacks and configuration values without breaking inheritance.
- """
- for key, value in attributes.iteritems():
- if not hasattr(self, key):
- setattr(self, key, value)
-
- def overlay(self, block_start_string=missing, block_end_string=missing,
- variable_start_string=missing, variable_end_string=missing,
- comment_start_string=missing, comment_end_string=missing,
- line_statement_prefix=missing, line_comment_prefix=missing,
- trim_blocks=missing, extensions=missing, optimized=missing,
- undefined=missing, finalize=missing, autoescape=missing,
- loader=missing, cache_size=missing, auto_reload=missing,
- bytecode_cache=missing):
- """Create a new overlay environment that shares all the data with the
- current environment except of cache and the overridden attributes.
- Extensions cannot be removed for an overlayed environment. An overlayed
- environment automatically gets all the extensions of the environment it
- is linked to plus optional extra extensions.
-
- Creating overlays should happen after the initial environment was set
- up completely. Not all attributes are truly linked, some are just
- copied over so modifications on the original environment may not shine
- through.
- """
- args = dict(locals())
- del args['self'], args['cache_size'], args['extensions']
-
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.overlayed = True
- rv.linked_to = self
-
- for key, value in args.iteritems():
- if value is not missing:
- setattr(rv, key, value)
-
- if cache_size is not missing:
- rv.cache = create_cache(cache_size)
- else:
- rv.cache = copy_cache(self.cache)
-
- rv.extensions = {}
- for key, value in self.extensions.iteritems():
- rv.extensions[key] = value.bind(rv)
- if extensions is not missing:
- rv.extensions.update(load_extensions(rv, extensions))
-
- return _environment_sanity_check(rv)
-
- lexer = property(get_lexer, doc="The lexer for this environment.")
-
- def iter_extensions(self):
- """Iterates over the extensions by priority."""
- return iter(sorted(self.extensions.values(),
- key=lambda x: x.priority))
-
- def getitem(self, obj, argument):
- """Get an item or attribute of an object but prefer the item."""
- try:
- return obj[argument]
- except (TypeError, LookupError):
- if isinstance(argument, basestring):
- try:
- attr = str(argument)
- except:
- pass
- else:
- try:
- return getattr(obj, attr)
- except AttributeError:
- pass
- return self.undefined(obj=obj, name=argument)
-
- def getattr(self, obj, attribute):
- """Get an item or attribute of an object but prefer the attribute.
- Unlike :meth:`getitem` the attribute *must* be a bytestring.
- """
- try:
- return getattr(obj, attribute)
- except AttributeError:
- pass
- try:
- return obj[attribute]
- except (TypeError, LookupError, AttributeError):
- return self.undefined(obj=obj, name=attribute)
-
- @internalcode
- def parse(self, source, name=None, filename=None):
- """Parse the sourcecode and return the abstract syntax tree. This
- tree of nodes is used by the compiler to convert the template into
- executable source- or bytecode. This is useful for debugging or to
- extract information from templates.
-
- If you are :ref:`developing Jinja2 extensions <writing-extensions>`
- this gives you a good overview of the node tree generated.
- """
- try:
- return self._parse(source, name, filename)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def _parse(self, source, name, filename):
- """Internal parsing function used by `parse` and `compile`."""
- return Parser(self, source, name, _encode_filename(filename)).parse()
-
- def lex(self, source, name=None, filename=None):
- """Lex the given sourcecode and return a generator that yields
- tokens as tuples in the form ``(lineno, token_type, value)``.
- This can be useful for :ref:`extension development <writing-extensions>`
- and debugging templates.
-
- This does not perform preprocessing. If you want the preprocessing
- of the extensions to be applied you have to filter source through
- the :meth:`preprocess` method.
- """
- source = unicode(source)
- try:
- return self.lexer.tokeniter(source, name, filename)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def preprocess(self, source, name=None, filename=None):
- """Preprocesses the source with all extensions. This is automatically
- called for all parsing and compiling methods but *not* for :meth:`lex`
- because there you usually only want the actual source tokenized.
- """
- return reduce(lambda s, e: e.preprocess(s, name, filename),
- self.iter_extensions(), unicode(source))
-
- def _tokenize(self, source, name, filename=None, state=None):
- """Called by the parser to do the preprocessing and filtering
- for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
- """
- source = self.preprocess(source, name, filename)
- stream = self.lexer.tokenize(source, name, filename, state)
- for ext in self.iter_extensions():
- stream = ext.filter_stream(stream)
- if not isinstance(stream, TokenStream):
- stream = TokenStream(stream, name, filename)
- return stream
-
- def _generate(self, source, name, filename, defer_init=False):
- """Internal hook that can be overriden to hook a different generate
- method in.
-
- .. versionadded:: 2.5
- """
- return generate(source, self, name, filename, defer_init=defer_init)
-
- def _compile(self, source, filename):
- """Internal hook that can be overriden to hook a different compile
- method in.
-
- .. versionadded:: 2.5
- """
- return compile(source, filename, 'exec')
-
- @internalcode
- def compile(self, source, name=None, filename=None, raw=False,
- defer_init=False):
- """Compile a node or template source code. The `name` parameter is
- the load name of the template after it was joined using
- :meth:`join_path` if necessary, not the filename on the file system.
- the `filename` parameter is the estimated filename of the template on
- the file system. If the template came from a database or memory this
- can be omitted.
-
- The return value of this method is a python code object. If the `raw`
- parameter is `True` the return value will be a string with python
- code equivalent to the bytecode returned otherwise. This method is
- mainly used internally.
-
- `defer_init` is use internally to aid the module code generator. This
- causes the generated code to be able to import without the global
- environment variable to be set.
-
- .. versionadded:: 2.4
- `defer_init` parameter added.
- """
- source_hint = None
- try:
- if isinstance(source, basestring):
- source_hint = source
- source = self._parse(source, name, filename)
- if self.optimized:
- source = optimize(source, self)
- source = self._generate(source, name, filename,
- defer_init=defer_init)
- if raw:
- return source
- if filename is None:
- filename = '<template>'
- else:
- filename = _encode_filename(filename)
- return self._compile(source, filename)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- self.handle_exception(exc_info, source_hint=source)
-
- def compile_expression(self, source, undefined_to_none=True):
- """A handy helper method that returns a callable that accepts keyword
- arguments that appear as variables in the expression. If called it
- returns the result of the expression.
-
- This is useful if applications want to use the same rules as Jinja
- in template "configuration files" or similar situations.
-
- Example usage:
-
- >>> env = Environment()
- >>> expr = env.compile_expression('foo == 42')
- >>> expr(foo=23)
- False
- >>> expr(foo=42)
- True
-
- Per default the return value is converted to `None` if the
- expression returns an undefined value. This can be changed
- by setting `undefined_to_none` to `False`.
-
- >>> env.compile_expression('var')() is None
- True
- >>> env.compile_expression('var', undefined_to_none=False)()
- Undefined
-
- .. versionadded:: 2.1
- """
- parser = Parser(self, source, state='variable')
- exc_info = None
- try:
- expr = parser.parse_expression()
- if not parser.stream.eos:
- raise TemplateSyntaxError('chunk after expression',
- parser.stream.current.lineno,
- None, None)
- expr.set_environment(self)
- except TemplateSyntaxError:
- exc_info = sys.exc_info()
- if exc_info is not None:
- self.handle_exception(exc_info, source_hint=source)
- body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)]
- template = self.from_string(nodes.Template(body, lineno=1))
- return TemplateExpression(template, undefined_to_none)
-
- def compile_templates(self, target, extensions=None, filter_func=None,
- zip='deflated', log_function=None,
- ignore_errors=True, py_compile=False):
- """Compiles all the templates the loader can find, compiles them
- and stores them in `target`. If `zip` is `None`, instead of in a
- zipfile, the templates will be will be stored in a directory.
- By default a deflate zip algorithm is used, to switch to
- the stored algorithm, `zip` can be set to ``'stored'``.
-
- `extensions` and `filter_func` are passed to :meth:`list_templates`.
- Each template returned will be compiled to the target folder or
- zipfile.
-
- By default template compilation errors are ignored. In case a
- log function is provided, errors are logged. If you want template
- syntax errors to abort the compilation you can set `ignore_errors`
- to `False` and you will get an exception on syntax errors.
-
- If `py_compile` is set to `True` .pyc files will be written to the
- target instead of standard .py files.
-
- .. versionadded:: 2.4
- """
- from jinja2.loaders import ModuleLoader
-
- if log_function is None:
- log_function = lambda x: None
-
- if py_compile:
- import imp, struct, marshal
- py_header = imp.get_magic() + \
- u'\xff\xff\xff\xff'.encode('iso-8859-15')
-
- def write_file(filename, data, mode):
- if zip:
- info = ZipInfo(filename)
- info.external_attr = 0755 << 16L
- zip_file.writestr(info, data)
- else:
- f = open(os.path.join(target, filename), mode)
- try:
- f.write(data)
- finally:
- f.close()
-
- if zip is not None:
- from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
- zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED,
- stored=ZIP_STORED)[zip])
- log_function('Compiling into Zip archive "%s"' % target)
- else:
- if not os.path.isdir(target):
- os.makedirs(target)
- log_function('Compiling into folder "%s"' % target)
-
- try:
- for name in self.list_templates(extensions, filter_func):
- source, filename, _ = self.loader.get_source(self, name)
- try:
- code = self.compile(source, name, filename, True, True)
- except TemplateSyntaxError, e:
- if not ignore_errors:
- raise
- log_function('Could not compile "%s": %s' % (name, e))
- continue
-
- filename = ModuleLoader.get_module_filename(name)
-
- if py_compile:
- c = self._compile(code, _encode_filename(filename))
- write_file(filename + 'c', py_header +
- marshal.dumps(c), 'wb')
- log_function('Byte-compiled "%s" as %s' %
- (name, filename + 'c'))
- else:
- write_file(filename, code, 'w')
- log_function('Compiled "%s" as %s' % (name, filename))
- finally:
- if zip:
- zip_file.close()
-
- log_function('Finished compiling templates')
-
- def list_templates(self, extensions=None, filter_func=None):
- """Returns a list of templates for this environment. This requires
- that the loader supports the loader's
- :meth:`~BaseLoader.list_templates` method.
-
- If there are other files in the template folder besides the
- actual templates, the returned list can be filtered. There are two
- ways: either `extensions` is set to a list of file extensions for
- templates, or a `filter_func` can be provided which is a callable that
- is passed a template name and should return `True` if it should end up
- in the result list.
-
- If the loader does not support that, a :exc:`TypeError` is raised.
- """
- x = self.loader.list_templates()
- if extensions is not None:
- if filter_func is not None:
- raise TypeError('either extensions or filter_func '
- 'can be passed, but not both')
- filter_func = lambda x: '.' in x and \
- x.rsplit('.', 1)[1] in extensions
- if filter_func is not None:
- x = filter(filter_func, x)
- return x
-
- def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
- """Exception handling helper. This is used internally to either raise
- rewritten exceptions or return a rendered traceback for the template.
- """
- global _make_traceback
- if exc_info is None:
- exc_info = sys.exc_info()
-
- # the debugging module is imported when it's used for the first time.
- # we're doing a lot of stuff there and for applications that do not
- # get any exceptions in template rendering there is no need to load
- # all of that.
- if _make_traceback is None:
- from jinja2.debug import make_traceback as _make_traceback
- traceback = _make_traceback(exc_info, source_hint)
- if rendered and self.exception_formatter is not None:
- return self.exception_formatter(traceback)
- if self.exception_handler is not None:
- self.exception_handler(traceback)
- exc_type, exc_value, tb = traceback.standard_exc_info
- raise exc_type, exc_value, tb
-
- def join_path(self, template, parent):
- """Join a template with the parent. By default all the lookups are
- relative to the loader root so this method returns the `template`
- parameter unchanged, but if the paths should be relative to the
- parent template, this function can be used to calculate the real
- template name.
-
- Subclasses may override this method and implement template path
- joining here.
- """
- return template
-
- @internalcode
- def _load_template(self, name, globals):
- if self.loader is None:
- raise TypeError('no loader for this environment specified')
- if self.cache is not None:
- template = self.cache.get(name)
- if template is not None and (not self.auto_reload or \
- template.is_up_to_date):
- return template
- template = self.loader.load(self, name, globals)
- if self.cache is not None:
- self.cache[name] = template
- return template
-
- @internalcode
- def get_template(self, name, parent=None, globals=None):
- """Load a template from the loader. If a loader is configured this
- method ask the loader for the template and returns a :class:`Template`.
- If the `parent` parameter is not `None`, :meth:`join_path` is called
- to get the real template name before loading.
-
- The `globals` parameter can be used to provide template wide globals.
- These variables are available in the context at render time.
-
- If the template does not exist a :exc:`TemplateNotFound` exception is
- raised.
-
- .. versionchanged:: 2.4
- If `name` is a :class:`Template` object it is returned from the
- function unchanged.
- """
- if isinstance(name, Template):
- return name
- if parent is not None:
- name = self.join_path(name, parent)
- return self._load_template(name, self.make_globals(globals))
-
- @internalcode
- def select_template(self, names, parent=None, globals=None):
- """Works like :meth:`get_template` but tries a number of templates
- before it fails. If it cannot find any of the templates, it will
- raise a :exc:`TemplatesNotFound` exception.
-
- .. versionadded:: 2.3
-
- .. versionchanged:: 2.4
- If `names` contains a :class:`Template` object it is returned
- from the function unchanged.
- """
- if not names:
- raise TemplatesNotFound(message=u'Tried to select from an empty list '
- u'of templates.')
- globals = self.make_globals(globals)
- for name in names:
- if isinstance(name, Template):
- return name
- if parent is not None:
- name = self.join_path(name, parent)
- try:
- return self._load_template(name, globals)
- except TemplateNotFound:
- pass
- raise TemplatesNotFound(names)
-
- @internalcode
- def get_or_select_template(self, template_name_or_list,
- parent=None, globals=None):
- """Does a typecheck and dispatches to :meth:`select_template`
- if an iterable of template names is given, otherwise to
- :meth:`get_template`.
-
- .. versionadded:: 2.3
- """
- if isinstance(template_name_or_list, basestring):
- return self.get_template(template_name_or_list, parent, globals)
- elif isinstance(template_name_or_list, Template):
- return template_name_or_list
- return self.select_template(template_name_or_list, parent, globals)
-
- def from_string(self, source, globals=None, template_class=None):
- """Load a template from a string. This parses the source given and
- returns a :class:`Template` object.
- """
- globals = self.make_globals(globals)
- cls = template_class or self.template_class
- return cls.from_code(self, self.compile(source), globals, None)
-
- def make_globals(self, d):
- """Return a dict for the globals."""
- if not d:
- return self.globals
- return dict(self.globals, **d)
-
-
-class Template(object):
- """The central template object. This class represents a compiled template
- and is used to evaluate it.
-
- Normally the template object is generated from an :class:`Environment` but
- it also has a constructor that makes it possible to create a template
- instance directly using the constructor. It takes the same arguments as
- the environment constructor but it's not possible to specify a loader.
-
- Every template object has a few methods and members that are guaranteed
- to exist. However it's important that a template object should be
- considered immutable. Modifications on the object are not supported.
-
- Template objects created from the constructor rather than an environment
- do have an `environment` attribute that points to a temporary environment
- that is probably shared with other templates created with the constructor
- and compatible settings.
-
- >>> template = Template('Hello {{ name }}!')
- >>> template.render(name='John Doe')
- u'Hello John Doe!'
-
- >>> stream = template.stream(name='John Doe')
- >>> stream.next()
- u'Hello John Doe!'
- >>> stream.next()
- Traceback (most recent call last):
- ...
- StopIteration
- """
-
- def __new__(cls, source,
- block_start_string=BLOCK_START_STRING,
- block_end_string=BLOCK_END_STRING,
- variable_start_string=VARIABLE_START_STRING,
- variable_end_string=VARIABLE_END_STRING,
- comment_start_string=COMMENT_START_STRING,
- comment_end_string=COMMENT_END_STRING,
- line_statement_prefix=LINE_STATEMENT_PREFIX,
- line_comment_prefix=LINE_COMMENT_PREFIX,
- trim_blocks=TRIM_BLOCKS,
- newline_sequence=NEWLINE_SEQUENCE,
- extensions=(),
- optimized=True,
- undefined=Undefined,
- finalize=None,
- autoescape=False):
- env = get_spontaneous_environment(
- block_start_string, block_end_string, variable_start_string,
- variable_end_string, comment_start_string, comment_end_string,
- line_statement_prefix, line_comment_prefix, trim_blocks,
- newline_sequence, frozenset(extensions), optimized, undefined,
- finalize, autoescape, None, 0, False, None)
- return env.from_string(source, template_class=cls)
-
- @classmethod
- def from_code(cls, environment, code, globals, uptodate=None):
- """Creates a template object from compiled code and the globals. This
- is used by the loaders and environment to create a template object.
- """
- namespace = {
- 'environment': environment,
- '__file__': code.co_filename
- }
- exec code in namespace
- rv = cls._from_namespace(environment, namespace, globals)
- rv._uptodate = uptodate
- return rv
-
- @classmethod
- def from_module_dict(cls, environment, module_dict, globals):
- """Creates a template object from a module. This is used by the
- module loader to create a template object.
-
- .. versionadded:: 2.4
- """
- return cls._from_namespace(environment, module_dict, globals)
-
- @classmethod
- def _from_namespace(cls, environment, namespace, globals):
- t = object.__new__(cls)
- t.environment = environment
- t.globals = globals
- t.name = namespace['name']
- t.filename = namespace['__file__']
- t.blocks = namespace['blocks']
-
- # render function and module
- t.root_render_func = namespace['root']
- t._module = None
-
- # debug and loader helpers
- t._debug_info = namespace['debug_info']
- t._uptodate = None
-
- # store the reference
- namespace['environment'] = environment
- namespace['__jinja_template__'] = t
-
- return t
-
- def render(self, *args, **kwargs):
- """This method accepts the same arguments as the `dict` constructor:
- A dict, a dict subclass or some keyword arguments. If no arguments
- are given the context will be empty. These two calls do the same::
-
- template.render(knights='that say nih')
- template.render({'knights': 'that say nih'})
-
- This will return the rendered template as unicode string.
- """
- vars = dict(*args, **kwargs)
- try:
- return concat(self.root_render_func(self.new_context(vars)))
- except:
- exc_info = sys.exc_info()
- return self.environment.handle_exception(exc_info, True)
-
- def stream(self, *args, **kwargs):
- """Works exactly like :meth:`generate` but returns a
- :class:`TemplateStream`.
- """
- return TemplateStream(self.generate(*args, **kwargs))
-
- def generate(self, *args, **kwargs):
- """For very large templates it can be useful to not render the whole
- template at once but evaluate each statement after another and yield
- piece for piece. This method basically does exactly that and returns
- a generator that yields one item after another as unicode strings.
-
- It accepts the same arguments as :meth:`render`.
- """
- vars = dict(*args, **kwargs)
- try:
- for event in self.root_render_func(self.new_context(vars)):
- yield event
- except:
- exc_info = sys.exc_info()
- else:
- return
- yield self.environment.handle_exception(exc_info, True)
-
- def new_context(self, vars=None, shared=False, locals=None):
- """Create a new :class:`Context` for this template. The vars
- provided will be passed to the template. Per default the globals
- are added to the context. If shared is set to `True` the data
- is passed as it to the context without adding the globals.
-
- `locals` can be a dict of local variables for internal usage.
- """
- return new_context(self.environment, self.name, self.blocks,
- vars, shared, self.globals, locals)
-
- def make_module(self, vars=None, shared=False, locals=None):
- """This method works like the :attr:`module` attribute when called
- without arguments but it will evaluate the template on every call
- rather than caching it. It's also possible to provide
- a dict which is then used as context. The arguments are the same
- as for the :meth:`new_context` method.
- """
- return TemplateModule(self, self.new_context(vars, shared, locals))
-
- @property
- def module(self):
- """The template as module. This is used for imports in the
- template runtime but is also useful if one wants to access
- exported template variables from the Python layer:
-
- >>> t = Template('{% macro foo() %}42{% endmacro %}23')
- >>> unicode(t.module)
- u'23'
- >>> t.module.foo()
- u'42'
- """
- if self._module is not None:
- return self._module
- self._module = rv = self.make_module()
- return rv
-
- def get_corresponding_lineno(self, lineno):
- """Return the source line number of a line number in the
- generated bytecode as they are not in sync.
- """
- for template_line, code_line in reversed(self.debug_info):
- if code_line <= lineno:
- return template_line
- return 1
-
- @property
- def is_up_to_date(self):
- """If this variable is `False` there is a newer version available."""
- if self._uptodate is None:
- return True
- return self._uptodate()
-
- @property
- def debug_info(self):
- """The debug info mapping."""
- return [tuple(map(int, x.split('='))) for x in
- self._debug_info.split('&')]
-
- def __repr__(self):
- if self.name is None:
- name = 'memory:%x' % id(self)
- else:
- name = repr(self.name)
- return '<%s %s>' % (self.__class__.__name__, name)
-
-
-class TemplateModule(object):
- """Represents an imported template. All the exported names of the
- template are available as attributes on this object. Additionally
- converting it into an unicode- or bytestrings renders the contents.
- """
-
- def __init__(self, template, context):
- self._body_stream = list(template.root_render_func(context))
- self.__dict__.update(context.get_exported())
- self.__name__ = template.name
-
- def __html__(self):
- return Markup(concat(self._body_stream))
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return concat(self._body_stream)
-
- def __repr__(self):
- if self.__name__ is None:
- name = 'memory:%x' % id(self)
- else:
- name = repr(self.__name__)
- return '<%s %s>' % (self.__class__.__name__, name)
-
-
-class TemplateExpression(object):
- """The :meth:`jinja2.Environment.compile_expression` method returns an
- instance of this object. It encapsulates the expression-like access
- to the template with an expression it wraps.
- """
-
- def __init__(self, template, undefined_to_none):
- self._template = template
- self._undefined_to_none = undefined_to_none
-
- def __call__(self, *args, **kwargs):
- context = self._template.new_context(dict(*args, **kwargs))
- consume(self._template.root_render_func(context))
- rv = context.vars['result']
- if self._undefined_to_none and isinstance(rv, Undefined):
- rv = None
- return rv
-
-
-class TemplateStream(object):
- """A template stream works pretty much like an ordinary python generator
- but it can buffer multiple items to reduce the number of total iterations.
- Per default the output is unbuffered which means that for every unbuffered
- instruction in the template one unicode string is yielded.
-
- If buffering is enabled with a buffer size of 5, five items are combined
- into a new unicode string. This is mainly useful if you are streaming
- big templates to a client via WSGI which flushes after each iteration.
- """
-
- def __init__(self, gen):
- self._gen = gen
- self.disable_buffering()
-
- def dump(self, fp, encoding=None, errors='strict'):
- """Dump the complete stream into a file or file-like object.
- Per default unicode strings are written, if you want to encode
- before writing specifiy an `encoding`.
-
- Example usage::
-
- Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
- """
- close = False
- if isinstance(fp, basestring):
- fp = file(fp, 'w')
- close = True
- try:
- if encoding is not None:
- iterable = (x.encode(encoding, errors) for x in self)
- else:
- iterable = self
- if hasattr(fp, 'writelines'):
- fp.writelines(iterable)
- else:
- for item in iterable:
- fp.write(item)
- finally:
- if close:
- fp.close()
-
- def disable_buffering(self):
- """Disable the output buffering."""
- self._next = self._gen.next
- self.buffered = False
-
- def enable_buffering(self, size=5):
- """Enable buffering. Buffer `size` items before yielding them."""
- if size <= 1:
- raise ValueError('buffer size too small')
-
- def generator(next):
- buf = []
- c_size = 0
- push = buf.append
-
- while 1:
- try:
- while c_size < size:
- c = next()
- push(c)
- if c:
- c_size += 1
- except StopIteration:
- if not c_size:
- return
- yield concat(buf)
- del buf[:]
- c_size = 0
-
- self.buffered = True
- self._next = generator(self._gen.next).next
-
- def __iter__(self):
- return self
-
- def next(self):
- return self._next()
-
-
-# hook in default template class. if anyone reads this comment: ignore that
-# it's possible to use custom templates ;-)
-Environment.template_class = Template
diff --git a/module/lib/jinja2/exceptions.py b/module/lib/jinja2/exceptions.py
deleted file mode 100644
index 771f6a8d7..000000000
--- a/module/lib/jinja2/exceptions.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.exceptions
- ~~~~~~~~~~~~~~~~~
-
- Jinja exceptions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-
-
-class TemplateError(Exception):
- """Baseclass for all template errors."""
-
- def __init__(self, message=None):
- if message is not None:
- message = unicode(message).encode('utf-8')
- Exception.__init__(self, message)
-
- @property
- def message(self):
- if self.args:
- message = self.args[0]
- if message is not None:
- return message.decode('utf-8', 'replace')
-
-
-class TemplateNotFound(IOError, LookupError, TemplateError):
- """Raised if a template does not exist."""
-
- # looks weird, but removes the warning descriptor that just
- # bogusly warns us about message being deprecated
- message = None
-
- def __init__(self, name, message=None):
- IOError.__init__(self)
- if message is None:
- message = name
- self.message = message
- self.name = name
- self.templates = [name]
-
- def __str__(self):
- return self.message.encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return self.message
-
-
-class TemplatesNotFound(TemplateNotFound):
- """Like :class:`TemplateNotFound` but raised if multiple templates
- are selected. This is a subclass of :class:`TemplateNotFound`
- exception, so just catching the base exception will catch both.
-
- .. versionadded:: 2.2
- """
-
- def __init__(self, names=(), message=None):
- if message is None:
- message = u'non of the templates given were found: ' + \
- u', '.join(map(unicode, names))
- TemplateNotFound.__init__(self, names and names[-1] or None, message)
- self.templates = list(names)
-
-
-class TemplateSyntaxError(TemplateError):
- """Raised to tell the user that there is a problem with the template."""
-
- def __init__(self, message, lineno, name=None, filename=None):
- TemplateError.__init__(self, message)
- self.lineno = lineno
- self.name = name
- self.filename = filename
- self.source = None
-
- # this is set to True if the debug.translate_syntax_error
- # function translated the syntax error into a new traceback
- self.translated = False
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- # for translated errors we only return the message
- if self.translated:
- return self.message
-
- # otherwise attach some stuff
- location = 'line %d' % self.lineno
- name = self.filename or self.name
- if name:
- location = 'File "%s", %s' % (name, location)
- lines = [self.message, ' ' + location]
-
- # if the source is set, add the line to the output
- if self.source is not None:
- try:
- line = self.source.splitlines()[self.lineno - 1]
- except IndexError:
- line = None
- if line:
- lines.append(' ' + line.strip())
-
- return u'\n'.join(lines)
-
-
-class TemplateAssertionError(TemplateSyntaxError):
- """Like a template syntax error, but covers cases where something in the
- template caused an error at compile time that wasn't necessarily caused
- by a syntax error. However it's a direct subclass of
- :exc:`TemplateSyntaxError` and has the same attributes.
- """
-
-
-class TemplateRuntimeError(TemplateError):
- """A generic runtime error in the template engine. Under some situations
- Jinja may raise this exception.
- """
-
-
-class UndefinedError(TemplateRuntimeError):
- """Raised if a template tries to operate on :class:`Undefined`."""
-
-
-class SecurityError(TemplateRuntimeError):
- """Raised if a template tries to do something insecure if the
- sandbox is enabled.
- """
-
-
-class FilterArgumentError(TemplateRuntimeError):
- """This error is raised if a filter was called with inappropriate
- arguments
- """
diff --git a/module/lib/jinja2/ext.py b/module/lib/jinja2/ext.py
deleted file mode 100644
index ceb38953a..000000000
--- a/module/lib/jinja2/ext.py
+++ /dev/null
@@ -1,610 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.ext
- ~~~~~~~~~~
-
- Jinja extensions allow to add custom tags similar to the way django custom
- tags work. By default two example extensions exist: an i18n and a cache
- extension.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-from collections import deque
-from jinja2 import nodes
-from jinja2.defaults import *
-from jinja2.environment import Environment
-from jinja2.runtime import Undefined, concat
-from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
-from jinja2.utils import contextfunction, import_string, Markup, next
-
-
-# the only real useful gettext functions for a Jinja template. Note
-# that ugettext must be assigned to gettext as Jinja doesn't support
-# non unicode strings.
-GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
-
-
-class ExtensionRegistry(type):
- """Gives the extension an unique identifier."""
-
- def __new__(cls, name, bases, d):
- rv = type.__new__(cls, name, bases, d)
- rv.identifier = rv.__module__ + '.' + rv.__name__
- return rv
-
-
-class Extension(object):
- """Extensions can be used to add extra functionality to the Jinja template
- system at the parser level. Custom extensions are bound to an environment
- but may not store environment specific data on `self`. The reason for
- this is that an extension can be bound to another environment (for
- overlays) by creating a copy and reassigning the `environment` attribute.
-
- As extensions are created by the environment they cannot accept any
- arguments for configuration. One may want to work around that by using
- a factory function, but that is not possible as extensions are identified
- by their import name. The correct way to configure the extension is
- storing the configuration values on the environment. Because this way the
- environment ends up acting as central configuration storage the
- attributes may clash which is why extensions have to ensure that the names
- they choose for configuration are not too generic. ``prefix`` for example
- is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
- name as includes the name of the extension (fragment cache).
- """
- __metaclass__ = ExtensionRegistry
-
- #: if this extension parses this is the list of tags it's listening to.
- tags = set()
-
- #: the priority of that extension. This is especially useful for
- #: extensions that preprocess values. A lower value means higher
- #: priority.
- #:
- #: .. versionadded:: 2.4
- priority = 100
-
- def __init__(self, environment):
- self.environment = environment
-
- def bind(self, environment):
- """Create a copy of this extension bound to another environment."""
- rv = object.__new__(self.__class__)
- rv.__dict__.update(self.__dict__)
- rv.environment = environment
- return rv
-
- def preprocess(self, source, name, filename=None):
- """This method is called before the actual lexing and can be used to
- preprocess the source. The `filename` is optional. The return value
- must be the preprocessed source.
- """
- return source
-
- def filter_stream(self, stream):
- """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
- to filter tokens returned. This method has to return an iterable of
- :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
- :class:`~jinja2.lexer.TokenStream`.
-
- In the `ext` folder of the Jinja2 source distribution there is a file
- called `inlinegettext.py` which implements a filter that utilizes this
- method.
- """
- return stream
-
- def parse(self, parser):
- """If any of the :attr:`tags` matched this method is called with the
- parser as first argument. The token the parser stream is pointing at
- is the name token that matched. This method has to return one or a
- list of multiple nodes.
- """
- raise NotImplementedError()
-
- def attr(self, name, lineno=None):
- """Return an attribute node for the current extension. This is useful
- to pass constants on extensions to generated template code::
-
- self.attr('_my_attribute', lineno=lineno)
- """
- return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
-
- def call_method(self, name, args=None, kwargs=None, dyn_args=None,
- dyn_kwargs=None, lineno=None):
- """Call a method of the extension. This is a shortcut for
- :meth:`attr` + :class:`jinja2.nodes.Call`.
- """
- if args is None:
- args = []
- if kwargs is None:
- kwargs = []
- return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
- dyn_args, dyn_kwargs, lineno=lineno)
-
-
-@contextfunction
-def _gettext_alias(__context, *args, **kwargs):
- return __context.call(__context.resolve('gettext'), *args, **kwargs)
-
-
-def _make_new_gettext(func):
- @contextfunction
- def gettext(__context, __string, **variables):
- rv = __context.call(func, __string)
- if __context.eval_ctx.autoescape:
- rv = Markup(rv)
- return rv % variables
- return gettext
-
-
-def _make_new_ngettext(func):
- @contextfunction
- def ngettext(__context, __singular, __plural, __num, **variables):
- variables.setdefault('num', __num)
- rv = __context.call(func, __singular, __plural, __num)
- if __context.eval_ctx.autoescape:
- rv = Markup(rv)
- return rv % variables
- return ngettext
-
-
-class InternationalizationExtension(Extension):
- """This extension adds gettext support to Jinja2."""
- tags = set(['trans'])
-
- # TODO: the i18n extension is currently reevaluating values in a few
- # situations. Take this example:
- # {% trans count=something() %}{{ count }} foo{% pluralize
- # %}{{ count }} fooss{% endtrans %}
- # something is called twice here. One time for the gettext value and
- # the other time for the n-parameter of the ngettext function.
-
- def __init__(self, environment):
- Extension.__init__(self, environment)
- environment.globals['_'] = _gettext_alias
- environment.extend(
- install_gettext_translations=self._install,
- install_null_translations=self._install_null,
- install_gettext_callables=self._install_callables,
- uninstall_gettext_translations=self._uninstall,
- extract_translations=self._extract,
- newstyle_gettext=False
- )
-
- def _install(self, translations, newstyle=None):
- gettext = getattr(translations, 'ugettext', None)
- if gettext is None:
- gettext = translations.gettext
- ngettext = getattr(translations, 'ungettext', None)
- if ngettext is None:
- ngettext = translations.ngettext
- self._install_callables(gettext, ngettext, newstyle)
-
- def _install_null(self, newstyle=None):
- self._install_callables(
- lambda x: x,
- lambda s, p, n: (n != 1 and (p,) or (s,))[0],
- newstyle
- )
-
- def _install_callables(self, gettext, ngettext, newstyle=None):
- if newstyle is not None:
- self.environment.newstyle_gettext = newstyle
- if self.environment.newstyle_gettext:
- gettext = _make_new_gettext(gettext)
- ngettext = _make_new_ngettext(ngettext)
- self.environment.globals.update(
- gettext=gettext,
- ngettext=ngettext
- )
-
- def _uninstall(self, translations):
- for key in 'gettext', 'ngettext':
- self.environment.globals.pop(key, None)
-
- def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
- if isinstance(source, basestring):
- source = self.environment.parse(source)
- return extract_from_ast(source, gettext_functions)
-
- def parse(self, parser):
- """Parse a translatable tag."""
- lineno = next(parser.stream).lineno
- num_called_num = False
-
- # find all the variables referenced. Additionally a variable can be
- # defined in the body of the trans block too, but this is checked at
- # a later state.
- plural_expr = None
- variables = {}
- while parser.stream.current.type != 'block_end':
- if variables:
- parser.stream.expect('comma')
-
- # skip colon for python compatibility
- if parser.stream.skip_if('colon'):
- break
-
- name = parser.stream.expect('name')
- if name.value in variables:
- parser.fail('translatable variable %r defined twice.' %
- name.value, name.lineno,
- exc=TemplateAssertionError)
-
- # expressions
- if parser.stream.current.type == 'assign':
- next(parser.stream)
- variables[name.value] = var = parser.parse_expression()
- else:
- variables[name.value] = var = nodes.Name(name.value, 'load')
-
- if plural_expr is None:
- plural_expr = var
- num_called_num = name.value == 'num'
-
- parser.stream.expect('block_end')
-
- plural = plural_names = None
- have_plural = False
- referenced = set()
-
- # now parse until endtrans or pluralize
- singular_names, singular = self._parse_block(parser, True)
- if singular_names:
- referenced.update(singular_names)
- if plural_expr is None:
- plural_expr = nodes.Name(singular_names[0], 'load')
- num_called_num = singular_names[0] == 'num'
-
- # if we have a pluralize block, we parse that too
- if parser.stream.current.test('name:pluralize'):
- have_plural = True
- next(parser.stream)
- if parser.stream.current.type != 'block_end':
- name = parser.stream.expect('name')
- if name.value not in variables:
- parser.fail('unknown variable %r for pluralization' %
- name.value, name.lineno,
- exc=TemplateAssertionError)
- plural_expr = variables[name.value]
- num_called_num = name.value == 'num'
- parser.stream.expect('block_end')
- plural_names, plural = self._parse_block(parser, False)
- next(parser.stream)
- referenced.update(plural_names)
- else:
- next(parser.stream)
-
- # register free names as simple name expressions
- for var in referenced:
- if var not in variables:
- variables[var] = nodes.Name(var, 'load')
-
- if not have_plural:
- plural_expr = None
- elif plural_expr is None:
- parser.fail('pluralize without variables', lineno)
-
- node = self._make_node(singular, plural, variables, plural_expr,
- bool(referenced),
- num_called_num and have_plural)
- node.set_lineno(lineno)
- return node
-
- def _parse_block(self, parser, allow_pluralize):
- """Parse until the next block tag with a given name."""
- referenced = []
- buf = []
- while 1:
- if parser.stream.current.type == 'data':
- buf.append(parser.stream.current.value.replace('%', '%%'))
- next(parser.stream)
- elif parser.stream.current.type == 'variable_begin':
- next(parser.stream)
- name = parser.stream.expect('name').value
- referenced.append(name)
- buf.append('%%(%s)s' % name)
- parser.stream.expect('variable_end')
- elif parser.stream.current.type == 'block_begin':
- next(parser.stream)
- if parser.stream.current.test('name:endtrans'):
- break
- elif parser.stream.current.test('name:pluralize'):
- if allow_pluralize:
- break
- parser.fail('a translatable section can have only one '
- 'pluralize section')
- parser.fail('control structures in translatable sections are '
- 'not allowed')
- elif parser.stream.eos:
- parser.fail('unclosed translation block')
- else:
- assert False, 'internal parser error'
-
- return referenced, concat(buf)
-
- def _make_node(self, singular, plural, variables, plural_expr,
- vars_referenced, num_called_num):
- """Generates a useful node from the data provided."""
- # no variables referenced? no need to escape for old style
- # gettext invocations only if there are vars.
- if not vars_referenced and not self.environment.newstyle_gettext:
- singular = singular.replace('%%', '%')
- if plural:
- plural = plural.replace('%%', '%')
-
- # singular only:
- if plural_expr is None:
- gettext = nodes.Name('gettext', 'load')
- node = nodes.Call(gettext, [nodes.Const(singular)],
- [], None, None)
-
- # singular and plural
- else:
- ngettext = nodes.Name('ngettext', 'load')
- node = nodes.Call(ngettext, [
- nodes.Const(singular),
- nodes.Const(plural),
- plural_expr
- ], [], None, None)
-
- # in case newstyle gettext is used, the method is powerful
- # enough to handle the variable expansion and autoescape
- # handling itself
- if self.environment.newstyle_gettext:
- for key, value in variables.iteritems():
- # the function adds that later anyways in case num was
- # called num, so just skip it.
- if num_called_num and key == 'num':
- continue
- node.kwargs.append(nodes.Keyword(key, value))
-
- # otherwise do that here
- else:
- # mark the return value as safe if we are in an
- # environment with autoescaping turned on
- node = nodes.MarkSafeIfAutoescape(node)
- if variables:
- node = nodes.Mod(node, nodes.Dict([
- nodes.Pair(nodes.Const(key), value)
- for key, value in variables.items()
- ]))
- return nodes.Output([node])
-
-
-class ExprStmtExtension(Extension):
- """Adds a `do` tag to Jinja2 that works like the print statement just
- that it doesn't print the return value.
- """
- tags = set(['do'])
-
- def parse(self, parser):
- node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
- node.node = parser.parse_tuple()
- return node
-
-
-class LoopControlExtension(Extension):
- """Adds break and continue to the template engine."""
- tags = set(['break', 'continue'])
-
- def parse(self, parser):
- token = next(parser.stream)
- if token.value == 'break':
- return nodes.Break(lineno=token.lineno)
- return nodes.Continue(lineno=token.lineno)
-
-
-class WithExtension(Extension):
- """Adds support for a django-like with block."""
- tags = set(['with'])
-
- def parse(self, parser):
- node = nodes.Scope(lineno=next(parser.stream).lineno)
- assignments = []
- while parser.stream.current.type != 'block_end':
- lineno = parser.stream.current.lineno
- if assignments:
- parser.stream.expect('comma')
- target = parser.parse_assign_target()
- parser.stream.expect('assign')
- expr = parser.parse_expression()
- assignments.append(nodes.Assign(target, expr, lineno=lineno))
- node.body = assignments + \
- list(parser.parse_statements(('name:endwith',),
- drop_needle=True))
- return node
-
-
-class AutoEscapeExtension(Extension):
- """Changes auto escape rules for a scope."""
- tags = set(['autoescape'])
-
- def parse(self, parser):
- node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
- node.options = [
- nodes.Keyword('autoescape', parser.parse_expression())
- ]
- node.body = parser.parse_statements(('name:endautoescape',),
- drop_needle=True)
- return nodes.Scope([node])
-
-
-def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
- babel_style=True):
- """Extract localizable strings from the given template node. Per
- default this function returns matches in babel style that means non string
- parameters as well as keyword arguments are returned as `None`. This
- allows Babel to figure out what you really meant if you are using
- gettext functions that allow keyword arguments for placeholder expansion.
- If you don't want that behavior set the `babel_style` parameter to `False`
- which causes only strings to be returned and parameters are always stored
- in tuples. As a consequence invalid gettext calls (calls without a single
- string parameter or string parameters after non-string parameters) are
- skipped.
-
- This example explains the behavior:
-
- >>> from jinja2 import Environment
- >>> env = Environment()
- >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
- >>> list(extract_from_ast(node))
- [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
- >>> list(extract_from_ast(node, babel_style=False))
- [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
-
- For every string found this function yields a ``(lineno, function,
- message)`` tuple, where:
-
- * ``lineno`` is the number of the line on which the string was found,
- * ``function`` is the name of the ``gettext`` function used (if the
- string was extracted from embedded Python code), and
- * ``message`` is the string itself (a ``unicode`` object, or a tuple
- of ``unicode`` objects for functions with multiple string arguments).
-
- This extraction function operates on the AST and is because of that unable
- to extract any comments. For comment support you have to use the babel
- extraction interface or extract comments yourself.
- """
- for node in node.find_all(nodes.Call):
- if not isinstance(node.node, nodes.Name) or \
- node.node.name not in gettext_functions:
- continue
-
- strings = []
- for arg in node.args:
- if isinstance(arg, nodes.Const) and \
- isinstance(arg.value, basestring):
- strings.append(arg.value)
- else:
- strings.append(None)
-
- for arg in node.kwargs:
- strings.append(None)
- if node.dyn_args is not None:
- strings.append(None)
- if node.dyn_kwargs is not None:
- strings.append(None)
-
- if not babel_style:
- strings = tuple(x for x in strings if x is not None)
- if not strings:
- continue
- else:
- if len(strings) == 1:
- strings = strings[0]
- else:
- strings = tuple(strings)
- yield node.lineno, node.node.name, strings
-
-
-class _CommentFinder(object):
- """Helper class to find comments in a token stream. Can only
- find comments for gettext calls forwards. Once the comment
- from line 4 is found, a comment for line 1 will not return a
- usable value.
- """
-
- def __init__(self, tokens, comment_tags):
- self.tokens = tokens
- self.comment_tags = comment_tags
- self.offset = 0
- self.last_lineno = 0
-
- def find_backwards(self, offset):
- try:
- for _, token_type, token_value in \
- reversed(self.tokens[self.offset:offset]):
- if token_type in ('comment', 'linecomment'):
- try:
- prefix, comment = token_value.split(None, 1)
- except ValueError:
- continue
- if prefix in self.comment_tags:
- return [comment.rstrip()]
- return []
- finally:
- self.offset = offset
-
- def find_comments(self, lineno):
- if not self.comment_tags or self.last_lineno > lineno:
- return []
- for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
- if token_lineno > lineno:
- return self.find_backwards(self.offset + idx)
- return self.find_backwards(len(self.tokens))
-
-
-def babel_extract(fileobj, keywords, comment_tags, options):
- """Babel extraction method for Jinja templates.
-
- .. versionchanged:: 2.3
- Basic support for translation comments was added. If `comment_tags`
- is now set to a list of keywords for extraction, the extractor will
- try to find the best preceeding comment that begins with one of the
- keywords. For best results, make sure to not have more than one
- gettext call in one line of code and the matching comment in the
- same line or the line before.
-
- .. versionchanged:: 2.5.1
- The `newstyle_gettext` flag can be set to `True` to enable newstyle
- gettext calls.
-
- :param fileobj: the file-like object the messages should be extracted from
- :param keywords: a list of keywords (i.e. function names) that should be
- recognized as translation functions
- :param comment_tags: a list of translator tags to search for and include
- in the results.
- :param options: a dictionary of additional options (optional)
- :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
- (comments will be empty currently)
- """
- extensions = set()
- for extension in options.get('extensions', '').split(','):
- extension = extension.strip()
- if not extension:
- continue
- extensions.add(import_string(extension))
- if InternationalizationExtension not in extensions:
- extensions.add(InternationalizationExtension)
-
- def getbool(options, key, default=False):
- options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true')
-
- environment = Environment(
- options.get('block_start_string', BLOCK_START_STRING),
- options.get('block_end_string', BLOCK_END_STRING),
- options.get('variable_start_string', VARIABLE_START_STRING),
- options.get('variable_end_string', VARIABLE_END_STRING),
- options.get('comment_start_string', COMMENT_START_STRING),
- options.get('comment_end_string', COMMENT_END_STRING),
- options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
- options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
- getbool(options, 'trim_blocks', TRIM_BLOCKS),
- NEWLINE_SEQUENCE, frozenset(extensions),
- cache_size=0,
- auto_reload=False
- )
-
- if getbool(options, 'newstyle_gettext'):
- environment.newstyle_gettext = True
-
- source = fileobj.read().decode(options.get('encoding', 'utf-8'))
- try:
- node = environment.parse(source)
- tokens = list(environment.lex(environment.preprocess(source)))
- except TemplateSyntaxError, e:
- # skip templates with syntax errors
- return
-
- finder = _CommentFinder(tokens, comment_tags)
- for lineno, func, message in extract_from_ast(node, keywords):
- yield lineno, func, message, finder.find_comments(lineno)
-
-
-#: nicer import names
-i18n = InternationalizationExtension
-do = ExprStmtExtension
-loopcontrols = LoopControlExtension
-with_ = WithExtension
-autoescape = AutoEscapeExtension
diff --git a/module/lib/jinja2/filters.py b/module/lib/jinja2/filters.py
deleted file mode 100644
index d1848e434..000000000
--- a/module/lib/jinja2/filters.py
+++ /dev/null
@@ -1,719 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.filters
- ~~~~~~~~~~~~~~
-
- Bundled jinja filters.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-import math
-from random import choice
-from operator import itemgetter
-from itertools import imap, groupby
-from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
-from jinja2.runtime import Undefined
-from jinja2.exceptions import FilterArgumentError, SecurityError
-
-
-_word_re = re.compile(r'\w+(?u)')
-
-
-def contextfilter(f):
- """Decorator for marking context dependent filters. The current
- :class:`Context` will be passed as first argument.
- """
- f.contextfilter = True
- return f
-
-
-def evalcontextfilter(f):
- """Decorator for marking eval-context dependent filters. An eval
- context object is passed as first argument. For more information
- about the eval context, see :ref:`eval-context`.
-
- .. versionadded:: 2.4
- """
- f.evalcontextfilter = True
- return f
-
-
-def environmentfilter(f):
- """Decorator for marking evironment dependent filters. The current
- :class:`Environment` is passed to the filter as first argument.
- """
- f.environmentfilter = True
- return f
-
-
-def do_forceescape(value):
- """Enforce HTML escaping. This will probably double escape variables."""
- if hasattr(value, '__html__'):
- value = value.__html__()
- return escape(unicode(value))
-
-
-@evalcontextfilter
-def do_replace(eval_ctx, s, old, new, count=None):
- """Return a copy of the value with all occurrences of a substring
- replaced with a new one. The first argument is the substring
- that should be replaced, the second is the replacement string.
- If the optional third argument ``count`` is given, only the first
- ``count`` occurrences are replaced:
-
- .. sourcecode:: jinja
-
- {{ "Hello World"|replace("Hello", "Goodbye") }}
- -> Goodbye World
-
- {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
- -> d'oh, d'oh, aaargh
- """
- if count is None:
- count = -1
- if not eval_ctx.autoescape:
- return unicode(s).replace(unicode(old), unicode(new), count)
- if hasattr(old, '__html__') or hasattr(new, '__html__') and \
- not hasattr(s, '__html__'):
- s = escape(s)
- else:
- s = soft_unicode(s)
- return s.replace(soft_unicode(old), soft_unicode(new), count)
-
-
-def do_upper(s):
- """Convert a value to uppercase."""
- return soft_unicode(s).upper()
-
-
-def do_lower(s):
- """Convert a value to lowercase."""
- return soft_unicode(s).lower()
-
-
-@evalcontextfilter
-def do_xmlattr(_eval_ctx, d, autospace=True):
- """Create an SGML/XML attribute string based on the items in a dict.
- All values that are neither `none` nor `undefined` are automatically
- escaped:
-
- .. sourcecode:: html+jinja
-
- <ul{{ {'class': 'my_list', 'missing': none,
- 'id': 'list-%d'|format(variable)}|xmlattr }}>
- ...
- </ul>
-
- Results in something like this:
-
- .. sourcecode:: html
-
- <ul class="my_list" id="list-42">
- ...
- </ul>
-
- As you can see it automatically prepends a space in front of the item
- if the filter returned something unless the second parameter is false.
- """
- rv = u' '.join(
- u'%s="%s"' % (escape(key), escape(value))
- for key, value in d.iteritems()
- if value is not None and not isinstance(value, Undefined)
- )
- if autospace and rv:
- rv = u' ' + rv
- if _eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-def do_capitalize(s):
- """Capitalize a value. The first character will be uppercase, all others
- lowercase.
- """
- return soft_unicode(s).capitalize()
-
-
-def do_title(s):
- """Return a titlecased version of the value. I.e. words will start with
- uppercase letters, all remaining characters are lowercase.
- """
- return soft_unicode(s).title()
-
-
-def do_dictsort(value, case_sensitive=False, by='key'):
- """Sort a dict and yield (key, value) pairs. Because python dicts are
- unsorted you may want to use this function to order them by either
- key or value:
-
- .. sourcecode:: jinja
-
- {% for item in mydict|dictsort %}
- sort the dict by key, case insensitive
-
- {% for item in mydict|dicsort(true) %}
- sort the dict by key, case sensitive
-
- {% for item in mydict|dictsort(false, 'value') %}
- sort the dict by key, case insensitive, sorted
- normally and ordered by value.
- """
- if by == 'key':
- pos = 0
- elif by == 'value':
- pos = 1
- else:
- raise FilterArgumentError('You can only sort by either '
- '"key" or "value"')
- def sort_func(item):
- value = item[pos]
- if isinstance(value, basestring) and not case_sensitive:
- value = value.lower()
- return value
-
- return sorted(value.items(), key=sort_func)
-
-
-def do_sort(value, reverse=False, case_sensitive=False):
- """Sort an iterable. Per default it sorts ascending, if you pass it
- true as first argument it will reverse the sorting.
-
- If the iterable is made of strings the third parameter can be used to
- control the case sensitiveness of the comparison which is disabled by
- default.
-
- .. sourcecode:: jinja
-
- {% for item in iterable|sort %}
- ...
- {% endfor %}
- """
- if not case_sensitive:
- def sort_func(item):
- if isinstance(item, basestring):
- item = item.lower()
- return item
- else:
- sort_func = None
- return sorted(value, key=sort_func, reverse=reverse)
-
-
-def do_default(value, default_value=u'', boolean=False):
- """If the value is undefined it will return the passed default value,
- otherwise the value of the variable:
-
- .. sourcecode:: jinja
-
- {{ my_variable|default('my_variable is not defined') }}
-
- This will output the value of ``my_variable`` if the variable was
- defined, otherwise ``'my_variable is not defined'``. If you want
- to use default with variables that evaluate to false you have to
- set the second parameter to `true`:
-
- .. sourcecode:: jinja
-
- {{ ''|default('the string was empty', true) }}
- """
- if (boolean and not value) or isinstance(value, Undefined):
- return default_value
- return value
-
-
-@evalcontextfilter
-def do_join(eval_ctx, value, d=u''):
- """Return a string which is the concatenation of the strings in the
- sequence. The separator between elements is an empty string per
- default, you can define it with the optional parameter:
-
- .. sourcecode:: jinja
-
- {{ [1, 2, 3]|join('|') }}
- -> 1|2|3
-
- {{ [1, 2, 3]|join }}
- -> 123
- """
- # no automatic escaping? joining is a lot eaiser then
- if not eval_ctx.autoescape:
- return unicode(d).join(imap(unicode, value))
-
- # if the delimiter doesn't have an html representation we check
- # if any of the items has. If yes we do a coercion to Markup
- if not hasattr(d, '__html__'):
- value = list(value)
- do_escape = False
- for idx, item in enumerate(value):
- if hasattr(item, '__html__'):
- do_escape = True
- else:
- value[idx] = unicode(item)
- if do_escape:
- d = escape(d)
- else:
- d = unicode(d)
- return d.join(value)
-
- # no html involved, to normal joining
- return soft_unicode(d).join(imap(soft_unicode, value))
-
-
-def do_center(value, width=80):
- """Centers the value in a field of a given width."""
- return unicode(value).center(width)
-
-
-@environmentfilter
-def do_first(environment, seq):
- """Return the first item of a sequence."""
- try:
- return iter(seq).next()
- except StopIteration:
- return environment.undefined('No first item, sequence was empty.')
-
-
-@environmentfilter
-def do_last(environment, seq):
- """Return the last item of a sequence."""
- try:
- return iter(reversed(seq)).next()
- except StopIteration:
- return environment.undefined('No last item, sequence was empty.')
-
-
-@environmentfilter
-def do_random(environment, seq):
- """Return a random item from the sequence."""
- try:
- return choice(seq)
- except IndexError:
- return environment.undefined('No random item, sequence was empty.')
-
-
-def do_filesizeformat(value, binary=False):
- """Format the value like a 'human-readable' file size (i.e. 13 KB,
- 4.1 MB, 102 bytes, etc). Per default decimal prefixes are used (mega,
- giga, etc.), if the second parameter is set to `True` the binary
- prefixes are used (mebi, gibi).
- """
- bytes = float(value)
- base = binary and 1024 or 1000
- middle = binary and 'i' or ''
- if bytes < base:
- return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
- elif bytes < base * base:
- return "%.1f K%sB" % (bytes / base, middle)
- elif bytes < base * base * base:
- return "%.1f M%sB" % (bytes / (base * base), middle)
- return "%.1f G%sB" % (bytes / (base * base * base), middle)
-
-
-def do_pprint(value, verbose=False):
- """Pretty print a variable. Useful for debugging.
-
- With Jinja 1.2 onwards you can pass it a parameter. If this parameter
- is truthy the output will be more verbose (this requires `pretty`)
- """
- return pformat(value, verbose=verbose)
-
-
-@evalcontextfilter
-def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
- """Converts URLs in plain text into clickable links.
-
- If you pass the filter an additional integer it will shorten the urls
- to that number. Also a third argument exists that makes the urls
- "nofollow":
-
- .. sourcecode:: jinja
-
- {{ mytext|urlize(40, true) }}
- links are shortened to 40 chars and defined with rel="nofollow"
- """
- rv = urlize(value, trim_url_limit, nofollow)
- if eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-def do_indent(s, width=4, indentfirst=False):
- """Return a copy of the passed string, each line indented by
- 4 spaces. The first line is not indented. If you want to
- change the number of spaces or indent the first line too
- you can pass additional parameters to the filter:
-
- .. sourcecode:: jinja
-
- {{ mytext|indent(2, true) }}
- indent by two spaces and indent the first line too.
- """
- indention = u' ' * width
- rv = (u'\n' + indention).join(s.splitlines())
- if indentfirst:
- rv = indention + rv
- return rv
-
-
-def do_truncate(s, length=255, killwords=False, end='...'):
- """Return a truncated copy of the string. The length is specified
- with the first parameter which defaults to ``255``. If the second
- parameter is ``true`` the filter will cut the text at length. Otherwise
- it will try to save the last word. If the text was in fact
- truncated it will append an ellipsis sign (``"..."``). If you want a
- different ellipsis sign than ``"..."`` you can specify it using the
- third parameter.
-
- .. sourcecode jinja::
-
- {{ mytext|truncate(300, false, '&raquo;') }}
- truncate mytext to 300 chars, don't split up words, use a
- right pointing double arrow as ellipsis sign.
- """
- if len(s) <= length:
- return s
- elif killwords:
- return s[:length] + end
- words = s.split(' ')
- result = []
- m = 0
- for word in words:
- m += len(word) + 1
- if m > length:
- break
- result.append(word)
- result.append(end)
- return u' '.join(result)
-
-
-def do_wordwrap(s, width=79, break_long_words=True):
- """
- Return a copy of the string passed to the filter wrapped after
- ``79`` characters. You can override this default using the first
- parameter. If you set the second parameter to `false` Jinja will not
- split words apart if they are longer than `width`.
- """
- import textwrap
- return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
- replace_whitespace=False,
- break_long_words=break_long_words))
-
-
-def do_wordcount(s):
- """Count the words in that string."""
- return len(_word_re.findall(s))
-
-
-def do_int(value, default=0):
- """Convert the value into an integer. If the
- conversion doesn't work it will return ``0``. You can
- override this default using the first parameter.
- """
- try:
- return int(value)
- except (TypeError, ValueError):
- # this quirk is necessary so that "42.23"|int gives 42.
- try:
- return int(float(value))
- except (TypeError, ValueError):
- return default
-
-
-def do_float(value, default=0.0):
- """Convert the value into a floating point number. If the
- conversion doesn't work it will return ``0.0``. You can
- override this default using the first parameter.
- """
- try:
- return float(value)
- except (TypeError, ValueError):
- return default
-
-
-def do_format(value, *args, **kwargs):
- """
- Apply python string formatting on an object:
-
- .. sourcecode:: jinja
-
- {{ "%s - %s"|format("Hello?", "Foo!") }}
- -> Hello? - Foo!
- """
- if args and kwargs:
- raise FilterArgumentError('can\'t handle positional and keyword '
- 'arguments at the same time')
- return soft_unicode(value) % (kwargs or args)
-
-
-def do_trim(value):
- """Strip leading and trailing whitespace."""
- return soft_unicode(value).strip()
-
-
-def do_striptags(value):
- """Strip SGML/XML tags and replace adjacent whitespace by one space.
- """
- if hasattr(value, '__html__'):
- value = value.__html__()
- return Markup(unicode(value)).striptags()
-
-
-def do_slice(value, slices, fill_with=None):
- """Slice an iterator and return a list of lists containing
- those items. Useful if you want to create a div containing
- three ul tags that represent columns:
-
- .. sourcecode:: html+jinja
-
- <div class="columwrapper">
- {%- for column in items|slice(3) %}
- <ul class="column-{{ loop.index }}">
- {%- for item in column %}
- <li>{{ item }}</li>
- {%- endfor %}
- </ul>
- {%- endfor %}
- </div>
-
- If you pass it a second argument it's used to fill missing
- values on the last iteration.
- """
- seq = list(value)
- length = len(seq)
- items_per_slice = length // slices
- slices_with_extra = length % slices
- offset = 0
- for slice_number in xrange(slices):
- start = offset + slice_number * items_per_slice
- if slice_number < slices_with_extra:
- offset += 1
- end = offset + (slice_number + 1) * items_per_slice
- tmp = seq[start:end]
- if fill_with is not None and slice_number >= slices_with_extra:
- tmp.append(fill_with)
- yield tmp
-
-
-def do_batch(value, linecount, fill_with=None):
- """
- A filter that batches items. It works pretty much like `slice`
- just the other way round. It returns a list of lists with the
- given number of items. If you provide a second parameter this
- is used to fill missing items. See this example:
-
- .. sourcecode:: html+jinja
-
- <table>
- {%- for row in items|batch(3, '&nbsp;') %}
- <tr>
- {%- for column in row %}
- <td>{{ column }}</td>
- {%- endfor %}
- </tr>
- {%- endfor %}
- </table>
- """
- result = []
- tmp = []
- for item in value:
- if len(tmp) == linecount:
- yield tmp
- tmp = []
- tmp.append(item)
- if tmp:
- if fill_with is not None and len(tmp) < linecount:
- tmp += [fill_with] * (linecount - len(tmp))
- yield tmp
-
-
-def do_round(value, precision=0, method='common'):
- """Round the number to a given precision. The first
- parameter specifies the precision (default is ``0``), the
- second the rounding method:
-
- - ``'common'`` rounds either up or down
- - ``'ceil'`` always rounds up
- - ``'floor'`` always rounds down
-
- If you don't specify a method ``'common'`` is used.
-
- .. sourcecode:: jinja
-
- {{ 42.55|round }}
- -> 43.0
- {{ 42.55|round(1, 'floor') }}
- -> 42.5
-
- Note that even if rounded to 0 precision, a float is returned. If
- you need a real integer, pipe it through `int`:
-
- .. sourcecode:: jinja
-
- {{ 42.55|round|int }}
- -> 43
- """
- if not method in ('common', 'ceil', 'floor'):
- raise FilterArgumentError('method must be common, ceil or floor')
- if method == 'common':
- return round(value, precision)
- func = getattr(math, method)
- return func(value * (10 ** precision)) / (10 ** precision)
-
-
-@environmentfilter
-def do_groupby(environment, value, attribute):
- """Group a sequence of objects by a common attribute.
-
- If you for example have a list of dicts or objects that represent persons
- with `gender`, `first_name` and `last_name` attributes and you want to
- group all users by genders you can do something like the following
- snippet:
-
- .. sourcecode:: html+jinja
-
- <ul>
- {% for group in persons|groupby('gender') %}
- <li>{{ group.grouper }}<ul>
- {% for person in group.list %}
- <li>{{ person.first_name }} {{ person.last_name }}</li>
- {% endfor %}</ul></li>
- {% endfor %}
- </ul>
-
- Additionally it's possible to use tuple unpacking for the grouper and
- list:
-
- .. sourcecode:: html+jinja
-
- <ul>
- {% for grouper, list in persons|groupby('gender') %}
- ...
- {% endfor %}
- </ul>
-
- As you can see the item we're grouping by is stored in the `grouper`
- attribute and the `list` contains all the objects that have this grouper
- in common.
- """
- expr = lambda x: environment.getitem(x, attribute)
- return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
-
-
-class _GroupTuple(tuple):
- __slots__ = ()
- grouper = property(itemgetter(0))
- list = property(itemgetter(1))
-
- def __new__(cls, (key, value)):
- return tuple.__new__(cls, (key, list(value)))
-
-
-def do_list(value):
- """Convert the value into a list. If it was a string the returned list
- will be a list of characters.
- """
- return list(value)
-
-
-def do_mark_safe(value):
- """Mark the value as safe which means that in an environment with automatic
- escaping enabled this variable will not be escaped.
- """
- return Markup(value)
-
-
-def do_mark_unsafe(value):
- """Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
- return unicode(value)
-
-
-def do_reverse(value):
- """Reverse the object or return an iterator the iterates over it the other
- way round.
- """
- if isinstance(value, basestring):
- return value[::-1]
- try:
- return reversed(value)
- except TypeError:
- try:
- rv = list(value)
- rv.reverse()
- return rv
- except TypeError:
- raise FilterArgumentError('argument must be iterable')
-
-
-@environmentfilter
-def do_attr(environment, obj, name):
- """Get an attribute of an object. ``foo|attr("bar")`` works like
- ``foo["bar"]`` just that always an attribute is returned and items are not
- looked up.
-
- See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
- """
- try:
- name = str(name)
- except UnicodeError:
- pass
- else:
- try:
- value = getattr(obj, name)
- except AttributeError:
- pass
- else:
- if environment.sandboxed and not \
- environment.is_safe_attribute(obj, name, value):
- return environment.unsafe_undefined(obj, name)
- return value
- return environment.undefined(obj=obj, name=name)
-
-
-FILTERS = {
- 'attr': do_attr,
- 'replace': do_replace,
- 'upper': do_upper,
- 'lower': do_lower,
- 'escape': escape,
- 'e': escape,
- 'forceescape': do_forceescape,
- 'capitalize': do_capitalize,
- 'title': do_title,
- 'default': do_default,
- 'd': do_default,
- 'join': do_join,
- 'count': len,
- 'dictsort': do_dictsort,
- 'sort': do_sort,
- 'length': len,
- 'reverse': do_reverse,
- 'center': do_center,
- 'indent': do_indent,
- 'title': do_title,
- 'capitalize': do_capitalize,
- 'first': do_first,
- 'last': do_last,
- 'random': do_random,
- 'filesizeformat': do_filesizeformat,
- 'pprint': do_pprint,
- 'truncate': do_truncate,
- 'wordwrap': do_wordwrap,
- 'wordcount': do_wordcount,
- 'int': do_int,
- 'float': do_float,
- 'string': soft_unicode,
- 'list': do_list,
- 'urlize': do_urlize,
- 'format': do_format,
- 'trim': do_trim,
- 'striptags': do_striptags,
- 'slice': do_slice,
- 'batch': do_batch,
- 'sum': sum,
- 'abs': abs,
- 'round': do_round,
- 'groupby': do_groupby,
- 'safe': do_mark_safe,
- 'xmlattr': do_xmlattr
-}
diff --git a/module/lib/jinja2/lexer.py b/module/lib/jinja2/lexer.py
deleted file mode 100644
index 0d3f69617..000000000
--- a/module/lib/jinja2/lexer.py
+++ /dev/null
@@ -1,681 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.lexer
- ~~~~~~~~~~~~
-
- This module implements a Jinja / Python combination lexer. The
- `Lexer` class provided by this module is used to do some preprocessing
- for Jinja.
-
- On the one hand it filters out invalid operators like the bitshift
- operators we don't allow in templates. On the other hand it separates
- template code and python code in expressions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-from operator import itemgetter
-from collections import deque
-from jinja2.exceptions import TemplateSyntaxError
-from jinja2.utils import LRUCache, next
-
-
-# cache for the lexers. Exists in order to be able to have multiple
-# environments with the same lexer
-_lexer_cache = LRUCache(50)
-
-# static regular expressions
-whitespace_re = re.compile(r'\s+', re.U)
-string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
- r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
-integer_re = re.compile(r'\d+')
-
-# we use the unicode identifier rule if this python version is able
-# to handle unicode identifiers, otherwise the standard ASCII one.
-try:
- compile('föö', '<unknown>', 'eval')
-except SyntaxError:
- name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
-else:
- from jinja2 import _stringdefs
- name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
- _stringdefs.xid_continue))
-
-float_re = re.compile(r'(?<!\.)\d+\.\d+')
-newline_re = re.compile(r'(\r\n|\r|\n)')
-
-# internal the tokens and keep references to them
-TOKEN_ADD = intern('add')
-TOKEN_ASSIGN = intern('assign')
-TOKEN_COLON = intern('colon')
-TOKEN_COMMA = intern('comma')
-TOKEN_DIV = intern('div')
-TOKEN_DOT = intern('dot')
-TOKEN_EQ = intern('eq')
-TOKEN_FLOORDIV = intern('floordiv')
-TOKEN_GT = intern('gt')
-TOKEN_GTEQ = intern('gteq')
-TOKEN_LBRACE = intern('lbrace')
-TOKEN_LBRACKET = intern('lbracket')
-TOKEN_LPAREN = intern('lparen')
-TOKEN_LT = intern('lt')
-TOKEN_LTEQ = intern('lteq')
-TOKEN_MOD = intern('mod')
-TOKEN_MUL = intern('mul')
-TOKEN_NE = intern('ne')
-TOKEN_PIPE = intern('pipe')
-TOKEN_POW = intern('pow')
-TOKEN_RBRACE = intern('rbrace')
-TOKEN_RBRACKET = intern('rbracket')
-TOKEN_RPAREN = intern('rparen')
-TOKEN_SEMICOLON = intern('semicolon')
-TOKEN_SUB = intern('sub')
-TOKEN_TILDE = intern('tilde')
-TOKEN_WHITESPACE = intern('whitespace')
-TOKEN_FLOAT = intern('float')
-TOKEN_INTEGER = intern('integer')
-TOKEN_NAME = intern('name')
-TOKEN_STRING = intern('string')
-TOKEN_OPERATOR = intern('operator')
-TOKEN_BLOCK_BEGIN = intern('block_begin')
-TOKEN_BLOCK_END = intern('block_end')
-TOKEN_VARIABLE_BEGIN = intern('variable_begin')
-TOKEN_VARIABLE_END = intern('variable_end')
-TOKEN_RAW_BEGIN = intern('raw_begin')
-TOKEN_RAW_END = intern('raw_end')
-TOKEN_COMMENT_BEGIN = intern('comment_begin')
-TOKEN_COMMENT_END = intern('comment_end')
-TOKEN_COMMENT = intern('comment')
-TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
-TOKEN_LINESTATEMENT_END = intern('linestatement_end')
-TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
-TOKEN_LINECOMMENT_END = intern('linecomment_end')
-TOKEN_LINECOMMENT = intern('linecomment')
-TOKEN_DATA = intern('data')
-TOKEN_INITIAL = intern('initial')
-TOKEN_EOF = intern('eof')
-
-# bind operators to token types
-operators = {
- '+': TOKEN_ADD,
- '-': TOKEN_SUB,
- '/': TOKEN_DIV,
- '//': TOKEN_FLOORDIV,
- '*': TOKEN_MUL,
- '%': TOKEN_MOD,
- '**': TOKEN_POW,
- '~': TOKEN_TILDE,
- '[': TOKEN_LBRACKET,
- ']': TOKEN_RBRACKET,
- '(': TOKEN_LPAREN,
- ')': TOKEN_RPAREN,
- '{': TOKEN_LBRACE,
- '}': TOKEN_RBRACE,
- '==': TOKEN_EQ,
- '!=': TOKEN_NE,
- '>': TOKEN_GT,
- '>=': TOKEN_GTEQ,
- '<': TOKEN_LT,
- '<=': TOKEN_LTEQ,
- '=': TOKEN_ASSIGN,
- '.': TOKEN_DOT,
- ':': TOKEN_COLON,
- '|': TOKEN_PIPE,
- ',': TOKEN_COMMA,
- ';': TOKEN_SEMICOLON
-}
-
-reverse_operators = dict([(v, k) for k, v in operators.iteritems()])
-assert len(operators) == len(reverse_operators), 'operators dropped'
-operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
- sorted(operators, key=lambda x: -len(x))))
-
-ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
- TOKEN_COMMENT_END, TOKEN_WHITESPACE,
- TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
- TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
-ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
- TOKEN_COMMENT, TOKEN_LINECOMMENT])
-
-
-def _describe_token_type(token_type):
- if token_type in reverse_operators:
- return reverse_operators[token_type]
- return {
- TOKEN_COMMENT_BEGIN: 'begin of comment',
- TOKEN_COMMENT_END: 'end of comment',
- TOKEN_COMMENT: 'comment',
- TOKEN_LINECOMMENT: 'comment',
- TOKEN_BLOCK_BEGIN: 'begin of statement block',
- TOKEN_BLOCK_END: 'end of statement block',
- TOKEN_VARIABLE_BEGIN: 'begin of print statement',
- TOKEN_VARIABLE_END: 'end of print statement',
- TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
- TOKEN_LINESTATEMENT_END: 'end of line statement',
- TOKEN_DATA: 'template data / text',
- TOKEN_EOF: 'end of template'
- }.get(token_type, token_type)
-
-
-def describe_token(token):
- """Returns a description of the token."""
- if token.type == 'name':
- return token.value
- return _describe_token_type(token.type)
-
-
-def describe_token_expr(expr):
- """Like `describe_token` but for token expressions."""
- if ':' in expr:
- type, value = expr.split(':', 1)
- if type == 'name':
- return value
- else:
- type = expr
- return _describe_token_type(type)
-
-
-def count_newlines(value):
- """Count the number of newline characters in the string. This is
- useful for extensions that filter a stream.
- """
- return len(newline_re.findall(value))
-
-
-def compile_rules(environment):
- """Compiles all the rules from the environment into a list of rules."""
- e = re.escape
- rules = [
- (len(environment.comment_start_string), 'comment',
- e(environment.comment_start_string)),
- (len(environment.block_start_string), 'block',
- e(environment.block_start_string)),
- (len(environment.variable_start_string), 'variable',
- e(environment.variable_start_string))
- ]
-
- if environment.line_statement_prefix is not None:
- rules.append((len(environment.line_statement_prefix), 'linestatement',
- r'^\s*' + e(environment.line_statement_prefix)))
- if environment.line_comment_prefix is not None:
- rules.append((len(environment.line_comment_prefix), 'linecomment',
- r'(?:^|(?<=\S))[^\S\r\n]*' +
- e(environment.line_comment_prefix)))
-
- return [x[1:] for x in sorted(rules, reverse=True)]
-
-
-class Failure(object):
- """Class that raises a `TemplateSyntaxError` if called.
- Used by the `Lexer` to specify known errors.
- """
-
- def __init__(self, message, cls=TemplateSyntaxError):
- self.message = message
- self.error_class = cls
-
- def __call__(self, lineno, filename):
- raise self.error_class(self.message, lineno, filename)
-
-
-class Token(tuple):
- """Token class."""
- __slots__ = ()
- lineno, type, value = (property(itemgetter(x)) for x in range(3))
-
- def __new__(cls, lineno, type, value):
- return tuple.__new__(cls, (lineno, intern(str(type)), value))
-
- def __str__(self):
- if self.type in reverse_operators:
- return reverse_operators[self.type]
- elif self.type == 'name':
- return self.value
- return self.type
-
- def test(self, expr):
- """Test a token against a token expression. This can either be a
- token type or ``'token_type:token_value'``. This can only test
- against string values and types.
- """
- # here we do a regular string equality check as test_any is usually
- # passed an iterable of not interned strings.
- if self.type == expr:
- return True
- elif ':' in expr:
- return expr.split(':', 1) == [self.type, self.value]
- return False
-
- def test_any(self, *iterable):
- """Test against multiple token expressions."""
- for expr in iterable:
- if self.test(expr):
- return True
- return False
-
- def __repr__(self):
- return 'Token(%r, %r, %r)' % (
- self.lineno,
- self.type,
- self.value
- )
-
-
-class TokenStreamIterator(object):
- """The iterator for tokenstreams. Iterate over the stream
- until the eof token is reached.
- """
-
- def __init__(self, stream):
- self.stream = stream
-
- def __iter__(self):
- return self
-
- def next(self):
- token = self.stream.current
- if token.type is TOKEN_EOF:
- self.stream.close()
- raise StopIteration()
- next(self.stream)
- return token
-
-
-class TokenStream(object):
- """A token stream is an iterable that yields :class:`Token`\s. The
- parser however does not iterate over it but calls :meth:`next` to go
- one token ahead. The current active token is stored as :attr:`current`.
- """
-
- def __init__(self, generator, name, filename):
- self._next = iter(generator).next
- self._pushed = deque()
- self.name = name
- self.filename = filename
- self.closed = False
- self.current = Token(1, TOKEN_INITIAL, '')
- next(self)
-
- def __iter__(self):
- return TokenStreamIterator(self)
-
- def __nonzero__(self):
- return bool(self._pushed) or self.current.type is not TOKEN_EOF
-
- eos = property(lambda x: not x, doc="Are we at the end of the stream?")
-
- def push(self, token):
- """Push a token back to the stream."""
- self._pushed.append(token)
-
- def look(self):
- """Look at the next token."""
- old_token = next(self)
- result = self.current
- self.push(result)
- self.current = old_token
- return result
-
- def skip(self, n=1):
- """Got n tokens ahead."""
- for x in xrange(n):
- next(self)
-
- def next_if(self, expr):
- """Perform the token test and return the token if it matched.
- Otherwise the return value is `None`.
- """
- if self.current.test(expr):
- return next(self)
-
- def skip_if(self, expr):
- """Like :meth:`next_if` but only returns `True` or `False`."""
- return self.next_if(expr) is not None
-
- def next(self):
- """Go one token ahead and return the old one"""
- rv = self.current
- if self._pushed:
- self.current = self._pushed.popleft()
- elif self.current.type is not TOKEN_EOF:
- try:
- self.current = self._next()
- except StopIteration:
- self.close()
- return rv
-
- def close(self):
- """Close the stream."""
- self.current = Token(self.current.lineno, TOKEN_EOF, '')
- self._next = None
- self.closed = True
-
- def expect(self, expr):
- """Expect a given token type and return it. This accepts the same
- argument as :meth:`jinja2.lexer.Token.test`.
- """
- if not self.current.test(expr):
- expr = describe_token_expr(expr)
- if self.current.type is TOKEN_EOF:
- raise TemplateSyntaxError('unexpected end of template, '
- 'expected %r.' % expr,
- self.current.lineno,
- self.name, self.filename)
- raise TemplateSyntaxError("expected token %r, got %r" %
- (expr, describe_token(self.current)),
- self.current.lineno,
- self.name, self.filename)
- try:
- return self.current
- finally:
- next(self)
-
-
-def get_lexer(environment):
- """Return a lexer which is probably cached."""
- key = (environment.block_start_string,
- environment.block_end_string,
- environment.variable_start_string,
- environment.variable_end_string,
- environment.comment_start_string,
- environment.comment_end_string,
- environment.line_statement_prefix,
- environment.line_comment_prefix,
- environment.trim_blocks,
- environment.newline_sequence)
- lexer = _lexer_cache.get(key)
- if lexer is None:
- lexer = Lexer(environment)
- _lexer_cache[key] = lexer
- return lexer
-
-
-class Lexer(object):
- """Class that implements a lexer for a given environment. Automatically
- created by the environment class, usually you don't have to do that.
-
- Note that the lexer is not automatically bound to an environment.
- Multiple environments can share the same lexer.
- """
-
- def __init__(self, environment):
- # shortcuts
- c = lambda x: re.compile(x, re.M | re.S)
- e = re.escape
-
- # lexing rules for tags
- tag_rules = [
- (whitespace_re, TOKEN_WHITESPACE, None),
- (float_re, TOKEN_FLOAT, None),
- (integer_re, TOKEN_INTEGER, None),
- (name_re, TOKEN_NAME, None),
- (string_re, TOKEN_STRING, None),
- (operator_re, TOKEN_OPERATOR, None)
- ]
-
- # assamble the root lexing rule. because "|" is ungreedy
- # we have to sort by length so that the lexer continues working
- # as expected when we have parsing rules like <% for block and
- # <%= for variables. (if someone wants asp like syntax)
- # variables are just part of the rules if variable processing
- # is required.
- root_tag_rules = compile_rules(environment)
-
- # block suffix if trimming is enabled
- block_suffix_re = environment.trim_blocks and '\\n?' or ''
-
- self.newline_sequence = environment.newline_sequence
-
- # global lexing rules
- self.rules = {
- 'root': [
- # directives
- (c('(.*?)(?:%s)' % '|'.join(
- [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
- e(environment.block_start_string),
- e(environment.block_start_string),
- e(environment.block_end_string),
- e(environment.block_end_string)
- )] + [
- r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, r)
- for n, r in root_tag_rules
- ])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
- # data
- (c('.+'), TOKEN_DATA, None)
- ],
- # comments
- TOKEN_COMMENT_BEGIN: [
- (c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
- e(environment.comment_end_string),
- e(environment.comment_end_string),
- block_suffix_re
- )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
- (c('(.)'), (Failure('Missing end of comment tag'),), None)
- ],
- # blocks
- TOKEN_BLOCK_BEGIN: [
- (c('(?:\-%s\s*|%s)%s' % (
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re
- )), TOKEN_BLOCK_END, '#pop'),
- ] + tag_rules,
- # variables
- TOKEN_VARIABLE_BEGIN: [
- (c('\-%s\s*|%s' % (
- e(environment.variable_end_string),
- e(environment.variable_end_string)
- )), TOKEN_VARIABLE_END, '#pop')
- ] + tag_rules,
- # raw block
- TOKEN_RAW_BEGIN: [
- (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
- e(environment.block_start_string),
- e(environment.block_start_string),
- e(environment.block_end_string),
- e(environment.block_end_string),
- block_suffix_re
- )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
- (c('(.)'), (Failure('Missing end of raw directive'),), None)
- ],
- # line statements
- TOKEN_LINESTATEMENT_BEGIN: [
- (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
- ] + tag_rules,
- # line comments
- TOKEN_LINECOMMENT_BEGIN: [
- (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
- TOKEN_LINECOMMENT_END), '#pop')
- ]
- }
-
- def _normalize_newlines(self, value):
- """Called for strings and template data to normlize it to unicode."""
- return newline_re.sub(self.newline_sequence, value)
-
- def tokenize(self, source, name=None, filename=None, state=None):
- """Calls tokeniter + tokenize and wraps it in a token stream.
- """
- stream = self.tokeniter(source, name, filename, state)
- return TokenStream(self.wrap(stream, name, filename), name, filename)
-
- def wrap(self, stream, name=None, filename=None):
- """This is called with the stream as returned by `tokenize` and wraps
- every token in a :class:`Token` and converts the value.
- """
- for lineno, token, value in stream:
- if token in ignored_tokens:
- continue
- elif token == 'linestatement_begin':
- token = 'block_begin'
- elif token == 'linestatement_end':
- token = 'block_end'
- # we are not interested in those tokens in the parser
- elif token in ('raw_begin', 'raw_end'):
- continue
- elif token == 'data':
- value = self._normalize_newlines(value)
- elif token == 'keyword':
- token = value
- elif token == 'name':
- value = str(value)
- elif token == 'string':
- # try to unescape string
- try:
- value = self._normalize_newlines(value[1:-1]) \
- .encode('ascii', 'backslashreplace') \
- .decode('unicode-escape')
- except Exception, e:
- msg = str(e).split(':')[-1].strip()
- raise TemplateSyntaxError(msg, lineno, name, filename)
- # if we can express it as bytestring (ascii only)
- # we do that for support of semi broken APIs
- # as datetime.datetime.strftime. On python 3 this
- # call becomes a noop thanks to 2to3
- try:
- value = str(value)
- except UnicodeError:
- pass
- elif token == 'integer':
- value = int(value)
- elif token == 'float':
- value = float(value)
- elif token == 'operator':
- token = operators[value]
- yield Token(lineno, token, value)
-
- def tokeniter(self, source, name, filename=None, state=None):
- """This method tokenizes the text and returns the tokens in a
- generator. Use this method if you just want to tokenize a template.
- """
- source = '\n'.join(unicode(source).splitlines())
- pos = 0
- lineno = 1
- stack = ['root']
- if state is not None and state != 'root':
- assert state in ('variable', 'block'), 'invalid state'
- stack.append(state + '_begin')
- else:
- state = 'root'
- statetokens = self.rules[stack[-1]]
- source_length = len(source)
-
- balancing_stack = []
-
- while 1:
- # tokenizer loop
- for regex, tokens, new_state in statetokens:
- m = regex.match(source, pos)
- # if no match we try again with the next rule
- if m is None:
- continue
-
- # we only match blocks and variables if brances / parentheses
- # are balanced. continue parsing with the lower rule which
- # is the operator rule. do this only if the end tags look
- # like operators
- if balancing_stack and \
- tokens in ('variable_end', 'block_end',
- 'linestatement_end'):
- continue
-
- # tuples support more options
- if isinstance(tokens, tuple):
- for idx, token in enumerate(tokens):
- # failure group
- if token.__class__ is Failure:
- raise token(lineno, filename)
- # bygroup is a bit more complex, in that case we
- # yield for the current token the first named
- # group that matched
- elif token == '#bygroup':
- for key, value in m.groupdict().iteritems():
- if value is not None:
- yield lineno, key, value
- lineno += value.count('\n')
- break
- else:
- raise RuntimeError('%r wanted to resolve '
- 'the token dynamically'
- ' but no group matched'
- % regex)
- # normal group
- else:
- data = m.group(idx + 1)
- if data or token not in ignore_if_empty:
- yield lineno, token, data
- lineno += data.count('\n')
-
- # strings as token just are yielded as it.
- else:
- data = m.group()
- # update brace/parentheses balance
- if tokens == 'operator':
- if data == '{':
- balancing_stack.append('}')
- elif data == '(':
- balancing_stack.append(')')
- elif data == '[':
- balancing_stack.append(']')
- elif data in ('}', ')', ']'):
- if not balancing_stack:
- raise TemplateSyntaxError('unexpected \'%s\'' %
- data, lineno, name,
- filename)
- expected_op = balancing_stack.pop()
- if expected_op != data:
- raise TemplateSyntaxError('unexpected \'%s\', '
- 'expected \'%s\'' %
- (data, expected_op),
- lineno, name,
- filename)
- # yield items
- if data or tokens not in ignore_if_empty:
- yield lineno, tokens, data
- lineno += data.count('\n')
-
- # fetch new position into new variable so that we can check
- # if there is a internal parsing error which would result
- # in an infinite loop
- pos2 = m.end()
-
- # handle state changes
- if new_state is not None:
- # remove the uppermost state
- if new_state == '#pop':
- stack.pop()
- # resolve the new state by group checking
- elif new_state == '#bygroup':
- for key, value in m.groupdict().iteritems():
- if value is not None:
- stack.append(key)
- break
- else:
- raise RuntimeError('%r wanted to resolve the '
- 'new state dynamically but'
- ' no group matched' %
- regex)
- # direct state name given
- else:
- stack.append(new_state)
- statetokens = self.rules[stack[-1]]
- # we are still at the same position and no stack change.
- # this means a loop without break condition, avoid that and
- # raise error
- elif pos2 == pos:
- raise RuntimeError('%r yielded empty string without '
- 'stack change' % regex)
- # publish new function and start again
- pos = pos2
- break
- # if loop terminated without break we havn't found a single match
- # either we are at the end of the file or we have a problem
- else:
- # end of text
- if pos >= source_length:
- return
- # something went wrong
- raise TemplateSyntaxError('unexpected char %r at %d' %
- (source[pos], pos), lineno,
- name, filename)
diff --git a/module/lib/jinja2/loaders.py b/module/lib/jinja2/loaders.py
deleted file mode 100644
index bd435e8b0..000000000
--- a/module/lib/jinja2/loaders.py
+++ /dev/null
@@ -1,449 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.loaders
- ~~~~~~~~~~~~~~
-
- Jinja loader classes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import os
-import sys
-import weakref
-from types import ModuleType
-from os import path
-try:
- from hashlib import sha1
-except ImportError:
- from sha import new as sha1
-from jinja2.exceptions import TemplateNotFound
-from jinja2.utils import LRUCache, open_if_exists, internalcode
-
-
-def split_template_path(template):
- """Split a path into segments and perform a sanity check. If it detects
- '..' in the path it will raise a `TemplateNotFound` error.
- """
- pieces = []
- for piece in template.split('/'):
- if path.sep in piece \
- or (path.altsep and path.altsep in piece) or \
- piece == path.pardir:
- raise TemplateNotFound(template)
- elif piece and piece != '.':
- pieces.append(piece)
- return pieces
-
-
-class BaseLoader(object):
- """Baseclass for all loaders. Subclass this and override `get_source` to
- implement a custom loading mechanism. The environment provides a
- `get_template` method that calls the loader's `load` method to get the
- :class:`Template` object.
-
- A very basic example for a loader that looks up templates on the file
- system could look like this::
-
- from jinja2 import BaseLoader, TemplateNotFound
- from os.path import join, exists, getmtime
-
- class MyLoader(BaseLoader):
-
- def __init__(self, path):
- self.path = path
-
- def get_source(self, environment, template):
- path = join(self.path, template)
- if not exists(path):
- raise TemplateNotFound(template)
- mtime = getmtime(path)
- with file(path) as f:
- source = f.read().decode('utf-8')
- return source, path, lambda: mtime == getmtime(path)
- """
-
- #: if set to `False` it indicates that the loader cannot provide access
- #: to the source of templates.
- #:
- #: .. versionadded:: 2.4
- has_source_access = True
-
- def get_source(self, environment, template):
- """Get the template source, filename and reload helper for a template.
- It's passed the environment and template name and has to return a
- tuple in the form ``(source, filename, uptodate)`` or raise a
- `TemplateNotFound` error if it can't locate the template.
-
- The source part of the returned tuple must be the source of the
- template as unicode string or a ASCII bytestring. The filename should
- be the name of the file on the filesystem if it was loaded from there,
- otherwise `None`. The filename is used by python for the tracebacks
- if no loader extension is used.
-
- The last item in the tuple is the `uptodate` function. If auto
- reloading is enabled it's always called to check if the template
- changed. No arguments are passed so the function must store the
- old state somewhere (for example in a closure). If it returns `False`
- the template will be reloaded.
- """
- if not self.has_source_access:
- raise RuntimeError('%s cannot provide access to the source' %
- self.__class__.__name__)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- """Iterates over all templates. If the loader does not support that
- it should raise a :exc:`TypeError` which is the default behavior.
- """
- raise TypeError('this loader cannot iterate over all templates')
-
- @internalcode
- def load(self, environment, name, globals=None):
- """Loads a template. This method looks up the template in the cache
- or loads one by calling :meth:`get_source`. Subclasses should not
- override this method as loaders working on collections of other
- loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
- will not call this method but `get_source` directly.
- """
- code = None
- if globals is None:
- globals = {}
-
- # first we try to get the source for this template together
- # with the filename and the uptodate function.
- source, filename, uptodate = self.get_source(environment, name)
-
- # try to load the code from the bytecode cache if there is a
- # bytecode cache configured.
- bcc = environment.bytecode_cache
- if bcc is not None:
- bucket = bcc.get_bucket(environment, name, filename, source)
- code = bucket.code
-
- # if we don't have code so far (not cached, no longer up to
- # date) etc. we compile the template
- if code is None:
- code = environment.compile(source, name, filename)
-
- # if the bytecode cache is available and the bucket doesn't
- # have a code so far, we give the bucket the new code and put
- # it back to the bytecode cache.
- if bcc is not None and bucket.code is None:
- bucket.code = code
- bcc.set_bucket(bucket)
-
- return environment.template_class.from_code(environment, code,
- globals, uptodate)
-
-
-class FileSystemLoader(BaseLoader):
- """Loads templates from the file system. This loader can find templates
- in folders on the file system and is the preferred way to load them.
-
- The loader takes the path to the templates as string, or if multiple
- locations are wanted a list of them which is then looked up in the
- given order:
-
- >>> loader = FileSystemLoader('/path/to/templates')
- >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
-
- Per default the template encoding is ``'utf-8'`` which can be changed
- by setting the `encoding` parameter to something else.
- """
-
- def __init__(self, searchpath, encoding='utf-8'):
- if isinstance(searchpath, basestring):
- searchpath = [searchpath]
- self.searchpath = list(searchpath)
- self.encoding = encoding
-
- def get_source(self, environment, template):
- pieces = split_template_path(template)
- for searchpath in self.searchpath:
- filename = path.join(searchpath, *pieces)
- f = open_if_exists(filename)
- if f is None:
- continue
- try:
- contents = f.read().decode(self.encoding)
- finally:
- f.close()
-
- mtime = path.getmtime(filename)
- def uptodate():
- try:
- return path.getmtime(filename) == mtime
- except OSError:
- return False
- return contents, filename, uptodate
- raise TemplateNotFound(template)
-
- def list_templates(self):
- found = set()
- for searchpath in self.searchpath:
- for dirpath, dirnames, filenames in os.walk(searchpath):
- for filename in filenames:
- template = os.path.join(dirpath, filename) \
- [len(searchpath):].strip(os.path.sep) \
- .replace(os.path.sep, '/')
- if template[:2] == './':
- template = template[2:]
- if template not in found:
- found.add(template)
- return sorted(found)
-
-
-class PackageLoader(BaseLoader):
- """Load templates from python eggs or packages. It is constructed with
- the name of the python package and the path to the templates in that
- package::
-
- loader = PackageLoader('mypackage', 'views')
-
- If the package path is not given, ``'templates'`` is assumed.
-
- Per default the template encoding is ``'utf-8'`` which can be changed
- by setting the `encoding` parameter to something else. Due to the nature
- of eggs it's only possible to reload templates if the package was loaded
- from the file system and not a zip file.
- """
-
- def __init__(self, package_name, package_path='templates',
- encoding='utf-8'):
- from pkg_resources import DefaultProvider, ResourceManager, \
- get_provider
- provider = get_provider(package_name)
- self.encoding = encoding
- self.manager = ResourceManager()
- self.filesystem_bound = isinstance(provider, DefaultProvider)
- self.provider = provider
- self.package_path = package_path
-
- def get_source(self, environment, template):
- pieces = split_template_path(template)
- p = '/'.join((self.package_path,) + tuple(pieces))
- if not self.provider.has_resource(p):
- raise TemplateNotFound(template)
-
- filename = uptodate = None
- if self.filesystem_bound:
- filename = self.provider.get_resource_filename(self.manager, p)
- mtime = path.getmtime(filename)
- def uptodate():
- try:
- return path.getmtime(filename) == mtime
- except OSError:
- return False
-
- source = self.provider.get_resource_string(self.manager, p)
- return source.decode(self.encoding), filename, uptodate
-
- def list_templates(self):
- path = self.package_path
- if path[:2] == './':
- path = path[2:]
- elif path == '.':
- path = ''
- offset = len(path)
- results = []
- def _walk(path):
- for filename in self.provider.resource_listdir(path):
- fullname = path + '/' + filename
- if self.provider.resource_isdir(fullname):
- for item in _walk(fullname):
- results.append(item)
- else:
- results.append(fullname[offset:].lstrip('/'))
- _walk(path)
- results.sort()
- return results
-
-
-class DictLoader(BaseLoader):
- """Loads a template from a python dict. It's passed a dict of unicode
- strings bound to template names. This loader is useful for unittesting:
-
- >>> loader = DictLoader({'index.html': 'source here'})
-
- Because auto reloading is rarely useful this is disabled per default.
- """
-
- def __init__(self, mapping):
- self.mapping = mapping
-
- def get_source(self, environment, template):
- if template in self.mapping:
- source = self.mapping[template]
- return source, None, lambda: source != self.mapping.get(template)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- return sorted(self.mapping)
-
-
-class FunctionLoader(BaseLoader):
- """A loader that is passed a function which does the loading. The
- function becomes the name of the template passed and has to return either
- an unicode string with the template source, a tuple in the form ``(source,
- filename, uptodatefunc)`` or `None` if the template does not exist.
-
- >>> def load_template(name):
- ... if name == 'index.html':
- ... return '...'
- ...
- >>> loader = FunctionLoader(load_template)
-
- The `uptodatefunc` is a function that is called if autoreload is enabled
- and has to return `True` if the template is still up to date. For more
- details have a look at :meth:`BaseLoader.get_source` which has the same
- return value.
- """
-
- def __init__(self, load_func):
- self.load_func = load_func
-
- def get_source(self, environment, template):
- rv = self.load_func(template)
- if rv is None:
- raise TemplateNotFound(template)
- elif isinstance(rv, basestring):
- return rv, None, None
- return rv
-
-
-class PrefixLoader(BaseLoader):
- """A loader that is passed a dict of loaders where each loader is bound
- to a prefix. The prefix is delimited from the template by a slash per
- default, which can be changed by setting the `delimiter` argument to
- something else::
-
- loader = PrefixLoader({
- 'app1': PackageLoader('mypackage.app1'),
- 'app2': PackageLoader('mypackage.app2')
- })
-
- By loading ``'app1/index.html'`` the file from the app1 package is loaded,
- by loading ``'app2/index.html'`` the file from the second.
- """
-
- def __init__(self, mapping, delimiter='/'):
- self.mapping = mapping
- self.delimiter = delimiter
-
- def get_source(self, environment, template):
- try:
- prefix, name = template.split(self.delimiter, 1)
- loader = self.mapping[prefix]
- except (ValueError, KeyError):
- raise TemplateNotFound(template)
- try:
- return loader.get_source(environment, name)
- except TemplateNotFound:
- # re-raise the exception with the correct fileame here.
- # (the one that includes the prefix)
- raise TemplateNotFound(template)
-
- def list_templates(self):
- result = []
- for prefix, loader in self.mapping.iteritems():
- for template in loader.list_templates():
- result.append(prefix + self.delimiter + template)
- return result
-
-
-class ChoiceLoader(BaseLoader):
- """This loader works like the `PrefixLoader` just that no prefix is
- specified. If a template could not be found by one loader the next one
- is tried.
-
- >>> loader = ChoiceLoader([
- ... FileSystemLoader('/path/to/user/templates'),
- ... FileSystemLoader('/path/to/system/templates')
- ... ])
-
- This is useful if you want to allow users to override builtin templates
- from a different location.
- """
-
- def __init__(self, loaders):
- self.loaders = loaders
-
- def get_source(self, environment, template):
- for loader in self.loaders:
- try:
- return loader.get_source(environment, template)
- except TemplateNotFound:
- pass
- raise TemplateNotFound(template)
-
- def list_templates(self):
- found = set()
- for loader in self.loaders:
- found.update(loader.list_templates())
- return sorted(found)
-
-
-class _TemplateModule(ModuleType):
- """Like a normal module but with support for weak references"""
-
-
-class ModuleLoader(BaseLoader):
- """This loader loads templates from precompiled templates.
-
- Example usage:
-
- >>> loader = ChoiceLoader([
- ... ModuleLoader('/path/to/compiled/templates'),
- ... FileSystemLoader('/path/to/templates')
- ... ])
- """
-
- has_source_access = False
-
- def __init__(self, path):
- package_name = '_jinja2_module_templates_%x' % id(self)
-
- # create a fake module that looks for the templates in the
- # path given.
- mod = _TemplateModule(package_name)
- if isinstance(path, basestring):
- path = [path]
- else:
- path = list(path)
- mod.__path__ = path
-
- sys.modules[package_name] = weakref.proxy(mod,
- lambda x: sys.modules.pop(package_name, None))
-
- # the only strong reference, the sys.modules entry is weak
- # so that the garbage collector can remove it once the
- # loader that created it goes out of business.
- self.module = mod
- self.package_name = package_name
-
- @staticmethod
- def get_template_key(name):
- return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
-
- @staticmethod
- def get_module_filename(name):
- return ModuleLoader.get_template_key(name) + '.py'
-
- @internalcode
- def load(self, environment, name, globals=None):
- key = self.get_template_key(name)
- module = '%s.%s' % (self.package_name, key)
- mod = getattr(self.module, module, None)
- if mod is None:
- try:
- mod = __import__(module, None, None, ['root'])
- except ImportError:
- raise TemplateNotFound(name)
-
- # remove the entry from sys.modules, we only want the attribute
- # on the module object we have stored on the loader.
- sys.modules.pop(module, None)
-
- return environment.template_class.from_module_dict(
- environment, mod.__dict__, globals)
diff --git a/module/lib/jinja2/meta.py b/module/lib/jinja2/meta.py
deleted file mode 100644
index 3a779a5e9..000000000
--- a/module/lib/jinja2/meta.py
+++ /dev/null
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.meta
- ~~~~~~~~~~~
-
- This module implements various functions that exposes information about
- templates that might be interesting for various kinds of applications.
-
- :copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2 import nodes
-from jinja2.compiler import CodeGenerator
-
-
-class TrackingCodeGenerator(CodeGenerator):
- """We abuse the code generator for introspection."""
-
- def __init__(self, environment):
- CodeGenerator.__init__(self, environment, '<introspection>',
- '<introspection>')
- self.undeclared_identifiers = set()
-
- def write(self, x):
- """Don't write."""
-
- def pull_locals(self, frame):
- """Remember all undeclared identifiers."""
- self.undeclared_identifiers.update(frame.identifiers.undeclared)
-
-
-def find_undeclared_variables(ast):
- """Returns a set of all variables in the AST that will be looked up from
- the context at runtime. Because at compile time it's not known which
- variables will be used depending on the path the execution takes at
- runtime, all variables are returned.
-
- >>> from jinja2 import Environment, meta
- >>> env = Environment()
- >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
- >>> meta.find_undeclared_variables(ast)
- set(['bar'])
-
- .. admonition:: Implementation
-
- Internally the code generator is used for finding undeclared variables.
- This is good to know because the code generator might raise a
- :exc:`TemplateAssertionError` during compilation and as a matter of
- fact this function can currently raise that exception as well.
- """
- codegen = TrackingCodeGenerator(ast.environment)
- codegen.visit(ast)
- return codegen.undeclared_identifiers
-
-
-def find_referenced_templates(ast):
- """Finds all the referenced templates from the AST. This will return an
- iterator over all the hardcoded template extensions, inclusions and
- imports. If dynamic inheritance or inclusion is used, `None` will be
- yielded.
-
- >>> from jinja2 import Environment, meta
- >>> env = Environment()
- >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
- >>> list(meta.find_referenced_templates(ast))
- ['layout.html', None]
-
- This function is useful for dependency tracking. For example if you want
- to rebuild parts of the website after a layout template has changed.
- """
- for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
- nodes.Include)):
- if not isinstance(node.template, nodes.Const):
- # a tuple with some non consts in there
- if isinstance(node.template, (nodes.Tuple, nodes.List)):
- for template_name in node.template.items:
- # something const, only yield the strings and ignore
- # non-string consts that really just make no sense
- if isinstance(template_name, nodes.Const):
- if isinstance(template_name.value, basestring):
- yield template_name.value
- # something dynamic in there
- else:
- yield None
- # something dynamic we don't know about here
- else:
- yield None
- continue
- # constant is a basestring, direct template name
- if isinstance(node.template.value, basestring):
- yield node.template.value
- # a tuple or list (latter *should* not happen) made of consts,
- # yield the consts that are strings. We could warn here for
- # non string values
- elif isinstance(node, nodes.Include) and \
- isinstance(node.template.value, (tuple, list)):
- for template_name in node.template.value:
- if isinstance(template_name, basestring):
- yield template_name
- # something else we don't care about, we could warn here
- else:
- yield None
diff --git a/module/lib/jinja2/nodes.py b/module/lib/jinja2/nodes.py
deleted file mode 100644
index 6446c70ea..000000000
--- a/module/lib/jinja2/nodes.py
+++ /dev/null
@@ -1,901 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.nodes
- ~~~~~~~~~~~~
-
- This module implements additional nodes derived from the ast base node.
-
- It also provides some node tree helper functions like `in_lineno` and
- `get_nodes` used by the parser and translator in order to normalize
- python and jinja nodes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import operator
-from itertools import chain, izip
-from collections import deque
-from jinja2.utils import Markup, MethodType, FunctionType
-
-
-#: the types we support for context functions
-_context_function_types = (FunctionType, MethodType)
-
-
-_binop_to_func = {
- '*': operator.mul,
- '/': operator.truediv,
- '//': operator.floordiv,
- '**': operator.pow,
- '%': operator.mod,
- '+': operator.add,
- '-': operator.sub
-}
-
-_uaop_to_func = {
- 'not': operator.not_,
- '+': operator.pos,
- '-': operator.neg
-}
-
-_cmpop_to_func = {
- 'eq': operator.eq,
- 'ne': operator.ne,
- 'gt': operator.gt,
- 'gteq': operator.ge,
- 'lt': operator.lt,
- 'lteq': operator.le,
- 'in': lambda a, b: a in b,
- 'notin': lambda a, b: a not in b
-}
-
-
-class Impossible(Exception):
- """Raised if the node could not perform a requested action."""
-
-
-class NodeType(type):
- """A metaclass for nodes that handles the field and attribute
- inheritance. fields and attributes from the parent class are
- automatically forwarded to the child."""
-
- def __new__(cls, name, bases, d):
- for attr in 'fields', 'attributes':
- storage = []
- storage.extend(getattr(bases[0], attr, ()))
- storage.extend(d.get(attr, ()))
- assert len(bases) == 1, 'multiple inheritance not allowed'
- assert len(storage) == len(set(storage)), 'layout conflict'
- d[attr] = tuple(storage)
- d.setdefault('abstract', False)
- return type.__new__(cls, name, bases, d)
-
-
-class EvalContext(object):
- """Holds evaluation time information. Custom attributes can be attached
- to it in extensions.
- """
-
- def __init__(self, environment, template_name=None):
- if callable(environment.autoescape):
- self.autoescape = environment.autoescape(template_name)
- else:
- self.autoescape = environment.autoescape
- self.volatile = False
-
- def save(self):
- return self.__dict__.copy()
-
- def revert(self, old):
- self.__dict__.clear()
- self.__dict__.update(old)
-
-
-def get_eval_context(node, ctx):
- if ctx is None:
- if node.environment is None:
- raise RuntimeError('if no eval context is passed, the '
- 'node must have an attached '
- 'environment.')
- return EvalContext(node.environment)
- return ctx
-
-
-class Node(object):
- """Baseclass for all Jinja2 nodes. There are a number of nodes available
- of different types. There are three major types:
-
- - :class:`Stmt`: statements
- - :class:`Expr`: expressions
- - :class:`Helper`: helper nodes
- - :class:`Template`: the outermost wrapper node
-
- All nodes have fields and attributes. Fields may be other nodes, lists,
- or arbitrary values. Fields are passed to the constructor as regular
- positional arguments, attributes as keyword arguments. Each node has
- two attributes: `lineno` (the line number of the node) and `environment`.
- The `environment` attribute is set at the end of the parsing process for
- all nodes automatically.
- """
- __metaclass__ = NodeType
- fields = ()
- attributes = ('lineno', 'environment')
- abstract = True
-
- def __init__(self, *fields, **attributes):
- if self.abstract:
- raise TypeError('abstract nodes are not instanciable')
- if fields:
- if len(fields) != len(self.fields):
- if not self.fields:
- raise TypeError('%r takes 0 arguments' %
- self.__class__.__name__)
- raise TypeError('%r takes 0 or %d argument%s' % (
- self.__class__.__name__,
- len(self.fields),
- len(self.fields) != 1 and 's' or ''
- ))
- for name, arg in izip(self.fields, fields):
- setattr(self, name, arg)
- for attr in self.attributes:
- setattr(self, attr, attributes.pop(attr, None))
- if attributes:
- raise TypeError('unknown attribute %r' %
- iter(attributes).next())
-
- def iter_fields(self, exclude=None, only=None):
- """This method iterates over all fields that are defined and yields
- ``(key, value)`` tuples. Per default all fields are returned, but
- it's possible to limit that to some fields by providing the `only`
- parameter or to exclude some using the `exclude` parameter. Both
- should be sets or tuples of field names.
- """
- for name in self.fields:
- if (exclude is only is None) or \
- (exclude is not None and name not in exclude) or \
- (only is not None and name in only):
- try:
- yield name, getattr(self, name)
- except AttributeError:
- pass
-
- def iter_child_nodes(self, exclude=None, only=None):
- """Iterates over all direct child nodes of the node. This iterates
- over all fields and yields the values of they are nodes. If the value
- of a field is a list all the nodes in that list are returned.
- """
- for field, item in self.iter_fields(exclude, only):
- if isinstance(item, list):
- for n in item:
- if isinstance(n, Node):
- yield n
- elif isinstance(item, Node):
- yield item
-
- def find(self, node_type):
- """Find the first node of a given type. If no such node exists the
- return value is `None`.
- """
- for result in self.find_all(node_type):
- return result
-
- def find_all(self, node_type):
- """Find all the nodes of a given type. If the type is a tuple,
- the check is performed for any of the tuple items.
- """
- for child in self.iter_child_nodes():
- if isinstance(child, node_type):
- yield child
- for result in child.find_all(node_type):
- yield result
-
- def set_ctx(self, ctx):
- """Reset the context of a node and all child nodes. Per default the
- parser will all generate nodes that have a 'load' context as it's the
- most common one. This method is used in the parser to set assignment
- targets and other nodes to a store context.
- """
- todo = deque([self])
- while todo:
- node = todo.popleft()
- if 'ctx' in node.fields:
- node.ctx = ctx
- todo.extend(node.iter_child_nodes())
- return self
-
- def set_lineno(self, lineno, override=False):
- """Set the line numbers of the node and children."""
- todo = deque([self])
- while todo:
- node = todo.popleft()
- if 'lineno' in node.attributes:
- if node.lineno is None or override:
- node.lineno = lineno
- todo.extend(node.iter_child_nodes())
- return self
-
- def set_environment(self, environment):
- """Set the environment for all nodes."""
- todo = deque([self])
- while todo:
- node = todo.popleft()
- node.environment = environment
- todo.extend(node.iter_child_nodes())
- return self
-
- def __eq__(self, other):
- return type(self) is type(other) and \
- tuple(self.iter_fields()) == tuple(other.iter_fields())
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
- arg in self.fields)
- )
-
-
-class Stmt(Node):
- """Base node for all statements."""
- abstract = True
-
-
-class Helper(Node):
- """Nodes that exist in a specific context only."""
- abstract = True
-
-
-class Template(Node):
- """Node that represents a template. This must be the outermost node that
- is passed to the compiler.
- """
- fields = ('body',)
-
-
-class Output(Stmt):
- """A node that holds multiple expressions which are then printed out.
- This is used both for the `print` statement and the regular template data.
- """
- fields = ('nodes',)
-
-
-class Extends(Stmt):
- """Represents an extends statement."""
- fields = ('template',)
-
-
-class For(Stmt):
- """The for loop. `target` is the target for the iteration (usually a
- :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
- of nodes that are used as loop-body, and `else_` a list of nodes for the
- `else` block. If no else node exists it has to be an empty list.
-
- For filtered nodes an expression can be stored as `test`, otherwise `None`.
- """
- fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
-
-
-class If(Stmt):
- """If `test` is true, `body` is rendered, else `else_`."""
- fields = ('test', 'body', 'else_')
-
-
-class Macro(Stmt):
- """A macro definition. `name` is the name of the macro, `args` a list of
- arguments and `defaults` a list of defaults if there are any. `body` is
- a list of nodes for the macro body.
- """
- fields = ('name', 'args', 'defaults', 'body')
-
-
-class CallBlock(Stmt):
- """Like a macro without a name but a call instead. `call` is called with
- the unnamed macro as `caller` argument this node holds.
- """
- fields = ('call', 'args', 'defaults', 'body')
-
-
-class FilterBlock(Stmt):
- """Node for filter sections."""
- fields = ('body', 'filter')
-
-
-class Block(Stmt):
- """A node that represents a block."""
- fields = ('name', 'body', 'scoped')
-
-
-class Include(Stmt):
- """A node that represents the include tag."""
- fields = ('template', 'with_context', 'ignore_missing')
-
-
-class Import(Stmt):
- """A node that represents the import tag."""
- fields = ('template', 'target', 'with_context')
-
-
-class FromImport(Stmt):
- """A node that represents the from import tag. It's important to not
- pass unsafe names to the name attribute. The compiler translates the
- attribute lookups directly into getattr calls and does *not* use the
- subscript callback of the interface. As exported variables may not
- start with double underscores (which the parser asserts) this is not a
- problem for regular Jinja code, but if this node is used in an extension
- extra care must be taken.
-
- The list of names may contain tuples if aliases are wanted.
- """
- fields = ('template', 'names', 'with_context')
-
-
-class ExprStmt(Stmt):
- """A statement that evaluates an expression and discards the result."""
- fields = ('node',)
-
-
-class Assign(Stmt):
- """Assigns an expression to a target."""
- fields = ('target', 'node')
-
-
-class Expr(Node):
- """Baseclass for all expressions."""
- abstract = True
-
- def as_const(self, eval_ctx=None):
- """Return the value of the expression as constant or raise
- :exc:`Impossible` if this was not possible.
-
- An :class:`EvalContext` can be provided, if none is given
- a default context is created which requires the nodes to have
- an attached environment.
-
- .. versionchanged:: 2.4
- the `eval_ctx` parameter was added.
- """
- raise Impossible()
-
- def can_assign(self):
- """Check if it's possible to assign something to this node."""
- return False
-
-
-class BinExpr(Expr):
- """Baseclass for all binary expressions."""
- fields = ('left', 'right')
- operator = None
- abstract = True
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- f = _binop_to_func[self.operator]
- try:
- return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
- except:
- raise Impossible()
-
-
-class UnaryExpr(Expr):
- """Baseclass for all unary expressions."""
- fields = ('node',)
- operator = None
- abstract = True
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- f = _uaop_to_func[self.operator]
- try:
- return f(self.node.as_const(eval_ctx))
- except:
- raise Impossible()
-
-
-class Name(Expr):
- """Looks up a name or stores a value in a name.
- The `ctx` of the node can be one of the following values:
-
- - `store`: store a value in the name
- - `load`: load that name
- - `param`: like `store` but if the name was defined as function parameter.
- """
- fields = ('name', 'ctx')
-
- def can_assign(self):
- return self.name not in ('true', 'false', 'none',
- 'True', 'False', 'None')
-
-
-class Literal(Expr):
- """Baseclass for literals."""
- abstract = True
-
-
-class Const(Literal):
- """All constant values. The parser will return this node for simple
- constants such as ``42`` or ``"foo"`` but it can be used to store more
- complex values such as lists too. Only constants with a safe
- representation (objects where ``eval(repr(x)) == x`` is true).
- """
- fields = ('value',)
-
- def as_const(self, eval_ctx=None):
- return self.value
-
- @classmethod
- def from_untrusted(cls, value, lineno=None, environment=None):
- """Return a const object if the value is representable as
- constant value in the generated code, otherwise it will raise
- an `Impossible` exception.
- """
- from compiler import has_safe_repr
- if not has_safe_repr(value):
- raise Impossible()
- return cls(value, lineno=lineno, environment=environment)
-
-
-class TemplateData(Literal):
- """A constant template string."""
- fields = ('data',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- if eval_ctx.autoescape:
- return Markup(self.data)
- return self.data
-
-
-class Tuple(Literal):
- """For loop unpacking and some other things like multiple arguments
- for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
- is used for loading the names or storing.
- """
- fields = ('items', 'ctx')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return tuple(x.as_const(eval_ctx) for x in self.items)
-
- def can_assign(self):
- for item in self.items:
- if not item.can_assign():
- return False
- return True
-
-
-class List(Literal):
- """Any list literal such as ``[1, 2, 3]``"""
- fields = ('items',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return [x.as_const(eval_ctx) for x in self.items]
-
-
-class Dict(Literal):
- """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
- :class:`Pair` nodes.
- """
- fields = ('items',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return dict(x.as_const(eval_ctx) for x in self.items)
-
-
-class Pair(Helper):
- """A key, value pair for dicts."""
- fields = ('key', 'value')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
-
-
-class Keyword(Helper):
- """A key, value pair for keyword arguments where key is a string."""
- fields = ('key', 'value')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.key, self.value.as_const(eval_ctx)
-
-
-class CondExpr(Expr):
- """A conditional expression (inline if expression). (``{{
- foo if bar else baz }}``)
- """
- fields = ('test', 'expr1', 'expr2')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if self.test.as_const(eval_ctx):
- return self.expr1.as_const(eval_ctx)
-
- # if we evaluate to an undefined object, we better do that at runtime
- if self.expr2 is None:
- raise Impossible()
-
- return self.expr2.as_const(eval_ctx)
-
-
-class Filter(Expr):
- """This node applies a filter on an expression. `name` is the name of
- the filter, the rest of the fields are the same as for :class:`Call`.
-
- If the `node` of a filter is `None` the contents of the last buffer are
- filtered. Buffers are created by macros and filter blocks.
- """
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile or self.node is None:
- raise Impossible()
- # we have to be careful here because we call filter_ below.
- # if this variable would be called filter, 2to3 would wrap the
- # call in a list beause it is assuming we are talking about the
- # builtin filter function here which no longer returns a list in
- # python 3. because of that, do not rename filter_ to filter!
- filter_ = self.environment.filters.get(self.name)
- if filter_ is None or getattr(filter_, 'contextfilter', False):
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
- args = [x.as_const(eval_ctx) for x in self.args]
- if getattr(filter_, 'evalcontextfilter', False):
- args.insert(0, eval_ctx)
- elif getattr(filter_, 'environmentfilter', False):
- args.insert(0, self.environment)
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except:
- raise Impossible()
- try:
- return filter_(obj, *args, **kwargs)
- except:
- raise Impossible()
-
-
-class Test(Expr):
- """Applies a test on an expression. `name` is the name of the test, the
- rest of the fields are the same as for :class:`Call`.
- """
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
-
-class Call(Expr):
- """Calls an expression. `args` is a list of arguments, `kwargs` a list
- of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
- and `dyn_kwargs` has to be either `None` or a node that is used as
- node for dynamic positional (``*args``) or keyword (``**kwargs``)
- arguments.
- """
- fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
-
- # don't evaluate context functions
- args = [x.as_const(eval_ctx) for x in self.args]
- if isinstance(obj, _context_function_types):
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif getattr(obj, 'evalcontextfunction', False):
- args.insert(0, eval_ctx)
- elif getattr(obj, 'environmentfunction', False):
- args.insert(0, self.environment)
-
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except:
- raise Impossible()
- try:
- return obj(*args, **kwargs)
- except:
- raise Impossible()
-
-
-class Getitem(Expr):
- """Get an attribute or item from an expression and prefer the item."""
- fields = ('node', 'arg', 'ctx')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if self.ctx != 'load':
- raise Impossible()
- try:
- return self.environment.getitem(self.node.as_const(eval_ctx),
- self.arg.as_const(eval_ctx))
- except:
- raise Impossible()
-
- def can_assign(self):
- return False
-
-
-class Getattr(Expr):
- """Get an attribute or item from an expression that is a ascii-only
- bytestring and prefer the attribute.
- """
- fields = ('node', 'attr', 'ctx')
-
- def as_const(self, eval_ctx=None):
- if self.ctx != 'load':
- raise Impossible()
- try:
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.environment.getattr(self.node.as_const(eval_ctx),
- self.attr)
- except:
- raise Impossible()
-
- def can_assign(self):
- return False
-
-
-class Slice(Expr):
- """Represents a slice object. This must only be used as argument for
- :class:`Subscript`.
- """
- fields = ('start', 'stop', 'step')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- def const(obj):
- if obj is None:
- return None
- return obj.as_const(eval_ctx)
- return slice(const(self.start), const(self.stop), const(self.step))
-
-
-class Concat(Expr):
- """Concatenates the list of expressions provided after converting them to
- unicode.
- """
- fields = ('nodes',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
-
-
-class Compare(Expr):
- """Compares an expression with some other expressions. `ops` must be a
- list of :class:`Operand`\s.
- """
- fields = ('expr', 'ops')
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- result = value = self.expr.as_const(eval_ctx)
- try:
- for op in self.ops:
- new_value = op.expr.as_const(eval_ctx)
- result = _cmpop_to_func[op.op](value, new_value)
- value = new_value
- except:
- raise Impossible()
- return result
-
-
-class Operand(Helper):
- """Holds an operator and an expression."""
- fields = ('op', 'expr')
-
-if __debug__:
- Operand.__doc__ += '\nThe following operators are available: ' + \
- ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
- set(_uaop_to_func) | set(_cmpop_to_func)))
-
-
-class Mul(BinExpr):
- """Multiplies the left with the right node."""
- operator = '*'
-
-
-class Div(BinExpr):
- """Divides the left by the right node."""
- operator = '/'
-
-
-class FloorDiv(BinExpr):
- """Divides the left by the right node and truncates conver the
- result into an integer by truncating.
- """
- operator = '//'
-
-
-class Add(BinExpr):
- """Add the left to the right node."""
- operator = '+'
-
-
-class Sub(BinExpr):
- """Substract the right from the left node."""
- operator = '-'
-
-
-class Mod(BinExpr):
- """Left modulo right."""
- operator = '%'
-
-
-class Pow(BinExpr):
- """Left to the power of right."""
- operator = '**'
-
-
-class And(BinExpr):
- """Short circuited AND."""
- operator = 'and'
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
-
-
-class Or(BinExpr):
- """Short circuited OR."""
- operator = 'or'
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
-
-
-class Not(UnaryExpr):
- """Negate the expression."""
- operator = 'not'
-
-
-class Neg(UnaryExpr):
- """Make the expression negative."""
- operator = '-'
-
-
-class Pos(UnaryExpr):
- """Make the expression positive (noop for most expressions)"""
- operator = '+'
-
-
-# Helpers for extensions
-
-
-class EnvironmentAttribute(Expr):
- """Loads an attribute from the environment object. This is useful for
- extensions that want to call a callback stored on the environment.
- """
- fields = ('name',)
-
-
-class ExtensionAttribute(Expr):
- """Returns the attribute of an extension bound to the environment.
- The identifier is the identifier of the :class:`Extension`.
-
- This node is usually constructed by calling the
- :meth:`~jinja2.ext.Extension.attr` method on an extension.
- """
- fields = ('identifier', 'name')
-
-
-class ImportedName(Expr):
- """If created with an import name the import name is returned on node
- access. For example ``ImportedName('cgi.escape')`` returns the `escape`
- function from the cgi module on evaluation. Imports are optimized by the
- compiler so there is no need to assign them to local variables.
- """
- fields = ('importname',)
-
-
-class InternalName(Expr):
- """An internal name in the compiler. You cannot create these nodes
- yourself but the parser provides a
- :meth:`~jinja2.parser.Parser.free_identifier` method that creates
- a new identifier for you. This identifier is not available from the
- template and is not threated specially by the compiler.
- """
- fields = ('name',)
-
- def __init__(self):
- raise TypeError('Can\'t create internal names. Use the '
- '`free_identifier` method on a parser.')
-
-
-class MarkSafe(Expr):
- """Mark the wrapped expression as safe (wrap it as `Markup`)."""
- fields = ('expr',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- return Markup(self.expr.as_const(eval_ctx))
-
-
-class MarkSafeIfAutoescape(Expr):
- """Mark the wrapped expression as safe (wrap it as `Markup`) but
- only if autoescaping is active.
-
- .. versionadded:: 2.5
- """
- fields = ('expr',)
-
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- expr = self.expr.as_const(eval_ctx)
- if eval_ctx.autoescape:
- return Markup(expr)
- return expr
-
-
-class ContextReference(Expr):
- """Returns the current template context. It can be used like a
- :class:`Name` node, with a ``'load'`` ctx and will return the
- current :class:`~jinja2.runtime.Context` object.
-
- Here an example that assigns the current template name to a
- variable named `foo`::
-
- Assign(Name('foo', ctx='store'),
- Getattr(ContextReference(), 'name'))
- """
-
-
-class Continue(Stmt):
- """Continue a loop."""
-
-
-class Break(Stmt):
- """Break a loop."""
-
-
-class Scope(Stmt):
- """An artificial scope."""
- fields = ('body',)
-
-
-class EvalContextModifier(Stmt):
- """Modifies the eval context. For each option that should be modified,
- a :class:`Keyword` has to be added to the :attr:`options` list.
-
- Example to change the `autoescape` setting::
-
- EvalContextModifier(options=[Keyword('autoescape', Const(True))])
- """
- fields = ('options',)
-
-
-class ScopedEvalContextModifier(EvalContextModifier):
- """Modifies the eval context and reverts it later. Works exactly like
- :class:`EvalContextModifier` but will only modify the
- :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
- """
- fields = ('body',)
-
-
-# make sure nobody creates custom nodes
-def _failing_new(*args, **kwargs):
- raise TypeError('can\'t create custom node types')
-NodeType.__new__ = staticmethod(_failing_new); del _failing_new
diff --git a/module/lib/jinja2/parser.py b/module/lib/jinja2/parser.py
deleted file mode 100644
index d44229ad0..000000000
--- a/module/lib/jinja2/parser.py
+++ /dev/null
@@ -1,896 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.parser
- ~~~~~~~~~~~~~
-
- Implements the template parser.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-from jinja2 import nodes
-from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
-from jinja2.utils import next
-from jinja2.lexer import describe_token, describe_token_expr
-
-
-#: statements that callinto
-_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
- 'macro', 'include', 'from', 'import',
- 'set'])
-_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
-
-
-class Parser(object):
- """This is the central parsing class Jinja2 uses. It's passed to
- extensions and can be used to parse expressions or statements.
- """
-
- def __init__(self, environment, source, name=None, filename=None,
- state=None):
- self.environment = environment
- self.stream = environment._tokenize(source, name, filename, state)
- self.name = name
- self.filename = filename
- self.closed = False
- self.extensions = {}
- for extension in environment.iter_extensions():
- for tag in extension.tags:
- self.extensions[tag] = extension.parse
- self._last_identifier = 0
- self._tag_stack = []
- self._end_token_stack = []
-
- def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
- """Convenience method that raises `exc` with the message, passed
- line number or last line number as well as the current name and
- filename.
- """
- if lineno is None:
- lineno = self.stream.current.lineno
- raise exc(msg, lineno, self.name, self.filename)
-
- def _fail_ut_eof(self, name, end_token_stack, lineno):
- expected = []
- for exprs in end_token_stack:
- expected.extend(map(describe_token_expr, exprs))
- if end_token_stack:
- currently_looking = ' or '.join(
- "'%s'" % describe_token_expr(expr)
- for expr in end_token_stack[-1])
- else:
- currently_looking = None
-
- if name is None:
- message = ['Unexpected end of template.']
- else:
- message = ['Encountered unknown tag \'%s\'.' % name]
-
- if currently_looking:
- if name is not None and name in expected:
- message.append('You probably made a nesting mistake. Jinja '
- 'is expecting this tag, but currently looking '
- 'for %s.' % currently_looking)
- else:
- message.append('Jinja was looking for the following tags: '
- '%s.' % currently_looking)
-
- if self._tag_stack:
- message.append('The innermost block that needs to be '
- 'closed is \'%s\'.' % self._tag_stack[-1])
-
- self.fail(' '.join(message), lineno)
-
- def fail_unknown_tag(self, name, lineno=None):
- """Called if the parser encounters an unknown tag. Tries to fail
- with a human readable error message that could help to identify
- the problem.
- """
- return self._fail_ut_eof(name, self._end_token_stack, lineno)
-
- def fail_eof(self, end_tokens=None, lineno=None):
- """Like fail_unknown_tag but for end of template situations."""
- stack = list(self._end_token_stack)
- if end_tokens is not None:
- stack.append(end_tokens)
- return self._fail_ut_eof(None, stack, lineno)
-
- def is_tuple_end(self, extra_end_rules=None):
- """Are we at the end of a tuple?"""
- if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
- return True
- elif extra_end_rules is not None:
- return self.stream.current.test_any(extra_end_rules)
- return False
-
- def free_identifier(self, lineno=None):
- """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
- self._last_identifier += 1
- rv = object.__new__(nodes.InternalName)
- nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
- return rv
-
- def parse_statement(self):
- """Parse a single statement."""
- token = self.stream.current
- if token.type != 'name':
- self.fail('tag name expected', token.lineno)
- self._tag_stack.append(token.value)
- pop_tag = True
- try:
- if token.value in _statement_keywords:
- return getattr(self, 'parse_' + self.stream.current.value)()
- if token.value == 'call':
- return self.parse_call_block()
- if token.value == 'filter':
- return self.parse_filter_block()
- ext = self.extensions.get(token.value)
- if ext is not None:
- return ext(self)
-
- # did not work out, remove the token we pushed by accident
- # from the stack so that the unknown tag fail function can
- # produce a proper error message.
- self._tag_stack.pop()
- pop_tag = False
- self.fail_unknown_tag(token.value, token.lineno)
- finally:
- if pop_tag:
- self._tag_stack.pop()
-
- def parse_statements(self, end_tokens, drop_needle=False):
- """Parse multiple statements into a list until one of the end tokens
- is reached. This is used to parse the body of statements as it also
- parses template data if appropriate. The parser checks first if the
- current token is a colon and skips it if there is one. Then it checks
- for the block end and parses until if one of the `end_tokens` is
- reached. Per default the active token in the stream at the end of
- the call is the matched end token. If this is not wanted `drop_needle`
- can be set to `True` and the end token is removed.
- """
- # the first token may be a colon for python compatibility
- self.stream.skip_if('colon')
-
- # in the future it would be possible to add whole code sections
- # by adding some sort of end of statement token and parsing those here.
- self.stream.expect('block_end')
- result = self.subparse(end_tokens)
-
- # we reached the end of the template too early, the subparser
- # does not check for this, so we do that now
- if self.stream.current.type == 'eof':
- self.fail_eof(end_tokens)
-
- if drop_needle:
- next(self.stream)
- return result
-
- def parse_set(self):
- """Parse an assign statement."""
- lineno = next(self.stream).lineno
- target = self.parse_assign_target()
- self.stream.expect('assign')
- expr = self.parse_tuple()
- return nodes.Assign(target, expr, lineno=lineno)
-
- def parse_for(self):
- """Parse a for loop."""
- lineno = self.stream.expect('name:for').lineno
- target = self.parse_assign_target(extra_end_rules=('name:in',))
- self.stream.expect('name:in')
- iter = self.parse_tuple(with_condexpr=False,
- extra_end_rules=('name:recursive',))
- test = None
- if self.stream.skip_if('name:if'):
- test = self.parse_expression()
- recursive = self.stream.skip_if('name:recursive')
- body = self.parse_statements(('name:endfor', 'name:else'))
- if next(self.stream).value == 'endfor':
- else_ = []
- else:
- else_ = self.parse_statements(('name:endfor',), drop_needle=True)
- return nodes.For(target, iter, body, else_, test,
- recursive, lineno=lineno)
-
- def parse_if(self):
- """Parse an if construct."""
- node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
- while 1:
- node.test = self.parse_tuple(with_condexpr=False)
- node.body = self.parse_statements(('name:elif', 'name:else',
- 'name:endif'))
- token = next(self.stream)
- if token.test('name:elif'):
- new_node = nodes.If(lineno=self.stream.current.lineno)
- node.else_ = [new_node]
- node = new_node
- continue
- elif token.test('name:else'):
- node.else_ = self.parse_statements(('name:endif',),
- drop_needle=True)
- else:
- node.else_ = []
- break
- return result
-
- def parse_block(self):
- node = nodes.Block(lineno=next(self.stream).lineno)
- node.name = self.stream.expect('name').value
- node.scoped = self.stream.skip_if('name:scoped')
-
- # common problem people encounter when switching from django
- # to jinja. we do not support hyphens in block names, so let's
- # raise a nicer error message in that case.
- if self.stream.current.type == 'sub':
- self.fail('Block names in Jinja have to be valid Python '
- 'identifiers and may not contain hypens, use an '
- 'underscore instead.')
-
- node.body = self.parse_statements(('name:endblock',), drop_needle=True)
- self.stream.skip_if('name:' + node.name)
- return node
-
- def parse_extends(self):
- node = nodes.Extends(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- return node
-
- def parse_import_context(self, node, default):
- if self.stream.current.test_any('name:with', 'name:without') and \
- self.stream.look().test('name:context'):
- node.with_context = next(self.stream).value == 'with'
- self.stream.skip()
- else:
- node.with_context = default
- return node
-
- def parse_include(self):
- node = nodes.Include(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- if self.stream.current.test('name:ignore') and \
- self.stream.look().test('name:missing'):
- node.ignore_missing = True
- self.stream.skip(2)
- else:
- node.ignore_missing = False
- return self.parse_import_context(node, True)
-
- def parse_import(self):
- node = nodes.Import(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- self.stream.expect('name:as')
- node.target = self.parse_assign_target(name_only=True).name
- return self.parse_import_context(node, False)
-
- def parse_from(self):
- node = nodes.FromImport(lineno=next(self.stream).lineno)
- node.template = self.parse_expression()
- self.stream.expect('name:import')
- node.names = []
-
- def parse_context():
- if self.stream.current.value in ('with', 'without') and \
- self.stream.look().test('name:context'):
- node.with_context = next(self.stream).value == 'with'
- self.stream.skip()
- return True
- return False
-
- while 1:
- if node.names:
- self.stream.expect('comma')
- if self.stream.current.type == 'name':
- if parse_context():
- break
- target = self.parse_assign_target(name_only=True)
- if target.name.startswith('_'):
- self.fail('names starting with an underline can not '
- 'be imported', target.lineno,
- exc=TemplateAssertionError)
- if self.stream.skip_if('name:as'):
- alias = self.parse_assign_target(name_only=True)
- node.names.append((target.name, alias.name))
- else:
- node.names.append(target.name)
- if parse_context() or self.stream.current.type != 'comma':
- break
- else:
- break
- if not hasattr(node, 'with_context'):
- node.with_context = False
- self.stream.skip_if('comma')
- return node
-
- def parse_signature(self, node):
- node.args = args = []
- node.defaults = defaults = []
- self.stream.expect('lparen')
- while self.stream.current.type != 'rparen':
- if args:
- self.stream.expect('comma')
- arg = self.parse_assign_target(name_only=True)
- arg.set_ctx('param')
- if self.stream.skip_if('assign'):
- defaults.append(self.parse_expression())
- args.append(arg)
- self.stream.expect('rparen')
-
- def parse_call_block(self):
- node = nodes.CallBlock(lineno=next(self.stream).lineno)
- if self.stream.current.type == 'lparen':
- self.parse_signature(node)
- else:
- node.args = []
- node.defaults = []
-
- node.call = self.parse_expression()
- if not isinstance(node.call, nodes.Call):
- self.fail('expected call', node.lineno)
- node.body = self.parse_statements(('name:endcall',), drop_needle=True)
- return node
-
- def parse_filter_block(self):
- node = nodes.FilterBlock(lineno=next(self.stream).lineno)
- node.filter = self.parse_filter(None, start_inline=True)
- node.body = self.parse_statements(('name:endfilter',),
- drop_needle=True)
- return node
-
- def parse_macro(self):
- node = nodes.Macro(lineno=next(self.stream).lineno)
- node.name = self.parse_assign_target(name_only=True).name
- self.parse_signature(node)
- node.body = self.parse_statements(('name:endmacro',),
- drop_needle=True)
- return node
-
- def parse_print(self):
- node = nodes.Output(lineno=next(self.stream).lineno)
- node.nodes = []
- while self.stream.current.type != 'block_end':
- if node.nodes:
- self.stream.expect('comma')
- node.nodes.append(self.parse_expression())
- return node
-
- def parse_assign_target(self, with_tuple=True, name_only=False,
- extra_end_rules=None):
- """Parse an assignment target. As Jinja2 allows assignments to
- tuples, this function can parse all allowed assignment targets. Per
- default assignments to tuples are parsed, that can be disable however
- by setting `with_tuple` to `False`. If only assignments to names are
- wanted `name_only` can be set to `True`. The `extra_end_rules`
- parameter is forwarded to the tuple parsing function.
- """
- if name_only:
- token = self.stream.expect('name')
- target = nodes.Name(token.value, 'store', lineno=token.lineno)
- else:
- if with_tuple:
- target = self.parse_tuple(simplified=True,
- extra_end_rules=extra_end_rules)
- else:
- target = self.parse_primary()
- target.set_ctx('store')
- if not target.can_assign():
- self.fail('can\'t assign to %r' % target.__class__.
- __name__.lower(), target.lineno)
- return target
-
- def parse_expression(self, with_condexpr=True):
- """Parse an expression. Per default all expressions are parsed, if
- the optional `with_condexpr` parameter is set to `False` conditional
- expressions are not parsed.
- """
- if with_condexpr:
- return self.parse_condexpr()
- return self.parse_or()
-
- def parse_condexpr(self):
- lineno = self.stream.current.lineno
- expr1 = self.parse_or()
- while self.stream.skip_if('name:if'):
- expr2 = self.parse_or()
- if self.stream.skip_if('name:else'):
- expr3 = self.parse_condexpr()
- else:
- expr3 = None
- expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
- lineno = self.stream.current.lineno
- return expr1
-
- def parse_or(self):
- lineno = self.stream.current.lineno
- left = self.parse_and()
- while self.stream.skip_if('name:or'):
- right = self.parse_and()
- left = nodes.Or(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_and(self):
- lineno = self.stream.current.lineno
- left = self.parse_not()
- while self.stream.skip_if('name:and'):
- right = self.parse_not()
- left = nodes.And(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_not(self):
- if self.stream.current.test('name:not'):
- lineno = next(self.stream).lineno
- return nodes.Not(self.parse_not(), lineno=lineno)
- return self.parse_compare()
-
- def parse_compare(self):
- lineno = self.stream.current.lineno
- expr = self.parse_add()
- ops = []
- while 1:
- token_type = self.stream.current.type
- if token_type in _compare_operators:
- next(self.stream)
- ops.append(nodes.Operand(token_type, self.parse_add()))
- elif self.stream.skip_if('name:in'):
- ops.append(nodes.Operand('in', self.parse_add()))
- elif self.stream.current.test('name:not') and \
- self.stream.look().test('name:in'):
- self.stream.skip(2)
- ops.append(nodes.Operand('notin', self.parse_add()))
- else:
- break
- lineno = self.stream.current.lineno
- if not ops:
- return expr
- return nodes.Compare(expr, ops, lineno=lineno)
-
- def parse_add(self):
- lineno = self.stream.current.lineno
- left = self.parse_sub()
- while self.stream.current.type == 'add':
- next(self.stream)
- right = self.parse_sub()
- left = nodes.Add(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_sub(self):
- lineno = self.stream.current.lineno
- left = self.parse_concat()
- while self.stream.current.type == 'sub':
- next(self.stream)
- right = self.parse_concat()
- left = nodes.Sub(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_concat(self):
- lineno = self.stream.current.lineno
- args = [self.parse_mul()]
- while self.stream.current.type == 'tilde':
- next(self.stream)
- args.append(self.parse_mul())
- if len(args) == 1:
- return args[0]
- return nodes.Concat(args, lineno=lineno)
-
- def parse_mul(self):
- lineno = self.stream.current.lineno
- left = self.parse_div()
- while self.stream.current.type == 'mul':
- next(self.stream)
- right = self.parse_div()
- left = nodes.Mul(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_div(self):
- lineno = self.stream.current.lineno
- left = self.parse_floordiv()
- while self.stream.current.type == 'div':
- next(self.stream)
- right = self.parse_floordiv()
- left = nodes.Div(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_floordiv(self):
- lineno = self.stream.current.lineno
- left = self.parse_mod()
- while self.stream.current.type == 'floordiv':
- next(self.stream)
- right = self.parse_mod()
- left = nodes.FloorDiv(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_mod(self):
- lineno = self.stream.current.lineno
- left = self.parse_pow()
- while self.stream.current.type == 'mod':
- next(self.stream)
- right = self.parse_pow()
- left = nodes.Mod(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_pow(self):
- lineno = self.stream.current.lineno
- left = self.parse_unary()
- while self.stream.current.type == 'pow':
- next(self.stream)
- right = self.parse_unary()
- left = nodes.Pow(left, right, lineno=lineno)
- lineno = self.stream.current.lineno
- return left
-
- def parse_unary(self, with_filter=True):
- token_type = self.stream.current.type
- lineno = self.stream.current.lineno
- if token_type == 'sub':
- next(self.stream)
- node = nodes.Neg(self.parse_unary(False), lineno=lineno)
- elif token_type == 'add':
- next(self.stream)
- node = nodes.Pos(self.parse_unary(False), lineno=lineno)
- else:
- node = self.parse_primary()
- node = self.parse_postfix(node)
- if with_filter:
- node = self.parse_filter_expr(node)
- return node
-
- def parse_primary(self):
- token = self.stream.current
- if token.type == 'name':
- if token.value in ('true', 'false', 'True', 'False'):
- node = nodes.Const(token.value in ('true', 'True'),
- lineno=token.lineno)
- elif token.value in ('none', 'None'):
- node = nodes.Const(None, lineno=token.lineno)
- else:
- node = nodes.Name(token.value, 'load', lineno=token.lineno)
- next(self.stream)
- elif token.type == 'string':
- next(self.stream)
- buf = [token.value]
- lineno = token.lineno
- while self.stream.current.type == 'string':
- buf.append(self.stream.current.value)
- next(self.stream)
- node = nodes.Const(''.join(buf), lineno=lineno)
- elif token.type in ('integer', 'float'):
- next(self.stream)
- node = nodes.Const(token.value, lineno=token.lineno)
- elif token.type == 'lparen':
- next(self.stream)
- node = self.parse_tuple(explicit_parentheses=True)
- self.stream.expect('rparen')
- elif token.type == 'lbracket':
- node = self.parse_list()
- elif token.type == 'lbrace':
- node = self.parse_dict()
- else:
- self.fail("unexpected '%s'" % describe_token(token), token.lineno)
- return node
-
- def parse_tuple(self, simplified=False, with_condexpr=True,
- extra_end_rules=None, explicit_parentheses=False):
- """Works like `parse_expression` but if multiple expressions are
- delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
- This method could also return a regular expression instead of a tuple
- if no commas where found.
-
- The default parsing mode is a full tuple. If `simplified` is `True`
- only names and literals are parsed. The `no_condexpr` parameter is
- forwarded to :meth:`parse_expression`.
-
- Because tuples do not require delimiters and may end in a bogus comma
- an extra hint is needed that marks the end of a tuple. For example
- for loops support tuples between `for` and `in`. In that case the
- `extra_end_rules` is set to ``['name:in']``.
-
- `explicit_parentheses` is true if the parsing was triggered by an
- expression in parentheses. This is used to figure out if an empty
- tuple is a valid expression or not.
- """
- lineno = self.stream.current.lineno
- if simplified:
- parse = self.parse_primary
- elif with_condexpr:
- parse = self.parse_expression
- else:
- parse = lambda: self.parse_expression(with_condexpr=False)
- args = []
- is_tuple = False
- while 1:
- if args:
- self.stream.expect('comma')
- if self.is_tuple_end(extra_end_rules):
- break
- args.append(parse())
- if self.stream.current.type == 'comma':
- is_tuple = True
- else:
- break
- lineno = self.stream.current.lineno
-
- if not is_tuple:
- if args:
- return args[0]
-
- # if we don't have explicit parentheses, an empty tuple is
- # not a valid expression. This would mean nothing (literally
- # nothing) in the spot of an expression would be an empty
- # tuple.
- if not explicit_parentheses:
- self.fail('Expected an expression, got \'%s\'' %
- describe_token(self.stream.current))
-
- return nodes.Tuple(args, 'load', lineno=lineno)
-
- def parse_list(self):
- token = self.stream.expect('lbracket')
- items = []
- while self.stream.current.type != 'rbracket':
- if items:
- self.stream.expect('comma')
- if self.stream.current.type == 'rbracket':
- break
- items.append(self.parse_expression())
- self.stream.expect('rbracket')
- return nodes.List(items, lineno=token.lineno)
-
- def parse_dict(self):
- token = self.stream.expect('lbrace')
- items = []
- while self.stream.current.type != 'rbrace':
- if items:
- self.stream.expect('comma')
- if self.stream.current.type == 'rbrace':
- break
- key = self.parse_expression()
- self.stream.expect('colon')
- value = self.parse_expression()
- items.append(nodes.Pair(key, value, lineno=key.lineno))
- self.stream.expect('rbrace')
- return nodes.Dict(items, lineno=token.lineno)
-
- def parse_postfix(self, node):
- while 1:
- token_type = self.stream.current.type
- if token_type == 'dot' or token_type == 'lbracket':
- node = self.parse_subscript(node)
- # calls are valid both after postfix expressions (getattr
- # and getitem) as well as filters and tests
- elif token_type == 'lparen':
- node = self.parse_call(node)
- else:
- break
- return node
-
- def parse_filter_expr(self, node):
- while 1:
- token_type = self.stream.current.type
- if token_type == 'pipe':
- node = self.parse_filter(node)
- elif token_type == 'name' and self.stream.current.value == 'is':
- node = self.parse_test(node)
- # calls are valid both after postfix expressions (getattr
- # and getitem) as well as filters and tests
- elif token_type == 'lparen':
- node = self.parse_call(node)
- else:
- break
- return node
-
- def parse_subscript(self, node):
- token = next(self.stream)
- if token.type == 'dot':
- attr_token = self.stream.current
- next(self.stream)
- if attr_token.type == 'name':
- return nodes.Getattr(node, attr_token.value, 'load',
- lineno=token.lineno)
- elif attr_token.type != 'integer':
- self.fail('expected name or number', attr_token.lineno)
- arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
- if token.type == 'lbracket':
- priority_on_attribute = False
- args = []
- while self.stream.current.type != 'rbracket':
- if args:
- self.stream.expect('comma')
- args.append(self.parse_subscribed())
- self.stream.expect('rbracket')
- if len(args) == 1:
- arg = args[0]
- else:
- arg = nodes.Tuple(args, 'load', lineno=token.lineno)
- return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
- self.fail('expected subscript expression', self.lineno)
-
- def parse_subscribed(self):
- lineno = self.stream.current.lineno
-
- if self.stream.current.type == 'colon':
- next(self.stream)
- args = [None]
- else:
- node = self.parse_expression()
- if self.stream.current.type != 'colon':
- return node
- next(self.stream)
- args = [node]
-
- if self.stream.current.type == 'colon':
- args.append(None)
- elif self.stream.current.type not in ('rbracket', 'comma'):
- args.append(self.parse_expression())
- else:
- args.append(None)
-
- if self.stream.current.type == 'colon':
- next(self.stream)
- if self.stream.current.type not in ('rbracket', 'comma'):
- args.append(self.parse_expression())
- else:
- args.append(None)
- else:
- args.append(None)
-
- return nodes.Slice(lineno=lineno, *args)
-
- def parse_call(self, node):
- token = self.stream.expect('lparen')
- args = []
- kwargs = []
- dyn_args = dyn_kwargs = None
- require_comma = False
-
- def ensure(expr):
- if not expr:
- self.fail('invalid syntax for function call expression',
- token.lineno)
-
- while self.stream.current.type != 'rparen':
- if require_comma:
- self.stream.expect('comma')
- # support for trailing comma
- if self.stream.current.type == 'rparen':
- break
- if self.stream.current.type == 'mul':
- ensure(dyn_args is None and dyn_kwargs is None)
- next(self.stream)
- dyn_args = self.parse_expression()
- elif self.stream.current.type == 'pow':
- ensure(dyn_kwargs is None)
- next(self.stream)
- dyn_kwargs = self.parse_expression()
- else:
- ensure(dyn_args is None and dyn_kwargs is None)
- if self.stream.current.type == 'name' and \
- self.stream.look().type == 'assign':
- key = self.stream.current.value
- self.stream.skip(2)
- value = self.parse_expression()
- kwargs.append(nodes.Keyword(key, value,
- lineno=value.lineno))
- else:
- ensure(not kwargs)
- args.append(self.parse_expression())
-
- require_comma = True
- self.stream.expect('rparen')
-
- if node is None:
- return args, kwargs, dyn_args, dyn_kwargs
- return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
- lineno=token.lineno)
-
- def parse_filter(self, node, start_inline=False):
- while self.stream.current.type == 'pipe' or start_inline:
- if not start_inline:
- next(self.stream)
- token = self.stream.expect('name')
- name = token.value
- while self.stream.current.type == 'dot':
- next(self.stream)
- name += '.' + self.stream.expect('name').value
- if self.stream.current.type == 'lparen':
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
- else:
- args = []
- kwargs = []
- dyn_args = dyn_kwargs = None
- node = nodes.Filter(node, name, args, kwargs, dyn_args,
- dyn_kwargs, lineno=token.lineno)
- start_inline = False
- return node
-
- def parse_test(self, node):
- token = next(self.stream)
- if self.stream.current.test('name:not'):
- next(self.stream)
- negated = True
- else:
- negated = False
- name = self.stream.expect('name').value
- while self.stream.current.type == 'dot':
- next(self.stream)
- name += '.' + self.stream.expect('name').value
- dyn_args = dyn_kwargs = None
- kwargs = []
- if self.stream.current.type == 'lparen':
- args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
- elif self.stream.current.type in ('name', 'string', 'integer',
- 'float', 'lparen', 'lbracket',
- 'lbrace') and not \
- self.stream.current.test_any('name:else', 'name:or',
- 'name:and'):
- if self.stream.current.test('name:is'):
- self.fail('You cannot chain multiple tests with is')
- args = [self.parse_expression()]
- else:
- args = []
- node = nodes.Test(node, name, args, kwargs, dyn_args,
- dyn_kwargs, lineno=token.lineno)
- if negated:
- node = nodes.Not(node, lineno=token.lineno)
- return node
-
- def subparse(self, end_tokens=None):
- body = []
- data_buffer = []
- add_data = data_buffer.append
-
- if end_tokens is not None:
- self._end_token_stack.append(end_tokens)
-
- def flush_data():
- if data_buffer:
- lineno = data_buffer[0].lineno
- body.append(nodes.Output(data_buffer[:], lineno=lineno))
- del data_buffer[:]
-
- try:
- while self.stream:
- token = self.stream.current
- if token.type == 'data':
- if token.value:
- add_data(nodes.TemplateData(token.value,
- lineno=token.lineno))
- next(self.stream)
- elif token.type == 'variable_begin':
- next(self.stream)
- add_data(self.parse_tuple(with_condexpr=True))
- self.stream.expect('variable_end')
- elif token.type == 'block_begin':
- flush_data()
- next(self.stream)
- if end_tokens is not None and \
- self.stream.current.test_any(*end_tokens):
- return body
- rv = self.parse_statement()
- if isinstance(rv, list):
- body.extend(rv)
- else:
- body.append(rv)
- self.stream.expect('block_end')
- else:
- raise AssertionError('internal parsing error')
-
- flush_data()
- finally:
- if end_tokens is not None:
- self._end_token_stack.pop()
-
- return body
-
- def parse(self):
- """Parse the whole template into a `Template` node."""
- result = nodes.Template(self.subparse(), lineno=1)
- result.set_environment(self.environment)
- return result
diff --git a/module/lib/jinja2/runtime.py b/module/lib/jinja2/runtime.py
deleted file mode 100644
index 6fea3aa4f..000000000
--- a/module/lib/jinja2/runtime.py
+++ /dev/null
@@ -1,544 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.runtime
- ~~~~~~~~~~~~~~
-
- Runtime helpers.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-import sys
-from itertools import chain, imap
-from jinja2.nodes import EvalContext, _context_function_types
-from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
- concat, internalcode, next, object_type_repr
-from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
- TemplateNotFound
-
-
-# these variables are exported to the template runtime
-__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
- 'TemplateRuntimeError', 'missing', 'concat', 'escape',
- 'markup_join', 'unicode_join', 'to_string', 'identity',
- 'TemplateNotFound']
-
-#: the name of the function that is used to convert something into
-#: a string. 2to3 will adopt that automatically and the generated
-#: code can take advantage of it.
-to_string = unicode
-
-#: the identity function. Useful for certain things in the environment
-identity = lambda x: x
-
-
-def markup_join(seq):
- """Concatenation that escapes if necessary and converts to unicode."""
- buf = []
- iterator = imap(soft_unicode, seq)
- for arg in iterator:
- buf.append(arg)
- if hasattr(arg, '__html__'):
- return Markup(u'').join(chain(buf, iterator))
- return concat(buf)
-
-
-def unicode_join(seq):
- """Simple args to unicode conversion and concatenation."""
- return concat(imap(unicode, seq))
-
-
-def new_context(environment, template_name, blocks, vars=None,
- shared=None, globals=None, locals=None):
- """Internal helper to for context creation."""
- if vars is None:
- vars = {}
- if shared:
- parent = vars
- else:
- parent = dict(globals or (), **vars)
- if locals:
- # if the parent is shared a copy should be created because
- # we don't want to modify the dict passed
- if shared:
- parent = dict(parent)
- for key, value in locals.iteritems():
- if key[:2] == 'l_' and value is not missing:
- parent[key[2:]] = value
- return Context(environment, parent, template_name, blocks)
-
-
-class TemplateReference(object):
- """The `self` in templates."""
-
- def __init__(self, context):
- self.__context = context
-
- def __getitem__(self, name):
- blocks = self.__context.blocks[name]
- wrap = self.__context.eval_ctx.autoescape and \
- Markup or (lambda x: x)
- return BlockReference(name, self.__context, blocks, 0)
-
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self.__context.name
- )
-
-
-class Context(object):
- """The template context holds the variables of a template. It stores the
- values passed to the template and also the names the template exports.
- Creating instances is neither supported nor useful as it's created
- automatically at various stages of the template evaluation and should not
- be created by hand.
-
- The context is immutable. Modifications on :attr:`parent` **must not**
- happen and modifications on :attr:`vars` are allowed from generated
- template code only. Template filters and global functions marked as
- :func:`contextfunction`\s get the active context passed as first argument
- and are allowed to access the context read-only.
-
- The template context supports read only dict operations (`get`,
- `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
- `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
- method that doesn't fail with a `KeyError` but returns an
- :class:`Undefined` object for missing variables.
- """
- __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
- 'name', 'blocks', '__weakref__')
-
- def __init__(self, environment, parent, name, blocks):
- self.parent = parent
- self.vars = {}
- self.environment = environment
- self.eval_ctx = EvalContext(self.environment, name)
- self.exported_vars = set()
- self.name = name
-
- # create the initial mapping of blocks. Whenever template inheritance
- # takes place the runtime will update this mapping with the new blocks
- # from the template.
- self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
-
- def super(self, name, current):
- """Render a parent block."""
- try:
- blocks = self.blocks[name]
- index = blocks.index(current) + 1
- blocks[index]
- except LookupError:
- return self.environment.undefined('there is no parent block '
- 'called %r.' % name,
- name='super')
- return BlockReference(name, self, blocks, index)
-
- def get(self, key, default=None):
- """Returns an item from the template context, if it doesn't exist
- `default` is returned.
- """
- try:
- return self[key]
- except KeyError:
- return default
-
- def resolve(self, key):
- """Looks up a variable like `__getitem__` or `get` but returns an
- :class:`Undefined` object with the name of the name looked up.
- """
- if key in self.vars:
- return self.vars[key]
- if key in self.parent:
- return self.parent[key]
- return self.environment.undefined(name=key)
-
- def get_exported(self):
- """Get a new dict with the exported variables."""
- return dict((k, self.vars[k]) for k in self.exported_vars)
-
- def get_all(self):
- """Return a copy of the complete context as dict including the
- exported variables.
- """
- return dict(self.parent, **self.vars)
-
- @internalcode
- def call(__self, __obj, *args, **kwargs):
- """Call the callable with the arguments and keyword arguments
- provided but inject the active context or environment as first
- argument if the callable is a :func:`contextfunction` or
- :func:`environmentfunction`.
- """
- if __debug__:
- __traceback_hide__ = True
- if isinstance(__obj, _context_function_types):
- if getattr(__obj, 'contextfunction', 0):
- args = (__self,) + args
- elif getattr(__obj, 'evalcontextfunction', 0):
- args = (__self.eval_ctx,) + args
- elif getattr(__obj, 'environmentfunction', 0):
- args = (__self.environment,) + args
- try:
- return __obj(*args, **kwargs)
- except StopIteration:
- return __self.environment.undefined('value was undefined because '
- 'a callable raised a '
- 'StopIteration exception')
-
- def derived(self, locals=None):
- """Internal helper function to create a derived context."""
- context = new_context(self.environment, self.name, {},
- self.parent, True, None, locals)
- context.eval_ctx = self.eval_ctx
- context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
- return context
-
- def _all(meth):
- proxy = lambda self: getattr(self.get_all(), meth)()
- proxy.__doc__ = getattr(dict, meth).__doc__
- proxy.__name__ = meth
- return proxy
-
- keys = _all('keys')
- values = _all('values')
- items = _all('items')
-
- # not available on python 3
- if hasattr(dict, 'iterkeys'):
- iterkeys = _all('iterkeys')
- itervalues = _all('itervalues')
- iteritems = _all('iteritems')
- del _all
-
- def __contains__(self, name):
- return name in self.vars or name in self.parent
-
- def __getitem__(self, key):
- """Lookup a variable or raise `KeyError` if the variable is
- undefined.
- """
- item = self.resolve(key)
- if isinstance(item, Undefined):
- raise KeyError(key)
- return item
-
- def __repr__(self):
- return '<%s %s of %r>' % (
- self.__class__.__name__,
- repr(self.get_all()),
- self.name
- )
-
-
-# register the context as mapping if possible
-try:
- from collections import Mapping
- Mapping.register(Context)
-except ImportError:
- pass
-
-
-class BlockReference(object):
- """One block on a template reference."""
-
- def __init__(self, name, context, stack, depth):
- self.name = name
- self._context = context
- self._stack = stack
- self._depth = depth
-
- @property
- def super(self):
- """Super the block."""
- if self._depth + 1 >= len(self._stack):
- return self._context.environment. \
- undefined('there is no parent block called %r.' %
- self.name, name='super')
- return BlockReference(self.name, self._context, self._stack,
- self._depth + 1)
-
- @internalcode
- def __call__(self):
- rv = concat(self._stack[self._depth](self._context))
- if self._context.eval_ctx.autoescape:
- rv = Markup(rv)
- return rv
-
-
-class LoopContext(object):
- """A loop context for dynamic iteration."""
-
- def __init__(self, iterable, recurse=None):
- self._iterator = iter(iterable)
- self._recurse = recurse
- self.index0 = -1
-
- # try to get the length of the iterable early. This must be done
- # here because there are some broken iterators around where there
- # __len__ is the number of iterations left (i'm looking at your
- # listreverseiterator!).
- try:
- self._length = len(iterable)
- except (TypeError, AttributeError):
- self._length = None
-
- def cycle(self, *args):
- """Cycles among the arguments with the current loop index."""
- if not args:
- raise TypeError('no items for cycling given')
- return args[self.index0 % len(args)]
-
- first = property(lambda x: x.index0 == 0)
- last = property(lambda x: x.index0 + 1 == x.length)
- index = property(lambda x: x.index0 + 1)
- revindex = property(lambda x: x.length - x.index0)
- revindex0 = property(lambda x: x.length - x.index)
-
- def __len__(self):
- return self.length
-
- def __iter__(self):
- return LoopContextIterator(self)
-
- @internalcode
- def loop(self, iterable):
- if self._recurse is None:
- raise TypeError('Tried to call non recursive loop. Maybe you '
- "forgot the 'recursive' modifier.")
- return self._recurse(iterable, self._recurse)
-
- # a nifty trick to enhance the error message if someone tried to call
- # the the loop without or with too many arguments.
- __call__ = loop
- del loop
-
- @property
- def length(self):
- if self._length is None:
- # if was not possible to get the length of the iterator when
- # the loop context was created (ie: iterating over a generator)
- # we have to convert the iterable into a sequence and use the
- # length of that.
- iterable = tuple(self._iterator)
- self._iterator = iter(iterable)
- self._length = len(iterable) + self.index0 + 1
- return self._length
-
- def __repr__(self):
- return '<%s %r/%r>' % (
- self.__class__.__name__,
- self.index,
- self.length
- )
-
-
-class LoopContextIterator(object):
- """The iterator for a loop context."""
- __slots__ = ('context',)
-
- def __init__(self, context):
- self.context = context
-
- def __iter__(self):
- return self
-
- def next(self):
- ctx = self.context
- ctx.index0 += 1
- return next(ctx._iterator), ctx
-
-
-class Macro(object):
- """Wraps a macro function."""
-
- def __init__(self, environment, func, name, arguments, defaults,
- catch_kwargs, catch_varargs, caller):
- self._environment = environment
- self._func = func
- self._argument_count = len(arguments)
- self.name = name
- self.arguments = arguments
- self.defaults = defaults
- self.catch_kwargs = catch_kwargs
- self.catch_varargs = catch_varargs
- self.caller = caller
-
- @internalcode
- def __call__(self, *args, **kwargs):
- # try to consume the positional arguments
- arguments = list(args[:self._argument_count])
- off = len(arguments)
-
- # if the number of arguments consumed is not the number of
- # arguments expected we start filling in keyword arguments
- # and defaults.
- if off != self._argument_count:
- for idx, name in enumerate(self.arguments[len(arguments):]):
- try:
- value = kwargs.pop(name)
- except KeyError:
- try:
- value = self.defaults[idx - self._argument_count + off]
- except IndexError:
- value = self._environment.undefined(
- 'parameter %r was not provided' % name, name=name)
- arguments.append(value)
-
- # it's important that the order of these arguments does not change
- # if not also changed in the compiler's `function_scoping` method.
- # the order is caller, keyword arguments, positional arguments!
- if self.caller:
- caller = kwargs.pop('caller', None)
- if caller is None:
- caller = self._environment.undefined('No caller defined',
- name='caller')
- arguments.append(caller)
- if self.catch_kwargs:
- arguments.append(kwargs)
- elif kwargs:
- raise TypeError('macro %r takes no keyword argument %r' %
- (self.name, next(iter(kwargs))))
- if self.catch_varargs:
- arguments.append(args[self._argument_count:])
- elif len(args) > self._argument_count:
- raise TypeError('macro %r takes not more than %d argument(s)' %
- (self.name, len(self.arguments)))
- return self._func(*arguments)
-
- def __repr__(self):
- return '<%s %s>' % (
- self.__class__.__name__,
- self.name is None and 'anonymous' or repr(self.name)
- )
-
-
-class Undefined(object):
- """The default undefined type. This undefined type can be printed and
- iterated over, but every other access will raise an :exc:`UndefinedError`:
-
- >>> foo = Undefined(name='foo')
- >>> str(foo)
- ''
- >>> not foo
- True
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
- '_undefined_exception')
-
- def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
- self._undefined_hint = hint
- self._undefined_obj = obj
- self._undefined_name = name
- self._undefined_exception = exc
-
- @internalcode
- def _fail_with_undefined_error(self, *args, **kwargs):
- """Regular callback function for undefined objects that raises an
- `UndefinedError` on call.
- """
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- hint = '%r is undefined' % self._undefined_name
- elif not isinstance(self._undefined_name, basestring):
- hint = '%s has no element %r' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- else:
- hint = '%r has no attribute %r' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- else:
- hint = self._undefined_hint
- raise self._undefined_exception(hint)
-
- __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
- __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
- __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
- __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
- __int__ = __float__ = __complex__ = __pow__ = __rpow__ = \
- _fail_with_undefined_error
-
- def __str__(self):
- return unicode(self).encode('utf-8')
-
- # unicode goes after __str__ because we configured 2to3 to rename
- # __unicode__ to __str__. because the 2to3 tree is not designed to
- # remove nodes from it, we leave the above __str__ around and let
- # it override at runtime.
- def __unicode__(self):
- return u''
-
- def __len__(self):
- return 0
-
- def __iter__(self):
- if 0:
- yield None
-
- def __nonzero__(self):
- return False
-
- def __repr__(self):
- return 'Undefined'
-
-
-class DebugUndefined(Undefined):
- """An undefined that returns the debug info when printed.
-
- >>> foo = DebugUndefined(name='foo')
- >>> str(foo)
- '{{ foo }}'
- >>> not foo
- True
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ()
-
- def __unicode__(self):
- if self._undefined_hint is None:
- if self._undefined_obj is missing:
- return u'{{ %s }}' % self._undefined_name
- return '{{ no such element: %s[%r] }}' % (
- object_type_repr(self._undefined_obj),
- self._undefined_name
- )
- return u'{{ undefined value printed: %s }}' % self._undefined_hint
-
-
-class StrictUndefined(Undefined):
- """An undefined that barks on print and iteration as well as boolean
- tests and all kinds of comparisons. In other words: you can do nothing
- with it except checking if it's defined using the `defined` test.
-
- >>> foo = StrictUndefined(name='foo')
- >>> str(foo)
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- >>> not foo
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- >>> foo + 42
- Traceback (most recent call last):
- ...
- UndefinedError: 'foo' is undefined
- """
- __slots__ = ()
- __iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \
- __ne__ = __bool__ = Undefined._fail_with_undefined_error
-
-
-# remove remaining slots attributes, after the metaclass did the magic they
-# are unneeded and irritating as they contain wrong data for the subclasses.
-del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
diff --git a/module/lib/jinja2/sandbox.py b/module/lib/jinja2/sandbox.py
deleted file mode 100644
index 749719548..000000000
--- a/module/lib/jinja2/sandbox.py
+++ /dev/null
@@ -1,271 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.sandbox
- ~~~~~~~~~~~~~~
-
- Adds a sandbox layer to Jinja as it was the default behavior in the old
- Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
- default behavior is easier to use.
-
- The behavior can be changed by subclassing the environment.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD.
-"""
-import operator
-from jinja2.runtime import Undefined
-from jinja2.environment import Environment
-from jinja2.exceptions import SecurityError
-from jinja2.utils import FunctionType, MethodType, TracebackType, CodeType, \
- FrameType, GeneratorType
-
-
-#: maximum number of items a range may produce
-MAX_RANGE = 100000
-
-#: attributes of function objects that are considered unsafe.
-UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
- 'func_defaults', 'func_globals'])
-
-#: unsafe method attributes. function attributes are unsafe for methods too
-UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
-
-
-import warnings
-
-# make sure we don't warn in python 2.6 about stuff we don't care about
-warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
- module='jinja2.sandbox')
-
-from collections import deque
-
-_mutable_set_types = (set,)
-_mutable_mapping_types = (dict,)
-_mutable_sequence_types = (list,)
-
-
-# on python 2.x we can register the user collection types
-try:
- from UserDict import UserDict, DictMixin
- from UserList import UserList
- _mutable_mapping_types += (UserDict, DictMixin)
- _mutable_set_types += (UserList,)
-except ImportError:
- pass
-
-# if sets is still available, register the mutable set from there as well
-try:
- from sets import Set
- _mutable_set_types += (Set,)
-except ImportError:
- pass
-
-#: register Python 2.6 abstract base classes
-try:
- from collections import MutableSet, MutableMapping, MutableSequence
- _mutable_set_types += (MutableSet,)
- _mutable_mapping_types += (MutableMapping,)
- _mutable_sequence_types += (MutableSequence,)
-except ImportError:
- pass
-
-_mutable_spec = (
- (_mutable_set_types, frozenset([
- 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
- 'symmetric_difference_update', 'update'
- ])),
- (_mutable_mapping_types, frozenset([
- 'clear', 'pop', 'popitem', 'setdefault', 'update'
- ])),
- (_mutable_sequence_types, frozenset([
- 'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
- ])),
- (deque, frozenset([
- 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
- 'popleft', 'remove', 'rotate'
- ]))
-)
-
-
-def safe_range(*args):
- """A range that can't generate ranges with a length of more than
- MAX_RANGE items.
- """
- rng = xrange(*args)
- if len(rng) > MAX_RANGE:
- raise OverflowError('range too big, maximum size for range is %d' %
- MAX_RANGE)
- return rng
-
-
-def unsafe(f):
- """
- Mark a function or method as unsafe::
-
- @unsafe
- def delete(self):
- pass
- """
- f.unsafe_callable = True
- return f
-
-
-def is_internal_attribute(obj, attr):
- """Test if the attribute given is an internal python attribute. For
- example this function returns `True` for the `func_code` attribute of
- python objects. This is useful if the environment method
- :meth:`~SandboxedEnvironment.is_safe_attribute` is overriden.
-
- >>> from jinja2.sandbox import is_internal_attribute
- >>> is_internal_attribute(lambda: None, "func_code")
- True
- >>> is_internal_attribute((lambda x:x).func_code, 'co_code')
- True
- >>> is_internal_attribute(str, "upper")
- False
- """
- if isinstance(obj, FunctionType):
- if attr in UNSAFE_FUNCTION_ATTRIBUTES:
- return True
- elif isinstance(obj, MethodType):
- if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
- attr in UNSAFE_METHOD_ATTRIBUTES:
- return True
- elif isinstance(obj, type):
- if attr == 'mro':
- return True
- elif isinstance(obj, (CodeType, TracebackType, FrameType)):
- return True
- elif isinstance(obj, GeneratorType):
- if attr == 'gi_frame':
- return True
- return attr.startswith('__')
-
-
-def modifies_known_mutable(obj, attr):
- """This function checks if an attribute on a builtin mutable object
- (list, dict, set or deque) would modify it if called. It also supports
- the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
- with Python 2.6 onwards the abstract base classes `MutableSet`,
- `MutableMapping`, and `MutableSequence`.
-
- >>> modifies_known_mutable({}, "clear")
- True
- >>> modifies_known_mutable({}, "keys")
- False
- >>> modifies_known_mutable([], "append")
- True
- >>> modifies_known_mutable([], "index")
- False
-
- If called with an unsupported object (such as unicode) `False` is
- returned.
-
- >>> modifies_known_mutable("foo", "upper")
- False
- """
- for typespec, unsafe in _mutable_spec:
- if isinstance(obj, typespec):
- return attr in unsafe
- return False
-
-
-class SandboxedEnvironment(Environment):
- """The sandboxed environment. It works like the regular environment but
- tells the compiler to generate sandboxed code. Additionally subclasses of
- this environment may override the methods that tell the runtime what
- attributes or functions are safe to access.
-
- If the template tries to access insecure code a :exc:`SecurityError` is
- raised. However also other exceptions may occour during the rendering so
- the caller has to ensure that all exceptions are catched.
- """
- sandboxed = True
-
- def __init__(self, *args, **kwargs):
- Environment.__init__(self, *args, **kwargs)
- self.globals['range'] = safe_range
-
- def is_safe_attribute(self, obj, attr, value):
- """The sandboxed environment will call this method to check if the
- attribute of an object is safe to access. Per default all attributes
- starting with an underscore are considered private as well as the
- special attributes of internal python objects as returned by the
- :func:`is_internal_attribute` function.
- """
- return not (attr.startswith('_') or is_internal_attribute(obj, attr))
-
- def is_safe_callable(self, obj):
- """Check if an object is safely callable. Per default a function is
- considered safe unless the `unsafe_callable` attribute exists and is
- True. Override this method to alter the behavior, but this won't
- affect the `unsafe` decorator from this module.
- """
- return not (getattr(obj, 'unsafe_callable', False) or \
- getattr(obj, 'alters_data', False))
-
- def getitem(self, obj, argument):
- """Subscribe an object from sandboxed code."""
- try:
- return obj[argument]
- except (TypeError, LookupError):
- if isinstance(argument, basestring):
- try:
- attr = str(argument)
- except:
- pass
- else:
- try:
- value = getattr(obj, attr)
- except AttributeError:
- pass
- else:
- if self.is_safe_attribute(obj, argument, value):
- return value
- return self.unsafe_undefined(obj, argument)
- return self.undefined(obj=obj, name=argument)
-
- def getattr(self, obj, attribute):
- """Subscribe an object from sandboxed code and prefer the
- attribute. The attribute passed *must* be a bytestring.
- """
- try:
- value = getattr(obj, attribute)
- except AttributeError:
- try:
- return obj[attribute]
- except (TypeError, LookupError):
- pass
- else:
- if self.is_safe_attribute(obj, attribute, value):
- return value
- return self.unsafe_undefined(obj, attribute)
- return self.undefined(obj=obj, name=attribute)
-
- def unsafe_undefined(self, obj, attribute):
- """Return an undefined object for unsafe attributes."""
- return self.undefined('access to attribute %r of %r '
- 'object is unsafe.' % (
- attribute,
- obj.__class__.__name__
- ), name=attribute, obj=obj, exc=SecurityError)
-
- def call(__self, __context, __obj, *args, **kwargs):
- """Call an object from sandboxed code."""
- # the double prefixes are to avoid double keyword argument
- # errors when proxying the call.
- if not __self.is_safe_callable(__obj):
- raise SecurityError('%r is not safely callable' % (__obj,))
- return __context.call(__obj, *args, **kwargs)
-
-
-class ImmutableSandboxedEnvironment(SandboxedEnvironment):
- """Works exactly like the regular `SandboxedEnvironment` but does not
- permit modifications on the builtin mutable objects `list`, `set`, and
- `dict` by using the :func:`modifies_known_mutable` function.
- """
-
- def is_safe_attribute(self, obj, attr, value):
- if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
- return False
- return not modifies_known_mutable(obj, attr)
diff --git a/module/lib/jinja2/tests.py b/module/lib/jinja2/tests.py
deleted file mode 100644
index d257eca0a..000000000
--- a/module/lib/jinja2/tests.py
+++ /dev/null
@@ -1,146 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.tests
- ~~~~~~~~~~~~
-
- Jinja test functions. Used with the "is" operator.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-from jinja2.runtime import Undefined
-
-# nose, nothing here to test
-__test__ = False
-
-
-number_re = re.compile(r'^-?\d+(\.\d+)?$')
-regex_type = type(number_re)
-
-
-try:
- test_callable = callable
-except NameError:
- def test_callable(x):
- return hasattr(x, '__call__')
-
-
-def test_odd(value):
- """Return true if the variable is odd."""
- return value % 2 == 1
-
-
-def test_even(value):
- """Return true if the variable is even."""
- return value % 2 == 0
-
-
-def test_divisibleby(value, num):
- """Check if a variable is divisible by a number."""
- return value % num == 0
-
-
-def test_defined(value):
- """Return true if the variable is defined:
-
- .. sourcecode:: jinja
-
- {% if variable is defined %}
- value of variable: {{ variable }}
- {% else %}
- variable is not defined
- {% endif %}
-
- See the :func:`default` filter for a simple way to set undefined
- variables.
- """
- return not isinstance(value, Undefined)
-
-
-def test_undefined(value):
- """Like :func:`defined` but the other way round."""
- return isinstance(value, Undefined)
-
-
-def test_none(value):
- """Return true if the variable is none."""
- return value is None
-
-
-def test_lower(value):
- """Return true if the variable is lowercased."""
- return unicode(value).islower()
-
-
-def test_upper(value):
- """Return true if the variable is uppercased."""
- return unicode(value).isupper()
-
-
-def test_string(value):
- """Return true if the object is a string."""
- return isinstance(value, basestring)
-
-
-def test_number(value):
- """Return true if the variable is a number."""
- return isinstance(value, (int, long, float, complex))
-
-
-def test_sequence(value):
- """Return true if the variable is a sequence. Sequences are variables
- that are iterable.
- """
- try:
- len(value)
- value.__getitem__
- except:
- return False
- return True
-
-
-def test_sameas(value, other):
- """Check if an object points to the same memory address than another
- object:
-
- .. sourcecode:: jinja
-
- {% if foo.attribute is sameas false %}
- the foo attribute really is the `False` singleton
- {% endif %}
- """
- return value is other
-
-
-def test_iterable(value):
- """Check if it's possible to iterate over an object."""
- try:
- iter(value)
- except TypeError:
- return False
- return True
-
-
-def test_escaped(value):
- """Check if the value is escaped."""
- return hasattr(value, '__html__')
-
-
-TESTS = {
- 'odd': test_odd,
- 'even': test_even,
- 'divisibleby': test_divisibleby,
- 'defined': test_defined,
- 'undefined': test_undefined,
- 'none': test_none,
- 'lower': test_lower,
- 'upper': test_upper,
- 'string': test_string,
- 'number': test_number,
- 'sequence': test_sequence,
- 'iterable': test_iterable,
- 'callable': test_callable,
- 'sameas': test_sameas,
- 'escaped': test_escaped
-}
diff --git a/module/lib/jinja2/utils.py b/module/lib/jinja2/utils.py
deleted file mode 100644
index 7b77b8eb7..000000000
--- a/module/lib/jinja2/utils.py
+++ /dev/null
@@ -1,601 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- jinja2.utils
- ~~~~~~~~~~~~
-
- Utility functions.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import re
-import sys
-import errno
-try:
- from thread import allocate_lock
-except ImportError:
- from dummy_thread import allocate_lock
-from collections import deque
-from itertools import imap
-
-
-_word_split_re = re.compile(r'(\s+)')
-_punctuation_re = re.compile(
- '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
- '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
- '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
- )
-)
-_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
-_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
-_entity_re = re.compile(r'&([^;]+);')
-_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
-_digits = '0123456789'
-
-# special singleton representing missing values for the runtime
-missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
-
-# internal code
-internal_code = set()
-
-
-# concatenate a list of strings and convert them to unicode.
-# unfortunately there is a bug in python 2.4 and lower that causes
-# unicode.join trash the traceback.
-_concat = u''.join
-try:
- def _test_gen_bug():
- raise TypeError(_test_gen_bug)
- yield None
- _concat(_test_gen_bug())
-except TypeError, _error:
- if not _error.args or _error.args[0] is not _test_gen_bug:
- def concat(gen):
- try:
- return _concat(list(gen))
- except:
- # this hack is needed so that the current frame
- # does not show up in the traceback.
- exc_type, exc_value, tb = sys.exc_info()
- raise exc_type, exc_value, tb.tb_next
- else:
- concat = _concat
- del _test_gen_bug, _error
-
-
-# for python 2.x we create outselves a next() function that does the
-# basics without exception catching.
-try:
- next = next
-except NameError:
- def next(x):
- return x.next()
-
-
-# if this python version is unable to deal with unicode filenames
-# when passed to encode we let this function encode it properly.
-# This is used in a couple of places. As far as Jinja is concerned
-# filenames are unicode *or* bytestrings in 2.x and unicode only in
-# 3.x because compile cannot handle bytes
-if sys.version_info < (3, 0):
- def _encode_filename(filename):
- if isinstance(filename, unicode):
- return filename.encode('utf-8')
- return filename
-else:
- def _encode_filename(filename):
- assert filename is None or isinstance(filename, str), \
- 'filenames must be strings'
- return filename
-
-from keyword import iskeyword as is_python_keyword
-
-
-# common types. These do exist in the special types module too which however
-# does not exist in IronPython out of the box. Also that way we don't have
-# to deal with implementation specific stuff here
-class _C(object):
- def method(self): pass
-def _func():
- yield None
-FunctionType = type(_func)
-GeneratorType = type(_func())
-MethodType = type(_C.method)
-CodeType = type(_C.method.func_code)
-try:
- raise TypeError()
-except TypeError:
- _tb = sys.exc_info()[2]
- TracebackType = type(_tb)
- FrameType = type(_tb.tb_frame)
-del _C, _tb, _func
-
-
-def contextfunction(f):
- """This decorator can be used to mark a function or method context callable.
- A context callable is passed the active :class:`Context` as first argument when
- called from the template. This is useful if a function wants to get access
- to the context or functions provided on the context object. For example
- a function that returns a sorted list of template variables the current
- template exports could look like this::
-
- @contextfunction
- def get_exported_names(context):
- return sorted(context.exported_vars)
- """
- f.contextfunction = True
- return f
-
-
-def evalcontextfunction(f):
- """This decoraotr can be used to mark a function or method as an eval
- context callable. This is similar to the :func:`contextfunction`
- but instead of passing the context, an evaluation context object is
- passed. For more information about the eval context, see
- :ref:`eval-context`.
-
- .. versionadded:: 2.4
- """
- f.evalcontextfunction = True
- return f
-
-
-def environmentfunction(f):
- """This decorator can be used to mark a function or method as environment
- callable. This decorator works exactly like the :func:`contextfunction`
- decorator just that the first argument is the active :class:`Environment`
- and not context.
- """
- f.environmentfunction = True
- return f
-
-
-def internalcode(f):
- """Marks the function as internally used"""
- internal_code.add(f.func_code)
- return f
-
-
-def is_undefined(obj):
- """Check if the object passed is undefined. This does nothing more than
- performing an instance check against :class:`Undefined` but looks nicer.
- This can be used for custom filters or tests that want to react to
- undefined variables. For example a custom default filter can look like
- this::
-
- def default(var, default=''):
- if is_undefined(var):
- return default
- return var
- """
- from jinja2.runtime import Undefined
- return isinstance(obj, Undefined)
-
-
-def consume(iterable):
- """Consumes an iterable without doing anything with it."""
- for event in iterable:
- pass
-
-
-def clear_caches():
- """Jinja2 keeps internal caches for environments and lexers. These are
- used so that Jinja2 doesn't have to recreate environments and lexers all
- the time. Normally you don't have to care about that but if you are
- messuring memory consumption you may want to clean the caches.
- """
- from jinja2.environment import _spontaneous_environments
- from jinja2.lexer import _lexer_cache
- _spontaneous_environments.clear()
- _lexer_cache.clear()
-
-
-def import_string(import_name, silent=False):
- """Imports an object based on a string. This use useful if you want to
- use import paths as endpoints or something similar. An import path can
- be specified either in dotted notation (``xml.sax.saxutils.escape``)
- or with a colon as object delimiter (``xml.sax.saxutils:escape``).
-
- If the `silent` is True the return value will be `None` if the import
- fails.
-
- :return: imported object
- """
- try:
- if ':' in import_name:
- module, obj = import_name.split(':', 1)
- elif '.' in import_name:
- items = import_name.split('.')
- module = '.'.join(items[:-1])
- obj = items[-1]
- else:
- return __import__(import_name)
- return getattr(__import__(module, None, None, [obj]), obj)
- except (ImportError, AttributeError):
- if not silent:
- raise
-
-
-def open_if_exists(filename, mode='rb'):
- """Returns a file descriptor for the filename if that file exists,
- otherwise `None`.
- """
- try:
- return open(filename, mode)
- except IOError, e:
- if e.errno not in (errno.ENOENT, errno.EISDIR):
- raise
-
-
-def object_type_repr(obj):
- """Returns the name of the object's type. For some recognized
- singletons the name of the object is returned instead. (For
- example for `None` and `Ellipsis`).
- """
- if obj is None:
- return 'None'
- elif obj is Ellipsis:
- return 'Ellipsis'
- # __builtin__ in 2.x, builtins in 3.x
- if obj.__class__.__module__ in ('__builtin__', 'builtins'):
- name = obj.__class__.__name__
- else:
- name = obj.__class__.__module__ + '.' + obj.__class__.__name__
- return '%s object' % name
-
-
-def pformat(obj, verbose=False):
- """Prettyprint an object. Either use the `pretty` library or the
- builtin `pprint`.
- """
- try:
- from pretty import pretty
- return pretty(obj, verbose=verbose)
- except ImportError:
- from pprint import pformat
- return pformat(obj)
-
-
-def urlize(text, trim_url_limit=None, nofollow=False):
- """Converts any URLs in text into clickable links. Works on http://,
- https:// and www. links. Links can have trailing punctuation (periods,
- commas, close-parens) and leading punctuation (opening parens) and
- it'll still do the right thing.
-
- If trim_url_limit is not None, the URLs in link text will be limited
- to trim_url_limit characters.
-
- If nofollow is True, the URLs in link text will get a rel="nofollow"
- attribute.
- """
- trim_url = lambda x, limit=trim_url_limit: limit is not None \
- and (x[:limit] + (len(x) >=limit and '...'
- or '')) or x
- words = _word_split_re.split(unicode(escape(text)))
- nofollow_attr = nofollow and ' rel="nofollow"' or ''
- for i, word in enumerate(words):
- match = _punctuation_re.match(word)
- if match:
- lead, middle, trail = match.groups()
- if middle.startswith('www.') or (
- '@' not in middle and
- not middle.startswith('http://') and
- len(middle) > 0 and
- middle[0] in _letters + _digits and (
- middle.endswith('.org') or
- middle.endswith('.net') or
- middle.endswith('.com')
- )):
- middle = '<a href="http://%s"%s>%s</a>' % (middle,
- nofollow_attr, trim_url(middle))
- if middle.startswith('http://') or \
- middle.startswith('https://'):
- middle = '<a href="%s"%s>%s</a>' % (middle,
- nofollow_attr, trim_url(middle))
- if '@' in middle and not middle.startswith('www.') and \
- not ':' in middle and _simple_email_re.match(middle):
- middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
- if lead + middle + trail != word:
- words[i] = lead + middle + trail
- return u''.join(words)
-
-
-def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
- """Generate some lorem impsum for the template."""
- from jinja2.constants import LOREM_IPSUM_WORDS
- from random import choice, randrange
- words = LOREM_IPSUM_WORDS.split()
- result = []
-
- for _ in xrange(n):
- next_capitalized = True
- last_comma = last_fullstop = 0
- word = None
- last = None
- p = []
-
- # each paragraph contains out of 20 to 100 words.
- for idx, _ in enumerate(xrange(randrange(min, max))):
- while True:
- word = choice(words)
- if word != last:
- last = word
- break
- if next_capitalized:
- word = word.capitalize()
- next_capitalized = False
- # add commas
- if idx - randrange(3, 8) > last_comma:
- last_comma = idx
- last_fullstop += 2
- word += ','
- # add end of sentences
- if idx - randrange(10, 20) > last_fullstop:
- last_comma = last_fullstop = idx
- word += '.'
- next_capitalized = True
- p.append(word)
-
- # ensure that the paragraph ends with a dot.
- p = u' '.join(p)
- if p.endswith(','):
- p = p[:-1] + '.'
- elif not p.endswith('.'):
- p += '.'
- result.append(p)
-
- if not html:
- return u'\n\n'.join(result)
- return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
-
-
-class LRUCache(object):
- """A simple LRU Cache implementation."""
-
- # this is fast for small capacities (something below 1000) but doesn't
- # scale. But as long as it's only used as storage for templates this
- # won't do any harm.
-
- def __init__(self, capacity):
- self.capacity = capacity
- self._mapping = {}
- self._queue = deque()
- self._postinit()
-
- def _postinit(self):
- # alias all queue methods for faster lookup
- self._popleft = self._queue.popleft
- self._pop = self._queue.pop
- if hasattr(self._queue, 'remove'):
- self._remove = self._queue.remove
- self._wlock = allocate_lock()
- self._append = self._queue.append
-
- def _remove(self, obj):
- """Python 2.4 compatibility."""
- for idx, item in enumerate(self._queue):
- if item == obj:
- del self._queue[idx]
- break
-
- def __getstate__(self):
- return {
- 'capacity': self.capacity,
- '_mapping': self._mapping,
- '_queue': self._queue
- }
-
- def __setstate__(self, d):
- self.__dict__.update(d)
- self._postinit()
-
- def __getnewargs__(self):
- return (self.capacity,)
-
- def copy(self):
- """Return an shallow copy of the instance."""
- rv = self.__class__(self.capacity)
- rv._mapping.update(self._mapping)
- rv._queue = deque(self._queue)
- return rv
-
- def get(self, key, default=None):
- """Return an item from the cache dict or `default`"""
- try:
- return self[key]
- except KeyError:
- return default
-
- def setdefault(self, key, default=None):
- """Set `default` if the key is not in the cache otherwise
- leave unchanged. Return the value of this key.
- """
- try:
- return self[key]
- except KeyError:
- self[key] = default
- return default
-
- def clear(self):
- """Clear the cache."""
- self._wlock.acquire()
- try:
- self._mapping.clear()
- self._queue.clear()
- finally:
- self._wlock.release()
-
- def __contains__(self, key):
- """Check if a key exists in this cache."""
- return key in self._mapping
-
- def __len__(self):
- """Return the current size of the cache."""
- return len(self._mapping)
-
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self._mapping
- )
-
- def __getitem__(self, key):
- """Get an item from the cache. Moves the item up so that it has the
- highest priority then.
-
- Raise an `KeyError` if it does not exist.
- """
- rv = self._mapping[key]
- if self._queue[-1] != key:
- try:
- self._remove(key)
- except ValueError:
- # if something removed the key from the container
- # when we read, ignore the ValueError that we would
- # get otherwise.
- pass
- self._append(key)
- return rv
-
- def __setitem__(self, key, value):
- """Sets the value for an item. Moves the item up so that it
- has the highest priority then.
- """
- self._wlock.acquire()
- try:
- if key in self._mapping:
- try:
- self._remove(key)
- except ValueError:
- # __getitem__ is not locked, it might happen
- pass
- elif len(self._mapping) == self.capacity:
- del self._mapping[self._popleft()]
- self._append(key)
- self._mapping[key] = value
- finally:
- self._wlock.release()
-
- def __delitem__(self, key):
- """Remove an item from the cache dict.
- Raise an `KeyError` if it does not exist.
- """
- self._wlock.acquire()
- try:
- del self._mapping[key]
- try:
- self._remove(key)
- except ValueError:
- # __getitem__ is not locked, it might happen
- pass
- finally:
- self._wlock.release()
-
- def items(self):
- """Return a list of items."""
- result = [(key, self._mapping[key]) for key in list(self._queue)]
- result.reverse()
- return result
-
- def iteritems(self):
- """Iterate over all items."""
- return iter(self.items())
-
- def values(self):
- """Return a list of all values."""
- return [x[1] for x in self.items()]
-
- def itervalue(self):
- """Iterate over all values."""
- return iter(self.values())
-
- def keys(self):
- """Return a list of all keys ordered by most recent usage."""
- return list(self)
-
- def iterkeys(self):
- """Iterate over all keys in the cache dict, ordered by
- the most recent usage.
- """
- return reversed(tuple(self._queue))
-
- __iter__ = iterkeys
-
- def __reversed__(self):
- """Iterate over the values in the cache dict, oldest items
- coming first.
- """
- return iter(tuple(self._queue))
-
- __copy__ = copy
-
-
-# register the LRU cache as mutable mapping if possible
-try:
- from collections import MutableMapping
- MutableMapping.register(LRUCache)
-except ImportError:
- pass
-
-
-class Cycler(object):
- """A cycle helper for templates."""
-
- def __init__(self, *items):
- if not items:
- raise RuntimeError('at least one item has to be provided')
- self.items = items
- self.reset()
-
- def reset(self):
- """Resets the cycle."""
- self.pos = 0
-
- @property
- def current(self):
- """Returns the current item."""
- return self.items[self.pos]
-
- def next(self):
- """Goes one item ahead and returns it."""
- rv = self.current
- self.pos = (self.pos + 1) % len(self.items)
- return rv
-
-
-class Joiner(object):
- """A joining helper for templates."""
-
- def __init__(self, sep=u', '):
- self.sep = sep
- self.used = False
-
- def __call__(self):
- if not self.used:
- self.used = True
- return u''
- return self.sep
-
-
-# try markupsafe first, if that fails go with Jinja2's bundled version
-# of markupsafe. Markupsafe was previously Jinja2's implementation of
-# the Markup object but was moved into a separate package in a patchleve
-# release
-try:
- from markupsafe import Markup, escape, soft_unicode
-except ImportError:
- from jinja2._markupsafe import Markup, escape, soft_unicode
-
-
-# partials
-try:
- from functools import partial
-except ImportError:
- class partial(object):
- def __init__(self, _func, *args, **kwargs):
- self._func = _func
- self._args = args
- self._kwargs = kwargs
- def __call__(self, *args, **kwargs):
- kwargs.update(self._kwargs)
- return self._func(*(self._args + args), **kwargs)
diff --git a/module/lib/simplejson/__init__.py b/module/lib/simplejson/__init__.py
deleted file mode 100644
index ef5c0db48..000000000
--- a/module/lib/simplejson/__init__.py
+++ /dev/null
@@ -1,466 +0,0 @@
-r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
-JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
-interchange format.
-
-:mod:`simplejson` exposes an API familiar to users of the standard library
-:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
-version of the :mod:`json` library contained in Python 2.6, but maintains
-compatibility with Python 2.4 and Python 2.5 and (currently) has
-significant performance advantages, even without using the optional C
-extension for speedups.
-
-Encoding basic Python object hierarchies::
-
- >>> import simplejson as json
- >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
- '["foo", {"bar": ["baz", null, 1.0, 2]}]'
- >>> print json.dumps("\"foo\bar")
- "\"foo\bar"
- >>> print json.dumps(u'\u1234')
- "\u1234"
- >>> print json.dumps('\\')
- "\\"
- >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
- {"a": 0, "b": 0, "c": 0}
- >>> from StringIO import StringIO
- >>> io = StringIO()
- >>> json.dump(['streaming API'], io)
- >>> io.getvalue()
- '["streaming API"]'
-
-Compact encoding::
-
- >>> import simplejson as json
- >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
- '[1,2,3,{"4":5,"6":7}]'
-
-Pretty printing::
-
- >>> import simplejson as json
- >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' ')
- >>> print '\n'.join([l.rstrip() for l in s.splitlines()])
- {
- "4": 5,
- "6": 7
- }
-
-Decoding JSON::
-
- >>> import simplejson as json
- >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
- >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
- True
- >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
- True
- >>> from StringIO import StringIO
- >>> io = StringIO('["streaming API"]')
- >>> json.load(io)[0] == 'streaming API'
- True
-
-Specializing JSON object decoding::
-
- >>> import simplejson as json
- >>> def as_complex(dct):
- ... if '__complex__' in dct:
- ... return complex(dct['real'], dct['imag'])
- ... return dct
- ...
- >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
- ... object_hook=as_complex)
- (1+2j)
- >>> from decimal import Decimal
- >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
- True
-
-Specializing JSON object encoding::
-
- >>> import simplejson as json
- >>> def encode_complex(obj):
- ... if isinstance(obj, complex):
- ... return [obj.real, obj.imag]
- ... raise TypeError(repr(o) + " is not JSON serializable")
- ...
- >>> json.dumps(2 + 1j, default=encode_complex)
- '[2.0, 1.0]'
- >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
- '[2.0, 1.0]'
- >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
- '[2.0, 1.0]'
-
-
-Using simplejson.tool from the shell to validate and pretty-print::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-"""
-__version__ = '2.2.1'
-__all__ = [
- 'dump', 'dumps', 'load', 'loads',
- 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
- 'OrderedDict',
-]
-
-__author__ = 'Bob Ippolito <bob@redivi.com>'
-
-from decimal import Decimal
-
-from decoder import JSONDecoder, JSONDecodeError
-from encoder import JSONEncoder
-def _import_OrderedDict():
- import collections
- try:
- return collections.OrderedDict
- except AttributeError:
- import ordered_dict
- return ordered_dict.OrderedDict
-OrderedDict = _import_OrderedDict()
-
-def _import_c_make_encoder():
- try:
- from simplejson._speedups import make_encoder
- return make_encoder
- except ImportError:
- return None
-
-_default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
- use_decimal=True,
- namedtuple_as_object=True,
- tuple_as_array=True,
-)
-
-def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, use_decimal=True,
- namedtuple_as_object=True, tuple_as_array=True,
- **kw):
- """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
- ``.write()``-supporting file-like object).
-
- If ``skipkeys`` is true then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the some chunks written to ``fp``
- may be ``unicode`` instances, subject to normal Python ``str`` to
- ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
- understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
- to cause an error.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
- in strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If *indent* is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- If *use_decimal* is true (default: ``True``) then decimal.Decimal
- will be natively serialized to JSON with full precision.
-
- If *namedtuple_as_object* is true (default: ``True``),
- :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
- as JSON objects.
-
- If *tuple_as_array* is true (default: ``True``),
- :class:`tuple` (and subclasses) will be encoded as JSON arrays.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and use_decimal
- and namedtuple_as_object and tuple_as_array and not kw):
- iterable = _default_encoder.iterencode(obj)
- else:
- if cls is None:
- cls = JSONEncoder
- iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding,
- default=default, use_decimal=use_decimal,
- namedtuple_as_object=namedtuple_as_object,
- tuple_as_array=tuple_as_array,
- **kw).iterencode(obj)
- # could accelerate with writelines in some versions of Python, at
- # a debuggability cost
- for chunk in iterable:
- fp.write(chunk)
-
-
-def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
- allow_nan=True, cls=None, indent=None, separators=None,
- encoding='utf-8', default=None, use_decimal=True,
- namedtuple_as_object=True,
- tuple_as_array=True,
- **kw):
- """Serialize ``obj`` to a JSON formatted ``str``.
-
- If ``skipkeys`` is false then ``dict`` keys that are not basic types
- (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
- will be skipped instead of raising a ``TypeError``.
-
- If ``ensure_ascii`` is false, then the return value will be a
- ``unicode`` instance subject to normal Python ``str`` to ``unicode``
- coercion rules instead of being escaped to an ASCII ``str``.
-
- If ``check_circular`` is false, then the circular reference check
- for container types will be skipped and a circular reference will
- result in an ``OverflowError`` (or worse).
-
- If ``allow_nan`` is false, then it will be a ``ValueError`` to
- serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
- strict compliance of the JSON specification, instead of using the
- JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
-
- If ``indent`` is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If ``separators`` is an ``(item_separator, dict_separator)`` tuple
- then it will be used instead of the default ``(', ', ': ')`` separators.
- ``(',', ':')`` is the most compact JSON representation.
-
- ``encoding`` is the character encoding for str instances, default is UTF-8.
-
- ``default(obj)`` is a function that should return a serializable version
- of obj or raise TypeError. The default simply raises TypeError.
-
- If *use_decimal* is true (default: ``True``) then decimal.Decimal
- will be natively serialized to JSON with full precision.
-
- If *namedtuple_as_object* is true (default: ``True``),
- :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
- as JSON objects.
-
- If *tuple_as_array* is true (default: ``True``),
- :class:`tuple` (and subclasses) will be encoded as JSON arrays.
-
- To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
- ``.default()`` method to serialize additional types), specify it with
- the ``cls`` kwarg.
-
- """
- # cached encoder
- if (not skipkeys and ensure_ascii and
- check_circular and allow_nan and
- cls is None and indent is None and separators is None and
- encoding == 'utf-8' and default is None and use_decimal
- and namedtuple_as_object and tuple_as_array and not kw):
- return _default_encoder.encode(obj)
- if cls is None:
- cls = JSONEncoder
- return cls(
- skipkeys=skipkeys, ensure_ascii=ensure_ascii,
- check_circular=check_circular, allow_nan=allow_nan, indent=indent,
- separators=separators, encoding=encoding, default=default,
- use_decimal=use_decimal,
- namedtuple_as_object=namedtuple_as_object,
- tuple_as_array=tuple_as_array,
- **kw).encode(obj)
-
-
-_default_decoder = JSONDecoder(encoding=None, object_hook=None,
- object_pairs_hook=None)
-
-
-def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, object_pairs_hook=None,
- use_decimal=False, namedtuple_as_object=True, tuple_as_array=True,
- **kw):
- """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
- a JSON document) to a Python object.
-
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- If *use_decimal* is true (default: ``False``) then it implies
- parse_float=decimal.Decimal for parity with ``dump``.
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- return loads(fp.read(),
- encoding=encoding, cls=cls, object_hook=object_hook,
- parse_float=parse_float, parse_int=parse_int,
- parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
- use_decimal=use_decimal, **kw)
-
-
-def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, object_pairs_hook=None,
- use_decimal=False, **kw):
- """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
- document) to a Python object.
-
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- If *use_decimal* is true (default: ``False``) then it implies
- parse_float=decimal.Decimal for parity with ``dump``.
-
- To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
- kwarg.
-
- """
- if (cls is None and encoding is None and object_hook is None and
- parse_int is None and parse_float is None and
- parse_constant is None and object_pairs_hook is None
- and not use_decimal and not kw):
- return _default_decoder.decode(s)
- if cls is None:
- cls = JSONDecoder
- if object_hook is not None:
- kw['object_hook'] = object_hook
- if object_pairs_hook is not None:
- kw['object_pairs_hook'] = object_pairs_hook
- if parse_float is not None:
- kw['parse_float'] = parse_float
- if parse_int is not None:
- kw['parse_int'] = parse_int
- if parse_constant is not None:
- kw['parse_constant'] = parse_constant
- if use_decimal:
- if parse_float is not None:
- raise TypeError("use_decimal=True implies parse_float=Decimal")
- kw['parse_float'] = Decimal
- return cls(encoding=encoding, **kw).decode(s)
-
-
-def _toggle_speedups(enabled):
- import simplejson.decoder as dec
- import simplejson.encoder as enc
- import simplejson.scanner as scan
- c_make_encoder = _import_c_make_encoder()
- if enabled:
- dec.scanstring = dec.c_scanstring or dec.py_scanstring
- enc.c_make_encoder = c_make_encoder
- enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
- enc.py_encode_basestring_ascii)
- scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
- else:
- dec.scanstring = dec.py_scanstring
- enc.c_make_encoder = None
- enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
- scan.make_scanner = scan.py_make_scanner
- dec.make_scanner = scan.make_scanner
- global _default_decoder
- _default_decoder = JSONDecoder(
- encoding=None,
- object_hook=None,
- object_pairs_hook=None,
- )
- global _default_encoder
- _default_encoder = JSONEncoder(
- skipkeys=False,
- ensure_ascii=True,
- check_circular=True,
- allow_nan=True,
- indent=None,
- separators=None,
- encoding='utf-8',
- default=None,
- )
diff --git a/module/lib/simplejson/decoder.py b/module/lib/simplejson/decoder.py
deleted file mode 100644
index e5496d6e7..000000000
--- a/module/lib/simplejson/decoder.py
+++ /dev/null
@@ -1,421 +0,0 @@
-"""Implementation of JSONDecoder
-"""
-import re
-import sys
-import struct
-
-from simplejson.scanner import make_scanner
-def _import_c_scanstring():
- try:
- from simplejson._speedups import scanstring
- return scanstring
- except ImportError:
- return None
-c_scanstring = _import_c_scanstring()
-
-__all__ = ['JSONDecoder']
-
-FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
-
-def _floatconstants():
- _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
- # The struct module in Python 2.4 would get frexp() out of range here
- # when an endian is specified in the format string. Fixed in Python 2.5+
- if sys.byteorder != 'big':
- _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
- nan, inf = struct.unpack('dd', _BYTES)
- return nan, inf, -inf
-
-NaN, PosInf, NegInf = _floatconstants()
-
-
-class JSONDecodeError(ValueError):
- """Subclass of ValueError with the following additional properties:
-
- msg: The unformatted error message
- doc: The JSON document being parsed
- pos: The start index of doc where parsing failed
- end: The end index of doc where parsing failed (may be None)
- lineno: The line corresponding to pos
- colno: The column corresponding to pos
- endlineno: The line corresponding to end (may be None)
- endcolno: The column corresponding to end (may be None)
-
- """
- def __init__(self, msg, doc, pos, end=None):
- ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
- self.msg = msg
- self.doc = doc
- self.pos = pos
- self.end = end
- self.lineno, self.colno = linecol(doc, pos)
- if end is not None:
- self.endlineno, self.endcolno = linecol(doc, end)
- else:
- self.endlineno, self.endcolno = None, None
-
-
-def linecol(doc, pos):
- lineno = doc.count('\n', 0, pos) + 1
- if lineno == 1:
- colno = pos
- else:
- colno = pos - doc.rindex('\n', 0, pos)
- return lineno, colno
-
-
-def errmsg(msg, doc, pos, end=None):
- # Note that this function is called from _speedups
- lineno, colno = linecol(doc, pos)
- if end is None:
- #fmt = '{0}: line {1} column {2} (char {3})'
- #return fmt.format(msg, lineno, colno, pos)
- fmt = '%s: line %d column %d (char %d)'
- return fmt % (msg, lineno, colno, pos)
- endlineno, endcolno = linecol(doc, end)
- #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
- #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
- fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
- return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
-
-
-_CONSTANTS = {
- '-Infinity': NegInf,
- 'Infinity': PosInf,
- 'NaN': NaN,
-}
-
-STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
-BACKSLASH = {
- '"': u'"', '\\': u'\\', '/': u'/',
- 'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
-}
-
-DEFAULT_ENCODING = "utf-8"
-
-def py_scanstring(s, end, encoding=None, strict=True,
- _b=BACKSLASH, _m=STRINGCHUNK.match):
- """Scan the string s for a JSON string. End is the index of the
- character in s after the quote that started the JSON string.
- Unescapes all valid JSON string escape sequences and raises ValueError
- on attempt to decode an invalid string. If strict is False then literal
- control characters are allowed in the string.
-
- Returns a tuple of the decoded string and the index of the character in s
- after the end quote."""
- if encoding is None:
- encoding = DEFAULT_ENCODING
- chunks = []
- _append = chunks.append
- begin = end - 1
- while 1:
- chunk = _m(s, end)
- if chunk is None:
- raise JSONDecodeError(
- "Unterminated string starting at", s, begin)
- end = chunk.end()
- content, terminator = chunk.groups()
- # Content is contains zero or more unescaped string characters
- if content:
- if not isinstance(content, unicode):
- content = unicode(content, encoding)
- _append(content)
- # Terminator is the end of string, a literal control character,
- # or a backslash denoting that an escape sequence follows
- if terminator == '"':
- break
- elif terminator != '\\':
- if strict:
- msg = "Invalid control character %r at" % (terminator,)
- #msg = "Invalid control character {0!r} at".format(terminator)
- raise JSONDecodeError(msg, s, end)
- else:
- _append(terminator)
- continue
- try:
- esc = s[end]
- except IndexError:
- raise JSONDecodeError(
- "Unterminated string starting at", s, begin)
- # If not a unicode escape sequence, must be in the lookup table
- if esc != 'u':
- try:
- char = _b[esc]
- except KeyError:
- msg = "Invalid \\escape: " + repr(esc)
- raise JSONDecodeError(msg, s, end)
- end += 1
- else:
- # Unicode escape sequence
- esc = s[end + 1:end + 5]
- next_end = end + 5
- if len(esc) != 4:
- msg = "Invalid \\uXXXX escape"
- raise JSONDecodeError(msg, s, end)
- uni = int(esc, 16)
- # Check for surrogate pair on UCS-4 systems
- if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
- msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
- if not s[end + 5:end + 7] == '\\u':
- raise JSONDecodeError(msg, s, end)
- esc2 = s[end + 7:end + 11]
- if len(esc2) != 4:
- raise JSONDecodeError(msg, s, end)
- uni2 = int(esc2, 16)
- uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
- next_end += 6
- char = unichr(uni)
- end = next_end
- # Append the unescaped character
- _append(char)
- return u''.join(chunks), end
-
-
-# Use speedup if available
-scanstring = c_scanstring or py_scanstring
-
-WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
-WHITESPACE_STR = ' \t\n\r'
-
-def JSONObject((s, end), encoding, strict, scan_once, object_hook,
- object_pairs_hook, memo=None,
- _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- # Backwards compatibility
- if memo is None:
- memo = {}
- memo_get = memo.setdefault
- pairs = []
- # Use a slice to prevent IndexError from being raised, the following
- # check will raise a more specific ValueError if the string is empty
- nextchar = s[end:end + 1]
- # Normally we expect nextchar == '"'
- if nextchar != '"':
- if nextchar in _ws:
- end = _w(s, end).end()
- nextchar = s[end:end + 1]
- # Trivial empty object
- if nextchar == '}':
- if object_pairs_hook is not None:
- result = object_pairs_hook(pairs)
- return result, end + 1
- pairs = {}
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end + 1
- elif nextchar != '"':
- raise JSONDecodeError("Expecting property name", s, end)
- end += 1
- while True:
- key, end = scanstring(s, end, encoding, strict)
- key = memo_get(key, key)
-
- # To skip some function call overhead we optimize the fast paths where
- # the JSON key separator is ": " or just ":".
- if s[end:end + 1] != ':':
- end = _w(s, end).end()
- if s[end:end + 1] != ':':
- raise JSONDecodeError("Expecting : delimiter", s, end)
-
- end += 1
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise JSONDecodeError("Expecting object", s, end)
- pairs.append((key, value))
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
- end += 1
-
- if nextchar == '}':
- break
- elif nextchar != ',':
- raise JSONDecodeError("Expecting , delimiter", s, end - 1)
-
- try:
- nextchar = s[end]
- if nextchar in _ws:
- end += 1
- nextchar = s[end]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end]
- except IndexError:
- nextchar = ''
-
- end += 1
- if nextchar != '"':
- raise JSONDecodeError("Expecting property name", s, end - 1)
-
- if object_pairs_hook is not None:
- result = object_pairs_hook(pairs)
- return result, end
- pairs = dict(pairs)
- if object_hook is not None:
- pairs = object_hook(pairs)
- return pairs, end
-
-def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
- values = []
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- # Look-ahead for trivial empty array
- if nextchar == ']':
- return values, end + 1
- _append = values.append
- while True:
- try:
- value, end = scan_once(s, end)
- except StopIteration:
- raise JSONDecodeError("Expecting object", s, end)
- _append(value)
- nextchar = s[end:end + 1]
- if nextchar in _ws:
- end = _w(s, end + 1).end()
- nextchar = s[end:end + 1]
- end += 1
- if nextchar == ']':
- break
- elif nextchar != ',':
- raise JSONDecodeError("Expecting , delimiter", s, end)
-
- try:
- if s[end] in _ws:
- end += 1
- if s[end] in _ws:
- end = _w(s, end + 1).end()
- except IndexError:
- pass
-
- return values, end
-
-class JSONDecoder(object):
- """Simple JSON <http://json.org> decoder
-
- Performs the following translations in decoding by default:
-
- +---------------+-------------------+
- | JSON | Python |
- +===============+===================+
- | object | dict |
- +---------------+-------------------+
- | array | list |
- +---------------+-------------------+
- | string | unicode |
- +---------------+-------------------+
- | number (int) | int, long |
- +---------------+-------------------+
- | number (real) | float |
- +---------------+-------------------+
- | true | True |
- +---------------+-------------------+
- | false | False |
- +---------------+-------------------+
- | null | None |
- +---------------+-------------------+
-
- It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
- their corresponding ``float`` values, which is outside the JSON spec.
-
- """
-
- def __init__(self, encoding=None, object_hook=None, parse_float=None,
- parse_int=None, parse_constant=None, strict=True,
- object_pairs_hook=None):
- """
- *encoding* determines the encoding used to interpret any
- :class:`str` objects decoded by this instance (``'utf-8'`` by
- default). It has no effect when decoding :class:`unicode` objects.
-
- Note that currently only encodings that are a superset of ASCII work,
- strings of other encodings should be passed in as :class:`unicode`.
-
- *object_hook*, if specified, will be called with the result of every
- JSON object decoded and its return value will be used in place of the
- given :class:`dict`. This can be used to provide custom
- deserializations (e.g. to support JSON-RPC class hinting).
-
- *object_pairs_hook* is an optional function that will be called with
- the result of any object literal decode with an ordered list of pairs.
- The return value of *object_pairs_hook* will be used instead of the
- :class:`dict`. This feature can be used to implement custom decoders
- that rely on the order that the key and value pairs are decoded (for
- example, :func:`collections.OrderedDict` will remember the order of
- insertion). If *object_hook* is also defined, the *object_pairs_hook*
- takes priority.
-
- *parse_float*, if specified, will be called with the string of every
- JSON float to be decoded. By default, this is equivalent to
- ``float(num_str)``. This can be used to use another datatype or parser
- for JSON floats (e.g. :class:`decimal.Decimal`).
-
- *parse_int*, if specified, will be called with the string of every
- JSON int to be decoded. By default, this is equivalent to
- ``int(num_str)``. This can be used to use another datatype or parser
- for JSON integers (e.g. :class:`float`).
-
- *parse_constant*, if specified, will be called with one of the
- following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
- can be used to raise an exception if invalid JSON numbers are
- encountered.
-
- *strict* controls the parser's behavior when it encounters an
- invalid control character in a string. The default setting of
- ``True`` means that unescaped control characters are parse errors, if
- ``False`` then control characters will be allowed in strings.
-
- """
- self.encoding = encoding
- self.object_hook = object_hook
- self.object_pairs_hook = object_pairs_hook
- self.parse_float = parse_float or float
- self.parse_int = parse_int or int
- self.parse_constant = parse_constant or _CONSTANTS.__getitem__
- self.strict = strict
- self.parse_object = JSONObject
- self.parse_array = JSONArray
- self.parse_string = scanstring
- self.memo = {}
- self.scan_once = make_scanner(self)
-
- def decode(self, s, _w=WHITESPACE.match):
- """Return the Python representation of ``s`` (a ``str`` or ``unicode``
- instance containing a JSON document)
-
- """
- obj, end = self.raw_decode(s, idx=_w(s, 0).end())
- end = _w(s, end).end()
- if end != len(s):
- raise JSONDecodeError("Extra data", s, end, len(s))
- return obj
-
- def raw_decode(self, s, idx=0):
- """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
- beginning with a JSON document) and return a 2-tuple of the Python
- representation and the index in ``s`` where the document ended.
-
- This can be used to decode a JSON document from a string that may
- have extraneous data at the end.
-
- """
- try:
- obj, end = self.scan_once(s, idx)
- except StopIteration:
- raise JSONDecodeError("No JSON object could be decoded", s, idx)
- return obj, end
diff --git a/module/lib/simplejson/encoder.py b/module/lib/simplejson/encoder.py
deleted file mode 100644
index 5ec7440f1..000000000
--- a/module/lib/simplejson/encoder.py
+++ /dev/null
@@ -1,534 +0,0 @@
-"""Implementation of JSONEncoder
-"""
-import re
-from decimal import Decimal
-
-def _import_speedups():
- try:
- from simplejson import _speedups
- return _speedups.encode_basestring_ascii, _speedups.make_encoder
- except ImportError:
- return None, None
-c_encode_basestring_ascii, c_make_encoder = _import_speedups()
-
-from simplejson.decoder import PosInf
-
-ESCAPE = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\u2028\u2029]')
-ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
-HAS_UTF8 = re.compile(r'[\x80-\xff]')
-ESCAPE_DCT = {
- '\\': '\\\\',
- '"': '\\"',
- '\b': '\\b',
- '\f': '\\f',
- '\n': '\\n',
- '\r': '\\r',
- '\t': '\\t',
- u'\u2028': '\\u2028',
- u'\u2029': '\\u2029',
-}
-for i in range(0x20):
- #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
- ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
-
-FLOAT_REPR = repr
-
-def encode_basestring(s):
- """Return a JSON representation of a Python string
-
- """
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- return ESCAPE_DCT[match.group(0)]
- return u'"' + ESCAPE.sub(replace, s) + u'"'
-
-
-def py_encode_basestring_ascii(s):
- """Return an ASCII-only JSON representation of a Python string
-
- """
- if isinstance(s, str) and HAS_UTF8.search(s) is not None:
- s = s.decode('utf-8')
- def replace(match):
- s = match.group(0)
- try:
- return ESCAPE_DCT[s]
- except KeyError:
- n = ord(s)
- if n < 0x10000:
- #return '\\u{0:04x}'.format(n)
- return '\\u%04x' % (n,)
- else:
- # surrogate pair
- n -= 0x10000
- s1 = 0xd800 | ((n >> 10) & 0x3ff)
- s2 = 0xdc00 | (n & 0x3ff)
- #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
- return '\\u%04x\\u%04x' % (s1, s2)
- return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
-
-
-encode_basestring_ascii = (
- c_encode_basestring_ascii or py_encode_basestring_ascii)
-
-class JSONEncoder(object):
- """Extensible JSON <http://json.org> encoder for Python data structures.
-
- Supports the following objects and types by default:
-
- +-------------------+---------------+
- | Python | JSON |
- +===================+===============+
- | dict, namedtuple | object |
- +-------------------+---------------+
- | list, tuple | array |
- +-------------------+---------------+
- | str, unicode | string |
- +-------------------+---------------+
- | int, long, float | number |
- +-------------------+---------------+
- | True | true |
- +-------------------+---------------+
- | False | false |
- +-------------------+---------------+
- | None | null |
- +-------------------+---------------+
-
- To extend this to recognize other objects, subclass and implement a
- ``.default()`` method with another method that returns a serializable
- object for ``o`` if possible, otherwise it should call the superclass
- implementation (to raise ``TypeError``).
-
- """
- item_separator = ', '
- key_separator = ': '
- def __init__(self, skipkeys=False, ensure_ascii=True,
- check_circular=True, allow_nan=True, sort_keys=False,
- indent=None, separators=None, encoding='utf-8', default=None,
- use_decimal=True, namedtuple_as_object=True,
- tuple_as_array=True):
- """Constructor for JSONEncoder, with sensible defaults.
-
- If skipkeys is false, then it is a TypeError to attempt
- encoding of keys that are not str, int, long, float or None. If
- skipkeys is True, such items are simply skipped.
-
- If ensure_ascii is true, the output is guaranteed to be str
- objects with all incoming unicode characters escaped. If
- ensure_ascii is false, the output will be unicode object.
-
- If check_circular is true, then lists, dicts, and custom encoded
- objects will be checked for circular references during encoding to
- prevent an infinite recursion (which would cause an OverflowError).
- Otherwise, no such check takes place.
-
- If allow_nan is true, then NaN, Infinity, and -Infinity will be
- encoded as such. This behavior is not JSON specification compliant,
- but is consistent with most JavaScript based encoders and decoders.
- Otherwise, it will be a ValueError to encode such floats.
-
- If sort_keys is true, then the output of dictionaries will be
- sorted by key; this is useful for regression tests to ensure
- that JSON serializations can be compared on a day-to-day basis.
-
- If indent is a string, then JSON array elements and object members
- will be pretty-printed with a newline followed by that string repeated
- for each level of nesting. ``None`` (the default) selects the most compact
- representation without any newlines. For backwards compatibility with
- versions of simplejson earlier than 2.1.0, an integer is also accepted
- and is converted to a string with that many spaces.
-
- If specified, separators should be a (item_separator, key_separator)
- tuple. The default is (', ', ': '). To get the most compact JSON
- representation you should specify (',', ':') to eliminate whitespace.
-
- If specified, default is a function that gets called for objects
- that can't otherwise be serialized. It should return a JSON encodable
- version of the object or raise a ``TypeError``.
-
- If encoding is not None, then all input strings will be
- transformed into unicode using that encoding prior to JSON-encoding.
- The default is UTF-8.
-
- If use_decimal is true (not the default), ``decimal.Decimal`` will
- be supported directly by the encoder. For the inverse, decode JSON
- with ``parse_float=decimal.Decimal``.
-
- If namedtuple_as_object is true (the default), tuple subclasses with
- ``_asdict()`` methods will be encoded as JSON objects.
-
- If tuple_as_array is true (the default), tuple (and subclasses) will
- be encoded as JSON arrays.
- """
-
- self.skipkeys = skipkeys
- self.ensure_ascii = ensure_ascii
- self.check_circular = check_circular
- self.allow_nan = allow_nan
- self.sort_keys = sort_keys
- self.use_decimal = use_decimal
- self.namedtuple_as_object = namedtuple_as_object
- self.tuple_as_array = tuple_as_array
- if isinstance(indent, (int, long)):
- indent = ' ' * indent
- self.indent = indent
- if separators is not None:
- self.item_separator, self.key_separator = separators
- elif indent is not None:
- self.item_separator = ','
- if default is not None:
- self.default = default
- self.encoding = encoding
-
- def default(self, o):
- """Implement this method in a subclass such that it returns
- a serializable object for ``o``, or calls the base implementation
- (to raise a ``TypeError``).
-
- For example, to support arbitrary iterators, you could
- implement default like this::
-
- def default(self, o):
- try:
- iterable = iter(o)
- except TypeError:
- pass
- else:
- return list(iterable)
- return JSONEncoder.default(self, o)
-
- """
- raise TypeError(repr(o) + " is not JSON serializable")
-
- def encode(self, o):
- """Return a JSON string representation of a Python data structure.
-
- >>> from simplejson import JSONEncoder
- >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
- '{"foo": ["bar", "baz"]}'
-
- """
- # This is for extremely simple cases and benchmarks.
- if isinstance(o, basestring):
- if isinstance(o, str):
- _encoding = self.encoding
- if (_encoding is not None
- and not (_encoding == 'utf-8')):
- o = o.decode(_encoding)
- if self.ensure_ascii:
- return encode_basestring_ascii(o)
- else:
- return encode_basestring(o)
- # This doesn't pass the iterator directly to ''.join() because the
- # exceptions aren't as detailed. The list call should be roughly
- # equivalent to the PySequence_Fast that ''.join() would do.
- chunks = self.iterencode(o, _one_shot=True)
- if not isinstance(chunks, (list, tuple)):
- chunks = list(chunks)
- if self.ensure_ascii:
- return ''.join(chunks)
- else:
- return u''.join(chunks)
-
- def iterencode(self, o, _one_shot=False):
- """Encode the given object and yield each string
- representation as available.
-
- For example::
-
- for chunk in JSONEncoder().iterencode(bigobject):
- mysocket.write(chunk)
-
- """
- if self.check_circular:
- markers = {}
- else:
- markers = None
- if self.ensure_ascii:
- _encoder = encode_basestring_ascii
- else:
- _encoder = encode_basestring
- if self.encoding != 'utf-8':
- def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
- if isinstance(o, str):
- o = o.decode(_encoding)
- return _orig_encoder(o)
-
- def floatstr(o, allow_nan=self.allow_nan,
- _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
- # Check for specials. Note that this type of test is processor
- # and/or platform-specific, so do tests which don't depend on
- # the internals.
-
- if o != o:
- text = 'NaN'
- elif o == _inf:
- text = 'Infinity'
- elif o == _neginf:
- text = '-Infinity'
- else:
- return _repr(o)
-
- if not allow_nan:
- raise ValueError(
- "Out of range float values are not JSON compliant: " +
- repr(o))
-
- return text
-
-
- key_memo = {}
- if (_one_shot and c_make_encoder is not None
- and self.indent is None):
- _iterencode = c_make_encoder(
- markers, self.default, _encoder, self.indent,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
- self.namedtuple_as_object, self.tuple_as_array)
- else:
- _iterencode = _make_iterencode(
- markers, self.default, _encoder, self.indent, floatstr,
- self.key_separator, self.item_separator, self.sort_keys,
- self.skipkeys, _one_shot, self.use_decimal,
- self.namedtuple_as_object, self.tuple_as_array)
- try:
- return _iterencode(o, 0)
- finally:
- key_memo.clear()
-
-
-class JSONEncoderForHTML(JSONEncoder):
- """An encoder that produces JSON safe to embed in HTML.
-
- To embed JSON content in, say, a script tag on a web page, the
- characters &, < and > should be escaped. They cannot be escaped
- with the usual entities (e.g. &amp;) because they are not expanded
- within <script> tags.
- """
-
- def encode(self, o):
- # Override JSONEncoder.encode because it has hacks for
- # performance that make things more complicated.
- chunks = self.iterencode(o, True)
- if self.ensure_ascii:
- return ''.join(chunks)
- else:
- return u''.join(chunks)
-
- def iterencode(self, o, _one_shot=False):
- chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
- for chunk in chunks:
- chunk = chunk.replace('&', '\\u0026')
- chunk = chunk.replace('<', '\\u003c')
- chunk = chunk.replace('>', '\\u003e')
- yield chunk
-
-
-def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
- _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
- _use_decimal, _namedtuple_as_object, _tuple_as_array,
- ## HACK: hand-optimized bytecode; turn globals into locals
- False=False,
- True=True,
- ValueError=ValueError,
- basestring=basestring,
- Decimal=Decimal,
- dict=dict,
- float=float,
- id=id,
- int=int,
- isinstance=isinstance,
- list=list,
- long=long,
- str=str,
- tuple=tuple,
- ):
-
- def _iterencode_list(lst, _current_indent_level):
- if not lst:
- yield '[]'
- return
- if markers is not None:
- markerid = id(lst)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = lst
- buf = '['
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (_indent * _current_indent_level)
- separator = _item_separator + newline_indent
- buf += newline_indent
- else:
- newline_indent = None
- separator = _item_separator
- first = True
- for value in lst:
- if first:
- first = False
- else:
- buf = separator
- if isinstance(value, basestring):
- yield buf + _encoder(value)
- elif value is None:
- yield buf + 'null'
- elif value is True:
- yield buf + 'true'
- elif value is False:
- yield buf + 'false'
- elif isinstance(value, (int, long)):
- yield buf + str(value)
- elif isinstance(value, float):
- yield buf + _floatstr(value)
- elif _use_decimal and isinstance(value, Decimal):
- yield buf + str(value)
- else:
- yield buf
- if isinstance(value, list):
- chunks = _iterencode_list(value, _current_indent_level)
- elif (_namedtuple_as_object and isinstance(value, tuple) and
- hasattr(value, '_asdict')):
- chunks = _iterencode_dict(value._asdict(),
- _current_indent_level)
- elif _tuple_as_array and isinstance(value, tuple):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (_indent * _current_indent_level)
- yield ']'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode_dict(dct, _current_indent_level):
- if not dct:
- yield '{}'
- return
- if markers is not None:
- markerid = id(dct)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = dct
- yield '{'
- if _indent is not None:
- _current_indent_level += 1
- newline_indent = '\n' + (_indent * _current_indent_level)
- item_separator = _item_separator + newline_indent
- yield newline_indent
- else:
- newline_indent = None
- item_separator = _item_separator
- first = True
- if _sort_keys:
- items = dct.items()
- items.sort(key=lambda kv: kv[0])
- else:
- items = dct.iteritems()
- for key, value in items:
- if isinstance(key, basestring):
- pass
- # JavaScript is weakly typed for these, so it makes sense to
- # also allow them. Many encoders seem to do something like this.
- elif isinstance(key, float):
- key = _floatstr(key)
- elif key is True:
- key = 'true'
- elif key is False:
- key = 'false'
- elif key is None:
- key = 'null'
- elif isinstance(key, (int, long)):
- key = str(key)
- elif _skipkeys:
- continue
- else:
- raise TypeError("key " + repr(key) + " is not a string")
- if first:
- first = False
- else:
- yield item_separator
- yield _encoder(key)
- yield _key_separator
- if isinstance(value, basestring):
- yield _encoder(value)
- elif value is None:
- yield 'null'
- elif value is True:
- yield 'true'
- elif value is False:
- yield 'false'
- elif isinstance(value, (int, long)):
- yield str(value)
- elif isinstance(value, float):
- yield _floatstr(value)
- elif _use_decimal and isinstance(value, Decimal):
- yield str(value)
- else:
- if isinstance(value, list):
- chunks = _iterencode_list(value, _current_indent_level)
- elif (_namedtuple_as_object and isinstance(value, tuple) and
- hasattr(value, '_asdict')):
- chunks = _iterencode_dict(value._asdict(),
- _current_indent_level)
- elif _tuple_as_array and isinstance(value, tuple):
- chunks = _iterencode_list(value, _current_indent_level)
- elif isinstance(value, dict):
- chunks = _iterencode_dict(value, _current_indent_level)
- else:
- chunks = _iterencode(value, _current_indent_level)
- for chunk in chunks:
- yield chunk
- if newline_indent is not None:
- _current_indent_level -= 1
- yield '\n' + (_indent * _current_indent_level)
- yield '}'
- if markers is not None:
- del markers[markerid]
-
- def _iterencode(o, _current_indent_level):
- if isinstance(o, basestring):
- yield _encoder(o)
- elif o is None:
- yield 'null'
- elif o is True:
- yield 'true'
- elif o is False:
- yield 'false'
- elif isinstance(o, (int, long)):
- yield str(o)
- elif isinstance(o, float):
- yield _floatstr(o)
- elif isinstance(o, list):
- for chunk in _iterencode_list(o, _current_indent_level):
- yield chunk
- elif (_namedtuple_as_object and isinstance(o, tuple) and
- hasattr(o, '_asdict')):
- for chunk in _iterencode_dict(o._asdict(), _current_indent_level):
- yield chunk
- elif (_tuple_as_array and isinstance(o, tuple)):
- for chunk in _iterencode_list(o, _current_indent_level):
- yield chunk
- elif isinstance(o, dict):
- for chunk in _iterencode_dict(o, _current_indent_level):
- yield chunk
- elif _use_decimal and isinstance(o, Decimal):
- yield str(o)
- else:
- if markers is not None:
- markerid = id(o)
- if markerid in markers:
- raise ValueError("Circular reference detected")
- markers[markerid] = o
- o = _default(o)
- for chunk in _iterencode(o, _current_indent_level):
- yield chunk
- if markers is not None:
- del markers[markerid]
-
- return _iterencode
diff --git a/module/lib/simplejson/scanner.py b/module/lib/simplejson/scanner.py
deleted file mode 100644
index 54593a371..000000000
--- a/module/lib/simplejson/scanner.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""JSON token scanner
-"""
-import re
-def _import_c_make_scanner():
- try:
- from simplejson._speedups import make_scanner
- return make_scanner
- except ImportError:
- return None
-c_make_scanner = _import_c_make_scanner()
-
-__all__ = ['make_scanner']
-
-NUMBER_RE = re.compile(
- r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
- (re.VERBOSE | re.MULTILINE | re.DOTALL))
-
-def py_make_scanner(context):
- parse_object = context.parse_object
- parse_array = context.parse_array
- parse_string = context.parse_string
- match_number = NUMBER_RE.match
- encoding = context.encoding
- strict = context.strict
- parse_float = context.parse_float
- parse_int = context.parse_int
- parse_constant = context.parse_constant
- object_hook = context.object_hook
- object_pairs_hook = context.object_pairs_hook
- memo = context.memo
-
- def _scan_once(string, idx):
- try:
- nextchar = string[idx]
- except IndexError:
- raise StopIteration
-
- if nextchar == '"':
- return parse_string(string, idx + 1, encoding, strict)
- elif nextchar == '{':
- return parse_object((string, idx + 1), encoding, strict,
- _scan_once, object_hook, object_pairs_hook, memo)
- elif nextchar == '[':
- return parse_array((string, idx + 1), _scan_once)
- elif nextchar == 'n' and string[idx:idx + 4] == 'null':
- return None, idx + 4
- elif nextchar == 't' and string[idx:idx + 4] == 'true':
- return True, idx + 4
- elif nextchar == 'f' and string[idx:idx + 5] == 'false':
- return False, idx + 5
-
- m = match_number(string, idx)
- if m is not None:
- integer, frac, exp = m.groups()
- if frac or exp:
- res = parse_float(integer + (frac or '') + (exp or ''))
- else:
- res = parse_int(integer)
- return res, m.end()
- elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
- return parse_constant('NaN'), idx + 3
- elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
- return parse_constant('Infinity'), idx + 8
- elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
- return parse_constant('-Infinity'), idx + 9
- else:
- raise StopIteration
-
- def scan_once(string, idx):
- try:
- return _scan_once(string, idx)
- finally:
- memo.clear()
-
- return scan_once
-
-make_scanner = c_make_scanner or py_make_scanner
diff --git a/module/lib/simplejson/tool.py b/module/lib/simplejson/tool.py
deleted file mode 100644
index 73370db55..000000000
--- a/module/lib/simplejson/tool.py
+++ /dev/null
@@ -1,39 +0,0 @@
-r"""Command-line tool to validate and pretty-print JSON
-
-Usage::
-
- $ echo '{"json":"obj"}' | python -m simplejson.tool
- {
- "json": "obj"
- }
- $ echo '{ 1.2:3.4}' | python -m simplejson.tool
- Expecting property name: line 1 column 2 (char 2)
-
-"""
-import sys
-import simplejson as json
-
-def main():
- if len(sys.argv) == 1:
- infile = sys.stdin
- outfile = sys.stdout
- elif len(sys.argv) == 2:
- infile = open(sys.argv[1], 'rb')
- outfile = sys.stdout
- elif len(sys.argv) == 3:
- infile = open(sys.argv[1], 'rb')
- outfile = open(sys.argv[2], 'wb')
- else:
- raise SystemExit(sys.argv[0] + " [infile [outfile]]")
- try:
- obj = json.load(infile,
- object_pairs_hook=json.OrderedDict,
- use_decimal=True)
- except ValueError, e:
- raise SystemExit(e)
- json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
- outfile.write('\n')
-
-
-if __name__ == '__main__':
- main()
diff --git a/module/lib/thrift/TSCons.py b/module/lib/thrift/TSCons.py
deleted file mode 100644
index 24046256c..000000000
--- a/module/lib/thrift/TSCons.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from os import path
-from SCons.Builder import Builder
-
-def scons_env(env, add=''):
- opath = path.dirname(path.abspath('$TARGET'))
- lstr = 'thrift --gen cpp -o ' + opath + ' ' + add + ' $SOURCE'
- cppbuild = Builder(action = lstr)
- env.Append(BUILDERS = {'ThriftCpp' : cppbuild})
-
-def gen_cpp(env, dir, file):
- scons_env(env)
- suffixes = ['_types.h', '_types.cpp']
- targets = map(lambda s: 'gen-cpp/' + file + s, suffixes)
- return env.ThriftCpp(targets, dir+file+'.thrift')
diff --git a/module/lib/thrift/TSerialization.py b/module/lib/thrift/TSerialization.py
deleted file mode 100644
index b19f98aa8..000000000
--- a/module/lib/thrift/TSerialization.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from protocol import TBinaryProtocol
-from transport import TTransport
-
-def serialize(thrift_object, protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()):
- transport = TTransport.TMemoryBuffer()
- protocol = protocol_factory.getProtocol(transport)
- thrift_object.write(protocol)
- return transport.getvalue()
-
-def deserialize(base, buf, protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()):
- transport = TTransport.TMemoryBuffer(buf)
- protocol = protocol_factory.getProtocol(transport)
- base.read(protocol)
- return base
-
diff --git a/module/lib/thrift/Thrift.py b/module/lib/thrift/Thrift.py
deleted file mode 100644
index 1d271fcff..000000000
--- a/module/lib/thrift/Thrift.py
+++ /dev/null
@@ -1,154 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-import sys
-
-class TType:
- STOP = 0
- VOID = 1
- BOOL = 2
- BYTE = 3
- I08 = 3
- DOUBLE = 4
- I16 = 6
- I32 = 8
- I64 = 10
- STRING = 11
- UTF7 = 11
- STRUCT = 12
- MAP = 13
- SET = 14
- LIST = 15
- UTF8 = 16
- UTF16 = 17
-
- _VALUES_TO_NAMES = ( 'STOP',
- 'VOID',
- 'BOOL',
- 'BYTE',
- 'DOUBLE',
- None,
- 'I16',
- None,
- 'I32',
- None,
- 'I64',
- 'STRING',
- 'STRUCT',
- 'MAP',
- 'SET',
- 'LIST',
- 'UTF8',
- 'UTF16' )
-
-class TMessageType:
- CALL = 1
- REPLY = 2
- EXCEPTION = 3
- ONEWAY = 4
-
-class TProcessor:
-
- """Base class for procsessor, which works on two streams."""
-
- def process(iprot, oprot):
- pass
-
-class TException(Exception):
-
- """Base class for all thrift exceptions."""
-
- # BaseException.message is deprecated in Python v[2.6,3.0)
- if (2,6,0) <= sys.version_info < (3,0):
- def _get_message(self):
- return self._message
- def _set_message(self, message):
- self._message = message
- message = property(_get_message, _set_message)
-
- def __init__(self, message=None):
- Exception.__init__(self, message)
- self.message = message
-
-class TApplicationException(TException):
-
- """Application level thrift exceptions."""
-
- UNKNOWN = 0
- UNKNOWN_METHOD = 1
- INVALID_MESSAGE_TYPE = 2
- WRONG_METHOD_NAME = 3
- BAD_SEQUENCE_ID = 4
- MISSING_RESULT = 5
- INTERNAL_ERROR = 6
- PROTOCOL_ERROR = 7
-
- def __init__(self, type=UNKNOWN, message=None):
- TException.__init__(self, message)
- self.type = type
-
- def __str__(self):
- if self.message:
- return self.message
- elif self.type == self.UNKNOWN_METHOD:
- return 'Unknown method'
- elif self.type == self.INVALID_MESSAGE_TYPE:
- return 'Invalid message type'
- elif self.type == self.WRONG_METHOD_NAME:
- return 'Wrong method name'
- elif self.type == self.BAD_SEQUENCE_ID:
- return 'Bad sequence ID'
- elif self.type == self.MISSING_RESULT:
- return 'Missing result'
- else:
- return 'Default (unknown) TApplicationException'
-
- def read(self, iprot):
- iprot.readStructBegin()
- while True:
- (fname, ftype, fid) = iprot.readFieldBegin()
- if ftype == TType.STOP:
- break
- if fid == 1:
- if ftype == TType.STRING:
- self.message = iprot.readString();
- else:
- iprot.skip(ftype)
- elif fid == 2:
- if ftype == TType.I32:
- self.type = iprot.readI32();
- else:
- iprot.skip(ftype)
- else:
- iprot.skip(ftype)
- iprot.readFieldEnd()
- iprot.readStructEnd()
-
- def write(self, oprot):
- oprot.writeStructBegin('TApplicationException')
- if self.message != None:
- oprot.writeFieldBegin('message', TType.STRING, 1)
- oprot.writeString(self.message)
- oprot.writeFieldEnd()
- if self.type != None:
- oprot.writeFieldBegin('type', TType.I32, 2)
- oprot.writeI32(self.type)
- oprot.writeFieldEnd()
- oprot.writeFieldStop()
- oprot.writeStructEnd()
diff --git a/module/lib/thrift/protocol/TBase.py b/module/lib/thrift/protocol/TBase.py
deleted file mode 100644
index e675c7dc0..000000000
--- a/module/lib/thrift/protocol/TBase.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from thrift.Thrift import *
-from thrift.protocol import TBinaryProtocol
-from thrift.transport import TTransport
-
-try:
- from thrift.protocol import fastbinary
-except:
- fastbinary = None
-
-class TBase(object):
- __slots__ = []
-
- def __repr__(self):
- L = ['%s=%r' % (key, getattr(self, key))
- for key in self.__slots__ ]
- return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
- def __eq__(self, other):
- if not isinstance(other, self.__class__):
- return False
- for attr in self.__slots__:
- my_val = getattr(self, attr)
- other_val = getattr(other, attr)
- if my_val != other_val:
- return False
- return True
-
- def __ne__(self, other):
- return not (self == other)
-
- def read(self, iprot):
- if iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None and fastbinary is not None:
- fastbinary.decode_binary(self, iprot.trans, (self.__class__, self.thrift_spec))
- return
- iprot.readStruct(self, self.thrift_spec)
-
- def write(self, oprot):
- if oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and self.thrift_spec is not None and fastbinary is not None:
- oprot.trans.write(fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
- return
- oprot.writeStruct(self, self.thrift_spec)
-
-class TExceptionBase(Exception):
- # old style class so python2.4 can raise exceptions derived from this
- # This can't inherit from TBase because of that limitation.
- __slots__ = []
-
- __repr__ = TBase.__repr__.im_func
- __eq__ = TBase.__eq__.im_func
- __ne__ = TBase.__ne__.im_func
- read = TBase.read.im_func
- write = TBase.write.im_func
-
diff --git a/module/lib/thrift/protocol/TBinaryProtocol.py b/module/lib/thrift/protocol/TBinaryProtocol.py
deleted file mode 100644
index 50c6aa896..000000000
--- a/module/lib/thrift/protocol/TBinaryProtocol.py
+++ /dev/null
@@ -1,259 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from TProtocol import *
-from struct import pack, unpack
-
-class TBinaryProtocol(TProtocolBase):
-
- """Binary implementation of the Thrift protocol driver."""
-
- # NastyHaxx. Python 2.4+ on 32-bit machines forces hex constants to be
- # positive, converting this into a long. If we hardcode the int value
- # instead it'll stay in 32 bit-land.
-
- # VERSION_MASK = 0xffff0000
- VERSION_MASK = -65536
-
- # VERSION_1 = 0x80010000
- VERSION_1 = -2147418112
-
- TYPE_MASK = 0x000000ff
-
- def __init__(self, trans, strictRead=False, strictWrite=True):
- TProtocolBase.__init__(self, trans)
- self.strictRead = strictRead
- self.strictWrite = strictWrite
-
- def writeMessageBegin(self, name, type, seqid):
- if self.strictWrite:
- self.writeI32(TBinaryProtocol.VERSION_1 | type)
- self.writeString(name)
- self.writeI32(seqid)
- else:
- self.writeString(name)
- self.writeByte(type)
- self.writeI32(seqid)
-
- def writeMessageEnd(self):
- pass
-
- def writeStructBegin(self, name):
- pass
-
- def writeStructEnd(self):
- pass
-
- def writeFieldBegin(self, name, type, id):
- self.writeByte(type)
- self.writeI16(id)
-
- def writeFieldEnd(self):
- pass
-
- def writeFieldStop(self):
- self.writeByte(TType.STOP);
-
- def writeMapBegin(self, ktype, vtype, size):
- self.writeByte(ktype)
- self.writeByte(vtype)
- self.writeI32(size)
-
- def writeMapEnd(self):
- pass
-
- def writeListBegin(self, etype, size):
- self.writeByte(etype)
- self.writeI32(size)
-
- def writeListEnd(self):
- pass
-
- def writeSetBegin(self, etype, size):
- self.writeByte(etype)
- self.writeI32(size)
-
- def writeSetEnd(self):
- pass
-
- def writeBool(self, bool):
- if bool:
- self.writeByte(1)
- else:
- self.writeByte(0)
-
- def writeByte(self, byte):
- buff = pack("!b", byte)
- self.trans.write(buff)
-
- def writeI16(self, i16):
- buff = pack("!h", i16)
- self.trans.write(buff)
-
- def writeI32(self, i32):
- buff = pack("!i", i32)
- self.trans.write(buff)
-
- def writeI64(self, i64):
- buff = pack("!q", i64)
- self.trans.write(buff)
-
- def writeDouble(self, dub):
- buff = pack("!d", dub)
- self.trans.write(buff)
-
- def writeString(self, str):
- self.writeI32(len(str))
- self.trans.write(str)
-
- def readMessageBegin(self):
- sz = self.readI32()
- if sz < 0:
- version = sz & TBinaryProtocol.VERSION_MASK
- if version != TBinaryProtocol.VERSION_1:
- raise TProtocolException(type=TProtocolException.BAD_VERSION, message='Bad version in readMessageBegin: %d' % (sz))
- type = sz & TBinaryProtocol.TYPE_MASK
- name = self.readString()
- seqid = self.readI32()
- else:
- if self.strictRead:
- raise TProtocolException(type=TProtocolException.BAD_VERSION, message='No protocol version header')
- name = self.trans.readAll(sz)
- type = self.readByte()
- seqid = self.readI32()
- return (name, type, seqid)
-
- def readMessageEnd(self):
- pass
-
- def readStructBegin(self):
- pass
-
- def readStructEnd(self):
- pass
-
- def readFieldBegin(self):
- type = self.readByte()
- if type == TType.STOP:
- return (None, type, 0)
- id = self.readI16()
- return (None, type, id)
-
- def readFieldEnd(self):
- pass
-
- def readMapBegin(self):
- ktype = self.readByte()
- vtype = self.readByte()
- size = self.readI32()
- return (ktype, vtype, size)
-
- def readMapEnd(self):
- pass
-
- def readListBegin(self):
- etype = self.readByte()
- size = self.readI32()
- return (etype, size)
-
- def readListEnd(self):
- pass
-
- def readSetBegin(self):
- etype = self.readByte()
- size = self.readI32()
- return (etype, size)
-
- def readSetEnd(self):
- pass
-
- def readBool(self):
- byte = self.readByte()
- if byte == 0:
- return False
- return True
-
- def readByte(self):
- buff = self.trans.readAll(1)
- val, = unpack('!b', buff)
- return val
-
- def readI16(self):
- buff = self.trans.readAll(2)
- val, = unpack('!h', buff)
- return val
-
- def readI32(self):
- buff = self.trans.readAll(4)
- val, = unpack('!i', buff)
- return val
-
- def readI64(self):
- buff = self.trans.readAll(8)
- val, = unpack('!q', buff)
- return val
-
- def readDouble(self):
- buff = self.trans.readAll(8)
- val, = unpack('!d', buff)
- return val
-
- def readString(self):
- len = self.readI32()
- str = self.trans.readAll(len)
- return str
-
-
-class TBinaryProtocolFactory:
- def __init__(self, strictRead=False, strictWrite=True):
- self.strictRead = strictRead
- self.strictWrite = strictWrite
-
- def getProtocol(self, trans):
- prot = TBinaryProtocol(trans, self.strictRead, self.strictWrite)
- return prot
-
-
-class TBinaryProtocolAccelerated(TBinaryProtocol):
-
- """C-Accelerated version of TBinaryProtocol.
-
- This class does not override any of TBinaryProtocol's methods,
- but the generated code recognizes it directly and will call into
- our C module to do the encoding, bypassing this object entirely.
- We inherit from TBinaryProtocol so that the normal TBinaryProtocol
- encoding can happen if the fastbinary module doesn't work for some
- reason. (TODO(dreiss): Make this happen sanely in more cases.)
-
- In order to take advantage of the C module, just use
- TBinaryProtocolAccelerated instead of TBinaryProtocol.
-
- NOTE: This code was contributed by an external developer.
- The internal Thrift team has reviewed and tested it,
- but we cannot guarantee that it is production-ready.
- Please feel free to report bugs and/or success stories
- to the public mailing list.
- """
-
- pass
-
-
-class TBinaryProtocolAcceleratedFactory:
- def getProtocol(self, trans):
- return TBinaryProtocolAccelerated(trans)
diff --git a/module/lib/thrift/protocol/TCompactProtocol.py b/module/lib/thrift/protocol/TCompactProtocol.py
deleted file mode 100644
index 016a33171..000000000
--- a/module/lib/thrift/protocol/TCompactProtocol.py
+++ /dev/null
@@ -1,395 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from TProtocol import *
-from struct import pack, unpack
-
-__all__ = ['TCompactProtocol', 'TCompactProtocolFactory']
-
-CLEAR = 0
-FIELD_WRITE = 1
-VALUE_WRITE = 2
-CONTAINER_WRITE = 3
-BOOL_WRITE = 4
-FIELD_READ = 5
-CONTAINER_READ = 6
-VALUE_READ = 7
-BOOL_READ = 8
-
-def make_helper(v_from, container):
- def helper(func):
- def nested(self, *args, **kwargs):
- assert self.state in (v_from, container), (self.state, v_from, container)
- return func(self, *args, **kwargs)
- return nested
- return helper
-writer = make_helper(VALUE_WRITE, CONTAINER_WRITE)
-reader = make_helper(VALUE_READ, CONTAINER_READ)
-
-def makeZigZag(n, bits):
- return (n << 1) ^ (n >> (bits - 1))
-
-def fromZigZag(n):
- return (n >> 1) ^ -(n & 1)
-
-def writeVarint(trans, n):
- out = []
- while True:
- if n & ~0x7f == 0:
- out.append(n)
- break
- else:
- out.append((n & 0xff) | 0x80)
- n = n >> 7
- trans.write(''.join(map(chr, out)))
-
-def readVarint(trans):
- result = 0
- shift = 0
- while True:
- x = trans.readAll(1)
- byte = ord(x)
- result |= (byte & 0x7f) << shift
- if byte >> 7 == 0:
- return result
- shift += 7
-
-class CompactType:
- STOP = 0x00
- TRUE = 0x01
- FALSE = 0x02
- BYTE = 0x03
- I16 = 0x04
- I32 = 0x05
- I64 = 0x06
- DOUBLE = 0x07
- BINARY = 0x08
- LIST = 0x09
- SET = 0x0A
- MAP = 0x0B
- STRUCT = 0x0C
-
-CTYPES = {TType.STOP: CompactType.STOP,
- TType.BOOL: CompactType.TRUE, # used for collection
- TType.BYTE: CompactType.BYTE,
- TType.I16: CompactType.I16,
- TType.I32: CompactType.I32,
- TType.I64: CompactType.I64,
- TType.DOUBLE: CompactType.DOUBLE,
- TType.STRING: CompactType.BINARY,
- TType.STRUCT: CompactType.STRUCT,
- TType.LIST: CompactType.LIST,
- TType.SET: CompactType.SET,
- TType.MAP: CompactType.MAP
- }
-
-TTYPES = {}
-for k, v in CTYPES.items():
- TTYPES[v] = k
-TTYPES[CompactType.FALSE] = TType.BOOL
-del k
-del v
-
-class TCompactProtocol(TProtocolBase):
- "Compact implementation of the Thrift protocol driver."
-
- PROTOCOL_ID = 0x82
- VERSION = 1
- VERSION_MASK = 0x1f
- TYPE_MASK = 0xe0
- TYPE_SHIFT_AMOUNT = 5
-
- def __init__(self, trans):
- TProtocolBase.__init__(self, trans)
- self.state = CLEAR
- self.__last_fid = 0
- self.__bool_fid = None
- self.__bool_value = None
- self.__structs = []
- self.__containers = []
-
- def __writeVarint(self, n):
- writeVarint(self.trans, n)
-
- def writeMessageBegin(self, name, type, seqid):
- assert self.state == CLEAR
- self.__writeUByte(self.PROTOCOL_ID)
- self.__writeUByte(self.VERSION | (type << self.TYPE_SHIFT_AMOUNT))
- self.__writeVarint(seqid)
- self.__writeString(name)
- self.state = VALUE_WRITE
-
- def writeMessageEnd(self):
- assert self.state == VALUE_WRITE
- self.state = CLEAR
-
- def writeStructBegin(self, name):
- assert self.state in (CLEAR, CONTAINER_WRITE, VALUE_WRITE), self.state
- self.__structs.append((self.state, self.__last_fid))
- self.state = FIELD_WRITE
- self.__last_fid = 0
-
- def writeStructEnd(self):
- assert self.state == FIELD_WRITE
- self.state, self.__last_fid = self.__structs.pop()
-
- def writeFieldStop(self):
- self.__writeByte(0)
-
- def __writeFieldHeader(self, type, fid):
- delta = fid - self.__last_fid
- if 0 < delta <= 15:
- self.__writeUByte(delta << 4 | type)
- else:
- self.__writeByte(type)
- self.__writeI16(fid)
- self.__last_fid = fid
-
- def writeFieldBegin(self, name, type, fid):
- assert self.state == FIELD_WRITE, self.state
- if type == TType.BOOL:
- self.state = BOOL_WRITE
- self.__bool_fid = fid
- else:
- self.state = VALUE_WRITE
- self.__writeFieldHeader(CTYPES[type], fid)
-
- def writeFieldEnd(self):
- assert self.state in (VALUE_WRITE, BOOL_WRITE), self.state
- self.state = FIELD_WRITE
-
- def __writeUByte(self, byte):
- self.trans.write(pack('!B', byte))
-
- def __writeByte(self, byte):
- self.trans.write(pack('!b', byte))
-
- def __writeI16(self, i16):
- self.__writeVarint(makeZigZag(i16, 16))
-
- def __writeSize(self, i32):
- self.__writeVarint(i32)
-
- def writeCollectionBegin(self, etype, size):
- assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state
- if size <= 14:
- self.__writeUByte(size << 4 | CTYPES[etype])
- else:
- self.__writeUByte(0xf0 | CTYPES[etype])
- self.__writeSize(size)
- self.__containers.append(self.state)
- self.state = CONTAINER_WRITE
- writeSetBegin = writeCollectionBegin
- writeListBegin = writeCollectionBegin
-
- def writeMapBegin(self, ktype, vtype, size):
- assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state
- if size == 0:
- self.__writeByte(0)
- else:
- self.__writeSize(size)
- self.__writeUByte(CTYPES[ktype] << 4 | CTYPES[vtype])
- self.__containers.append(self.state)
- self.state = CONTAINER_WRITE
-
- def writeCollectionEnd(self):
- assert self.state == CONTAINER_WRITE, self.state
- self.state = self.__containers.pop()
- writeMapEnd = writeCollectionEnd
- writeSetEnd = writeCollectionEnd
- writeListEnd = writeCollectionEnd
-
- def writeBool(self, bool):
- if self.state == BOOL_WRITE:
- if bool:
- ctype = CompactType.TRUE
- else:
- ctype = CompactType.FALSE
- self.__writeFieldHeader(ctype, self.__bool_fid)
- elif self.state == CONTAINER_WRITE:
- if bool:
- self.__writeByte(CompactType.TRUE)
- else:
- self.__writeByte(CompactType.FALSE)
- else:
- raise AssertionError, "Invalid state in compact protocol"
-
- writeByte = writer(__writeByte)
- writeI16 = writer(__writeI16)
-
- @writer
- def writeI32(self, i32):
- self.__writeVarint(makeZigZag(i32, 32))
-
- @writer
- def writeI64(self, i64):
- self.__writeVarint(makeZigZag(i64, 64))
-
- @writer
- def writeDouble(self, dub):
- self.trans.write(pack('!d', dub))
-
- def __writeString(self, s):
- self.__writeSize(len(s))
- self.trans.write(s)
- writeString = writer(__writeString)
-
- def readFieldBegin(self):
- assert self.state == FIELD_READ, self.state
- type = self.__readUByte()
- if type & 0x0f == TType.STOP:
- return (None, 0, 0)
- delta = type >> 4
- if delta == 0:
- fid = self.__readI16()
- else:
- fid = self.__last_fid + delta
- self.__last_fid = fid
- type = type & 0x0f
- if type == CompactType.TRUE:
- self.state = BOOL_READ
- self.__bool_value = True
- elif type == CompactType.FALSE:
- self.state = BOOL_READ
- self.__bool_value = False
- else:
- self.state = VALUE_READ
- return (None, self.__getTType(type), fid)
-
- def readFieldEnd(self):
- assert self.state in (VALUE_READ, BOOL_READ), self.state
- self.state = FIELD_READ
-
- def __readUByte(self):
- result, = unpack('!B', self.trans.readAll(1))
- return result
-
- def __readByte(self):
- result, = unpack('!b', self.trans.readAll(1))
- return result
-
- def __readVarint(self):
- return readVarint(self.trans)
-
- def __readZigZag(self):
- return fromZigZag(self.__readVarint())
-
- def __readSize(self):
- result = self.__readVarint()
- if result < 0:
- raise TException("Length < 0")
- return result
-
- def readMessageBegin(self):
- assert self.state == CLEAR
- proto_id = self.__readUByte()
- if proto_id != self.PROTOCOL_ID:
- raise TProtocolException(TProtocolException.BAD_VERSION,
- 'Bad protocol id in the message: %d' % proto_id)
- ver_type = self.__readUByte()
- type = (ver_type & self.TYPE_MASK) >> self.TYPE_SHIFT_AMOUNT
- version = ver_type & self.VERSION_MASK
- if version != self.VERSION:
- raise TProtocolException(TProtocolException.BAD_VERSION,
- 'Bad version: %d (expect %d)' % (version, self.VERSION))
- seqid = self.__readVarint()
- name = self.__readString()
- return (name, type, seqid)
-
- def readMessageEnd(self):
- assert self.state == CLEAR
- assert len(self.__structs) == 0
-
- def readStructBegin(self):
- assert self.state in (CLEAR, CONTAINER_READ, VALUE_READ), self.state
- self.__structs.append((self.state, self.__last_fid))
- self.state = FIELD_READ
- self.__last_fid = 0
-
- def readStructEnd(self):
- assert self.state == FIELD_READ
- self.state, self.__last_fid = self.__structs.pop()
-
- def readCollectionBegin(self):
- assert self.state in (VALUE_READ, CONTAINER_READ), self.state
- size_type = self.__readUByte()
- size = size_type >> 4
- type = self.__getTType(size_type)
- if size == 15:
- size = self.__readSize()
- self.__containers.append(self.state)
- self.state = CONTAINER_READ
- return type, size
- readSetBegin = readCollectionBegin
- readListBegin = readCollectionBegin
-
- def readMapBegin(self):
- assert self.state in (VALUE_READ, CONTAINER_READ), self.state
- size = self.__readSize()
- types = 0
- if size > 0:
- types = self.__readUByte()
- vtype = self.__getTType(types)
- ktype = self.__getTType(types >> 4)
- self.__containers.append(self.state)
- self.state = CONTAINER_READ
- return (ktype, vtype, size)
-
- def readCollectionEnd(self):
- assert self.state == CONTAINER_READ, self.state
- self.state = self.__containers.pop()
- readSetEnd = readCollectionEnd
- readListEnd = readCollectionEnd
- readMapEnd = readCollectionEnd
-
- def readBool(self):
- if self.state == BOOL_READ:
- return self.__bool_value == CompactType.TRUE
- elif self.state == CONTAINER_READ:
- return self.__readByte() == CompactType.TRUE
- else:
- raise AssertionError, "Invalid state in compact protocol: %d" % self.state
-
- readByte = reader(__readByte)
- __readI16 = __readZigZag
- readI16 = reader(__readZigZag)
- readI32 = reader(__readZigZag)
- readI64 = reader(__readZigZag)
-
- @reader
- def readDouble(self):
- buff = self.trans.readAll(8)
- val, = unpack('!d', buff)
- return val
-
- def __readString(self):
- len = self.__readSize()
- return self.trans.readAll(len)
- readString = reader(__readString)
-
- def __getTType(self, byte):
- return TTYPES[byte & 0x0f]
-
-
-class TCompactProtocolFactory:
- def __init__(self):
- pass
-
- def getProtocol(self, trans):
- return TCompactProtocol(trans)
diff --git a/module/lib/thrift/protocol/TProtocol.py b/module/lib/thrift/protocol/TProtocol.py
deleted file mode 100644
index 7338ff68a..000000000
--- a/module/lib/thrift/protocol/TProtocol.py
+++ /dev/null
@@ -1,404 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from thrift.Thrift import *
-
-class TProtocolException(TException):
-
- """Custom Protocol Exception class"""
-
- UNKNOWN = 0
- INVALID_DATA = 1
- NEGATIVE_SIZE = 2
- SIZE_LIMIT = 3
- BAD_VERSION = 4
-
- def __init__(self, type=UNKNOWN, message=None):
- TException.__init__(self, message)
- self.type = type
-
-class TProtocolBase:
-
- """Base class for Thrift protocol driver."""
-
- def __init__(self, trans):
- self.trans = trans
-
- def writeMessageBegin(self, name, type, seqid):
- pass
-
- def writeMessageEnd(self):
- pass
-
- def writeStructBegin(self, name):
- pass
-
- def writeStructEnd(self):
- pass
-
- def writeFieldBegin(self, name, type, id):
- pass
-
- def writeFieldEnd(self):
- pass
-
- def writeFieldStop(self):
- pass
-
- def writeMapBegin(self, ktype, vtype, size):
- pass
-
- def writeMapEnd(self):
- pass
-
- def writeListBegin(self, etype, size):
- pass
-
- def writeListEnd(self):
- pass
-
- def writeSetBegin(self, etype, size):
- pass
-
- def writeSetEnd(self):
- pass
-
- def writeBool(self, bool):
- pass
-
- def writeByte(self, byte):
- pass
-
- def writeI16(self, i16):
- pass
-
- def writeI32(self, i32):
- pass
-
- def writeI64(self, i64):
- pass
-
- def writeDouble(self, dub):
- pass
-
- def writeString(self, str):
- pass
-
- def readMessageBegin(self):
- pass
-
- def readMessageEnd(self):
- pass
-
- def readStructBegin(self):
- pass
-
- def readStructEnd(self):
- pass
-
- def readFieldBegin(self):
- pass
-
- def readFieldEnd(self):
- pass
-
- def readMapBegin(self):
- pass
-
- def readMapEnd(self):
- pass
-
- def readListBegin(self):
- pass
-
- def readListEnd(self):
- pass
-
- def readSetBegin(self):
- pass
-
- def readSetEnd(self):
- pass
-
- def readBool(self):
- pass
-
- def readByte(self):
- pass
-
- def readI16(self):
- pass
-
- def readI32(self):
- pass
-
- def readI64(self):
- pass
-
- def readDouble(self):
- pass
-
- def readString(self):
- pass
-
- def skip(self, type):
- if type == TType.STOP:
- return
- elif type == TType.BOOL:
- self.readBool()
- elif type == TType.BYTE:
- self.readByte()
- elif type == TType.I16:
- self.readI16()
- elif type == TType.I32:
- self.readI32()
- elif type == TType.I64:
- self.readI64()
- elif type == TType.DOUBLE:
- self.readDouble()
- elif type == TType.STRING:
- self.readString()
- elif type == TType.STRUCT:
- name = self.readStructBegin()
- while True:
- (name, type, id) = self.readFieldBegin()
- if type == TType.STOP:
- break
- self.skip(type)
- self.readFieldEnd()
- self.readStructEnd()
- elif type == TType.MAP:
- (ktype, vtype, size) = self.readMapBegin()
- for i in range(size):
- self.skip(ktype)
- self.skip(vtype)
- self.readMapEnd()
- elif type == TType.SET:
- (etype, size) = self.readSetBegin()
- for i in range(size):
- self.skip(etype)
- self.readSetEnd()
- elif type == TType.LIST:
- (etype, size) = self.readListBegin()
- for i in range(size):
- self.skip(etype)
- self.readListEnd()
-
- # tuple of: ( 'reader method' name, is_container boolean, 'writer_method' name )
- _TTYPE_HANDLERS = (
- (None, None, False), # 0 == TType,STOP
- (None, None, False), # 1 == TType.VOID # TODO: handle void?
- ('readBool', 'writeBool', False), # 2 == TType.BOOL
- ('readByte', 'writeByte', False), # 3 == TType.BYTE and I08
- ('readDouble', 'writeDouble', False), # 4 == TType.DOUBLE
- (None, None, False), # 5, undefined
- ('readI16', 'writeI16', False), # 6 == TType.I16
- (None, None, False), # 7, undefined
- ('readI32', 'writeI32', False), # 8 == TType.I32
- (None, None, False), # 9, undefined
- ('readI64', 'writeI64', False), # 10 == TType.I64
- ('readString', 'writeString', False), # 11 == TType.STRING and UTF7
- ('readContainerStruct', 'writeContainerStruct', True), # 12 == TType.STRUCT
- ('readContainerMap', 'writeContainerMap', True), # 13 == TType.MAP
- ('readContainerSet', 'writeContainerSet', True), # 14 == TType.SET
- ('readContainerList', 'writeContainerList', True), # 15 == TType.LIST
- (None, None, False), # 16 == TType.UTF8 # TODO: handle utf8 types?
- (None, None, False)# 17 == TType.UTF16 # TODO: handle utf16 types?
- )
-
- def readFieldByTType(self, ttype, spec):
- try:
- (r_handler, w_handler, is_container) = self._TTYPE_HANDLERS[ttype]
- except IndexError:
- raise TProtocolException(type=TProtocolException.INVALID_DATA,
- message='Invalid field type %d' % (ttype))
- if r_handler is None:
- raise TProtocolException(type=TProtocolException.INVALID_DATA,
- message='Invalid field type %d' % (ttype))
- reader = getattr(self, r_handler)
- if not is_container:
- return reader()
- return reader(spec)
-
- def readContainerList(self, spec):
- results = []
- ttype, tspec = spec[0], spec[1]
- r_handler = self._TTYPE_HANDLERS[ttype][0]
- reader = getattr(self, r_handler)
- (list_type, list_len) = self.readListBegin()
- if tspec is None:
- # list values are simple types
- for idx in xrange(list_len):
- results.append(reader())
- else:
- # this is like an inlined readFieldByTType
- container_reader = self._TTYPE_HANDLERS[list_type][0]
- val_reader = getattr(self, container_reader)
- for idx in xrange(list_len):
- val = val_reader(tspec)
- results.append(val)
- self.readListEnd()
- return results
-
- def readContainerSet(self, spec):
- results = set()
- ttype, tspec = spec[0], spec[1]
- r_handler = self._TTYPE_HANDLERS[ttype][0]
- reader = getattr(self, r_handler)
- (set_type, set_len) = self.readSetBegin()
- if tspec is None:
- # set members are simple types
- for idx in xrange(set_len):
- results.add(reader())
- else:
- container_reader = self._TTYPE_HANDLERS[set_type][0]
- val_reader = getattr(self, container_reader)
- for idx in xrange(set_len):
- results.add(val_reader(tspec))
- self.readSetEnd()
- return results
-
- def readContainerStruct(self, spec):
- (obj_class, obj_spec) = spec
- obj = obj_class()
- obj.read(self)
- return obj
-
- def readContainerMap(self, spec):
- results = dict()
- key_ttype, key_spec = spec[0], spec[1]
- val_ttype, val_spec = spec[2], spec[3]
- (map_ktype, map_vtype, map_len) = self.readMapBegin()
- # TODO: compare types we just decoded with thrift_spec and abort/skip if types disagree
- key_reader = getattr(self, self._TTYPE_HANDLERS[key_ttype][0])
- val_reader = getattr(self, self._TTYPE_HANDLERS[val_ttype][0])
- # list values are simple types
- for idx in xrange(map_len):
- if key_spec is None:
- k_val = key_reader()
- else:
- k_val = self.readFieldByTType(key_ttype, key_spec)
- if val_spec is None:
- v_val = val_reader()
- else:
- v_val = self.readFieldByTType(val_ttype, val_spec)
- # this raises a TypeError with unhashable keys types. i.e. d=dict(); d[[0,1]] = 2 fails
- results[k_val] = v_val
- self.readMapEnd()
- return results
-
- def readStruct(self, obj, thrift_spec):
- self.readStructBegin()
- while True:
- (fname, ftype, fid) = self.readFieldBegin()
- if ftype == TType.STOP:
- break
- try:
- field = thrift_spec[fid]
- except IndexError:
- self.skip(ftype)
- else:
- if field is not None and ftype == field[1]:
- fname = field[2]
- fspec = field[3]
- val = self.readFieldByTType(ftype, fspec)
- setattr(obj, fname, val)
- else:
- self.skip(ftype)
- self.readFieldEnd()
- self.readStructEnd()
-
- def writeContainerStruct(self, val, spec):
- val.write(self)
-
- def writeContainerList(self, val, spec):
- self.writeListBegin(spec[0], len(val))
- r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
- e_writer = getattr(self, w_handler)
- if not is_container:
- for elem in val:
- e_writer(elem)
- else:
- for elem in val:
- e_writer(elem, spec[1])
- self.writeListEnd()
-
- def writeContainerSet(self, val, spec):
- self.writeSetBegin(spec[0], len(val))
- r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
- e_writer = getattr(self, w_handler)
- if not is_container:
- for elem in val:
- e_writer(elem)
- else:
- for elem in val:
- e_writer(elem, spec[1])
- self.writeSetEnd()
-
- def writeContainerMap(self, val, spec):
- k_type = spec[0]
- v_type = spec[2]
- ignore, ktype_name, k_is_container = self._TTYPE_HANDLERS[k_type]
- ignore, vtype_name, v_is_container = self._TTYPE_HANDLERS[v_type]
- k_writer = getattr(self, ktype_name)
- v_writer = getattr(self, vtype_name)
- self.writeMapBegin(k_type, v_type, len(val))
- for m_key, m_val in val.iteritems():
- if not k_is_container:
- k_writer(m_key)
- else:
- k_writer(m_key, spec[1])
- if not v_is_container:
- v_writer(m_val)
- else:
- v_writer(m_val, spec[3])
- self.writeMapEnd()
-
- def writeStruct(self, obj, thrift_spec):
- self.writeStructBegin(obj.__class__.__name__)
- for field in thrift_spec:
- if field is None:
- continue
- fname = field[2]
- val = getattr(obj, fname)
- if val is None:
- # skip writing out unset fields
- continue
- fid = field[0]
- ftype = field[1]
- fspec = field[3]
- # get the writer method for this value
- self.writeFieldBegin(fname, ftype, fid)
- self.writeFieldByTType(ftype, val, fspec)
- self.writeFieldEnd()
- self.writeFieldStop()
- self.writeStructEnd()
-
- def writeFieldByTType(self, ttype, val, spec):
- r_handler, w_handler, is_container = self._TTYPE_HANDLERS[ttype]
- writer = getattr(self, w_handler)
- if is_container:
- writer(val, spec)
- else:
- writer(val)
-
-class TProtocolFactory:
- def getProtocol(self, trans):
- pass
-
diff --git a/module/lib/thrift/protocol/__init__.py b/module/lib/thrift/protocol/__init__.py
deleted file mode 100644
index d53359b28..000000000
--- a/module/lib/thrift/protocol/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-__all__ = ['TProtocol', 'TBinaryProtocol', 'fastbinary', 'TBase']
diff --git a/module/lib/thrift/server/THttpServer.py b/module/lib/thrift/server/THttpServer.py
deleted file mode 100644
index 3047d9c00..000000000
--- a/module/lib/thrift/server/THttpServer.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-import BaseHTTPServer
-
-from thrift.server import TServer
-from thrift.transport import TTransport
-
-class ResponseException(Exception):
- """Allows handlers to override the HTTP response
-
- Normally, THttpServer always sends a 200 response. If a handler wants
- to override this behavior (e.g., to simulate a misconfigured or
- overloaded web server during testing), it can raise a ResponseException.
- The function passed to the constructor will be called with the
- RequestHandler as its only argument.
- """
- def __init__(self, handler):
- self.handler = handler
-
-
-class THttpServer(TServer.TServer):
- """A simple HTTP-based Thrift server
-
- This class is not very performant, but it is useful (for example) for
- acting as a mock version of an Apache-based PHP Thrift endpoint."""
-
- def __init__(self, processor, server_address,
- inputProtocolFactory, outputProtocolFactory = None,
- server_class = BaseHTTPServer.HTTPServer):
- """Set up protocol factories and HTTP server.
-
- See BaseHTTPServer for server_address.
- See TServer for protocol factories."""
-
- if outputProtocolFactory is None:
- outputProtocolFactory = inputProtocolFactory
-
- TServer.TServer.__init__(self, processor, None, None, None,
- inputProtocolFactory, outputProtocolFactory)
-
- thttpserver = self
-
- class RequestHander(BaseHTTPServer.BaseHTTPRequestHandler):
- def do_POST(self):
- # Don't care about the request path.
- itrans = TTransport.TFileObjectTransport(self.rfile)
- otrans = TTransport.TFileObjectTransport(self.wfile)
- itrans = TTransport.TBufferedTransport(itrans, int(self.headers['Content-Length']))
- otrans = TTransport.TMemoryBuffer()
- iprot = thttpserver.inputProtocolFactory.getProtocol(itrans)
- oprot = thttpserver.outputProtocolFactory.getProtocol(otrans)
- try:
- thttpserver.processor.process(iprot, oprot)
- except ResponseException, exn:
- exn.handler(self)
- else:
- self.send_response(200)
- self.send_header("content-type", "application/x-thrift")
- self.end_headers()
- self.wfile.write(otrans.getvalue())
-
- self.httpd = server_class(server_address, RequestHander)
-
- def serve(self):
- self.httpd.serve_forever()
diff --git a/module/lib/thrift/server/TNonblockingServer.py b/module/lib/thrift/server/TNonblockingServer.py
deleted file mode 100644
index ea348a0b6..000000000
--- a/module/lib/thrift/server/TNonblockingServer.py
+++ /dev/null
@@ -1,310 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-"""Implementation of non-blocking server.
-
-The main idea of the server is reciving and sending requests
-only from main thread.
-
-It also makes thread pool server in tasks terms, not connections.
-"""
-import threading
-import socket
-import Queue
-import select
-import struct
-import logging
-
-from thrift.transport import TTransport
-from thrift.protocol.TBinaryProtocol import TBinaryProtocolFactory
-
-__all__ = ['TNonblockingServer']
-
-class Worker(threading.Thread):
- """Worker is a small helper to process incoming connection."""
- def __init__(self, queue):
- threading.Thread.__init__(self)
- self.queue = queue
-
- def run(self):
- """Process queries from task queue, stop if processor is None."""
- while True:
- try:
- processor, iprot, oprot, otrans, callback = self.queue.get()
- if processor is None:
- break
- processor.process(iprot, oprot)
- callback(True, otrans.getvalue())
- except Exception:
- logging.exception("Exception while processing request")
- callback(False, '')
-
-WAIT_LEN = 0
-WAIT_MESSAGE = 1
-WAIT_PROCESS = 2
-SEND_ANSWER = 3
-CLOSED = 4
-
-def locked(func):
- "Decorator which locks self.lock."
- def nested(self, *args, **kwargs):
- self.lock.acquire()
- try:
- return func(self, *args, **kwargs)
- finally:
- self.lock.release()
- return nested
-
-def socket_exception(func):
- "Decorator close object on socket.error."
- def read(self, *args, **kwargs):
- try:
- return func(self, *args, **kwargs)
- except socket.error:
- self.close()
- return read
-
-class Connection:
- """Basic class is represented connection.
-
- It can be in state:
- WAIT_LEN --- connection is reading request len.
- WAIT_MESSAGE --- connection is reading request.
- WAIT_PROCESS --- connection has just read whole request and
- waits for call ready routine.
- SEND_ANSWER --- connection is sending answer string (including length
- of answer).
- CLOSED --- socket was closed and connection should be deleted.
- """
- def __init__(self, new_socket, wake_up):
- self.socket = new_socket
- self.socket.setblocking(False)
- self.status = WAIT_LEN
- self.len = 0
- self.message = ''
- self.lock = threading.Lock()
- self.wake_up = wake_up
-
- def _read_len(self):
- """Reads length of request.
-
- It's really paranoic routine and it may be replaced by
- self.socket.recv(4)."""
- read = self.socket.recv(4 - len(self.message))
- if len(read) == 0:
- # if we read 0 bytes and self.message is empty, it means client close
- # connection
- if len(self.message) != 0:
- logging.error("can't read frame size from socket")
- self.close()
- return
- self.message += read
- if len(self.message) == 4:
- self.len, = struct.unpack('!i', self.message)
- if self.len < 0:
- logging.error("negative frame size, it seems client"\
- " doesn't use FramedTransport")
- self.close()
- elif self.len == 0:
- logging.error("empty frame, it's really strange")
- self.close()
- else:
- self.message = ''
- self.status = WAIT_MESSAGE
-
- @socket_exception
- def read(self):
- """Reads data from stream and switch state."""
- assert self.status in (WAIT_LEN, WAIT_MESSAGE)
- if self.status == WAIT_LEN:
- self._read_len()
- # go back to the main loop here for simplicity instead of
- # falling through, even though there is a good chance that
- # the message is already available
- elif self.status == WAIT_MESSAGE:
- read = self.socket.recv(self.len - len(self.message))
- if len(read) == 0:
- logging.error("can't read frame from socket (get %d of %d bytes)" %
- (len(self.message), self.len))
- self.close()
- return
- self.message += read
- if len(self.message) == self.len:
- self.status = WAIT_PROCESS
-
- @socket_exception
- def write(self):
- """Writes data from socket and switch state."""
- assert self.status == SEND_ANSWER
- sent = self.socket.send(self.message)
- if sent == len(self.message):
- self.status = WAIT_LEN
- self.message = ''
- self.len = 0
- else:
- self.message = self.message[sent:]
-
- @locked
- def ready(self, all_ok, message):
- """Callback function for switching state and waking up main thread.
-
- This function is the only function witch can be called asynchronous.
-
- The ready can switch Connection to three states:
- WAIT_LEN if request was oneway.
- SEND_ANSWER if request was processed in normal way.
- CLOSED if request throws unexpected exception.
-
- The one wakes up main thread.
- """
- assert self.status == WAIT_PROCESS
- if not all_ok:
- self.close()
- self.wake_up()
- return
- self.len = ''
- if len(message) == 0:
- # it was a oneway request, do not write answer
- self.message = ''
- self.status = WAIT_LEN
- else:
- self.message = struct.pack('!i', len(message)) + message
- self.status = SEND_ANSWER
- self.wake_up()
-
- @locked
- def is_writeable(self):
- "Returns True if connection should be added to write list of select."
- return self.status == SEND_ANSWER
-
- # it's not necessary, but...
- @locked
- def is_readable(self):
- "Returns True if connection should be added to read list of select."
- return self.status in (WAIT_LEN, WAIT_MESSAGE)
-
- @locked
- def is_closed(self):
- "Returns True if connection is closed."
- return self.status == CLOSED
-
- def fileno(self):
- "Returns the file descriptor of the associated socket."
- return self.socket.fileno()
-
- def close(self):
- "Closes connection"
- self.status = CLOSED
- self.socket.close()
-
-class TNonblockingServer:
- """Non-blocking server."""
- def __init__(self, processor, lsocket, inputProtocolFactory=None,
- outputProtocolFactory=None, threads=10):
- self.processor = processor
- self.socket = lsocket
- self.in_protocol = inputProtocolFactory or TBinaryProtocolFactory()
- self.out_protocol = outputProtocolFactory or self.in_protocol
- self.threads = int(threads)
- self.clients = {}
- self.tasks = Queue.Queue()
- self._read, self._write = socket.socketpair()
- self.prepared = False
-
- def setNumThreads(self, num):
- """Set the number of worker threads that should be created."""
- # implement ThreadPool interface
- assert not self.prepared, "You can't change number of threads for working server"
- self.threads = num
-
- def prepare(self):
- """Prepares server for serve requests."""
- self.socket.listen()
- for _ in xrange(self.threads):
- thread = Worker(self.tasks)
- thread.setDaemon(True)
- thread.start()
- self.prepared = True
-
- def wake_up(self):
- """Wake up main thread.
-
- The server usualy waits in select call in we should terminate one.
- The simplest way is using socketpair.
-
- Select always wait to read from the first socket of socketpair.
-
- In this case, we can just write anything to the second socket from
- socketpair."""
- self._write.send('1')
-
- def _select(self):
- """Does select on open connections."""
- readable = [self.socket.handle.fileno(), self._read.fileno()]
- writable = []
- for i, connection in self.clients.items():
- if connection.is_readable():
- readable.append(connection.fileno())
- if connection.is_writeable():
- writable.append(connection.fileno())
- if connection.is_closed():
- del self.clients[i]
- return select.select(readable, writable, readable)
-
- def handle(self):
- """Handle requests.
-
- WARNING! You must call prepare BEFORE calling handle.
- """
- assert self.prepared, "You have to call prepare before handle"
- rset, wset, xset = self._select()
- for readable in rset:
- if readable == self._read.fileno():
- # don't care i just need to clean readable flag
- self._read.recv(1024)
- elif readable == self.socket.handle.fileno():
- client = self.socket.accept().handle
- self.clients[client.fileno()] = Connection(client, self.wake_up)
- else:
- connection = self.clients[readable]
- connection.read()
- if connection.status == WAIT_PROCESS:
- itransport = TTransport.TMemoryBuffer(connection.message)
- otransport = TTransport.TMemoryBuffer()
- iprot = self.in_protocol.getProtocol(itransport)
- oprot = self.out_protocol.getProtocol(otransport)
- self.tasks.put([self.processor, iprot, oprot,
- otransport, connection.ready])
- for writeable in wset:
- self.clients[writeable].write()
- for oob in xset:
- self.clients[oob].close()
- del self.clients[oob]
-
- def close(self):
- """Closes the server."""
- for _ in xrange(self.threads):
- self.tasks.put([None, None, None, None, None])
- self.socket.close()
- self.prepared = False
-
- def serve(self):
- """Serve forever."""
- self.prepare()
- while True:
- self.handle()
diff --git a/module/lib/thrift/server/TProcessPoolServer.py b/module/lib/thrift/server/TProcessPoolServer.py
deleted file mode 100644
index 7ed814a88..000000000
--- a/module/lib/thrift/server/TProcessPoolServer.py
+++ /dev/null
@@ -1,125 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-
-import logging
-from multiprocessing import Process, Value, Condition, reduction
-
-from TServer import TServer
-from thrift.transport.TTransport import TTransportException
-
-class TProcessPoolServer(TServer):
-
- """
- Server with a fixed size pool of worker subprocesses which service requests.
- Note that if you need shared state between the handlers - it's up to you!
- Written by Dvir Volk, doat.com
- """
-
- def __init__(self, * args):
- TServer.__init__(self, *args)
- self.numWorkers = 10
- self.workers = []
- self.isRunning = Value('b', False)
- self.stopCondition = Condition()
- self.postForkCallback = None
-
- def setPostForkCallback(self, callback):
- if not callable(callback):
- raise TypeError("This is not a callback!")
- self.postForkCallback = callback
-
- def setNumWorkers(self, num):
- """Set the number of worker threads that should be created"""
- self.numWorkers = num
-
- def workerProcess(self):
- """Loop around getting clients from the shared queue and process them."""
-
- if self.postForkCallback:
- self.postForkCallback()
-
- while self.isRunning.value == True:
- try:
- client = self.serverTransport.accept()
- self.serveClient(client)
- except (KeyboardInterrupt, SystemExit):
- return 0
- except Exception, x:
- logging.exception(x)
-
- def serveClient(self, client):
- """Process input/output from a client for as long as possible"""
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
- iprot = self.inputProtocolFactory.getProtocol(itrans)
- oprot = self.outputProtocolFactory.getProtocol(otrans)
-
- try:
- while True:
- self.processor.process(iprot, oprot)
- except TTransportException, tx:
- pass
- except Exception, x:
- logging.exception(x)
-
- itrans.close()
- otrans.close()
-
-
- def serve(self):
- """Start a fixed number of worker threads and put client into a queue"""
-
- #this is a shared state that can tell the workers to exit when set as false
- self.isRunning.value = True
-
- #first bind and listen to the port
- self.serverTransport.listen()
-
- #fork the children
- for i in range(self.numWorkers):
- try:
- w = Process(target=self.workerProcess)
- w.daemon = True
- w.start()
- self.workers.append(w)
- except Exception, x:
- logging.exception(x)
-
- #wait until the condition is set by stop()
-
- while True:
-
- self.stopCondition.acquire()
- try:
- self.stopCondition.wait()
- break
- except (SystemExit, KeyboardInterrupt):
- break
- except Exception, x:
- logging.exception(x)
-
- self.isRunning.value = False
-
- def stop(self):
- self.isRunning.value = False
- self.stopCondition.acquire()
- self.stopCondition.notify()
- self.stopCondition.release()
-
diff --git a/module/lib/thrift/server/TServer.py b/module/lib/thrift/server/TServer.py
deleted file mode 100644
index 8456e2d40..000000000
--- a/module/lib/thrift/server/TServer.py
+++ /dev/null
@@ -1,274 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-import logging
-import sys
-import os
-import traceback
-import threading
-import Queue
-
-from thrift.Thrift import TProcessor
-from thrift.transport import TTransport
-from thrift.protocol import TBinaryProtocol
-
-class TServer:
-
- """Base interface for a server, which must have a serve method."""
-
- """ 3 constructors for all servers:
- 1) (processor, serverTransport)
- 2) (processor, serverTransport, transportFactory, protocolFactory)
- 3) (processor, serverTransport,
- inputTransportFactory, outputTransportFactory,
- inputProtocolFactory, outputProtocolFactory)"""
- def __init__(self, *args):
- if (len(args) == 2):
- self.__initArgs__(args[0], args[1],
- TTransport.TTransportFactoryBase(),
- TTransport.TTransportFactoryBase(),
- TBinaryProtocol.TBinaryProtocolFactory(),
- TBinaryProtocol.TBinaryProtocolFactory())
- elif (len(args) == 4):
- self.__initArgs__(args[0], args[1], args[2], args[2], args[3], args[3])
- elif (len(args) == 6):
- self.__initArgs__(args[0], args[1], args[2], args[3], args[4], args[5])
-
- def __initArgs__(self, processor, serverTransport,
- inputTransportFactory, outputTransportFactory,
- inputProtocolFactory, outputProtocolFactory):
- self.processor = processor
- self.serverTransport = serverTransport
- self.inputTransportFactory = inputTransportFactory
- self.outputTransportFactory = outputTransportFactory
- self.inputProtocolFactory = inputProtocolFactory
- self.outputProtocolFactory = outputProtocolFactory
-
- def serve(self):
- pass
-
-class TSimpleServer(TServer):
-
- """Simple single-threaded server that just pumps around one transport."""
-
- def __init__(self, *args):
- TServer.__init__(self, *args)
-
- def serve(self):
- self.serverTransport.listen()
- while True:
- client = self.serverTransport.accept()
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
- iprot = self.inputProtocolFactory.getProtocol(itrans)
- oprot = self.outputProtocolFactory.getProtocol(otrans)
- try:
- while True:
- self.processor.process(iprot, oprot)
- except TTransport.TTransportException, tx:
- pass
- except Exception, x:
- logging.exception(x)
-
- itrans.close()
- otrans.close()
-
-class TThreadedServer(TServer):
-
- """Threaded server that spawns a new thread per each connection."""
-
- def __init__(self, *args, **kwargs):
- TServer.__init__(self, *args)
- self.daemon = kwargs.get("daemon", False)
-
- def serve(self):
- self.serverTransport.listen()
- while True:
- try:
- client = self.serverTransport.accept()
- t = threading.Thread(target = self.handle, args=(client,))
- t.setDaemon(self.daemon)
- t.start()
- except KeyboardInterrupt:
- raise
- except Exception, x:
- logging.exception(x)
-
- def handle(self, client):
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
- iprot = self.inputProtocolFactory.getProtocol(itrans)
- oprot = self.outputProtocolFactory.getProtocol(otrans)
- try:
- while True:
- self.processor.process(iprot, oprot)
- except TTransport.TTransportException, tx:
- pass
- except Exception, x:
- logging.exception(x)
-
- itrans.close()
- otrans.close()
-
-class TThreadPoolServer(TServer):
-
- """Server with a fixed size pool of threads which service requests."""
-
- def __init__(self, *args, **kwargs):
- TServer.__init__(self, *args)
- self.clients = Queue.Queue()
- self.threads = 10
- self.daemon = kwargs.get("daemon", False)
-
- def setNumThreads(self, num):
- """Set the number of worker threads that should be created"""
- self.threads = num
-
- def serveThread(self):
- """Loop around getting clients from the shared queue and process them."""
- while True:
- try:
- client = self.clients.get()
- self.serveClient(client)
- except Exception, x:
- logging.exception(x)
-
- def serveClient(self, client):
- """Process input/output from a client for as long as possible"""
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
- iprot = self.inputProtocolFactory.getProtocol(itrans)
- oprot = self.outputProtocolFactory.getProtocol(otrans)
- try:
- while True:
- self.processor.process(iprot, oprot)
- except TTransport.TTransportException, tx:
- pass
- except Exception, x:
- logging.exception(x)
-
- itrans.close()
- otrans.close()
-
- def serve(self):
- """Start a fixed number of worker threads and put client into a queue"""
- for i in range(self.threads):
- try:
- t = threading.Thread(target = self.serveThread)
- t.setDaemon(self.daemon)
- t.start()
- except Exception, x:
- logging.exception(x)
-
- # Pump the socket for clients
- self.serverTransport.listen()
- while True:
- try:
- client = self.serverTransport.accept()
- self.clients.put(client)
- except Exception, x:
- logging.exception(x)
-
-
-class TForkingServer(TServer):
-
- """A Thrift server that forks a new process for each request"""
- """
- This is more scalable than the threaded server as it does not cause
- GIL contention.
-
- Note that this has different semantics from the threading server.
- Specifically, updates to shared variables will no longer be shared.
- It will also not work on windows.
-
- This code is heavily inspired by SocketServer.ForkingMixIn in the
- Python stdlib.
- """
-
- def __init__(self, *args):
- TServer.__init__(self, *args)
- self.children = []
-
- def serve(self):
- def try_close(file):
- try:
- file.close()
- except IOError, e:
- logging.warning(e, exc_info=True)
-
-
- self.serverTransport.listen()
- while True:
- client = self.serverTransport.accept()
- try:
- pid = os.fork()
-
- if pid: # parent
- # add before collect, otherwise you race w/ waitpid
- self.children.append(pid)
- self.collect_children()
-
- # Parent must close socket or the connection may not get
- # closed promptly
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
- try_close(itrans)
- try_close(otrans)
- else:
- itrans = self.inputTransportFactory.getTransport(client)
- otrans = self.outputTransportFactory.getTransport(client)
-
- iprot = self.inputProtocolFactory.getProtocol(itrans)
- oprot = self.outputProtocolFactory.getProtocol(otrans)
-
- ecode = 0
- try:
- try:
- while True:
- self.processor.process(iprot, oprot)
- except TTransport.TTransportException, tx:
- pass
- except Exception, e:
- logging.exception(e)
- ecode = 1
- finally:
- try_close(itrans)
- try_close(otrans)
-
- os._exit(ecode)
-
- except TTransport.TTransportException, tx:
- pass
- except Exception, x:
- logging.exception(x)
-
-
- def collect_children(self):
- while self.children:
- try:
- pid, status = os.waitpid(0, os.WNOHANG)
- except os.error:
- pid = None
-
- if pid:
- self.children.remove(pid)
- else:
- break
-
-
diff --git a/module/lib/thrift/transport/THttpClient.py b/module/lib/thrift/transport/THttpClient.py
deleted file mode 100644
index 50269785c..000000000
--- a/module/lib/thrift/transport/THttpClient.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from TTransport import *
-from cStringIO import StringIO
-
-import urlparse
-import httplib
-import warnings
-import socket
-
-class THttpClient(TTransportBase):
-
- """Http implementation of TTransport base."""
-
- def __init__(self, uri_or_host, port=None, path=None):
- """THttpClient supports two different types constructor parameters.
-
- THttpClient(host, port, path) - deprecated
- THttpClient(uri)
-
- Only the second supports https."""
-
- if port is not None:
- warnings.warn("Please use the THttpClient('http://host:port/path') syntax", DeprecationWarning, stacklevel=2)
- self.host = uri_or_host
- self.port = port
- assert path
- self.path = path
- self.scheme = 'http'
- else:
- parsed = urlparse.urlparse(uri_or_host)
- self.scheme = parsed.scheme
- assert self.scheme in ('http', 'https')
- if self.scheme == 'http':
- self.port = parsed.port or httplib.HTTP_PORT
- elif self.scheme == 'https':
- self.port = parsed.port or httplib.HTTPS_PORT
- self.host = parsed.hostname
- self.path = parsed.path
- if parsed.query:
- self.path += '?%s' % parsed.query
- self.__wbuf = StringIO()
- self.__http = None
- self.__timeout = None
-
- def open(self):
- if self.scheme == 'http':
- self.__http = httplib.HTTP(self.host, self.port)
- else:
- self.__http = httplib.HTTPS(self.host, self.port)
-
- def close(self):
- self.__http.close()
- self.__http = None
-
- def isOpen(self):
- return self.__http != None
-
- def setTimeout(self, ms):
- if not hasattr(socket, 'getdefaulttimeout'):
- raise NotImplementedError
-
- if ms is None:
- self.__timeout = None
- else:
- self.__timeout = ms/1000.0
-
- def read(self, sz):
- return self.__http.file.read(sz)
-
- def write(self, buf):
- self.__wbuf.write(buf)
-
- def __withTimeout(f):
- def _f(*args, **kwargs):
- orig_timeout = socket.getdefaulttimeout()
- socket.setdefaulttimeout(args[0].__timeout)
- result = f(*args, **kwargs)
- socket.setdefaulttimeout(orig_timeout)
- return result
- return _f
-
- def flush(self):
- if self.isOpen():
- self.close()
- self.open();
-
- # Pull data out of buffer
- data = self.__wbuf.getvalue()
- self.__wbuf = StringIO()
-
- # HTTP request
- self.__http.putrequest('POST', self.path)
-
- # Write headers
- self.__http.putheader('Host', self.host)
- self.__http.putheader('Content-Type', 'application/x-thrift')
- self.__http.putheader('Content-Length', str(len(data)))
- self.__http.endheaders()
-
- # Write payload
- self.__http.send(data)
-
- # Get reply to flush the request
- self.code, self.message, self.headers = self.__http.getreply()
-
- # Decorate if we know how to timeout
- if hasattr(socket, 'getdefaulttimeout'):
- flush = __withTimeout(flush)
diff --git a/module/lib/thrift/transport/TSocket.py b/module/lib/thrift/transport/TSocket.py
deleted file mode 100644
index 4e0e1874f..000000000
--- a/module/lib/thrift/transport/TSocket.py
+++ /dev/null
@@ -1,163 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from TTransport import *
-import os
-import errno
-import socket
-import sys
-
-class TSocketBase(TTransportBase):
- def _resolveAddr(self):
- if self._unix_socket is not None:
- return [(socket.AF_UNIX, socket.SOCK_STREAM, None, None, self._unix_socket)]
- else:
- return socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE | socket.AI_ADDRCONFIG)
-
- def close(self):
- if self.handle:
- self.handle.close()
- self.handle = None
-
-class TSocket(TSocketBase):
- """Socket implementation of TTransport base."""
-
- def __init__(self, host='localhost', port=9090, unix_socket=None):
- """Initialize a TSocket
-
- @param host(str) The host to connect to.
- @param port(int) The (TCP) port to connect to.
- @param unix_socket(str) The filename of a unix socket to connect to.
- (host and port will be ignored.)
- """
-
- self.host = host
- self.port = port
- self.handle = None
- self._unix_socket = unix_socket
- self._timeout = None
-
- def setHandle(self, h):
- self.handle = h
-
- def isOpen(self):
- return self.handle is not None
-
- def setTimeout(self, ms):
- if ms is None:
- self._timeout = None
- else:
- self._timeout = ms/1000.0
-
- if self.handle is not None:
- self.handle.settimeout(self._timeout)
-
- def open(self):
- try:
- res0 = self._resolveAddr()
- for res in res0:
- self.handle = socket.socket(res[0], res[1])
- self.handle.settimeout(self._timeout)
- try:
- self.handle.connect(res[4])
- except socket.error, e:
- if res is not res0[-1]:
- continue
- else:
- raise e
- break
- except socket.error, e:
- if self._unix_socket:
- message = 'Could not connect to socket %s' % self._unix_socket
- else:
- message = 'Could not connect to %s:%d' % (self.host, self.port)
- raise TTransportException(type=TTransportException.NOT_OPEN, message=message)
-
- def read(self, sz):
- try:
- buff = self.handle.recv(sz)
- except socket.error, e:
- if (e.args[0] == errno.ECONNRESET and
- (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
- # freebsd and Mach don't follow POSIX semantic of recv
- # and fail with ECONNRESET if peer performed shutdown.
- # See corresponding comment and code in TSocket::read()
- # in lib/cpp/src/transport/TSocket.cpp.
- self.close()
- # Trigger the check to raise the END_OF_FILE exception below.
- buff = ''
- else:
- raise
- if len(buff) == 0:
- raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes')
- return buff
-
- def write(self, buff):
- if not self.handle:
- raise TTransportException(type=TTransportException.NOT_OPEN, message='Transport not open')
- sent = 0
- have = len(buff)
- while sent < have:
- plus = self.handle.send(buff)
- if plus == 0:
- raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket sent 0 bytes')
- sent += plus
- buff = buff[plus:]
-
- def flush(self):
- pass
-
-class TServerSocket(TSocketBase, TServerTransportBase):
- """Socket implementation of TServerTransport base."""
-
- def __init__(self, host=None, port=9090, unix_socket=None):
- self.host = host
- self.port = port
- self._unix_socket = unix_socket
- self.handle = None
-
- def listen(self):
- res0 = self._resolveAddr()
- for res in res0:
- if res[0] is socket.AF_INET6 or res is res0[-1]:
- break
-
- # We need remove the old unix socket if the file exists and
- # nobody is listening on it.
- if self._unix_socket:
- tmp = socket.socket(res[0], res[1])
- try:
- tmp.connect(res[4])
- except socket.error, err:
- eno, message = err.args
- if eno == errno.ECONNREFUSED:
- os.unlink(res[4])
-
- self.handle = socket.socket(res[0], res[1])
- self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- if hasattr(self.handle, 'settimeout'):
- self.handle.settimeout(None)
- self.handle.bind(res[4])
- self.handle.listen(128)
-
- def accept(self):
- client, addr = self.handle.accept()
- result = TSocket()
- result.setHandle(client)
- return result
diff --git a/module/lib/thrift/transport/TTransport.py b/module/lib/thrift/transport/TTransport.py
deleted file mode 100644
index 12e51a9bf..000000000
--- a/module/lib/thrift/transport/TTransport.py
+++ /dev/null
@@ -1,331 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-from cStringIO import StringIO
-from struct import pack,unpack
-from thrift.Thrift import TException
-
-class TTransportException(TException):
-
- """Custom Transport Exception class"""
-
- UNKNOWN = 0
- NOT_OPEN = 1
- ALREADY_OPEN = 2
- TIMED_OUT = 3
- END_OF_FILE = 4
-
- def __init__(self, type=UNKNOWN, message=None):
- TException.__init__(self, message)
- self.type = type
-
-class TTransportBase:
-
- """Base class for Thrift transport layer."""
-
- def isOpen(self):
- pass
-
- def open(self):
- pass
-
- def close(self):
- pass
-
- def read(self, sz):
- pass
-
- def readAll(self, sz):
- buff = ''
- have = 0
- while (have < sz):
- chunk = self.read(sz-have)
- have += len(chunk)
- buff += chunk
-
- if len(chunk) == 0:
- raise EOFError()
-
- return buff
-
- def write(self, buf):
- pass
-
- def flush(self):
- pass
-
-# This class should be thought of as an interface.
-class CReadableTransport:
- """base class for transports that are readable from C"""
-
- # TODO(dreiss): Think about changing this interface to allow us to use
- # a (Python, not c) StringIO instead, because it allows
- # you to write after reading.
-
- # NOTE: This is a classic class, so properties will NOT work
- # correctly for setting.
- @property
- def cstringio_buf(self):
- """A cStringIO buffer that contains the current chunk we are reading."""
- pass
-
- def cstringio_refill(self, partialread, reqlen):
- """Refills cstringio_buf.
-
- Returns the currently used buffer (which can but need not be the same as
- the old cstringio_buf). partialread is what the C code has read from the
- buffer, and should be inserted into the buffer before any more reads. The
- return value must be a new, not borrowed reference. Something along the
- lines of self._buf should be fine.
-
- If reqlen bytes can't be read, throw EOFError.
- """
- pass
-
-class TServerTransportBase:
-
- """Base class for Thrift server transports."""
-
- def listen(self):
- pass
-
- def accept(self):
- pass
-
- def close(self):
- pass
-
-class TTransportFactoryBase:
-
- """Base class for a Transport Factory"""
-
- def getTransport(self, trans):
- return trans
-
-class TBufferedTransportFactory:
-
- """Factory transport that builds buffered transports"""
-
- def getTransport(self, trans):
- buffered = TBufferedTransport(trans)
- return buffered
-
-
-class TBufferedTransport(TTransportBase,CReadableTransport):
-
- """Class that wraps another transport and buffers its I/O.
-
- The implementation uses a (configurable) fixed-size read buffer
- but buffers all writes until a flush is performed.
- """
-
- DEFAULT_BUFFER = 4096
-
- def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
- self.__trans = trans
- self.__wbuf = StringIO()
- self.__rbuf = StringIO("")
- self.__rbuf_size = rbuf_size
-
- def isOpen(self):
- return self.__trans.isOpen()
-
- def open(self):
- return self.__trans.open()
-
- def close(self):
- return self.__trans.close()
-
- def read(self, sz):
- ret = self.__rbuf.read(sz)
- if len(ret) != 0:
- return ret
-
- self.__rbuf = StringIO(self.__trans.read(max(sz, self.__rbuf_size)))
- return self.__rbuf.read(sz)
-
- def write(self, buf):
- self.__wbuf.write(buf)
-
- def flush(self):
- out = self.__wbuf.getvalue()
- # reset wbuf before write/flush to preserve state on underlying failure
- self.__wbuf = StringIO()
- self.__trans.write(out)
- self.__trans.flush()
-
- # Implement the CReadableTransport interface.
- @property
- def cstringio_buf(self):
- return self.__rbuf
-
- def cstringio_refill(self, partialread, reqlen):
- retstring = partialread
- if reqlen < self.__rbuf_size:
- # try to make a read of as much as we can.
- retstring += self.__trans.read(self.__rbuf_size)
-
- # but make sure we do read reqlen bytes.
- if len(retstring) < reqlen:
- retstring += self.__trans.readAll(reqlen - len(retstring))
-
- self.__rbuf = StringIO(retstring)
- return self.__rbuf
-
-class TMemoryBuffer(TTransportBase, CReadableTransport):
- """Wraps a cStringIO object as a TTransport.
-
- NOTE: Unlike the C++ version of this class, you cannot write to it
- then immediately read from it. If you want to read from a
- TMemoryBuffer, you must either pass a string to the constructor.
- TODO(dreiss): Make this work like the C++ version.
- """
-
- def __init__(self, value=None):
- """value -- a value to read from for stringio
-
- If value is set, this will be a transport for reading,
- otherwise, it is for writing"""
- if value is not None:
- self._buffer = StringIO(value)
- else:
- self._buffer = StringIO()
-
- def isOpen(self):
- return not self._buffer.closed
-
- def open(self):
- pass
-
- def close(self):
- self._buffer.close()
-
- def read(self, sz):
- return self._buffer.read(sz)
-
- def write(self, buf):
- self._buffer.write(buf)
-
- def flush(self):
- pass
-
- def getvalue(self):
- return self._buffer.getvalue()
-
- # Implement the CReadableTransport interface.
- @property
- def cstringio_buf(self):
- return self._buffer
-
- def cstringio_refill(self, partialread, reqlen):
- # only one shot at reading...
- raise EOFError()
-
-class TFramedTransportFactory:
-
- """Factory transport that builds framed transports"""
-
- def getTransport(self, trans):
- framed = TFramedTransport(trans)
- return framed
-
-
-class TFramedTransport(TTransportBase, CReadableTransport):
-
- """Class that wraps another transport and frames its I/O when writing."""
-
- def __init__(self, trans,):
- self.__trans = trans
- self.__rbuf = StringIO()
- self.__wbuf = StringIO()
-
- def isOpen(self):
- return self.__trans.isOpen()
-
- def open(self):
- return self.__trans.open()
-
- def close(self):
- return self.__trans.close()
-
- def read(self, sz):
- ret = self.__rbuf.read(sz)
- if len(ret) != 0:
- return ret
-
- self.readFrame()
- return self.__rbuf.read(sz)
-
- def readFrame(self):
- buff = self.__trans.readAll(4)
- sz, = unpack('!i', buff)
- self.__rbuf = StringIO(self.__trans.readAll(sz))
-
- def write(self, buf):
- self.__wbuf.write(buf)
-
- def flush(self):
- wout = self.__wbuf.getvalue()
- wsz = len(wout)
- # reset wbuf before write/flush to preserve state on underlying failure
- self.__wbuf = StringIO()
- # N.B.: Doing this string concatenation is WAY cheaper than making
- # two separate calls to the underlying socket object. Socket writes in
- # Python turn out to be REALLY expensive, but it seems to do a pretty
- # good job of managing string buffer operations without excessive copies
- buf = pack("!i", wsz) + wout
- self.__trans.write(buf)
- self.__trans.flush()
-
- # Implement the CReadableTransport interface.
- @property
- def cstringio_buf(self):
- return self.__rbuf
-
- def cstringio_refill(self, prefix, reqlen):
- # self.__rbuf will already be empty here because fastbinary doesn't
- # ask for a refill until the previous buffer is empty. Therefore,
- # we can start reading new frames immediately.
- while len(prefix) < reqlen:
- self.readFrame()
- prefix += self.__rbuf.getvalue()
- self.__rbuf = StringIO(prefix)
- return self.__rbuf
-
-
-class TFileObjectTransport(TTransportBase):
- """Wraps a file-like object to make it work as a Thrift transport."""
-
- def __init__(self, fileobj):
- self.fileobj = fileobj
-
- def isOpen(self):
- return True
-
- def close(self):
- self.fileobj.close()
-
- def read(self, sz):
- return self.fileobj.read(sz)
-
- def write(self, buf):
- self.fileobj.write(buf)
-
- def flush(self):
- self.fileobj.flush()
diff --git a/module/lib/thrift/transport/TTwisted.py b/module/lib/thrift/transport/TTwisted.py
deleted file mode 100644
index b6dcb4e0b..000000000
--- a/module/lib/thrift/transport/TTwisted.py
+++ /dev/null
@@ -1,219 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-from zope.interface import implements, Interface, Attribute
-from twisted.internet.protocol import Protocol, ServerFactory, ClientFactory, \
- connectionDone
-from twisted.internet import defer
-from twisted.protocols import basic
-from twisted.python import log
-from twisted.web import server, resource, http
-
-from thrift.transport import TTransport
-from cStringIO import StringIO
-
-
-class TMessageSenderTransport(TTransport.TTransportBase):
-
- def __init__(self):
- self.__wbuf = StringIO()
-
- def write(self, buf):
- self.__wbuf.write(buf)
-
- def flush(self):
- msg = self.__wbuf.getvalue()
- self.__wbuf = StringIO()
- self.sendMessage(msg)
-
- def sendMessage(self, message):
- raise NotImplementedError
-
-
-class TCallbackTransport(TMessageSenderTransport):
-
- def __init__(self, func):
- TMessageSenderTransport.__init__(self)
- self.func = func
-
- def sendMessage(self, message):
- self.func(message)
-
-
-class ThriftClientProtocol(basic.Int32StringReceiver):
-
- MAX_LENGTH = 2 ** 31 - 1
-
- def __init__(self, client_class, iprot_factory, oprot_factory=None):
- self._client_class = client_class
- self._iprot_factory = iprot_factory
- if oprot_factory is None:
- self._oprot_factory = iprot_factory
- else:
- self._oprot_factory = oprot_factory
-
- self.recv_map = {}
- self.started = defer.Deferred()
-
- def dispatch(self, msg):
- self.sendString(msg)
-
- def connectionMade(self):
- tmo = TCallbackTransport(self.dispatch)
- self.client = self._client_class(tmo, self._oprot_factory)
- self.started.callback(self.client)
-
- def connectionLost(self, reason=connectionDone):
- for k,v in self.client._reqs.iteritems():
- tex = TTransport.TTransportException(
- type=TTransport.TTransportException.END_OF_FILE,
- message='Connection closed')
- v.errback(tex)
-
- def stringReceived(self, frame):
- tr = TTransport.TMemoryBuffer(frame)
- iprot = self._iprot_factory.getProtocol(tr)
- (fname, mtype, rseqid) = iprot.readMessageBegin()
-
- try:
- method = self.recv_map[fname]
- except KeyError:
- method = getattr(self.client, 'recv_' + fname)
- self.recv_map[fname] = method
-
- method(iprot, mtype, rseqid)
-
-
-class ThriftServerProtocol(basic.Int32StringReceiver):
-
- MAX_LENGTH = 2 ** 31 - 1
-
- def dispatch(self, msg):
- self.sendString(msg)
-
- def processError(self, error):
- self.transport.loseConnection()
-
- def processOk(self, _, tmo):
- msg = tmo.getvalue()
-
- if len(msg) > 0:
- self.dispatch(msg)
-
- def stringReceived(self, frame):
- tmi = TTransport.TMemoryBuffer(frame)
- tmo = TTransport.TMemoryBuffer()
-
- iprot = self.factory.iprot_factory.getProtocol(tmi)
- oprot = self.factory.oprot_factory.getProtocol(tmo)
-
- d = self.factory.processor.process(iprot, oprot)
- d.addCallbacks(self.processOk, self.processError,
- callbackArgs=(tmo,))
-
-
-class IThriftServerFactory(Interface):
-
- processor = Attribute("Thrift processor")
-
- iprot_factory = Attribute("Input protocol factory")
-
- oprot_factory = Attribute("Output protocol factory")
-
-
-class IThriftClientFactory(Interface):
-
- client_class = Attribute("Thrift client class")
-
- iprot_factory = Attribute("Input protocol factory")
-
- oprot_factory = Attribute("Output protocol factory")
-
-
-class ThriftServerFactory(ServerFactory):
-
- implements(IThriftServerFactory)
-
- protocol = ThriftServerProtocol
-
- def __init__(self, processor, iprot_factory, oprot_factory=None):
- self.processor = processor
- self.iprot_factory = iprot_factory
- if oprot_factory is None:
- self.oprot_factory = iprot_factory
- else:
- self.oprot_factory = oprot_factory
-
-
-class ThriftClientFactory(ClientFactory):
-
- implements(IThriftClientFactory)
-
- protocol = ThriftClientProtocol
-
- def __init__(self, client_class, iprot_factory, oprot_factory=None):
- self.client_class = client_class
- self.iprot_factory = iprot_factory
- if oprot_factory is None:
- self.oprot_factory = iprot_factory
- else:
- self.oprot_factory = oprot_factory
-
- def buildProtocol(self, addr):
- p = self.protocol(self.client_class, self.iprot_factory,
- self.oprot_factory)
- p.factory = self
- return p
-
-
-class ThriftResource(resource.Resource):
-
- allowedMethods = ('POST',)
-
- def __init__(self, processor, inputProtocolFactory,
- outputProtocolFactory=None):
- resource.Resource.__init__(self)
- self.inputProtocolFactory = inputProtocolFactory
- if outputProtocolFactory is None:
- self.outputProtocolFactory = inputProtocolFactory
- else:
- self.outputProtocolFactory = outputProtocolFactory
- self.processor = processor
-
- def getChild(self, path, request):
- return self
-
- def _cbProcess(self, _, request, tmo):
- msg = tmo.getvalue()
- request.setResponseCode(http.OK)
- request.setHeader("content-type", "application/x-thrift")
- request.write(msg)
- request.finish()
-
- def render_POST(self, request):
- request.content.seek(0, 0)
- data = request.content.read()
- tmi = TTransport.TMemoryBuffer(data)
- tmo = TTransport.TMemoryBuffer()
-
- iprot = self.inputProtocolFactory.getProtocol(tmi)
- oprot = self.outputProtocolFactory.getProtocol(tmo)
-
- d = self.processor.process(iprot, oprot)
- d.addCallback(self._cbProcess, request, tmo)
- return server.NOT_DONE_YET
diff --git a/module/lib/thrift/transport/TZlibTransport.py b/module/lib/thrift/transport/TZlibTransport.py
deleted file mode 100644
index 784d4e1e0..000000000
--- a/module/lib/thrift/transport/TZlibTransport.py
+++ /dev/null
@@ -1,261 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-'''
-TZlibTransport provides a compressed transport and transport factory
-class, using the python standard library zlib module to implement
-data compression.
-'''
-
-from __future__ import division
-import zlib
-from cStringIO import StringIO
-from TTransport import TTransportBase, CReadableTransport
-
-class TZlibTransportFactory(object):
- '''
- Factory transport that builds zlib compressed transports.
-
- This factory caches the last single client/transport that it was passed
- and returns the same TZlibTransport object that was created.
-
- This caching means the TServer class will get the _same_ transport
- object for both input and output transports from this factory.
- (For non-threaded scenarios only, since the cache only holds one object)
-
- The purpose of this caching is to allocate only one TZlibTransport where
- only one is really needed (since it must have separate read/write buffers),
- and makes the statistics from getCompSavings() and getCompRatio()
- easier to understand.
- '''
-
- # class scoped cache of last transport given and zlibtransport returned
- _last_trans = None
- _last_z = None
-
- def getTransport(self, trans, compresslevel=9):
- '''Wrap a transport , trans, with the TZlibTransport
- compressed transport class, returning a new
- transport to the caller.
-
- @param compresslevel: The zlib compression level, ranging
- from 0 (no compression) to 9 (best compression). Defaults to 9.
- @type compresslevel: int
-
- This method returns a TZlibTransport which wraps the
- passed C{trans} TTransport derived instance.
- '''
- if trans == self._last_trans:
- return self._last_z
- ztrans = TZlibTransport(trans, compresslevel)
- self._last_trans = trans
- self._last_z = ztrans
- return ztrans
-
-
-class TZlibTransport(TTransportBase, CReadableTransport):
- '''
- Class that wraps a transport with zlib, compressing writes
- and decompresses reads, using the python standard
- library zlib module.
- '''
-
- # Read buffer size for the python fastbinary C extension,
- # the TBinaryProtocolAccelerated class.
- DEFAULT_BUFFSIZE = 4096
-
- def __init__(self, trans, compresslevel=9):
- '''
- Create a new TZlibTransport, wrapping C{trans}, another
- TTransport derived object.
-
- @param trans: A thrift transport object, i.e. a TSocket() object.
- @type trans: TTransport
- @param compresslevel: The zlib compression level, ranging
- from 0 (no compression) to 9 (best compression). Default is 9.
- @type compresslevel: int
- '''
- self.__trans = trans
- self.compresslevel = compresslevel
- self.__rbuf = StringIO()
- self.__wbuf = StringIO()
- self._init_zlib()
- self._init_stats()
-
- def _reinit_buffers(self):
- '''
- Internal method to initialize/reset the internal StringIO objects
- for read and write buffers.
- '''
- self.__rbuf = StringIO()
- self.__wbuf = StringIO()
-
- def _init_stats(self):
- '''
- Internal method to reset the internal statistics counters
- for compression ratios and bandwidth savings.
- '''
- self.bytes_in = 0
- self.bytes_out = 0
- self.bytes_in_comp = 0
- self.bytes_out_comp = 0
-
- def _init_zlib(self):
- '''
- Internal method for setting up the zlib compression and
- decompression objects.
- '''
- self._zcomp_read = zlib.decompressobj()
- self._zcomp_write = zlib.compressobj(self.compresslevel)
-
- def getCompRatio(self):
- '''
- Get the current measured compression ratios (in,out) from
- this transport.
-
- Returns a tuple of:
- (inbound_compression_ratio, outbound_compression_ratio)
-
- The compression ratios are computed as:
- compressed / uncompressed
-
- E.g., data that compresses by 10x will have a ratio of: 0.10
- and data that compresses to half of ts original size will
- have a ratio of 0.5
-
- None is returned if no bytes have yet been processed in
- a particular direction.
- '''
- r_percent, w_percent = (None, None)
- if self.bytes_in > 0:
- r_percent = self.bytes_in_comp / self.bytes_in
- if self.bytes_out > 0:
- w_percent = self.bytes_out_comp / self.bytes_out
- return (r_percent, w_percent)
-
- def getCompSavings(self):
- '''
- Get the current count of saved bytes due to data
- compression.
-
- Returns a tuple of:
- (inbound_saved_bytes, outbound_saved_bytes)
-
- Note: if compression is actually expanding your
- data (only likely with very tiny thrift objects), then
- the values returned will be negative.
- '''
- r_saved = self.bytes_in - self.bytes_in_comp
- w_saved = self.bytes_out - self.bytes_out_comp
- return (r_saved, w_saved)
-
- def isOpen(self):
- '''Return the underlying transport's open status'''
- return self.__trans.isOpen()
-
- def open(self):
- """Open the underlying transport"""
- self._init_stats()
- return self.__trans.open()
-
- def listen(self):
- '''Invoke the underlying transport's listen() method'''
- self.__trans.listen()
-
- def accept(self):
- '''Accept connections on the underlying transport'''
- return self.__trans.accept()
-
- def close(self):
- '''Close the underlying transport,'''
- self._reinit_buffers()
- self._init_zlib()
- return self.__trans.close()
-
- def read(self, sz):
- '''
- Read up to sz bytes from the decompressed bytes buffer, and
- read from the underlying transport if the decompression
- buffer is empty.
- '''
- ret = self.__rbuf.read(sz)
- if len(ret) > 0:
- return ret
- # keep reading from transport until something comes back
- while True:
- if self.readComp(sz):
- break
- ret = self.__rbuf.read(sz)
- return ret
-
- def readComp(self, sz):
- '''
- Read compressed data from the underlying transport, then
- decompress it and append it to the internal StringIO read buffer
- '''
- zbuf = self.__trans.read(sz)
- zbuf = self._zcomp_read.unconsumed_tail + zbuf
- buf = self._zcomp_read.decompress(zbuf)
- self.bytes_in += len(zbuf)
- self.bytes_in_comp += len(buf)
- old = self.__rbuf.read()
- self.__rbuf = StringIO(old + buf)
- if len(old) + len(buf) == 0:
- return False
- return True
-
- def write(self, buf):
- '''
- Write some bytes, putting them into the internal write
- buffer for eventual compression.
- '''
- self.__wbuf.write(buf)
-
- def flush(self):
- '''
- Flush any queued up data in the write buffer and ensure the
- compression buffer is flushed out to the underlying transport
- '''
- wout = self.__wbuf.getvalue()
- if len(wout) > 0:
- zbuf = self._zcomp_write.compress(wout)
- self.bytes_out += len(wout)
- self.bytes_out_comp += len(zbuf)
- else:
- zbuf = ''
- ztail = self._zcomp_write.flush(zlib.Z_SYNC_FLUSH)
- self.bytes_out_comp += len(ztail)
- if (len(zbuf) + len(ztail)) > 0:
- self.__wbuf = StringIO()
- self.__trans.write(zbuf + ztail)
- self.__trans.flush()
-
- @property
- def cstringio_buf(self):
- '''Implement the CReadableTransport interface'''
- return self.__rbuf
-
- def cstringio_refill(self, partialread, reqlen):
- '''Implement the CReadableTransport interface for refill'''
- retstring = partialread
- if reqlen < self.DEFAULT_BUFFSIZE:
- retstring += self.read(self.DEFAULT_BUFFSIZE)
- while len(retstring) < reqlen:
- retstring += self.read(reqlen - len(retstring))
- self.__rbuf = StringIO(retstring)
- return self.__rbuf
diff --git a/module/lib/thrift/transport/__init__.py b/module/lib/thrift/transport/__init__.py
deleted file mode 100644
index 46e54fe6b..000000000
--- a/module/lib/thrift/transport/__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-__all__ = ['TTransport', 'TSocket', 'THttpClient','TZlibTransport']
diff --git a/module/lib/wsgiserver/LICENSE.txt b/module/lib/wsgiserver/LICENSE.txt
deleted file mode 100644
index a15165ee2..000000000
--- a/module/lib/wsgiserver/LICENSE.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-Copyright (c) 2004-2007, CherryPy Team (team@cherrypy.org)
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * Neither the name of the CherryPy Team nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/module/lib/wsgiserver/__init__.py b/module/lib/wsgiserver/__init__.py
deleted file mode 100644
index c380e18b0..000000000
--- a/module/lib/wsgiserver/__init__.py
+++ /dev/null
@@ -1,1794 +0,0 @@
-"""A high-speed, production ready, thread pooled, generic WSGI server.
-
-Simplest example on how to use this module directly
-(without using CherryPy's application machinery):
-
- from cherrypy import wsgiserver
-
- def my_crazy_app(environ, start_response):
- status = '200 OK'
- response_headers = [('Content-type','text/plain')]
- start_response(status, response_headers)
- return ['Hello world!\n']
-
- server = wsgiserver.CherryPyWSGIServer(
- ('0.0.0.0', 8070), my_crazy_app,
- server_name='www.cherrypy.example')
-
-The CherryPy WSGI server can serve as many WSGI applications
-as you want in one instance by using a WSGIPathInfoDispatcher:
-
- d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
- server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
-
-Want SSL support? Just set these attributes:
-
- server.ssl_certificate = <filename>
- server.ssl_private_key = <filename>
-
- if __name__ == '__main__':
- try:
- server.start()
- except KeyboardInterrupt:
- server.stop()
-
-This won't call the CherryPy engine (application side) at all, only the
-WSGI server, which is independant from the rest of CherryPy. Don't
-let the name "CherryPyWSGIServer" throw you; the name merely reflects
-its origin, not its coupling.
-
-For those of you wanting to understand internals of this module, here's the
-basic call flow. The server's listening thread runs a very tight loop,
-sticking incoming connections onto a Queue:
-
- server = CherryPyWSGIServer(...)
- server.start()
- while True:
- tick()
- # This blocks until a request comes in:
- child = socket.accept()
- conn = HTTPConnection(child, ...)
- server.requests.put(conn)
-
-Worker threads are kept in a pool and poll the Queue, popping off and then
-handling each connection in turn. Each connection can consist of an arbitrary
-number of requests and their responses, so we run a nested loop:
-
- while True:
- conn = server.requests.get()
- conn.communicate()
- -> while True:
- req = HTTPRequest(...)
- req.parse_request()
- -> # Read the Request-Line, e.g. "GET /page HTTP/1.1"
- req.rfile.readline()
- req.read_headers()
- req.respond()
- -> response = wsgi_app(...)
- try:
- for chunk in response:
- if chunk:
- req.write(chunk)
- finally:
- if hasattr(response, "close"):
- response.close()
- if req.close_connection:
- return
-"""
-
-
-import base64
-import os
-import Queue
-import re
-quoted_slash = re.compile("(?i)%2F")
-import rfc822
-import socket
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
-_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
-
-import sys
-import threading
-import time
-import traceback
-from urllib import unquote
-from urlparse import urlparse
-import warnings
-
-try:
- from OpenSSL import SSL
- from OpenSSL import crypto
-except ImportError:
- SSL = None
-
-import errno
-
-def plat_specific_errors(*errnames):
- """Return error numbers for all errors in errnames on this platform.
-
- The 'errno' module contains different global constants depending on
- the specific platform (OS). This function will return the list of
- numeric values for a given list of potential names.
- """
- errno_names = dir(errno)
- nums = [getattr(errno, k) for k in errnames if k in errno_names]
- # de-dupe the list
- return dict.fromkeys(nums).keys()
-
-socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
-
-socket_errors_to_ignore = plat_specific_errors(
- "EPIPE",
- "EBADF", "WSAEBADF",
- "ENOTSOCK", "WSAENOTSOCK",
- "ETIMEDOUT", "WSAETIMEDOUT",
- "ECONNREFUSED", "WSAECONNREFUSED",
- "ECONNRESET", "WSAECONNRESET",
- "ECONNABORTED", "WSAECONNABORTED",
- "ENETRESET", "WSAENETRESET",
- "EHOSTDOWN", "EHOSTUNREACH",
- )
-socket_errors_to_ignore.append("timed out")
-
-socket_errors_nonblocking = plat_specific_errors(
- 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
-
-comma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING',
- 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL',
- 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT',
- 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE',
- 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING',
- 'WWW-AUTHENTICATE']
-
-
-class WSGIPathInfoDispatcher(object):
- """A WSGI dispatcher for dispatch based on the PATH_INFO.
-
- apps: a dict or list of (path_prefix, app) pairs.
- """
-
- def __init__(self, apps):
- try:
- apps = apps.items()
- except AttributeError:
- pass
-
- # Sort the apps by len(path), descending
- apps.sort()
- apps.reverse()
-
- # The path_prefix strings must start, but not end, with a slash.
- # Use "" instead of "/".
- self.apps = [(p.rstrip("/"), a) for p, a in apps]
-
- def __call__(self, environ, start_response):
- path = environ["PATH_INFO"] or "/"
- for p, app in self.apps:
- # The apps list should be sorted by length, descending.
- if path.startswith(p + "/") or path == p:
- environ = environ.copy()
- environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
- environ["PATH_INFO"] = path[len(p):]
- return app(environ, start_response)
-
- start_response('404 Not Found', [('Content-Type', 'text/plain'),
- ('Content-Length', '0')])
- return ['']
-
-
-class MaxSizeExceeded(Exception):
- pass
-
-class SizeCheckWrapper(object):
- """Wraps a file-like object, raising MaxSizeExceeded if too large."""
-
- def __init__(self, rfile, maxlen):
- self.rfile = rfile
- self.maxlen = maxlen
- self.bytes_read = 0
-
- def _check_length(self):
- if self.maxlen and self.bytes_read > self.maxlen:
- raise MaxSizeExceeded()
-
- def read(self, size=None):
- data = self.rfile.read(size)
- self.bytes_read += len(data)
- self._check_length()
- return data
-
- def readline(self, size=None):
- if size is not None:
- data = self.rfile.readline(size)
- self.bytes_read += len(data)
- self._check_length()
- return data
-
- # User didn't specify a size ...
- # We read the line in chunks to make sure it's not a 100MB line !
- res = []
- while True:
- data = self.rfile.readline(256)
- self.bytes_read += len(data)
- self._check_length()
- res.append(data)
- # See http://www.cherrypy.org/ticket/421
- if len(data) < 256 or data[-1:] == "\n":
- return ''.join(res)
-
- def readlines(self, sizehint=0):
- # Shamelessly stolen from StringIO
- total = 0
- lines = []
- line = self.readline()
- while line:
- lines.append(line)
- total += len(line)
- if 0 < sizehint <= total:
- break
- line = self.readline()
- return lines
-
- def close(self):
- self.rfile.close()
-
- def __iter__(self):
- return self
-
- def next(self):
- data = self.rfile.next()
- self.bytes_read += len(data)
- self._check_length()
- return data
-
-
-class HTTPRequest(object):
- """An HTTP Request (and response).
-
- A single HTTP connection may consist of multiple request/response pairs.
-
- send: the 'send' method from the connection's socket object.
- wsgi_app: the WSGI application to call.
- environ: a partial WSGI environ (server and connection entries).
- The caller MUST set the following entries:
- * All wsgi.* entries, including .input
- * SERVER_NAME and SERVER_PORT
- * Any SSL_* entries
- * Any custom entries like REMOTE_ADDR and REMOTE_PORT
- * SERVER_SOFTWARE: the value to write in the "Server" response header.
- * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of
- the response. From RFC 2145: "An HTTP server SHOULD send a
- response version equal to the highest version for which the
- server is at least conditionally compliant, and whose major
- version is less than or equal to the one received in the
- request. An HTTP server MUST NOT send a version for which
- it is not at least conditionally compliant."
-
- outheaders: a list of header tuples to write in the response.
- ready: when True, the request has been parsed and is ready to begin
- generating the response. When False, signals the calling Connection
- that the response should not be generated and the connection should
- close.
- close_connection: signals the calling Connection that the request
- should close. This does not imply an error! The client and/or
- server may each request that the connection be closed.
- chunked_write: if True, output will be encoded with the "chunked"
- transfer-coding. This value is set automatically inside
- send_headers.
- """
-
- max_request_header_size = 0
- max_request_body_size = 0
-
- def __init__(self, wfile, environ, wsgi_app):
- self.rfile = environ['wsgi.input']
- self.wfile = wfile
- self.environ = environ.copy()
- self.wsgi_app = wsgi_app
-
- self.ready = False
- self.started_response = False
- self.status = ""
- self.outheaders = []
- self.sent_headers = False
- self.close_connection = False
- self.chunked_write = False
-
- def parse_request(self):
- """Parse the next HTTP request start-line and message-headers."""
- self.rfile.maxlen = self.max_request_header_size
- self.rfile.bytes_read = 0
-
- try:
- self._parse_request()
- except MaxSizeExceeded:
- self.simple_response("413 Request Entity Too Large")
- return
-
- def _parse_request(self):
- # HTTP/1.1 connections are persistent by default. If a client
- # requests a page, then idles (leaves the connection open),
- # then rfile.readline() will raise socket.error("timed out").
- # Note that it does this based on the value given to settimeout(),
- # and doesn't need the client to request or acknowledge the close
- # (although your TCP stack might suffer for it: cf Apache's history
- # with FIN_WAIT_2).
- request_line = self.rfile.readline()
- if not request_line:
- # Force self.ready = False so the connection will close.
- self.ready = False
- return
-
- if request_line == "\r\n":
- # RFC 2616 sec 4.1: "...if the server is reading the protocol
- # stream at the beginning of a message and receives a CRLF
- # first, it should ignore the CRLF."
- # But only ignore one leading line! else we enable a DoS.
- request_line = self.rfile.readline()
- if not request_line:
- self.ready = False
- return
-
- environ = self.environ
-
- try:
- method, path, req_protocol = request_line.strip().split(" ", 2)
- except ValueError:
- self.simple_response(400, "Malformed Request-Line")
- return
-
- environ["REQUEST_METHOD"] = method
-
- # path may be an abs_path (including "http://host.domain.tld");
- scheme, location, path, params, qs, frag = urlparse(path)
-
- if frag:
- self.simple_response("400 Bad Request",
- "Illegal #fragment in Request-URI.")
- return
-
- if scheme:
- environ["wsgi.url_scheme"] = scheme
- if params:
- path = path + ";" + params
-
- environ["SCRIPT_NAME"] = ""
-
- # Unquote the path+params (e.g. "/this%20path" -> "this path").
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
- #
- # But note that "...a URI must be separated into its components
- # before the escaped characters within those components can be
- # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
- atoms = [unquote(x) for x in quoted_slash.split(path)]
- path = "%2F".join(atoms)
- environ["PATH_INFO"] = path
-
- # Note that, like wsgiref and most other WSGI servers,
- # we unquote the path but not the query string.
- environ["QUERY_STRING"] = qs
-
- # Compare request and server HTTP protocol versions, in case our
- # server does not support the requested protocol. Limit our output
- # to min(req, server). We want the following output:
- # request server actual written supported response
- # protocol protocol response protocol feature set
- # a 1.0 1.0 1.0 1.0
- # b 1.0 1.1 1.1 1.0
- # c 1.1 1.0 1.0 1.0
- # d 1.1 1.1 1.1 1.1
- # Notice that, in (b), the response will be "HTTP/1.1" even though
- # the client only understands 1.0. RFC 2616 10.5.6 says we should
- # only return 505 if the _major_ version is different.
- rp = int(req_protocol[5]), int(req_protocol[7])
- server_protocol = environ["ACTUAL_SERVER_PROTOCOL"]
- sp = int(server_protocol[5]), int(server_protocol[7])
- if sp[0] != rp[0]:
- self.simple_response("505 HTTP Version Not Supported")
- return
- # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
- environ["SERVER_PROTOCOL"] = req_protocol
- self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-
- # If the Request-URI was an absoluteURI, use its location atom.
- if location:
- environ["SERVER_NAME"] = location
-
- # then all the http headers
- try:
- self.read_headers()
- except ValueError, ex:
- self.simple_response("400 Bad Request", repr(ex.args))
- return
-
- mrbs = self.max_request_body_size
- if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs:
- self.simple_response("413 Request Entity Too Large")
- return
-
- # Persistent connection support
- if self.response_protocol == "HTTP/1.1":
- # Both server and client are HTTP/1.1
- if environ.get("HTTP_CONNECTION", "") == "close":
- self.close_connection = True
- else:
- # Either the server or client (or both) are HTTP/1.0
- if environ.get("HTTP_CONNECTION", "") != "Keep-Alive":
- self.close_connection = True
-
- # Transfer-Encoding support
- te = None
- if self.response_protocol == "HTTP/1.1":
- te = environ.get("HTTP_TRANSFER_ENCODING")
- if te:
- te = [x.strip().lower() for x in te.split(",") if x.strip()]
-
- self.chunked_read = False
-
- if te:
- for enc in te:
- if enc == "chunked":
- self.chunked_read = True
- else:
- # Note that, even if we see "chunked", we must reject
- # if there is an extension we don't recognize.
- self.simple_response("501 Unimplemented")
- self.close_connection = True
- return
-
- # From PEP 333:
- # "Servers and gateways that implement HTTP 1.1 must provide
- # transparent support for HTTP 1.1's "expect/continue" mechanism.
- # This may be done in any of several ways:
- # 1. Respond to requests containing an Expect: 100-continue request
- # with an immediate "100 Continue" response, and proceed normally.
- # 2. Proceed with the request normally, but provide the application
- # with a wsgi.input stream that will send the "100 Continue"
- # response if/when the application first attempts to read from
- # the input stream. The read request must then remain blocked
- # until the client responds.
- # 3. Wait until the client decides that the server does not support
- # expect/continue, and sends the request body on its own.
- # (This is suboptimal, and is not recommended.)
- #
- # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
- # but it seems like it would be a big slowdown for such a rare case.
- if environ.get("HTTP_EXPECT", "") == "100-continue":
- self.simple_response(100)
-
- self.ready = True
-
- def read_headers(self):
- """Read header lines from the incoming stream."""
- environ = self.environ
-
- while True:
- line = self.rfile.readline()
- if not line:
- # No more data--illegal end of headers
- raise ValueError("Illegal end of headers.")
-
- if line == '\r\n':
- # Normal end of headers
- break
-
- if line[0] in ' \t':
- # It's a continuation line.
- v = line.strip()
- else:
- k, v = line.split(":", 1)
- k, v = k.strip().upper(), v.strip()
- envname = "HTTP_" + k.replace("-", "_")
-
- if k in comma_separated_headers:
- existing = environ.get(envname)
- if existing:
- v = ", ".join((existing, v))
- environ[envname] = v
-
- ct = environ.pop("HTTP_CONTENT_TYPE", None)
- if ct is not None:
- environ["CONTENT_TYPE"] = ct
- cl = environ.pop("HTTP_CONTENT_LENGTH", None)
- if cl is not None:
- environ["CONTENT_LENGTH"] = cl
-
- def decode_chunked(self):
- """Decode the 'chunked' transfer coding."""
- cl = 0
- data = StringIO.StringIO()
- while True:
- line = self.rfile.readline().strip().split(";", 1)
- chunk_size = int(line.pop(0), 16)
- if chunk_size <= 0:
- break
-## if line: chunk_extension = line[0]
- cl += chunk_size
- data.write(self.rfile.read(chunk_size))
- crlf = self.rfile.read(2)
- if crlf != "\r\n":
- self.simple_response("400 Bad Request",
- "Bad chunked transfer coding "
- "(expected '\\r\\n', got %r)" % crlf)
- return
-
- # Grab any trailer headers
- self.read_headers()
-
- data.seek(0)
- self.environ["wsgi.input"] = data
- self.environ["CONTENT_LENGTH"] = str(cl) or ""
- return True
-
- def respond(self):
- """Call the appropriate WSGI app and write its iterable output."""
- # Set rfile.maxlen to ensure we don't read past Content-Length.
- # This will also be used to read the entire request body if errors
- # are raised before the app can read the body.
- if self.chunked_read:
- # If chunked, Content-Length will be 0.
- self.rfile.maxlen = self.max_request_body_size
- else:
- cl = int(self.environ.get("CONTENT_LENGTH", 0))
- if self.max_request_body_size:
- self.rfile.maxlen = min(cl, self.max_request_body_size)
- else:
- self.rfile.maxlen = cl
- self.rfile.bytes_read = 0
-
- try:
- self._respond()
- except MaxSizeExceeded:
- if not self.sent_headers:
- self.simple_response("413 Request Entity Too Large")
- return
-
- def _respond(self):
- if self.chunked_read:
- if not self.decode_chunked():
- self.close_connection = True
- return
-
- response = self.wsgi_app(self.environ, self.start_response)
- try:
- for chunk in response:
- # "The start_response callable must not actually transmit
- # the response headers. Instead, it must store them for the
- # server or gateway to transmit only after the first
- # iteration of the application return value that yields
- # a NON-EMPTY string, or upon the application's first
- # invocation of the write() callable." (PEP 333)
- if chunk:
- self.write(chunk)
- finally:
- if hasattr(response, "close"):
- response.close()
-
- if (self.ready and not self.sent_headers):
- self.sent_headers = True
- self.send_headers()
- if self.chunked_write:
- self.wfile.sendall("0\r\n\r\n")
-
- def simple_response(self, status, msg=""):
- """Write a simple response back to the client."""
- status = str(status)
- buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status),
- "Content-Length: %s\r\n" % len(msg),
- "Content-Type: text/plain\r\n"]
-
- if status[:3] == "413" and self.response_protocol == 'HTTP/1.1':
- # Request Entity Too Large
- self.close_connection = True
- buf.append("Connection: close\r\n")
-
- buf.append("\r\n")
- if msg:
- buf.append(msg)
-
- try:
- self.wfile.sendall("".join(buf))
- except socket.error, x:
- if x.args[0] not in socket_errors_to_ignore:
- raise
-
- def start_response(self, status, headers, exc_info = None):
- """WSGI callable to begin the HTTP response."""
- # "The application may call start_response more than once,
- # if and only if the exc_info argument is provided."
- if self.started_response and not exc_info:
- raise AssertionError("WSGI start_response called a second "
- "time with no exc_info.")
-
- # "if exc_info is provided, and the HTTP headers have already been
- # sent, start_response must raise an error, and should raise the
- # exc_info tuple."
- if self.sent_headers:
- try:
- raise exc_info[0], exc_info[1], exc_info[2]
- finally:
- exc_info = None
-
- self.started_response = True
- self.status = status
- self.outheaders.extend(headers)
- return self.write
-
- def write(self, chunk):
- """WSGI callable to write unbuffered data to the client.
-
- This method is also used internally by start_response (to write
- data from the iterable returned by the WSGI application).
- """
- if not self.started_response:
- raise AssertionError("WSGI write called before start_response.")
-
- if not self.sent_headers:
- self.sent_headers = True
- self.send_headers()
-
- if self.chunked_write and chunk:
- buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
- self.wfile.sendall("".join(buf))
- else:
- self.wfile.sendall(chunk)
-
- def send_headers(self):
- """Assert, process, and send the HTTP response message-headers."""
- hkeys = [key.lower() for key, value in self.outheaders]
- status = int(self.status[:3])
-
- if status == 413:
- # Request Entity Too Large. Close conn to avoid garbage.
- self.close_connection = True
- elif "content-length" not in hkeys:
- # "All 1xx (informational), 204 (no content),
- # and 304 (not modified) responses MUST NOT
- # include a message-body." So no point chunking.
- if status < 200 or status in (204, 205, 304):
- pass
- else:
- if (self.response_protocol == 'HTTP/1.1'
- and self.environ["REQUEST_METHOD"] != 'HEAD'):
- # Use the chunked transfer-coding
- self.chunked_write = True
- self.outheaders.append(("Transfer-Encoding", "chunked"))
- else:
- # Closing the conn is the only way to determine len.
- self.close_connection = True
-
- if "connection" not in hkeys:
- if self.response_protocol == 'HTTP/1.1':
- # Both server and client are HTTP/1.1 or better
- if self.close_connection:
- self.outheaders.append(("Connection", "close"))
- else:
- # Server and/or client are HTTP/1.0
- if not self.close_connection:
- self.outheaders.append(("Connection", "Keep-Alive"))
-
- if (not self.close_connection) and (not self.chunked_read):
- # Read any remaining request body data on the socket.
- # "If an origin server receives a request that does not include an
- # Expect request-header field with the "100-continue" expectation,
- # the request includes a request body, and the server responds
- # with a final status code before reading the entire request body
- # from the transport connection, then the server SHOULD NOT close
- # the transport connection until it has read the entire request,
- # or until the client closes the connection. Otherwise, the client
- # might not reliably receive the response message. However, this
- # requirement is not be construed as preventing a server from
- # defending itself against denial-of-service attacks, or from
- # badly broken client implementations."
- size = self.rfile.maxlen - self.rfile.bytes_read
- if size > 0:
- self.rfile.read(size)
-
- if "date" not in hkeys:
- self.outheaders.append(("Date", rfc822.formatdate()))
-
- if "server" not in hkeys:
- self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE']))
-
- buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"]
- try:
- buf += [k + ": " + v + "\r\n" for k, v in self.outheaders]
- except TypeError:
- if not isinstance(k, str):
- raise TypeError("WSGI response header key %r is not a string.")
- if not isinstance(v, str):
- raise TypeError("WSGI response header value %r is not a string.")
- else:
- raise
- buf.append("\r\n")
- self.wfile.sendall("".join(buf))
-
-
-class NoSSLError(Exception):
- """Exception raised when a client speaks HTTP to an HTTPS socket."""
- pass
-
-
-class FatalSSLAlert(Exception):
- """Exception raised when the SSL implementation signals a fatal alert."""
- pass
-
-
-if not _fileobject_uses_str_type:
- class CP_fileobject(socket._fileobject):
- """Faux file object attached to a socket object."""
-
- def sendall(self, data):
- """Sendall for non-blocking sockets."""
- while data:
- try:
- bytes_sent = self.send(data)
- data = data[bytes_sent:]
- except socket.error, e:
- if e.args[0] not in socket_errors_nonblocking:
- raise
-
- def send(self, data):
- return self._sock.send(data)
-
- def flush(self):
- if self._wbuf:
- buffer = "".join(self._wbuf)
- self._wbuf = []
- self.sendall(buffer)
-
- def recv(self, size):
- while True:
- try:
- return self._sock.recv(size)
- except socket.error, e:
- if (e.args[0] not in socket_errors_nonblocking
- and e.args[0] not in socket_error_eintr):
- raise
-
- def read(self, size=-1):
- # Use max, disallow tiny reads in a loop as they are very inefficient.
- # We never leave read() with any leftover data from a new recv() call
- # in our internal buffer.
- rbufsize = max(self._rbufsize, self.default_bufsize)
- # Our use of StringIO rather than lists of string objects returned by
- # recv() minimizes memory usage and fragmentation that occurs when
- # rbufsize is large compared to the typical return value of recv().
- buf = self._rbuf
- buf.seek(0, 2) # seek end
- if size < 0:
- # Read until EOF
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
- while True:
- data = self.recv(rbufsize)
- if not data:
- break
- buf.write(data)
- return buf.getvalue()
- else:
- # Read until size bytes or EOF seen, whichever comes first
- buf_len = buf.tell()
- if buf_len >= size:
- # Already have size bytes in our buffer? Extract and return.
- buf.seek(0)
- rv = buf.read(size)
- self._rbuf = StringIO.StringIO()
- self._rbuf.write(buf.read())
- return rv
-
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
- while True:
- left = size - buf_len
- # recv() will malloc the amount of memory given as its
- # parameter even though it often returns much less data
- # than that. The returned data string is short lived
- # as we copy it into a StringIO and free it. This avoids
- # fragmentation issues on many platforms.
- data = self.recv(left)
- if not data:
- break
- n = len(data)
- if n == size and not buf_len:
- # Shortcut. Avoid buffer data copies when:
- # - We have no data in our buffer.
- # AND
- # - Our call to recv returned exactly the
- # number of bytes we were asked to read.
- return data
- if n == left:
- buf.write(data)
- del data # explicit free
- break
- assert n <= left, "recv(%d) returned %d bytes" % (left, n)
- buf.write(data)
- buf_len += n
- del data # explicit free
- #assert buf_len == buf.tell()
- return buf.getvalue()
-
- def readline(self, size=-1):
- buf = self._rbuf
- buf.seek(0, 2) # seek end
- if buf.tell() > 0:
- # check if we already have it in our buffer
- buf.seek(0)
- bline = buf.readline(size)
- if bline.endswith('\n') or len(bline) == size:
- self._rbuf = StringIO.StringIO()
- self._rbuf.write(buf.read())
- return bline
- del bline
- if size < 0:
- # Read until \n or EOF, whichever comes first
- if self._rbufsize <= 1:
- # Speed up unbuffered case
- buf.seek(0)
- buffers = [buf.read()]
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
- data = None
- recv = self.recv
- while data != "\n":
- data = recv(1)
- if not data:
- break
- buffers.append(data)
- return "".join(buffers)
-
- buf.seek(0, 2) # seek end
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- buf.write(data[:nl])
- self._rbuf.write(data[nl:])
- del data
- break
- buf.write(data)
- return buf.getvalue()
- else:
- # Read until size bytes or \n or EOF seen, whichever comes first
- buf.seek(0, 2) # seek end
- buf_len = buf.tell()
- if buf_len >= size:
- buf.seek(0)
- rv = buf.read(size)
- self._rbuf = StringIO.StringIO()
- self._rbuf.write(buf.read())
- return rv
- self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- left = size - buf_len
- # did we just receive a newline?
- nl = data.find('\n', 0, left)
- if nl >= 0:
- nl += 1
- # save the excess data to _rbuf
- self._rbuf.write(data[nl:])
- if buf_len:
- buf.write(data[:nl])
- break
- else:
- # Shortcut. Avoid data copy through buf when returning
- # a substring of our first recv().
- return data[:nl]
- n = len(data)
- if n == size and not buf_len:
- # Shortcut. Avoid data copy through buf when
- # returning exactly all of our first recv().
- return data
- if n >= left:
- buf.write(data[:left])
- self._rbuf.write(data[left:])
- break
- buf.write(data)
- buf_len += n
- #assert buf_len == buf.tell()
- return buf.getvalue()
-
-else:
- class CP_fileobject(socket._fileobject):
- """Faux file object attached to a socket object."""
-
- def sendall(self, data):
- """Sendall for non-blocking sockets."""
- while data:
- try:
- bytes_sent = self.send(data)
- data = data[bytes_sent:]
- except socket.error, e:
- if e.args[0] not in socket_errors_nonblocking:
- raise
-
- def send(self, data):
- return self._sock.send(data)
-
- def flush(self):
- if self._wbuf:
- buffer = "".join(self._wbuf)
- self._wbuf = []
- self.sendall(buffer)
-
- def recv(self, size):
- while True:
- try:
- return self._sock.recv(size)
- except socket.error, e:
- if (e.args[0] not in socket_errors_nonblocking
- and e.args[0] not in socket_error_eintr):
- raise
-
- def read(self, size=-1):
- if size < 0:
- # Read until EOF
- buffers = [self._rbuf]
- self._rbuf = ""
- if self._rbufsize <= 1:
- recv_size = self.default_bufsize
- else:
- recv_size = self._rbufsize
-
- while True:
- data = self.recv(recv_size)
- if not data:
- break
- buffers.append(data)
- return "".join(buffers)
- else:
- # Read until size bytes or EOF seen, whichever comes first
- data = self._rbuf
- buf_len = len(data)
- if buf_len >= size:
- self._rbuf = data[size:]
- return data[:size]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ""
- while True:
- left = size - buf_len
- recv_size = max(self._rbufsize, left)
- data = self.recv(recv_size)
- if not data:
- break
- buffers.append(data)
- n = len(data)
- if n >= left:
- self._rbuf = data[left:]
- buffers[-1] = data[:left]
- break
- buf_len += n
- return "".join(buffers)
-
- def readline(self, size=-1):
- data = self._rbuf
- if size < 0:
- # Read until \n or EOF, whichever comes first
- if self._rbufsize <= 1:
- # Speed up unbuffered case
- assert data == ""
- buffers = []
- while data != "\n":
- data = self.recv(1)
- if not data:
- break
- buffers.append(data)
- return "".join(buffers)
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- return data[:nl]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ""
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- buffers.append(data)
- nl = data.find('\n')
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- buffers[-1] = data[:nl]
- break
- return "".join(buffers)
- else:
- # Read until size bytes or \n or EOF seen, whichever comes first
- nl = data.find('\n', 0, size)
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- return data[:nl]
- buf_len = len(data)
- if buf_len >= size:
- self._rbuf = data[size:]
- return data[:size]
- buffers = []
- if data:
- buffers.append(data)
- self._rbuf = ""
- while True:
- data = self.recv(self._rbufsize)
- if not data:
- break
- buffers.append(data)
- left = size - buf_len
- nl = data.find('\n', 0, left)
- if nl >= 0:
- nl += 1
- self._rbuf = data[nl:]
- buffers[-1] = data[:nl]
- break
- n = len(data)
- if n >= left:
- self._rbuf = data[left:]
- buffers[-1] = data[:left]
- break
- buf_len += n
- return "".join(buffers)
-
-
-class SSL_fileobject(CP_fileobject):
- """SSL file object attached to a socket object."""
-
- ssl_timeout = 3
- ssl_retry = .01
-
- def _safe_call(self, is_reader, call, *args, **kwargs):
- """Wrap the given call with SSL error-trapping.
-
- is_reader: if False EOF errors will be raised. If True, EOF errors
- will return "" (to emulate normal sockets).
- """
- start = time.time()
- while True:
- try:
- return call(*args, **kwargs)
- except SSL.WantReadError:
- # Sleep and try again. This is dangerous, because it means
- # the rest of the stack has no way of differentiating
- # between a "new handshake" error and "client dropped".
- # Note this isn't an endless loop: there's a timeout below.
- time.sleep(self.ssl_retry)
- except SSL.WantWriteError:
- time.sleep(self.ssl_retry)
- except SSL.SysCallError, e:
- if is_reader and e.args == (-1, 'Unexpected EOF'):
- return ""
-
- errnum = e.args[0]
- if is_reader and errnum in socket_errors_to_ignore:
- return ""
- raise socket.error(errnum)
- except SSL.Error, e:
- if is_reader and e.args == (-1, 'Unexpected EOF'):
- return ""
-
- thirdarg = None
- try:
- thirdarg = e.args[0][0][2]
- except IndexError:
- pass
-
- if thirdarg == 'http request':
- # The client is talking HTTP to an HTTPS server.
- raise NoSSLError()
- raise FatalSSLAlert(*e.args)
- except:
- raise
-
- if time.time() - start > self.ssl_timeout:
- raise socket.timeout("timed out")
-
- def recv(self, *args, **kwargs):
- buf = []
- r = super(SSL_fileobject, self).recv
- while True:
- data = self._safe_call(True, r, *args, **kwargs)
- buf.append(data)
- p = self._sock.pending()
- if not p:
- return "".join(buf)
-
- def sendall(self, *args, **kwargs):
- return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs)
-
- def send(self, *args, **kwargs):
- return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs)
-
-
-class HTTPConnection(object):
- """An HTTP connection (active socket).
-
- socket: the raw socket object (usually TCP) for this connection.
- wsgi_app: the WSGI application for this server/connection.
- environ: a WSGI environ template. This will be copied for each request.
-
- rfile: a fileobject for reading from the socket.
- send: a function for writing (+ flush) to the socket.
- """
-
- rbufsize = -1
- RequestHandlerClass = HTTPRequest
- environ = {"wsgi.version": (1, 0),
- "wsgi.url_scheme": "http",
- "wsgi.multithread": True,
- "wsgi.multiprocess": False,
- "wsgi.run_once": False,
- "wsgi.errors": sys.stderr,
- }
-
- def __init__(self, sock, wsgi_app, environ):
- self.socket = sock
- self.wsgi_app = wsgi_app
-
- # Copy the class environ into self.
- self.environ = self.environ.copy()
- self.environ.update(environ)
-
- if SSL and isinstance(sock, SSL.ConnectionType):
- timeout = sock.gettimeout()
- self.rfile = SSL_fileobject(sock, "rb", self.rbufsize)
- self.rfile.ssl_timeout = timeout
- self.wfile = SSL_fileobject(sock, "wb", -1)
- self.wfile.ssl_timeout = timeout
- else:
- self.rfile = CP_fileobject(sock, "rb", self.rbufsize)
- self.wfile = CP_fileobject(sock, "wb", -1)
-
- # Wrap wsgi.input but not HTTPConnection.rfile itself.
- # We're also not setting maxlen yet; we'll do that separately
- # for headers and body for each iteration of self.communicate
- # (if maxlen is 0 the wrapper doesn't check length).
- self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0)
-
- def communicate(self):
- """Read each request and respond appropriately."""
- try:
- while True:
- # (re)set req to None so that if something goes wrong in
- # the RequestHandlerClass constructor, the error doesn't
- # get written to the previous request.
- req = None
- req = self.RequestHandlerClass(self.wfile, self.environ,
- self.wsgi_app)
-
- # This order of operations should guarantee correct pipelining.
- req.parse_request()
- if not req.ready:
- return
-
- req.respond()
- if req.close_connection:
- return
-
- except socket.error, e:
- errnum = e.args[0]
- if errnum == 'timed out':
- if req and not req.sent_headers:
- req.simple_response("408 Request Timeout")
- elif errnum not in socket_errors_to_ignore:
- if req and not req.sent_headers:
- req.simple_response("500 Internal Server Error",
- format_exc())
- return
- except (KeyboardInterrupt, SystemExit):
- raise
- except FatalSSLAlert, e:
- # Close the connection.
- return
- except NoSSLError:
- if req and not req.sent_headers:
- # Unwrap our wfile
- req.wfile = CP_fileobject(self.socket._sock, "wb", -1)
- req.simple_response("400 Bad Request",
- "The client sent a plain HTTP request, but "
- "this server only speaks HTTPS on this port.")
- self.linger = True
- except Exception, e:
- if req and not req.sent_headers:
- req.simple_response("500 Internal Server Error", format_exc())
-
- linger = False
-
- def close(self):
- """Close the socket underlying this connection."""
- self.rfile.close()
-
- if not self.linger:
- # Python's socket module does NOT call close on the kernel socket
- # when you call socket.close(). We do so manually here because we
- # want this server to send a FIN TCP segment immediately. Note this
- # must be called *before* calling socket.close(), because the latter
- # drops its reference to the kernel socket.
- self.socket._sock.close()
- self.socket.close()
- else:
- # On the other hand, sometimes we want to hang around for a bit
- # to make sure the client has a chance to read our entire
- # response. Skipping the close() calls here delays the FIN
- # packet until the socket object is garbage-collected later.
- # Someday, perhaps, we'll do the full lingering_close that
- # Apache does, but not today.
- pass
-
-
-def format_exc(limit=None):
- """Like print_exc() but return a string. Backport for Python 2.3."""
- try:
- etype, value, tb = sys.exc_info()
- return ''.join(traceback.format_exception(etype, value, tb, limit))
- finally:
- etype = value = tb = None
-
-
-_SHUTDOWNREQUEST = None
-
-class WorkerThread(threading.Thread):
- """Thread which continuously polls a Queue for Connection objects.
-
- server: the HTTP Server which spawned this thread, and which owns the
- Queue and is placing active connections into it.
- ready: a simple flag for the calling server to know when this thread
- has begun polling the Queue.
-
- Due to the timing issues of polling a Queue, a WorkerThread does not
- check its own 'ready' flag after it has started. To stop the thread,
- it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
- (one for each running WorkerThread).
- """
-
- conn = None
-
- def __init__(self, server):
- self.ready = False
- self.server = server
- threading.Thread.__init__(self)
-
- def run(self):
- try:
- self.ready = True
- while True:
- conn = self.server.requests.get()
- if conn is _SHUTDOWNREQUEST:
- return
-
- self.conn = conn
- try:
- conn.communicate()
- finally:
- conn.close()
- self.conn = None
- except (KeyboardInterrupt, SystemExit), exc:
- self.server.interrupt = exc
-
-
-class ThreadPool(object):
- """A Request Queue for the CherryPyWSGIServer which pools threads.
-
- ThreadPool objects must provide min, get(), put(obj), start()
- and stop(timeout) attributes.
- """
-
- def __init__(self, server, min=10, max=-1):
- self.server = server
- self.min = min
- self.max = max
- self._threads = []
- self._queue = Queue.Queue()
- self.get = self._queue.get
-
- def start(self):
- """Start the pool of threads."""
- for i in xrange(self.min):
- self._threads.append(WorkerThread(self.server))
- for worker in self._threads:
- worker.setName("CP WSGIServer " + worker.getName())
- worker.start()
- for worker in self._threads:
- while not worker.ready:
- time.sleep(.1)
-
- def _get_idle(self):
- """Number of worker threads which are idle. Read-only."""
- return len([t for t in self._threads if t.conn is None])
- idle = property(_get_idle, doc=_get_idle.__doc__)
-
- def put(self, obj):
- self._queue.put(obj)
- if obj is _SHUTDOWNREQUEST:
- return
-
- def grow(self, amount):
- """Spawn new worker threads (not above self.max)."""
- for i in xrange(amount):
- if self.max > 0 and len(self._threads) >= self.max:
- break
- worker = WorkerThread(self.server)
- worker.setName("CP WSGIServer " + worker.getName())
- self._threads.append(worker)
- worker.start()
-
- def shrink(self, amount):
- """Kill off worker threads (not below self.min)."""
- # Grow/shrink the pool if necessary.
- # Remove any dead threads from our list
- for t in self._threads:
- if not t.isAlive():
- self._threads.remove(t)
- amount -= 1
-
- if amount > 0:
- for i in xrange(min(amount, len(self._threads) - self.min)):
- # Put a number of shutdown requests on the queue equal
- # to 'amount'. Once each of those is processed by a worker,
- # that worker will terminate and be culled from our list
- # in self.put.
- self._queue.put(_SHUTDOWNREQUEST)
-
- def stop(self, timeout=5):
- # Must shut down threads here so the code that calls
- # this method can know when all threads are stopped.
- for worker in self._threads:
- self._queue.put(_SHUTDOWNREQUEST)
-
- # Don't join currentThread (when stop is called inside a request).
- current = threading.currentThread()
- while self._threads:
- worker = self._threads.pop()
- if worker is not current and worker.isAlive():
- try:
- if timeout is None or timeout < 0:
- worker.join()
- else:
- worker.join(timeout)
- if worker.isAlive():
- # We exhausted the timeout.
- # Forcibly shut down the socket.
- c = worker.conn
- if c and not c.rfile.closed:
- if SSL and isinstance(c.socket, SSL.ConnectionType):
- # pyOpenSSL.socket.shutdown takes no args
- c.socket.shutdown()
- else:
- c.socket.shutdown(socket.SHUT_RD)
- worker.join()
- except (AssertionError,
- # Ignore repeated Ctrl-C.
- # See http://www.cherrypy.org/ticket/691.
- KeyboardInterrupt), exc1:
- pass
-
-
-
-class SSLConnection:
- """A thread-safe wrapper for an SSL.Connection.
-
- *args: the arguments to create the wrapped SSL.Connection(*args).
- """
-
- def __init__(self, *args):
- self._ssl_conn = SSL.Connection(*args)
- self._lock = threading.RLock()
-
- for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
- 'renegotiate', 'bind', 'listen', 'connect', 'accept',
- 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list',
- 'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
- 'makefile', 'get_app_data', 'set_app_data', 'state_string',
- 'sock_shutdown', 'get_peer_certificate', 'want_read',
- 'want_write', 'set_connect_state', 'set_accept_state',
- 'connect_ex', 'sendall', 'settimeout'):
- exec """def %s(self, *args):
- self._lock.acquire()
- try:
- return self._ssl_conn.%s(*args)
- finally:
- self._lock.release()
-""" % (f, f)
-
-
-try:
- import fcntl
-except ImportError:
- try:
- from ctypes import windll, WinError
- except ImportError:
- def prevent_socket_inheritance(sock):
- """Dummy function, since neither fcntl nor ctypes are available."""
- pass
- else:
- def prevent_socket_inheritance(sock):
- """Mark the given socket fd as non-inheritable (Windows)."""
- if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
- raise WinError()
-else:
- def prevent_socket_inheritance(sock):
- """Mark the given socket fd as non-inheritable (POSIX)."""
- fd = sock.fileno()
- old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
- fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
-
-
-class CherryPyWSGIServer(object):
- """An HTTP server for WSGI.
-
- bind_addr: The interface on which to listen for connections.
- For TCP sockets, a (host, port) tuple. Host values may be any IPv4
- or IPv6 address, or any valid hostname. The string 'localhost' is a
- synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
- The string '0.0.0.0' is a special IPv4 entry meaning "any active
- interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
- IPv6. The empty string or None are not allowed.
-
- For UNIX sockets, supply the filename as a string.
- wsgi_app: the WSGI 'application callable'; multiple WSGI applications
- may be passed as (path_prefix, app) pairs.
- numthreads: the number of worker threads to create (default 10).
- server_name: the string to set for WSGI's SERVER_NAME environ entry.
- Defaults to socket.gethostname().
- max: the maximum number of queued requests (defaults to -1 = no limit).
- request_queue_size: the 'backlog' argument to socket.listen();
- specifies the maximum number of queued connections (default 5).
- timeout: the timeout in seconds for accepted connections (default 10).
-
- nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket
- option.
-
- protocol: the version string to write in the Status-Line of all
- HTTP responses. For example, "HTTP/1.1" (the default). This
- also limits the supported features used in the response.
-
-
- SSL/HTTPS
- ---------
- The OpenSSL module must be importable for SSL functionality.
- You can obtain it from http://pyopenssl.sourceforge.net/
-
- ssl_certificate: the filename of the server SSL certificate.
- ssl_privatekey: the filename of the server's private key file.
-
- If either of these is None (both are None by default), this server
- will not use SSL. If both are given and are valid, they will be read
- on server start and used in the SSL context for the listening socket.
- """
-
- protocol = "HTTP/1.1"
- _bind_addr = "127.0.0.1"
- version = "CherryPy/3.1.2"
- ready = False
- _interrupt = None
-
- nodelay = True
-
- ConnectionClass = HTTPConnection
- environ = {}
-
- # Paths to certificate and private key files
- ssl_certificate = None
- ssl_private_key = None
-
- def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
- max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
- self.requests = ThreadPool(self, min=numthreads or 1, max=max)
-
- if callable(wsgi_app):
- # We've been handed a single wsgi_app, in CP-2.1 style.
- # Assume it's mounted at "".
- self.wsgi_app = wsgi_app
- else:
- # We've been handed a list of (path_prefix, wsgi_app) tuples,
- # so that the server can call different wsgi_apps, and also
- # correctly set SCRIPT_NAME.
- warnings.warn("The ability to pass multiple apps is deprecated "
- "and will be removed in 3.2. You should explicitly "
- "include a WSGIPathInfoDispatcher instead.",
- DeprecationWarning)
- self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app)
-
- self.bind_addr = bind_addr
- if not server_name:
- server_name = socket.gethostname()
- self.server_name = server_name
- self.request_queue_size = request_queue_size
-
- self.timeout = timeout
- self.shutdown_timeout = shutdown_timeout
-
- def _get_numthreads(self):
- return self.requests.min
- def _set_numthreads(self, value):
- self.requests.min = value
- numthreads = property(_get_numthreads, _set_numthreads)
-
- def __str__(self):
- return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
- self.bind_addr)
-
- def _get_bind_addr(self):
- return self._bind_addr
- def _set_bind_addr(self, value):
- if isinstance(value, tuple) and value[0] in ('', None):
- # Despite the socket module docs, using '' does not
- # allow AI_PASSIVE to work. Passing None instead
- # returns '0.0.0.0' like we want. In other words:
- # host AI_PASSIVE result
- # '' Y 192.168.x.y
- # '' N 192.168.x.y
- # None Y 0.0.0.0
- # None N 127.0.0.1
- # But since you can get the same effect with an explicit
- # '0.0.0.0', we deny both the empty string and None as values.
- raise ValueError("Host values of '' or None are not allowed. "
- "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
- "to listen on all active interfaces.")
- self._bind_addr = value
- bind_addr = property(_get_bind_addr, _set_bind_addr,
- doc="""The interface on which to listen for connections.
-
- For TCP sockets, a (host, port) tuple. Host values may be any IPv4
- or IPv6 address, or any valid hostname. The string 'localhost' is a
- synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
- The string '0.0.0.0' is a special IPv4 entry meaning "any active
- interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
- IPv6. The empty string or None are not allowed.
-
- For UNIX sockets, supply the filename as a string.""")
-
- def start(self):
- """Run the server forever."""
- # We don't have to trap KeyboardInterrupt or SystemExit here,
- # because cherrpy.server already does so, calling self.stop() for us.
- # If you're using this server with another framework, you should
- # trap those exceptions in whatever code block calls start().
- self._interrupt = None
-
- # Select the appropriate socket
- if isinstance(self.bind_addr, basestring):
- # AF_UNIX socket
-
- # So we can reuse the socket...
- try: os.unlink(self.bind_addr)
- except: pass
-
- # So everyone can access the socket...
- try: os.chmod(self.bind_addr, 0777)
- except: pass
-
- info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
- else:
- # AF_INET or AF_INET6 socket
- # Get the correct address family for our host (allows IPv6 addresses)
- host, port = self.bind_addr
- try:
- info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
- except socket.gaierror:
- # Probably a DNS issue. Assume IPv4.
- info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)]
-
- self.socket = None
- msg = "No socket could be created"
- for res in info:
- af, socktype, proto, canonname, sa = res
- try:
- self.bind(af, socktype, proto)
- except socket.error, msg:
- if self.socket:
- self.socket.close()
- self.socket = None
- continue
- break
- if not self.socket:
- raise socket.error, msg
-
- # Timeout so KeyboardInterrupt can be caught on Win32
- self.socket.settimeout(1)
- self.socket.listen(self.request_queue_size)
-
- # Create worker threads
- self.requests.start()
-
- self.ready = True
- while self.ready:
- self.tick()
- if self.interrupt:
- while self.interrupt is True:
- # Wait for self.stop() to complete. See _set_interrupt.
- time.sleep(0.1)
- if self.interrupt:
- raise self.interrupt
-
- def bind(self, family, type, proto=0):
- """Create (or recreate) the actual socket object."""
- self.socket = socket.socket(family, type, proto)
- prevent_socket_inheritance(self.socket)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- if self.nodelay:
- self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
- if self.ssl_certificate and self.ssl_private_key:
- if SSL is None:
- raise ImportError("You must install pyOpenSSL to use HTTPS.")
-
- # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.use_privatekey_file(self.ssl_private_key)
- ctx.use_certificate_file(self.ssl_certificate)
- self.socket = SSLConnection(ctx, self.socket)
- self.populate_ssl_environ()
-
- # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
- # activate dual-stack. See http://www.cherrypy.org/ticket/871.
- if (not isinstance(self.bind_addr, basestring)
- and self.bind_addr[0] == '::' and family == socket.AF_INET6):
- try:
- self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
- except (AttributeError, socket.error):
- # Apparently, the socket option is not available in
- # this machine's TCP stack
- pass
-
- self.socket.bind(self.bind_addr)
-
- def tick(self):
- """Accept a new connection and put it on the Queue."""
- try:
- s, addr = self.socket.accept()
- prevent_socket_inheritance(s)
- if not self.ready:
- return
- if hasattr(s, 'settimeout'):
- s.settimeout(self.timeout)
-
- environ = self.environ.copy()
- # SERVER_SOFTWARE is common for IIS. It's also helpful for
- # us to pass a default value for the "Server" response header.
- if environ.get("SERVER_SOFTWARE") is None:
- environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version
- # set a non-standard environ entry so the WSGI app can know what
- # the *real* server protocol is (and what features to support).
- # See http://www.faqs.org/rfcs/rfc2145.html.
- environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol
- environ["SERVER_NAME"] = self.server_name
-
- if isinstance(self.bind_addr, basestring):
- # AF_UNIX. This isn't really allowed by WSGI, which doesn't
- # address unix domain sockets. But it's better than nothing.
- environ["SERVER_PORT"] = ""
- else:
- environ["SERVER_PORT"] = str(self.bind_addr[1])
- # optional values
- # Until we do DNS lookups, omit REMOTE_HOST
- environ["REMOTE_ADDR"] = addr[0]
- environ["REMOTE_PORT"] = str(addr[1])
-
- conn = self.ConnectionClass(s, self.wsgi_app, environ)
- self.requests.put(conn)
- except socket.timeout:
- # The only reason for the timeout in start() is so we can
- # notice keyboard interrupts on Win32, which don't interrupt
- # accept() by default
- return
- except socket.error, x:
- if x.args[0] in socket_error_eintr:
- # I *think* this is right. EINTR should occur when a signal
- # is received during the accept() call; all docs say retry
- # the call, and I *think* I'm reading it right that Python
- # will then go ahead and poll for and handle the signal
- # elsewhere. See http://www.cherrypy.org/ticket/707.
- return
- if x.args[0] in socket_errors_nonblocking:
- # Just try again. See http://www.cherrypy.org/ticket/479.
- return
- if x.args[0] in socket_errors_to_ignore:
- # Our socket was closed.
- # See http://www.cherrypy.org/ticket/686.
- return
- raise
-
- def _get_interrupt(self):
- return self._interrupt
- def _set_interrupt(self, interrupt):
- self._interrupt = True
- self.stop()
- self._interrupt = interrupt
- interrupt = property(_get_interrupt, _set_interrupt,
- doc="Set this to an Exception instance to "
- "interrupt the server.")
-
- def stop(self):
- """Gracefully shutdown a server that is serving forever."""
- self.ready = False
-
- sock = getattr(self, "socket", None)
- if sock:
- if not isinstance(self.bind_addr, basestring):
- # Touch our own socket to make accept() return immediately.
- try:
- host, port = sock.getsockname()[:2]
- except socket.error, x:
- if x.args[0] not in socket_errors_to_ignore:
- raise
- else:
- # Note that we're explicitly NOT using AI_PASSIVE,
- # here, because we want an actual IP to touch.
- # localhost won't work if we've bound to a public IP,
- # but it will if we bound to '0.0.0.0' (INADDR_ANY).
- for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM):
- af, socktype, proto, canonname, sa = res
- s = None
- try:
- s = socket.socket(af, socktype, proto)
- # See http://groups.google.com/group/cherrypy-users/
- # browse_frm/thread/bbfe5eb39c904fe0
- s.settimeout(1.0)
- s.connect((host, port))
- s.close()
- except socket.error:
- if s:
- s.close()
- if hasattr(sock, "close"):
- sock.close()
- self.socket = None
-
- self.requests.stop(self.shutdown_timeout)
-
- def populate_ssl_environ(self):
- """Create WSGI environ entries to be merged into each request."""
- cert = open(self.ssl_certificate, 'rb').read()
- cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
- ssl_environ = {
- "wsgi.url_scheme": "https",
- "HTTPS": "on",
- # pyOpenSSL doesn't provide access to any of these AFAICT
-## 'SSL_PROTOCOL': 'SSLv2',
-## SSL_CIPHER string The cipher specification name
-## SSL_VERSION_INTERFACE string The mod_ssl program version
-## SSL_VERSION_LIBRARY string The OpenSSL program version
- }
-
- # Server certificate attributes
- ssl_environ.update({
- 'SSL_SERVER_M_VERSION': cert.get_version(),
- 'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
-## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
-## 'SSL_SERVER_V_END': Validity of server's certificate (end time),
- })
-
- for prefix, dn in [("I", cert.get_issuer()),
- ("S", cert.get_subject())]:
- # X509Name objects don't seem to have a way to get the
- # complete DN string. Use str() and slice it instead,
- # because str(dn) == "<X509Name object '/C=US/ST=...'>"
- dnstr = str(dn)[18:-2]
-
- wsgikey = 'SSL_SERVER_%s_DN' % prefix
- ssl_environ[wsgikey] = dnstr
-
- # The DN should be of the form: /k1=v1/k2=v2, but we must allow
- # for any value to contain slashes itself (in a URL).
- while dnstr:
- pos = dnstr.rfind("=")
- dnstr, value = dnstr[:pos], dnstr[pos + 1:]
- pos = dnstr.rfind("/")
- dnstr, key = dnstr[:pos], dnstr[pos + 1:]
- if key and value:
- wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
- ssl_environ[wsgikey] = value
-
- self.environ.update(ssl_environ)
-
diff --git a/module/network/Browser.py b/module/network/Browser.py
deleted file mode 100644
index d68a23687..000000000
--- a/module/network/Browser.py
+++ /dev/null
@@ -1,146 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from logging import getLogger
-
-from HTTPRequest import HTTPRequest
-from HTTPDownload import HTTPDownload
-
-
-class Browser(object):
- __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl")
-
- def __init__(self, bucket=None, options={}):
- self.log = getLogger("log")
-
- self.options = options #holds pycurl options
- self.bucket = bucket
-
- self.cj = None # needs to be setted later
- self._size = 0
-
- self.renewHTTPRequest()
- self.dl = None
-
-
- def renewHTTPRequest(self):
- if hasattr(self, "http"): self.http.close()
- self.http = HTTPRequest(self.cj, self.options)
-
- def setLastURL(self, val):
- self.http.lastURL = val
-
- # tunnel some attributes from HTTP Request to Browser
- lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL)
- lastURL = property(lambda self: self.http.lastURL, setLastURL)
- code = property(lambda self: self.http.code)
- cookieJar = property(lambda self: self.cj)
-
- def setCookieJar(self, cj):
- self.cj = cj
- self.http.cj = cj
-
- @property
- def speed(self):
- if self.dl:
- return self.dl.speed
- return 0
-
- @property
- def size(self):
- if self._size:
- return self._size
- if self.dl:
- return self.dl.size
- return 0
-
- @property
- def arrived(self):
- if self.dl:
- return self.dl.arrived
- return 0
-
- @property
- def percent(self):
- if not self.size: return 0
- return (self.arrived * 100) / self.size
-
- def clearCookies(self):
- if self.cj:
- self.cj.clear()
- self.http.clearCookies()
-
- def clearReferer(self):
- self.http.lastURL = None
-
- def abortDownloads(self):
- self.http.abort = True
- if self.dl:
- self._size = self.dl.size
- self.dl.abort = True
-
- def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False,
- progressNotify=None, disposition=False):
- """ this can also download ftp """
- self._size = 0
- self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None,
- self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition)
- name = self.dl.download(chunks, resume)
- self._size = self.dl.size
-
- self.dl = None
-
- return name
-
- def load(self, *args, **kwargs):
- """ retrieves page """
- return self.http.load(*args, **kwargs)
-
- def putHeader(self, name, value):
- """ add a header to the request """
- self.http.putHeader(name, value)
-
- def addAuth(self, pwd):
- """Adds user and pw for http auth
-
- :param pwd: string, user:password
- """
- self.options["auth"] = pwd
- self.renewHTTPRequest() #we need a new request
-
- def removeAuth(self):
- if "auth" in self.options: del self.options["auth"]
- self.renewHTTPRequest()
-
- def setOption(self, name, value):
- """Adds an option to the request, see HTTPRequest for existing ones"""
- self.options[name] = value
-
- def deleteOption(self, name):
- if name in self.options: del self.options[name]
-
- def clearHeaders(self):
- self.http.clearHeaders()
-
- def close(self):
- """ cleanup """
- if hasattr(self, "http"):
- self.http.close()
- del self.http
- if hasattr(self, "dl"):
- del self.dl
- if hasattr(self, "cj"):
- del self.cj
-
-if __name__ == "__main__":
- browser = Browser()#proxies={"socks5": "localhost:5000"})
- ip = "http://www.whatismyip.com/automation/n09230945.asp"
- #browser.getPage("http://google.com/search?q=bar")
- #browser.getPage("https://encrypted.google.com/")
- #print browser.getPage(ip)
- #print browser.getRedirectLocation("http://google.com/")
- #browser.getPage("https://encrypted.google.com/")
- #browser.getPage("http://google.com/search?q=bar")
-
- browser.httpDownload("http://speedtest.netcologne.de/test_10mb.bin", "test_10mb.bin")
-
diff --git a/module/network/Bucket.py b/module/network/Bucket.py
deleted file mode 100644
index 69da277ae..000000000
--- a/module/network/Bucket.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from time import time
-from threading import Lock
-
-class Bucket:
- def __init__(self):
- self.rate = 0
- self.tokens = 0
- self.timestamp = time()
- self.lock = Lock()
-
- def __nonzero__(self):
- return False if self.rate < 10240 else True
-
- def setRate(self, rate):
- self.lock.acquire()
- self.rate = int(rate)
- self.lock.release()
-
- def consumed(self, amount):
- """ return time the process have to sleep, after consumed specified amount """
- if self.rate < 10240: return 0 #min. 10kb, may become unresponsive otherwise
- self.lock.acquire()
-
- self.calc_tokens()
- self.tokens -= amount
-
- if self.tokens < 0:
- time = -self.tokens/float(self.rate)
- else:
- time = 0
-
-
- self.lock.release()
- return time
-
- def calc_tokens(self):
- if self.tokens < self.rate:
- now = time()
- delta = self.rate * (now - self.timestamp)
- self.tokens = min(self.rate, self.tokens + delta)
- self.timestamp = now
-
diff --git a/module/network/CookieJar.py b/module/network/CookieJar.py
deleted file mode 100644
index c05812334..000000000
--- a/module/network/CookieJar.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from time import time
-
-class CookieJar():
- def __init__(self, pluginname, account=None):
- self.cookies = {}
- self.plugin = pluginname
- self.account = account
-
- def addCookies(self, clist):
- for c in clist:
- name = c.split("\t")[5]
- self.cookies[name] = c
-
- def getCookies(self):
- return self.cookies.values()
-
- def parseCookie(self, name):
- if name in self.cookies:
- return self.cookies[name].split("\t")[6]
- else:
- return None
-
- def getCookie(self, name):
- return self.parseCookie(name)
-
- def setCookie(self, domain, name, value, path="/", exp=time()+3600*24*180):
- s = ".%s TRUE %s FALSE %s %s %s" % (domain, path, exp, name, value)
- self.cookies[name] = s
-
- def clear(self):
- self.cookies = {}
diff --git a/module/network/HTTPChunk.py b/module/network/HTTPChunk.py
deleted file mode 100644
index b637aef32..000000000
--- a/module/network/HTTPChunk.py
+++ /dev/null
@@ -1,293 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from os import remove, stat, fsync
-from os.path import exists
-from time import sleep
-from re import search
-from module.utils import fs_encode
-import codecs
-import pycurl
-
-from HTTPRequest import HTTPRequest
-
-class WrongFormat(Exception):
- pass
-
-
-class ChunkInfo():
- def __init__(self, name):
- self.name = unicode(name)
- self.size = 0
- self.resume = False
- self.chunks = []
-
- def __repr__(self):
- ret = "ChunkInfo: %s, %s\n" % (self.name, self.size)
- for i, c in enumerate(self.chunks):
- ret += "%s# %s\n" % (i, c[1])
-
- return ret
-
- def setSize(self, size):
- self.size = int(size)
-
- def addChunk(self, name, range):
- self.chunks.append((name, range))
-
- def clear(self):
- self.chunks = []
-
- def createChunks(self, chunks):
- self.clear()
- chunk_size = self.size / chunks
-
- current = 0
- for i in range(chunks):
- end = self.size - 1 if (i == chunks - 1) else current + chunk_size
- self.addChunk("%s.chunk%s" % (self.name, i), (current, end))
- current += chunk_size + 1
-
-
- def save(self):
- fs_name = fs_encode("%s.chunks" % self.name)
- fh = codecs.open(fs_name, "w", "utf_8")
- fh.write("name:%s\n" % self.name)
- fh.write("size:%s\n" % self.size)
- for i, c in enumerate(self.chunks):
- fh.write("#%d:\n" % i)
- fh.write("\tname:%s\n" % c[0])
- fh.write("\trange:%i-%i\n" % c[1])
- fh.close()
-
- @staticmethod
- def load(name):
- fs_name = fs_encode("%s.chunks" % name)
- if not exists(fs_name):
- raise IOError()
- fh = codecs.open(fs_name, "r", "utf_8")
- name = fh.readline()[:-1]
- size = fh.readline()[:-1]
- if name.startswith("name:") and size.startswith("size:"):
- name = name[5:]
- size = size[5:]
- else:
- fh.close()
- raise WrongFormat()
- ci = ChunkInfo(name)
- ci.loaded = True
- ci.setSize(size)
- while True:
- if not fh.readline(): #skip line
- break
- name = fh.readline()[1:-1]
- range = fh.readline()[1:-1]
- if name.startswith("name:") and range.startswith("range:"):
- name = name[5:]
- range = range[6:].split("-")
- else:
- raise WrongFormat()
-
- ci.addChunk(name, (long(range[0]), long(range[1])))
- fh.close()
- return ci
-
- def remove(self):
- fs_name = fs_encode("%s.chunks" % self.name)
- if exists(fs_name): remove(fs_name)
-
- def getCount(self):
- return len(self.chunks)
-
- def getChunkName(self, index):
- return self.chunks[index][0]
-
- def getChunkRange(self, index):
- return self.chunks[index][1]
-
-
-class HTTPChunk(HTTPRequest):
- def __init__(self, id, parent, range=None, resume=False):
- self.id = id
- self.p = parent # HTTPDownload instance
- self.range = range # tuple (start, end)
- self.resume = resume
- self.log = parent.log
-
- self.size = range[1] - range[0] if range else -1
- self.arrived = 0
- self.lastURL = self.p.referer
-
- self.c = pycurl.Curl()
-
- self.header = ""
- self.headerParsed = False #indicates if the header has been processed
-
- self.fp = None #file handle
-
- self.initHandle()
- self.setInterface(self.p.options)
-
- self.BOMChecked = False # check and remove byte order mark
-
- self.rep = None
-
- self.sleep = 0.000
- self.lastSize = 0
-
- def __repr__(self):
- return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived)
-
- @property
- def cj(self):
- return self.p.cj
-
- def getHandle(self):
- """ returns a Curl handle ready to use for perform/multiperform """
-
- self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj)
- self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody)
- self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
-
- # request all bytes, since some servers in russia seems to have a defect arihmetic unit
-
- fs_name = fs_encode(self.p.info.getChunkName(self.id))
- if self.resume:
- self.fp = open(fs_name, "ab")
- self.arrived = self.fp.tell()
- if not self.arrived:
- self.arrived = stat(fs_name).st_size
-
- if self.range:
- #do nothing if chunk already finished
- if self.arrived + self.range[0] >= self.range[1]: return None
-
- if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything
- range = "%i-" % (self.arrived + self.range[0])
- else:
- range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1))
-
- self.log.debug("Chunked resume with range %s" % range)
- self.c.setopt(pycurl.RANGE, range)
- else:
- self.log.debug("Resume File from %i" % self.arrived)
- self.c.setopt(pycurl.RESUME_FROM, self.arrived)
-
- else:
- if self.range:
- if self.id == len(self.p.info.chunks) - 1: # see above
- range = "%i-" % self.range[0]
- else:
- range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1))
-
- self.log.debug("Chunked with range %s" % range)
- self.c.setopt(pycurl.RANGE, range)
-
- self.fp = open(fs_name, "wb")
-
- return self.c
-
- def writeHeader(self, buf):
- self.header += buf
- #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
- # as first chunk, we will parse the headers
- if not self.range and self.header.endswith("\r\n\r\n"):
- self.parseHeader()
- elif not self.range and buf.startswith("150") and "data connection" in buf: #ftp file size parsing
- size = search(r"(\d+) bytes", buf)
- if size:
- self.p.size = int(size.group(1))
- self.p.chunkSupport = True
-
- self.headerParsed = True
-
- def writeBody(self, buf):
- #ignore BOM, it confuses unrar
- if not self.BOMChecked:
- if [ord(b) for b in buf[:3]] == [239, 187, 191]:
- buf = buf[3:]
- self.BOMChecked = True
-
- size = len(buf)
-
- self.arrived += size
-
- self.fp.write(buf)
-
- if self.p.bucket:
- sleep(self.p.bucket.consumed(size))
- else:
- # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller
- # otherwise reduce sleep time percentual (values are based on tests)
- # So in general cpu time is saved without reducing bandwith too much
-
- if size < self.lastSize:
- self.sleep += 0.002
- else:
- self.sleep *= 0.7
-
- self.lastSize = size
-
- sleep(self.sleep)
-
- if self.range and self.arrived > self.size:
- return 0 #close if we have enough data
-
-
- def parseHeader(self):
- """parse data from recieved header"""
- for orgline in self.decodeResponse(self.header).splitlines():
- line = orgline.strip().lower()
- if line.startswith("accept-ranges") and "bytes" in line:
- self.p.chunkSupport = True
-
- if line.startswith("content-disposition") and "filename=" in line:
- name = orgline.partition("filename=")[2]
- name = name.replace('"', "").replace("'", "").replace(";", "").strip()
- self.p.nameDisposition = name
- self.log.debug("Content-Disposition: %s" % name)
-
- if not self.resume and line.startswith("content-length"):
- self.p.size = int(line.split(":")[1])
-
- self.headerParsed = True
-
- def stop(self):
- """The download will not proceed after next call of writeBody"""
- self.range = [0,0]
- self.size = 0
-
- def resetRange(self):
- """ Reset the range, so the download will load all data available """
- self.range = None
-
- def setRange(self, range):
- self.range = range
- self.size = range[1] - range[0]
-
- def flushFile(self):
- """ flush and close file """
- self.fp.flush()
- fsync(self.fp.fileno()) #make sure everything was written to disk
- self.fp.close() #needs to be closed, or merging chunks will fail
-
- def close(self):
- """ closes everything, unusable after this """
- if self.fp: self.fp.close()
- self.c.close()
- if hasattr(self, "p"): del self.p \ No newline at end of file
diff --git a/module/network/HTTPDownload.py b/module/network/HTTPDownload.py
deleted file mode 100644
index fe8075539..000000000
--- a/module/network/HTTPDownload.py
+++ /dev/null
@@ -1,340 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-from os import remove, fsync
-from os.path import dirname
-from time import sleep, time
-from shutil import move
-from logging import getLogger
-
-import pycurl
-
-from HTTPChunk import ChunkInfo, HTTPChunk
-from HTTPRequest import BadHeader
-
-from module.plugins.Plugin import Abort
-from module.utils import save_join, fs_encode
-
-class HTTPDownload():
- """ loads a url http + ftp """
-
- def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
- options={}, progressNotify=None, disposition=False):
- self.url = url
- self.filename = filename #complete file destination, not only name
- self.get = get
- self.post = post
- self.referer = referer
- self.cj = cj #cookiejar if cookies are needed
- self.bucket = bucket
- self.options = options
- self.disposition = disposition
- # all arguments
-
- self.abort = False
- self.size = 0
- self.nameDisposition = None #will be parsed from content disposition
-
- self.chunks = []
-
- self.log = getLogger("log")
-
- try:
- self.info = ChunkInfo.load(filename)
- self.info.resume = True #resume is only possible with valid info file
- self.size = self.info.size
- self.infoSaved = True
- except IOError:
- self.info = ChunkInfo(filename)
-
- self.chunkSupport = None
- self.m = pycurl.CurlMulti()
-
- #needed for speed calculation
- self.lastArrived = []
- self.speeds = []
- self.lastSpeeds = [0, 0]
-
- self.progressNotify = progressNotify
-
- @property
- def speed(self):
- last = [sum(x) for x in self.lastSpeeds if x]
- return (sum(self.speeds) + sum(last)) / (1 + len(last))
-
- @property
- def arrived(self):
- return sum([c.arrived for c in self.chunks])
-
- @property
- def percent(self):
- if not self.size: return 0
- return (self.arrived * 100) / self.size
-
- def _copyChunks(self):
- init = fs_encode(self.info.getChunkName(0)) #initial chunk name
-
- if self.info.getCount() > 1:
- fo = open(init, "rb+") #first chunkfile
- for i in range(1, self.info.getCount()):
- #input file
- fo.seek(
- self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks
- fname = fs_encode("%s.chunk%d" % (self.filename, i))
- fi = open(fname, "rb")
- buf = 32 * 1024
- while True: #copy in chunks, consumes less memory
- data = fi.read(buf)
- if not data:
- break
- fo.write(data)
- fi.close()
- if fo.tell() < self.info.getChunkRange(i)[1]:
- fo.close()
- remove(init)
- self.info.remove() #there are probably invalid chunks
- raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.")
- remove(fname) #remove chunk
- fo.close()
-
- if self.nameDisposition and self.disposition:
- self.filename = save_join(dirname(self.filename), self.nameDisposition)
-
- move(init, fs_encode(self.filename))
- self.info.remove() #remove info file
-
- def download(self, chunks=1, resume=False):
- """ returns new filename or None """
-
- chunks = max(1, chunks)
- resume = self.info.resume and resume
-
- try:
- self._download(chunks, resume)
- except pycurl.error, e:
- #code 33 - no resume
- code = e.args[0]
- if code == 33:
- # try again without resume
- self.log.debug("Errno 33 -> Restart without resume")
-
- #remove old handles
- for chunk in self.chunks:
- self.closeChunk(chunk)
-
- return self._download(chunks, False)
- else:
- raise
- finally:
- self.close()
-
- if self.nameDisposition and self.disposition: return self.nameDisposition
- return None
-
- def _download(self, chunks, resume):
- if not resume:
- self.info.clear()
- self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #create an initial entry
-
- self.chunks = []
-
- init = HTTPChunk(0, self, None, resume) #initial chunk that will load complete file (if needed)
-
- self.chunks.append(init)
- self.m.add_handle(init.getHandle())
-
- lastFinishCheck = 0
- lastTimeCheck = 0
- chunksDone = set() # list of curl handles that are finished
- chunksCreated = False
- done = False
- if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can
- self.chunkSupport = True
-
- while 1:
- #need to create chunks
- if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk
-
- if not resume:
- self.info.setSize(self.size)
- self.info.createChunks(chunks)
- self.info.save()
-
- chunks = self.info.getCount()
-
- init.setRange(self.info.getChunkRange(0))
-
- for i in range(1, chunks):
- c = HTTPChunk(i, self, self.info.getChunkRange(i), resume)
-
- handle = c.getHandle()
- if handle:
- self.chunks.append(c)
- self.m.add_handle(handle)
- else:
- #close immediatly
- self.log.debug("Invalid curl handle -> closed")
- c.close()
-
- chunksCreated = True
-
- while 1:
- ret, num_handles = self.m.perform()
- if ret != pycurl.E_CALL_MULTI_PERFORM:
- break
-
- t = time()
-
- # reduce these calls
- while lastFinishCheck + 0.5 < t:
- # list of failed curl handles
- failed = []
- ex = None # save only last exception, we can only raise one anyway
-
- num_q, ok_list, err_list = self.m.info_read()
- for c in ok_list:
- chunk = self.findChunk(c)
- try: # check if the header implies success, else add it to failed list
- chunk.verifyHeader()
- except BadHeader, e:
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
- failed.append(chunk)
- ex = e
- else:
- chunksDone.add(c)
-
- for c in err_list:
- curl, errno, msg = c
- chunk = self.findChunk(curl)
- #test if chunk was finished
- if errno != 23 or "0 !=" not in msg:
- failed.append(chunk)
- ex = pycurl.error(errno, msg)
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex)))
- continue
-
- try: # check if the header implies success, else add it to failed list
- chunk.verifyHeader()
- except BadHeader, e:
- self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
- failed.append(chunk)
- ex = e
- else:
- chunksDone.add(curl)
- if not num_q: # no more infos to get
-
- # check if init is not finished so we reset download connections
- # note that other chunks are closed and downloaded with init too
- if failed and init not in failed and init.c not in chunksDone:
- self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex))))
-
- #list of chunks to clean and remove
- to_clean = filter(lambda x: x is not init, self.chunks)
- for chunk in to_clean:
- self.closeChunk(chunk)
- self.chunks.remove(chunk)
- remove(fs_encode(self.info.getChunkName(chunk.id)))
-
- #let first chunk load the rest and update the info file
- init.resetRange()
- self.info.clear()
- self.info.addChunk("%s.chunk0" % self.filename, (0, self.size))
- self.info.save()
- elif failed:
- raise ex
-
- lastFinishCheck = t
-
- if len(chunksDone) >= len(self.chunks):
- if len(chunksDone) > len(self.chunks):
- self.log.warning("Finished download chunks size incorrect, please report bug.")
- done = True #all chunks loaded
-
- break
-
- if done:
- break #all chunks loaded
-
- # calc speed once per second, averaging over 3 seconds
- if lastTimeCheck + 1 < t:
- diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in
- enumerate(self.chunks)]
-
- self.lastSpeeds[1] = self.lastSpeeds[0]
- self.lastSpeeds[0] = self.speeds
- self.speeds = [float(a) / (t - lastTimeCheck) for a in diff]
- self.lastArrived = [c.arrived for c in self.chunks]
- lastTimeCheck = t
- self.updateProgress()
-
- if self.abort:
- raise Abort()
-
- #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize
- self.m.select(1)
-
- for chunk in self.chunks:
- chunk.flushFile() #make sure downloads are written to disk
-
- self._copyChunks()
-
- def updateProgress(self):
- if self.progressNotify:
- self.progressNotify(self.percent)
-
- def findChunk(self, handle):
- """ linear search to find a chunk (should be ok since chunk size is usually low) """
- for chunk in self.chunks:
- if chunk.c == handle: return chunk
-
- def closeChunk(self, chunk):
- try:
- self.m.remove_handle(chunk.c)
- except pycurl.error, e:
- self.log.debug("Error removing chunk: %s" % str(e))
- finally:
- chunk.close()
-
- def close(self):
- """ cleanup """
- for chunk in self.chunks:
- self.closeChunk(chunk)
-
- self.chunks = []
- if hasattr(self, "m"):
- self.m.close()
- del self.m
- if hasattr(self, "cj"):
- del self.cj
- if hasattr(self, "info"):
- del self.info
-
-if __name__ == "__main__":
- url = "http://speedtest.netcologne.de/test_100mb.bin"
-
- from Bucket import Bucket
-
- bucket = Bucket()
- bucket.setRate(200 * 1024)
- bucket = None
-
- print "starting"
-
- dwnld = HTTPDownload(url, "test_100mb.bin", bucket=bucket)
- dwnld.download(chunks=3, resume=True)
diff --git a/module/network/HTTPRequest.py b/module/network/HTTPRequest.py
deleted file mode 100644
index 4747d937f..000000000
--- a/module/network/HTTPRequest.py
+++ /dev/null
@@ -1,306 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-import pycurl
-
-from codecs import getincrementaldecoder, lookup, BOM_UTF8
-from urllib import quote, urlencode
-from httplib import responses
-from logging import getLogger
-from cStringIO import StringIO
-
-from module.plugins.Plugin import Abort
-
-def myquote(url):
- return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
-
-def myurlencode(data):
- data = dict(data)
- return urlencode(dict((x.encode('utf_8') if isinstance(x, unicode) else x, \
- y.encode('utf_8') if isinstance(y, unicode) else y ) for x, y in data.iteritems()))
-
-bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
-
-class BadHeader(Exception):
- def __init__(self, code, content=""):
- Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)]))
- self.code = code
- self.content = content
-
-
-class HTTPRequest():
- def __init__(self, cookies=None, options=None):
- self.c = pycurl.Curl()
- self.rep = StringIO()
-
- self.cj = cookies #cookiejar
-
- self.lastURL = None
- self.lastEffectiveURL = None
- self.abort = False
- self.code = 0 # last http code
-
- self.header = ""
-
- self.headers = [] #temporary request header
-
- self.initHandle()
- self.setInterface(options)
-
- self.c.setopt(pycurl.WRITEFUNCTION, self.write)
- self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
-
- self.log = getLogger("log")
-
-
- def initHandle(self):
- """ sets common options to curl handle """
- self.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.c.setopt(pycurl.MAXREDIRS, 5)
- self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
- self.c.setopt(pycurl.NOSIGNAL, 1)
- self.c.setopt(pycurl.NOPROGRESS, 1)
- if hasattr(pycurl, "AUTOREFERER"):
- self.c.setopt(pycurl.AUTOREFERER, 1)
- self.c.setopt(pycurl.SSL_VERIFYPEER, 0)
- self.c.setopt(pycurl.LOW_SPEED_TIME, 30)
- self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
-
- #self.c.setopt(pycurl.VERBOSE, 1)
-
- self.c.setopt(pycurl.USERAGENT,
- "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0")
- if pycurl.version_info()[7]:
- self.c.setopt(pycurl.ENCODING, "gzip, deflate")
- self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*",
- "Accept-Language: en-US,en",
- "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
- "Connection: keep-alive",
- "Keep-Alive: 300",
- "Expect:"])
-
- def setInterface(self, options):
-
- interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"]
-
- if interface and interface.lower() != "none":
- self.c.setopt(pycurl.INTERFACE, str(interface))
-
- if proxy:
- if proxy["type"] == "socks4":
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
- elif proxy["type"] == "socks5":
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
- else:
- self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
-
- self.c.setopt(pycurl.PROXY, str(proxy["address"]))
- self.c.setopt(pycurl.PROXYPORT, proxy["port"])
-
- if proxy["username"]:
- self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"])))
-
- if ipv6:
- self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)
- else:
- self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
-
- if "auth" in options:
- self.c.setopt(pycurl.USERPWD, str(options["auth"]))
-
- if "timeout" in options:
- self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"])
-
-
- def addCookies(self):
- """ put cookies from curl handle to cj """
- if self.cj:
- self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST))
-
- def getCookies(self):
- """ add cookies from cj to curl handle """
- if self.cj:
- for c in self.cj.getCookies():
- self.c.setopt(pycurl.COOKIELIST, c)
- return
-
- def clearCookies(self):
- self.c.setopt(pycurl.COOKIELIST, "")
-
- def setRequestContext(self, url, get, post, referer, cookies, multipart=False):
- """ sets everything needed for the request """
-
- url = myquote(url)
-
- if get:
- get = urlencode(get)
- url = "%s?%s" % (url, get)
-
- self.c.setopt(pycurl.URL, url)
- self.c.lastUrl = url
-
- if post:
- self.c.setopt(pycurl.POST, 1)
- if not multipart:
- if type(post) == unicode:
- post = str(post) #unicode not allowed
- elif type(post) == str:
- pass
- else:
- post = myurlencode(post)
-
- self.c.setopt(pycurl.POSTFIELDS, post)
- else:
- post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()]
- self.c.setopt(pycurl.HTTPPOST, post)
- else:
- self.c.setopt(pycurl.POST, 0)
-
- if referer and self.lastURL:
- self.c.setopt(pycurl.REFERER, str(self.lastURL))
-
- if cookies:
- self.c.setopt(pycurl.COOKIEFILE, "")
- self.c.setopt(pycurl.COOKIEJAR, "")
- self.getCookies()
-
-
- def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False):
- """ load and returns a given page """
-
- self.setRequestContext(url, get, post, referer, cookies, multipart)
-
- self.header = ""
-
- self.c.setopt(pycurl.HTTPHEADER, self.headers)
-
- if just_header:
- self.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.c.setopt(pycurl.NOBODY, 1)
- self.c.perform()
- rep = self.header
-
- self.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.c.setopt(pycurl.NOBODY, 0)
-
- else:
- self.c.perform()
- rep = self.getResponse()
-
- self.c.setopt(pycurl.POSTFIELDS, "")
- self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL)
- self.code = self.verifyHeader()
-
- self.addCookies()
-
- if decode:
- rep = self.decodeResponse(rep)
-
- return rep
-
- def verifyHeader(self):
- """ raise an exceptions on bad headers """
- code = int(self.c.getinfo(pycurl.RESPONSE_CODE))
- if code in bad_headers:
- #404 will NOT raise an exception
- raise BadHeader(code, self.getResponse())
- return code
-
- def checkHeader(self):
- """ check if header indicates failure"""
- return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers
-
- def getResponse(self):
- """ retrieve response from string io """
- if self.rep is None: return ""
- value = self.rep.getvalue()
- self.rep.close()
- self.rep = StringIO()
- return value
-
- def decodeResponse(self, rep):
- """ decode with correct encoding, relies on header """
- header = self.header.splitlines()
- encoding = "utf8" # default encoding
-
- for line in header:
- line = line.lower().replace(" ", "")
- if not line.startswith("content-type:") or\
- ("text" not in line and "application" not in line):
- continue
-
- none, delemiter, charset = line.rpartition("charset=")
- if delemiter:
- charset = charset.split(";")
- if charset:
- encoding = charset[0]
-
- try:
- #self.log.debug("Decoded %s" % encoding )
- if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8):
- encoding = 'utf-8-sig'
-
- decoder = getincrementaldecoder(encoding)("replace")
- rep = decoder.decode(rep, True)
-
- #TODO: html_unescape as default
-
- except LookupError:
- self.log.debug("No Decoder foung for %s" % encoding)
- except Exception:
- self.log.debug("Error when decoding string from %s." % encoding)
-
- return rep
-
- def write(self, buf):
- """ writes response """
- if self.rep.tell() > 1000000 or self.abort:
- rep = self.getResponse()
- if self.abort: raise Abort()
- f = open("response.dump", "wb")
- f.write(rep)
- f.close()
- raise Exception("Loaded Url exceeded limit")
-
- self.rep.write(buf)
-
- def writeHeader(self, buf):
- """ writes header """
- self.header += buf
-
- def putHeader(self, name, value):
- self.headers.append("%s: %s" % (name, value))
-
- def clearHeaders(self):
- self.headers = []
-
- def close(self):
- """ cleanup, unusable after this """
- self.rep.close()
- if hasattr(self, "cj"):
- del self.cj
- if hasattr(self, "c"):
- self.c.close()
- del self.c
-
-if __name__ == "__main__":
- url = "http://pyload.org"
- c = HTTPRequest()
- print c.load(url)
-
diff --git a/module/network/RequestFactory.py b/module/network/RequestFactory.py
deleted file mode 100644
index 5b1528281..000000000
--- a/module/network/RequestFactory.py
+++ /dev/null
@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-
-from threading import Lock
-
-from Browser import Browser
-from Bucket import Bucket
-from HTTPRequest import HTTPRequest
-from CookieJar import CookieJar
-
-from XDCCRequest import XDCCRequest
-
-class RequestFactory():
- def __init__(self, core):
- self.lock = Lock()
- self.core = core
- self.bucket = Bucket()
- self.updateBucket()
- self.cookiejars = {}
-
- def iface(self):
- return self.core.config["download"]["interface"]
-
- def getRequest(self, pluginName, account=None, type="HTTP"):
- self.lock.acquire()
-
- if type == "XDCC":
- return XDCCRequest(proxies=self.getProxies())
-
- req = Browser(self.bucket, self.getOptions())
-
- if account:
- cj = self.getCookieJar(pluginName, account)
- req.setCookieJar(cj)
- else:
- req.setCookieJar(CookieJar(pluginName))
-
- self.lock.release()
- return req
-
- def getHTTPRequest(self, **kwargs):
- """ returns a http request, dont forget to close it ! """
- options = self.getOptions()
- options.update(kwargs) # submit kwargs as additional options
- return HTTPRequest(CookieJar(None), options)
-
- def getURL(self, *args, **kwargs):
- """ see HTTPRequest for argument list """
- h = HTTPRequest(None, self.getOptions())
- try:
- rep = h.load(*args, **kwargs)
- finally:
- h.close()
-
- return rep
-
- def getCookieJar(self, pluginName, account=None):
- if (pluginName, account) in self.cookiejars:
- return self.cookiejars[(pluginName, account)]
-
- cj = CookieJar(pluginName, account)
- self.cookiejars[(pluginName, account)] = cj
- return cj
-
- def getProxies(self):
- """ returns a proxy list for the request classes """
- if not self.core.config["proxy"]["proxy"]:
- return {}
- else:
- type = "http"
- setting = self.core.config["proxy"]["type"].lower()
- if setting == "socks4": type = "socks4"
- elif setting == "socks5": type = "socks5"
-
- username = None
- if self.core.config["proxy"]["username"] and self.core.config["proxy"]["username"].lower() != "none":
- username = self.core.config["proxy"]["username"]
-
- pw = None
- if self.core.config["proxy"]["password"] and self.core.config["proxy"]["password"].lower() != "none":
- pw = self.core.config["proxy"]["password"]
-
- return {
- "type": type,
- "address": self.core.config["proxy"]["address"],
- "port": self.core.config["proxy"]["port"],
- "username": username,
- "password": pw,
- }
-
- def getOptions(self):
- """returns options needed for pycurl"""
- return {"interface": self.iface(),
- "proxies": self.getProxies(),
- "ipv6": self.core.config["download"]["ipv6"]}
-
- def updateBucket(self):
- """ set values in the bucket according to settings"""
- if not self.core.config["download"]["limit_speed"]:
- self.bucket.setRate(-1)
- else:
- self.bucket.setRate(self.core.config["download"]["max_speed"] * 1024)
-
-# needs pyreq in global namespace
-def getURL(*args, **kwargs):
- return pyreq.getURL(*args, **kwargs)
-
-
-def getRequest(*args, **kwargs):
- return pyreq.getHTTPRequest()
diff --git a/module/network/XDCCRequest.py b/module/network/XDCCRequest.py
deleted file mode 100644
index f03798c17..000000000
--- a/module/network/XDCCRequest.py
+++ /dev/null
@@ -1,162 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: jeix
-"""
-
-import socket
-import re
-
-from os import remove
-from os.path import exists
-
-from time import time
-
-import struct
-from select import select
-
-from module.plugins.Plugin import Abort
-
-
-class XDCCRequest():
- def __init__(self, timeout=30, proxies={}):
-
- self.proxies = proxies
- self.timeout = timeout
-
- self.filesize = 0
- self.recv = 0
- self.speed = 0
-
- self.abort = False
-
-
- def createSocket(self):
- # proxytype = None
- # proxy = None
- # if self.proxies.has_key("socks5"):
- # proxytype = socks.PROXY_TYPE_SOCKS5
- # proxy = self.proxies["socks5"]
- # elif self.proxies.has_key("socks4"):
- # proxytype = socks.PROXY_TYPE_SOCKS4
- # proxy = self.proxies["socks4"]
- # if proxytype:
- # sock = socks.socksocket()
- # t = _parse_proxy(proxy)
- # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2])
- # else:
- # sock = socket.socket()
- # return sock
-
- return socket.socket()
-
- def download(self, ip, port, filename, irc, progressNotify=None):
-
- ircbuffer = ""
- lastUpdate = time()
- cumRecvLen = 0
-
- dccsock = self.createSocket()
-
- dccsock.settimeout(self.timeout)
- dccsock.connect((ip, port))
-
- if exists(filename):
- i = 0
- nameParts = filename.rpartition(".")
- while True:
- newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2])
- i += 1
-
- if not exists(newfilename):
- filename = newfilename
- break
-
- fh = open(filename, "wb")
-
- # recv loop for dcc socket
- while True:
- if self.abort:
- dccsock.close()
- fh.close()
- remove(filename)
- raise Abort()
-
- self._keepAlive(irc, ircbuffer)
-
- data = dccsock.recv(4096)
- dataLen = len(data)
- self.recv += dataLen
-
- cumRecvLen += dataLen
-
- now = time()
- timespan = now - lastUpdate
- if timespan > 1:
- self.speed = cumRecvLen / timespan
- cumRecvLen = 0
- lastUpdate = now
-
- if progressNotify:
- progressNotify(self.percent)
-
-
- if not data:
- break
-
- fh.write(data)
-
- # acknowledge data by sending number of recceived bytes
- dccsock.send(struct.pack('!I', self.recv))
-
- dccsock.close()
- fh.close()
-
- return filename
-
- def _keepAlive(self, sock, readbuffer):
- fdset = select([sock], [], [], 0)
- if sock not in fdset[0]:
- return
-
- readbuffer += sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- line = line.rstrip()
- first = line.split()
- if first[0] == "PING":
- sock.send("PONG %s\r\n" % first[1])
-
- def abortDownloads(self):
- self.abort = True
-
- @property
- def size(self):
- return self.filesize
-
- @property
- def arrived(self):
- return self.recv
-
- @property
- def percent(self):
- if not self.filesize: return 0
- return (self.recv * 100) / self.filesize
-
- def close(self):
- pass
diff --git a/module/plugins/Account.py b/module/plugins/Account.py
deleted file mode 100644
index bd7f97cba..000000000
--- a/module/plugins/Account.py
+++ /dev/null
@@ -1,281 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from random import choice
-from time import time
-from traceback import print_exc
-from threading import RLock
-
-from module.plugins.Plugin import Base
-from module.utils import compare_time, parseFileSize, lock
-
-
-class WrongPassword(Exception):
- pass
-
-
-class Account(Base):
- """
- Base class for every Account plugin.
- Just overwrite `login` and cookies will be stored and account becomes accessible in\
- associated hoster plugin. Plugin should also provide `loadAccountInfo`
- """
- __name__ = "Account"
- __type__ = "account"
- __version__ = "0.3"
-
- __description__ = """Base account plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
- #: after that time (in minutes) pyload will relogin the account
- login_timeout = 10 * 60
- #: after that time (in minutes) account data will be reloaded
- info_threshold = 10 * 60
-
-
- def __init__(self, manager, accounts):
- Base.__init__(self, manager.core)
-
- self.manager = manager
- self.accounts = {}
- self.infos = {} # cache for account information
- self.lock = RLock()
-
- self.timestamps = {}
- self.setAccounts(accounts)
- self.init()
-
- def init(self):
- pass
-
- def login(self, user, data, req):
- """login into account, the cookies will be saved so user can be recognized
-
- :param user: loginname
- :param data: data dictionary
- :param req: `Request` instance
- """
- pass
-
- @lock
- def _login(self, user, data):
- # set timestamp for login
- self.timestamps[user] = time()
-
- req = self.getAccountRequest(user)
- try:
- self.login(user, data, req)
- except WrongPassword:
- self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": _("Wrong Password")})
- success = data['valid'] = False
- except Exception, e:
- self.logWarning(
- _("Could not login with account %(user)s | %(msg)s") % {"user": user
- , "msg": e})
- success = data['valid'] = False
- if self.core.debug:
- print_exc()
- else:
- success = True
- finally:
- if req:
- req.close()
- return success
-
- def relogin(self, user):
- req = self.getAccountRequest(user)
- if req:
- req.cj.clear()
- req.close()
- if user in self.infos:
- del self.infos[user] #delete old information
-
- return self._login(user, self.accounts[user])
-
- def setAccounts(self, accounts):
- self.accounts = accounts
- for user, data in self.accounts.iteritems():
- self._login(user, data)
- self.infos[user] = {}
-
- def updateAccounts(self, user, password=None, options={}):
- """ updates account and return true if anything changed """
-
- if user in self.accounts:
- self.accounts[user]['valid'] = True #do not remove or accounts will not login
- if password:
- self.accounts[user]['password'] = password
- self.relogin(user)
- return True
- if options:
- before = self.accounts[user]['options']
- self.accounts[user]['options'].update(options)
- return self.accounts[user]['options'] != before
- else:
- self.accounts[user] = {"password": password, "options": options, "valid": True}
- self._login(user, self.accounts[user])
- return True
-
- def removeAccount(self, user):
- if user in self.accounts:
- del self.accounts[user]
- if user in self.infos:
- del self.infos[user]
- if user in self.timestamps:
- del self.timestamps[user]
-
- @lock
- def getAccountInfo(self, name, force=False):
- """retrieve account infos for an user, do **not** overwrite this method!\\
- just use it to retrieve infos in hoster plugins. see `loadAccountInfo`
-
- :param name: username
- :param force: reloads cached account information
- :return: dictionary with information
- """
- data = Account.loadAccountInfo(self, name)
-
- if force or name not in self.infos:
- self.logDebug("Get Account Info for %s" % name)
- req = self.getAccountRequest(name)
-
- try:
- infos = self.loadAccountInfo(name, req)
- if not type(infos) == dict:
- raise Exception("Wrong return format")
- except Exception, e:
- infos = {"error": str(e)}
-
- if req: req.close()
-
- self.logDebug("Account Info: %s" % str(infos))
-
- infos['timestamp'] = time()
- self.infos[name] = infos
- elif "timestamp" in self.infos[name] and self.infos[name][
- "timestamp"] + self.info_threshold * 60 < time():
- self.logDebug("Reached timeout for account data")
- self.scheduleRefresh(name)
-
- data.update(self.infos[name])
- return data
-
- def isPremium(self, user):
- info = self.getAccountInfo(user)
- return info['premium']
-
- def loadAccountInfo(self, name, req=None):
- """this should be overwritten in account plugin,\
- and retrieving account information for user
-
- :param name:
- :param req: `Request` instance
- :return:
- """
- return {
- "validuntil": None, # -1 for unlimited
- "login": name,
- #"password": self.accounts[name]['password'], #@XXX: security
- "options": self.accounts[name]['options'],
- "valid": self.accounts[name]['valid'],
- "trafficleft": None, # in kb, -1 for unlimited
- "maxtraffic": None,
- "premium": True, #useful for free accounts
- "timestamp": 0, #time this info was retrieved
- "type": self.__name__,
- }
-
- def getAllAccounts(self, force=False):
- return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()]
-
- def getAccountRequest(self, user=None):
- if not user:
- user, data = self.selectAccount()
- if not user:
- return None
-
- req = self.core.requestFactory.getRequest(self.__name__, user)
- return req
-
- def getAccountCookies(self, user=None):
- if not user:
- user, data = self.selectAccount()
- if not user:
- return None
-
- cj = self.core.requestFactory.getCookieJar(self.__name__, user)
- return cj
-
- def getAccountData(self, user):
- return self.accounts[user]
-
- def selectAccount(self):
- """ returns an valid account name and data"""
- usable = []
- for user, data in self.accounts.iteritems():
- if not data['valid']: continue
-
- if "time" in data['options'] and data['options']['time']:
- time_data = ""
- try:
- time_data = data['options']['time'][0]
- start, end = time_data.split("-")
- if not compare_time(start.split(":"), end.split(":")):
- continue
- except:
- self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data)
-
- if user in self.infos:
- if "validuntil" in self.infos[user]:
- if self.infos[user]['validuntil'] > 0 and time() > self.infos[user]['validuntil']:
- continue
- if "trafficleft" in self.infos[user]:
- if self.infos[user]['trafficleft'] == 0:
- continue
-
- usable.append((user, data))
-
- if not usable: return None, None
- return choice(usable)
-
- def canUse(self):
- return False if self.selectAccount() == (None, None) else True
-
- def parseTraffic(self, string): #returns kbyte
- return parseFileSize(string) / 1024
-
- def wrongPassword(self):
- raise WrongPassword
-
- def empty(self, user):
- if user in self.infos:
- self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user)
-
- self.infos[user].update({"trafficleft": 0})
- self.scheduleRefresh(user, 30 * 60)
-
- def expired(self, user):
- if user in self.infos:
- self.logWarning(_("Account %s is expired, checking again in 1h") % user)
-
- self.infos[user].update({"validuntil": time() - 1})
- self.scheduleRefresh(user, 60 * 60)
-
- def scheduleRefresh(self, user, time=0, force=True):
- """ add task to refresh account info to sheduler """
- self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time))
- self.core.scheduler.addJob(time, self.getAccountInfo, [user, force])
-
- @lock
- def checkLogin(self, user):
- """ checks if user is still logged in """
- if user in self.timestamps:
- if self.login_timeout > 0 and self.timestamps[user] + self.login_timeout * 60 < time():
- self.logDebug("Reached login timeout for %s" % user)
- return self.relogin(user)
- else:
- return True
- else:
- return False
diff --git a/module/plugins/AccountManager.py b/module/plugins/AccountManager.py
deleted file mode 100644
index 4b8063002..000000000
--- a/module/plugins/AccountManager.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from os.path import exists
-from shutil import copy
-
-from threading import Lock
-
-from module.PullEvents import AccountUpdateEvent
-from module.utils import chmod, lock
-
-ACC_VERSION = 1
-
-
-class AccountManager():
- """manages all accounts"""
-
- #----------------------------------------------------------------------
- def __init__(self, core):
- """Constructor"""
-
- self.core = core
- self.lock = Lock()
-
- self.initPlugins()
- self.saveAccounts() # save to add categories to conf
-
- def initPlugins(self):
- self.accounts = {} # key = ( plugin )
- self.plugins = {}
-
- self.initAccountPlugins()
- self.loadAccounts()
-
- def getAccountPlugin(self, plugin):
- """get account instance for plugin or None if anonymous"""
- if plugin in self.accounts:
- if plugin not in self.plugins:
- self.plugins[plugin] = self.core.pluginManager.loadClass("accounts", plugin)(self, self.accounts[plugin])
-
- return self.plugins[plugin]
- else:
- return None
-
- def getAccountPlugins(self):
- """ get all account instances"""
-
- plugins = []
- for plugin in self.accounts.keys():
- plugins.append(self.getAccountPlugin(plugin))
-
- return plugins
-
- #----------------------------------------------------------------------
- def loadAccounts(self):
- """loads all accounts available"""
-
- if not exists("accounts.conf"):
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION))
- f.close()
-
- f = open("accounts.conf", "rb")
- content = f.readlines()
- version = content[0].split(":")[1].strip() if content else ""
- f.close()
-
- if not version or int(version) < ACC_VERSION:
- copy("accounts.conf", "accounts.backup")
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION))
- f.close()
- self.core.log.warning(_("Account settings deleted, due to new config format."))
- return
-
- plugin = ""
- name = ""
-
- for line in content[1:]:
- line = line.strip()
-
- if not line: continue
- if line.startswith("#"): continue
- if line.startswith("version"): continue
-
- if line.endswith(":") and line.count(":") == 1:
- plugin = line[:-1]
- self.accounts[plugin] = {}
-
- elif line.startswith("@"):
- try:
- option = line[1:].split()
- self.accounts[plugin][name]['options'][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:])
- except:
- pass
-
- elif ":" in line:
- name, sep, pw = line.partition(":")
- self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True}
-
- #----------------------------------------------------------------------
- def saveAccounts(self):
- """save all account information"""
-
- f = open("accounts.conf", "wb")
- f.write("version: " + str(ACC_VERSION) + "\n")
-
- for plugin, accounts in self.accounts.iteritems():
- f.write("\n")
- f.write(plugin+":\n")
-
- for name,data in accounts.iteritems():
- f.write("\n\t%s:%s\n" % (name,data['password']) )
- if data['options']:
- for option, values in data['options'].iteritems():
- f.write("\t@%s %s\n" % (option, " ".join(values)))
-
- f.close()
- chmod(f.name, 0600)
-
- #----------------------------------------------------------------------
- def initAccountPlugins(self):
- """init names"""
- for name in self.core.pluginManager.getAccountPlugins():
- self.accounts[name] = {}
-
- @lock
- def updateAccount(self, plugin , user, password=None, options={}):
- """add or update account"""
- if plugin in self.accounts:
- p = self.getAccountPlugin(plugin)
- updated = p.updateAccounts(user, password, options)
- #since accounts is a ref in plugin self.accounts doesnt need to be updated here
-
- self.saveAccounts()
- if updated: p.scheduleRefresh(user, force=False)
-
- @lock
- def removeAccount(self, plugin, user):
- """remove account"""
-
- if plugin in self.accounts:
- p = self.getAccountPlugin(plugin)
- p.removeAccount(user)
-
- self.saveAccounts()
-
- @lock
- def getAccountInfos(self, force=True, refresh=False):
- data = {}
-
- if refresh:
- self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
- force = False
-
- for p in self.accounts.keys():
- if self.accounts[p]:
- p = self.getAccountPlugin(p)
- data[p.__name__] = p.getAllAccounts(force)
- else:
- data[p] = []
- e = AccountUpdateEvent()
- self.core.pullManager.addEvent(e)
- return data
-
- def sendChange(self):
- e = AccountUpdateEvent()
- self.core.pullManager.addEvent(e)
diff --git a/module/plugins/Container.py b/module/plugins/Container.py
deleted file mode 100644
index 63804d713..000000000
--- a/module/plugins/Container.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from os import remove
-from os.path import basename, exists
-
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class Container(Crypter):
- __name__ = "Container"
- __type__ = "container"
- __version__ = "0.1"
-
- __pattern__ = None
-
- __description__ = """Base container decrypter plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def preprocessing(self, thread):
- """prepare"""
-
- self.setup()
- self.thread = thread
-
- self.loadToDisk()
-
- self.decrypt(self.pyfile)
- self.deleteTmp()
-
- self.createPackages()
-
-
- def loadToDisk(self):
- """loads container to disk if its stored remotely and overwrite url,
- or check existent on several places at disk"""
-
- if self.pyfile.url.startswith("http"):
- self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1]
- content = self.load(self.pyfile.url)
- self.pyfile.url = save_join(self.config['general']['download_folder'], self.pyfile.name)
- f = open(self.pyfile.url, "wb" )
- f.write(content)
- f.close()
-
- else:
- self.pyfile.name = basename(self.pyfile.url)
- if not exists(self.pyfile.url):
- if exists(save_join(pypath, self.pyfile.url)):
- self.pyfile.url = save_join(pypath, self.pyfile.url)
- else:
- self.fail(_("File not exists."))
-
-
- def deleteTmp(self):
- if self.pyfile.name.startswith("tmp_"):
- remove(self.pyfile.url)
diff --git a/module/plugins/Crypter.py b/module/plugins/Crypter.py
deleted file mode 100644
index 74ae8d102..000000000
--- a/module/plugins/Crypter.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Plugin import Plugin
-
-
-class Crypter(Plugin):
- __name__ = "Crypter"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = None
-
- __description__ = """Base decrypter plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def __init__(self, pyfile):
- Plugin.__init__(self, pyfile)
-
- #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder )
- self.packages = []
-
- #: List of urls, pyLoad will generate packagenames
- self.urls = []
-
- self.multiDL = True
- self.limitDL = 0
-
-
- def preprocessing(self, thread):
- """prepare"""
- self.setup()
- self.thread = thread
-
- self.decrypt(self.pyfile)
-
- self.createPackages()
-
-
- def decrypt(self, pyfile):
- raise NotImplementedError
-
- def createPackages(self):
- """ create new packages from self.packages """
- for pack in self.packages:
-
- self.log.debug("Parsed package %(name)s with %(len)d links" % { "name" : pack[0], "len" : len(pack[1]) } )
-
- links = [x.decode("utf-8") for x in pack[1]]
-
- pid = self.core.api.addPackage(pack[0], links, self.pyfile.package().queue)
-
- if self.pyfile.package().password:
- self.core.api.setPackageData(pid, {"password": self.pyfile.package().password})
-
- if self.urls:
- self.core.api.generateAndAddPackages(self.urls)
diff --git a/module/plugins/Hook.py b/module/plugins/Hook.py
deleted file mode 100644
index 279f813d1..000000000
--- a/module/plugins/Hook.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from traceback import print_exc
-
-from module.plugins.Plugin import Base
-
-
-class Expose(object):
- """ used for decoration to declare rpc services """
-
- def __new__(cls, f, *args, **kwargs):
- hookManager.addRPC(f.__module__, f.func_name, f.func_doc)
- return f
-
-
-def threaded(f):
-
- def run(*args,**kwargs):
- hookManager.startThread(f, *args, **kwargs)
- return run
-
-
-class Hook(Base):
- """
- Base class for hook plugins.
- """
- __name__ = "Hook"
- __type__ = "hook"
- __version__ = "0.2"
-
- __threaded__ = []
- __config__ = [("name", "type", "desc", "default")]
-
- __description__ = """Interface for hook"""
- __author_name__ = ("mkaay", "RaNaN")
- __author_mail__ = ("mkaay@mkaay.de", "RaNaN@pyload.org")
-
- #: automatically register event listeners for functions, attribute will be deleted dont use it yourself
- event_map = None
-
- # Alternative to event_map
- #: List of events the plugin can handle, name the functions exactly like eventname.
- event_list = None # dont make duplicate entries in event_map
-
- #: periodic call interval in secondc
- interval = 60
-
-
- def __init__(self, core, manager):
- Base.__init__(self, core)
-
- #: Provide information in dict here, usable by API `getInfo`
- self.info = None
-
- #: Callback of periodical job task, used by hookmanager
- self.cb = None
-
- #: `HookManager`
- self.manager = manager
-
- #register events
- if self.event_map:
- for event, funcs in self.event_map.iteritems():
- if type(funcs) in (list, tuple):
- for f in funcs:
- self.manager.addEvent(event, getattr(self,f))
- else:
- self.manager.addEvent(event, getattr(self,funcs))
-
- #delete for various reasons
- self.event_map = None
-
- if self.event_list:
- for f in self.event_list:
- self.manager.addEvent(f, getattr(self,f))
-
- self.event_list = None
-
- self.initPeriodical()
- self.setup()
-
- def initPeriodical(self):
- if self.interval >=1:
- self.cb = self.core.scheduler.addJob(0, self._periodical, threaded=False)
-
- def _periodical(self):
- try:
- if self.isActivated(): self.periodical()
- except Exception, e:
- self.core.log.error(_("Error executing hooks: %s") % str(e))
- if self.core.debug:
- print_exc()
-
- self.cb = self.core.scheduler.addJob(self.interval, self._periodical, threaded=False)
-
-
- def __repr__(self):
- return "<Hook %s>" % self.__name__
-
- def setup(self):
- """ more init stuff if needed """
- pass
-
- def unload(self):
- """ called when hook was deactivated """
- pass
-
- def isActivated(self):
- """ checks if hook is activated"""
- return self.config.getPlugin(self.__name__, "activated")
-
-
- #event methods - overwrite these if needed
- def coreReady(self):
- pass
-
- def coreExiting(self):
- pass
-
- def downloadPreparing(self, pyfile):
- pass
-
- def downloadFinished(self, pyfile):
- pass
-
- def downloadFailed(self, pyfile):
- pass
-
- def packageFinished(self, pypack):
- pass
-
- def beforeReconnecting(self, ip):
- pass
-
- def afterReconnecting(self, ip):
- pass
-
- def periodical(self):
- pass
-
- def newCaptchaTask(self, task):
- """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """
- pass
-
- def captchaCorrect(self, task):
- pass
-
- def captchaInvalid(self, task):
- pass
diff --git a/module/plugins/Hoster.py b/module/plugins/Hoster.py
deleted file mode 100644
index bedfce129..000000000
--- a/module/plugins/Hoster.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Plugin import Plugin
-
-
-def getInfo(self):
- #result = [ .. (name, size, status, url) .. ]
- return
-
-
-class Hoster(Plugin):
- __name__ = "Hoster"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = None
-
- __description__ = """Base hoster plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
diff --git a/module/plugins/Plugin.py b/module/plugins/Plugin.py
deleted file mode 100644
index 68b2311b3..000000000
--- a/module/plugins/Plugin.py
+++ /dev/null
@@ -1,604 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import time, sleep
-from random import randint
-
-import os
-from os import remove, makedirs, chmod, stat
-from os.path import exists, join
-
-if os.name != "nt":
- from os import chown
- from pwd import getpwnam
- from grp import getgrnam
-
-from itertools import islice
-
-from module.utils import save_join, save_path, fs_encode, fs_decode
-
-
-def chunks(iterable, size):
- it = iter(iterable)
- item = list(islice(it, size))
- while item:
- yield item
- item = list(islice(it, size))
-
-
-class Abort(Exception):
- """ raised when aborted """
-
-
-class Fail(Exception):
- """ raised when failed """
-
-
-class Reconnect(Exception):
- """ raised when reconnected """
-
-
-class Retry(Exception):
- """ raised when start again from beginning """
-
-
-class SkipDownload(Exception):
- """ raised when download should be skipped """
-
-
-class Base(object):
- """
- A Base class with log/config/db methods *all* plugin types can use
- """
-
- def __init__(self, core):
- #: Core instance
- self.core = core
- #: logging instance
- self.log = core.log
- #: core config
- self.config = core.config
-
- #log functions
- def logInfo(self, *args):
- self.log.info("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logWarning(self, *args):
- self.log.warning("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logError(self, *args):
- self.log.error("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
- def logDebug(self, *args):
- self.log.debug("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
-
-
- def setConf(self, option, value):
- """ see `setConfig` """
- self.core.config.setPlugin(self.__name__, option, value)
-
- def setConfig(self, option, value):
- """ Set config value for current plugin
-
- :param option:
- :param value:
- :return:
- """
- self.setConf(option, value)
-
- def getConf(self, option):
- """ see `getConfig` """
- return self.core.config.getPlugin(self.__name__, option)
-
- def getConfig(self, option):
- """ Returns config value for current plugin
-
- :param option:
- :return:
- """
- return self.getConf(option)
-
- def setStorage(self, key, value):
- """ Saves a value persistently to the database """
- self.core.db.setStorage(self.__name__, key, value)
-
- def store(self, key, value):
- """ same as `setStorage` """
- self.core.db.setStorage(self.__name__, key, value)
-
- def getStorage(self, key=None, default=None):
- """ Retrieves saved value or dict of all saved entries if key is None """
- if key is not None:
- return self.core.db.getStorage(self.__name__, key) or default
- return self.core.db.getStorage(self.__name__, key)
-
- def retrieve(self, *args, **kwargs):
- """ same as `getStorage` """
- return self.getStorage(*args, **kwargs)
-
- def delStorage(self, key):
- """ Delete entry in db """
- self.core.db.delStorage(self.__name__, key)
-
-
-class Plugin(Base):
- """
- Base plugin for hoster/crypter.
- Overwrite `process` / `decrypt` in your subclassed plugin.
- """
- __name__ = "Plugin"
- __type__ = "hoster"
- __version__ = "0.4"
-
- __pattern__ = None
- __config__ = [("name", "type", "desc", "default")]
-
- __description__ = """Base plugin"""
- __author_name__ = ("RaNaN", "spoob", "mkaay")
- __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org", "mkaay@mkaay.de")
-
-
- def __init__(self, pyfile):
- Base.__init__(self, pyfile.m.core)
-
- self.wantReconnect = False
- #: enables simultaneous processing of multiple downloads
- self.multiDL = True
- self.limitDL = 0
- #: chunk limit
- self.chunkLimit = 1
- self.resumeDownload = False
-
- #: time() + wait in seconds
- self.waitUntil = 0
- self.waiting = False
-
- self.ocr = None #captcha reader instance
- #: account handler instance, see :py:class:`Account`
- self.account = pyfile.m.core.accountManager.getAccountPlugin(self.__name__)
-
- #: premium status
- self.premium = False
- #: username/login
- self.user = None
-
- if self.account and not self.account.canUse(): self.account = None
- if self.account:
- self.user, data = self.account.selectAccount()
- #: Browser instance, see `network.Browser`
- self.req = self.account.getAccountRequest(self.user)
- self.chunkLimit = -1 # chunk limit, -1 for unlimited
- #: enables resume (will be ignored if server dont accept chunks)
- self.resumeDownload = True
- self.multiDL = True #every hoster with account should provide multiple downloads
- #: premium status
- self.premium = self.account.isPremium(self.user)
- else:
- self.req = pyfile.m.core.requestFactory.getRequest(self.__name__)
-
- #: associated pyfile instance, see `PyFile`
- self.pyfile = pyfile
- self.thread = None # holds thread in future
-
- #: location where the last call to download was saved
- self.lastDownload = ""
- #: re match of the last call to `checkDownload`
- self.lastCheck = None
- #: js engine, see `JsEngine`
- self.js = self.core.js
- self.cTask = None #captcha task
-
- self.retries = 0 # amount of retries already made
- self.html = None # some plugins store html code here
-
- self.init()
-
- def getChunkCount(self):
- if self.chunkLimit <= 0:
- return self.config['download']['chunks']
- return min(self.config['download']['chunks'], self.chunkLimit)
-
- def __call__(self):
- return self.__name__
-
- def init(self):
- """initialize the plugin (in addition to `__init__`)"""
- pass
-
- def setup(self):
- """ setup for enviroment and other things, called before downloading (possibly more than one time)"""
- pass
-
- def preprocessing(self, thread):
- """ handles important things to do before starting """
- self.thread = thread
-
- if self.account:
- self.account.checkLogin(self.user)
- else:
- self.req.clearCookies()
-
- self.setup()
-
- self.pyfile.setStatus("starting")
-
- return self.process(self.pyfile)
-
-
- def process(self, pyfile):
- """the 'main' method of every plugin, you **have to** overwrite it"""
- raise NotImplementedError
-
- def resetAccount(self):
- """ dont use account and retry download """
- self.account = None
- self.req = self.core.requestFactory.getRequest(self.__name__)
- self.retry()
-
- def checksum(self, local_file=None):
- """
- return codes:
- 0 - checksum ok
- 1 - checksum wrong
- 5 - can't get checksum
- 10 - not implemented
- 20 - unknown error
- """
- #@TODO checksum check hook
-
- return True, 10
-
-
- def setWait(self, seconds, reconnect=False):
- """Set a specific wait time later used with `wait`
-
- :param seconds: wait time in seconds
- :param reconnect: True if a reconnect would avoid wait time
- """
- if reconnect:
- self.wantReconnect = True
- self.pyfile.waitUntil = time() + int(seconds)
-
- def wait(self):
- """ waits the time previously set """
- self.waiting = True
- self.pyfile.setStatus("waiting")
-
- while self.pyfile.waitUntil > time():
- self.thread.m.reconnecting.wait(2)
-
- if self.pyfile.abort: raise Abort
- if self.thread.m.reconnecting.isSet():
- self.waiting = False
- self.wantReconnect = False
- raise Reconnect
-
- self.waiting = False
- self.pyfile.setStatus("starting")
-
- def fail(self, reason):
- """ fail and give reason """
- raise Fail(reason)
-
- def offline(self):
- """ fail and indicate file is offline """
- raise Fail("offline")
-
- def tempOffline(self):
- """ fail and indicates file ist temporary offline, the core may take consequences """
- raise Fail("temp. offline")
-
- def retry(self, max_tries=3, wait_time=1, reason=""):
- """Retries and begin again from the beginning
-
- :param max_tries: number of maximum retries
- :param wait_time: time to wait in seconds
- :param reason: reason for retrying, will be passed to fail if max_tries reached
- """
- if 0 < max_tries <= self.retries:
- if not reason: reason = "Max retries reached"
- raise Fail(reason)
-
- self.wantReconnect = False
- self.setWait(wait_time)
- self.wait()
-
- self.retries += 1
- raise Retry(reason)
-
- def invalidCaptcha(self):
- if self.cTask:
- self.cTask.invalid()
-
- def correctCaptcha(self):
- if self.cTask:
- self.cTask.correct()
-
- def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
- result_type='textual'):
- """ Loads a captcha and decrypts it with ocr, plugin, user input
-
- :param url: url of captcha image
- :param get: get part for request
- :param post: post part for request
- :param cookies: True if cookies should be enabled
- :param forceUser: if True, ocr is not used
- :param imgtype: Type of the Image
- :param result_type: 'textual' if text is written on the captcha\
- or 'positional' for captcha where the user have to click\
- on a specific region on the captcha
-
- :return: result of decrypting
- """
-
- img = self.load(url, get=get, post=post, cookies=cookies)
-
- id = ("%.2f" % time())[-6:].replace(".", "")
- temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb")
- temp_file.write(img)
- temp_file.close()
-
- has_plugin = self.__name__ in self.core.pluginManager.captchaPlugins
-
- if self.core.captcha:
- Ocr = self.core.pluginManager.loadClass("captcha", self.__name__)
- else:
- Ocr = None
-
- if Ocr and not forceUser:
- sleep(randint(3000, 5000) / 1000.0)
- if self.pyfile.abort: raise Abort
-
- ocr = Ocr()
- result = ocr.get_captcha(temp_file.name)
- else:
- captchaManager = self.core.captchaManager
- task = captchaManager.newTask(img, imgtype, temp_file.name, result_type)
- self.cTask = task
- captchaManager.handleCaptcha(task)
-
- while task.isWaiting():
- if self.pyfile.abort:
- captchaManager.removeTask(task)
- raise Abort
- sleep(1)
-
- captchaManager.removeTask(task)
-
- if task.error and has_plugin: #ignore default error message since the user could use OCR
- self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting"))
- elif task.error:
- self.fail(task.error)
- elif not task.result:
- self.fail(_("No captcha result obtained in appropiate time by any of the plugins."))
-
- result = task.result
- self.log.debug("Received captcha result: %s" % str(result))
-
- if not self.core.debug:
- try:
- remove(temp_file.name)
- except:
- pass
-
- return result
-
-
- def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
- """Load content at url and returns it
-
- :param url:
- :param get:
- :param post:
- :param ref:
- :param cookies:
- :param just_header: if True only the header will be retrieved and returned as dict
- :param decode: Wether to decode the output according to http header, should be True in most cases
- :return: Loaded content
- """
- if self.pyfile.abort: raise Abort
- #utf8 vs decode -> please use decode attribute in all future plugins
- if type(url) == unicode: url = str(url)
-
- res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode)
-
- if self.core.debug:
- from inspect import currentframe
-
- frame = currentframe()
- if not exists(join("tmp", self.__name__)):
- makedirs(join("tmp", self.__name__))
-
- f = open(
- join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
- , "wb")
- del frame # delete the frame or it wont be cleaned
-
- try:
- tmp = res.encode("utf8")
- except:
- tmp = res
-
- f.write(tmp)
- f.close()
-
- if just_header:
- #parse header
- header = {"code": self.req.code}
- for line in res.splitlines():
- line = line.strip()
- if not line or ":" not in line: continue
-
- key, none, value = line.partition(":")
- key = key.lower().strip()
- value = value.strip()
-
- if key in header:
- if type(header[key]) == list:
- header[key].append(value)
- else:
- header[key] = [header[key], value]
- else:
- header[key] = value
- res = header
-
- return res
-
- def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False):
- """Downloads the content at url to download folder
-
- :param url:
- :param get:
- :param post:
- :param ref:
- :param cookies:
- :param disposition: if True and server provides content-disposition header\
- the filename will be changed if needed
- :return: The location where the file was saved
- """
-
- self.checkForSameFiles()
-
- self.pyfile.setStatus("downloading")
-
- download_folder = self.config['general']['download_folder']
-
- location = save_join(download_folder, self.pyfile.package().folder)
-
- if not exists(location):
- makedirs(location, int(self.core.config['permission']['folder'], 8))
-
- if self.core.config['permission']['change_dl'] and os.name != "nt":
- try:
- uid = getpwnam(self.config['permission']['user'])[2]
- gid = getgrnam(self.config['permission']['group'])[2]
-
- chown(location, uid, gid)
- except Exception, e:
- self.log.warning(_("Setting User and Group failed: %s") % str(e))
-
- # convert back to unicode
- location = fs_decode(location)
- name = save_path(self.pyfile.name)
-
- filename = join(location, name)
-
- self.core.hookManager.dispatchEvent("downloadStarts", self.pyfile, url, filename)
-
- try:
- newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies,
- chunks=self.getChunkCount(), resume=self.resumeDownload,
- progressNotify=self.pyfile.setProgress, disposition=disposition)
- finally:
- self.pyfile.size = self.req.size
-
- if disposition and newname and newname != name: #triple check, just to be sure
- self.log.info("%(name)s saved as %(newname)s" % {"name": name, "newname": newname})
- self.pyfile.name = newname
- filename = join(location, newname)
-
- fs_filename = fs_encode(filename)
-
- if self.core.config['permission']['change_file']:
- chmod(fs_filename, int(self.core.config['permission']['file'], 8))
-
- if self.core.config['permission']['change_dl'] and os.name != "nt":
- try:
- uid = getpwnam(self.config['permission']['user'])[2]
- gid = getgrnam(self.config['permission']['group'])[2]
-
- chown(fs_filename, uid, gid)
- except Exception, e:
- self.log.warning(_("Setting User and Group failed: %s") % str(e))
-
- self.lastDownload = filename
- return self.lastDownload
-
- def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0):
- """ checks the content of the last downloaded file, re match is saved to `lastCheck`
-
- :param rules: dict with names and rules to match (compiled regexp or strings)
- :param api_size: expected file size
- :param max_size: if the file is larger then it wont be checked
- :param delete: delete if matched
- :param read_size: amount of bytes to read from files larger then max_size
- :return: dictionary key of the first rule that matched
- """
- lastDownload = fs_encode(self.lastDownload)
- if not exists(lastDownload): return None
-
- size = stat(lastDownload)
- size = size.st_size
-
- if api_size and api_size <= size: return None
- elif size > max_size and not read_size: return None
- self.log.debug("Download Check triggered")
- f = open(lastDownload, "rb")
- content = f.read(read_size if read_size else -1)
- f.close()
- #produces encoding errors, better log to other file in the future?
- #self.log.debug("Content: %s" % content)
- for name, rule in rules.iteritems():
- if type(rule) in (str, unicode):
- if rule in content:
- if delete:
- remove(lastDownload)
- return name
- elif hasattr(rule, "search"):
- m = rule.search(content)
- if m:
- if delete:
- remove(lastDownload)
- self.lastCheck = m
- return name
-
-
- def getPassword(self):
- """ get the password the user provided in the package"""
- password = self.pyfile.package().password
- if not password: return ""
- return password
-
-
- def checkForSameFiles(self, starting=False):
- """ checks if same file was/is downloaded within same package
-
- :param starting: indicates that the current download is going to start
- :raises SkipDownload:
- """
-
- pack = self.pyfile.package()
-
- for pyfile in self.core.files.cache.values():
- if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder:
- if pyfile.status in (0, 12): #finished or downloading
- raise SkipDownload(pyfile.pluginname)
- elif pyfile.status in (
- 5, 7) and starting: #a download is waiting/starting and was appenrently started before
- raise SkipDownload(pyfile.pluginname)
-
- download_folder = self.config['general']['download_folder']
- location = save_join(download_folder, pack.folder, self.pyfile.name)
-
- if starting and self.core.config['download']['skip_existing'] and exists(location):
- size = os.stat(location).st_size
- if size >= self.pyfile.size:
- raise SkipDownload("File exists.")
-
- pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name)
- if pyfile:
- if exists(location):
- raise SkipDownload(pyfile[0])
-
- self.log.debug("File %s not skipped, because it does not exists." % self.pyfile.name)
-
- def clean(self):
- """ clean everything and remove references """
- if hasattr(self, "pyfile"):
- del self.pyfile
- if hasattr(self, "req"):
- self.req.close()
- del self.req
- if hasattr(self, "thread"):
- del self.thread
- if hasattr(self, "html"):
- del self.html
diff --git a/module/plugins/PluginManager.py b/module/plugins/PluginManager.py
deleted file mode 100644
index 9c7cab64c..000000000
--- a/module/plugins/PluginManager.py
+++ /dev/null
@@ -1,364 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import sys
-
-from itertools import chain
-from os import listdir, makedirs
-from os.path import isfile, join, exists, abspath
-from sys import version_info
-from traceback import print_exc
-
-from module.lib.SafeEval import const_eval as literal_eval
-
-from module.ConfigParser import IGNORE
-
-
-class PluginManager:
- ROOT = "module.plugins."
- USERROOT = "userplugins."
- TYPES = ("crypter", "container", "hoster", "captcha", "accounts", "hooks", "internal")
-
- PATTERN = re.compile(r'__pattern__.*=.*r("|\')([^"\']+)')
- VERSION = re.compile(r'__version__.*=.*("|\')([0-9.]+)')
- CONFIG = re.compile(r'__config__.*=.*\[([^\]]+)', re.MULTILINE)
- DESC = re.compile(r'__description__.?=.?("|"""|\')([^"\']+)')
-
-
- def __init__(self, core):
- self.core = core
-
- #self.config = self.core.config
- self.log = core.log
-
- self.plugins = {}
- self.createIndex()
-
- #register for import hook
- sys.meta_path.append(self)
-
-
- def createIndex(self):
- """create information for all plugins available"""
-
- sys.path.append(abspath(""))
-
- if not exists("userplugins"):
- makedirs("userplugins")
- if not exists(join("userplugins", "__init__.py")):
- f = open(join("userplugins", "__init__.py"), "wb")
- f.close()
-
- self.plugins['crypter'] = self.crypterPlugins = self.parse("crypter", pattern=True)
- self.plugins['container'] = self.containerPlugins = self.parse("container", pattern=True)
- self.plugins['hoster'] = self.hosterPlugins = self.parse("hoster", pattern=True)
-
- self.plugins['captcha'] = self.captchaPlugins = self.parse("captcha")
- self.plugins['accounts'] = self.accountPlugins = self.parse("accounts")
- self.plugins['hooks'] = self.hookPlugins = self.parse("hooks")
- self.plugins['internal'] = self.internalPlugins = self.parse("internal")
-
- self.log.debug("created index of plugins")
-
- def parse(self, folder, pattern=False, home={}):
- """
- returns dict with information
- home contains parsed plugins from module.
-
- {
- name : {path, version, config, (pattern, re), (plugin, class)}
- }
-
- """
- plugins = {}
- if home:
- pfolder = join("userplugins", folder)
- if not exists(pfolder):
- makedirs(pfolder)
- if not exists(join(pfolder, "__init__.py")):
- f = open(join(pfolder, "__init__.py"), "wb")
- f.close()
-
- else:
- pfolder = join(pypath, "module", "plugins", folder)
-
- for f in listdir(pfolder):
- if (isfile(join(pfolder, f)) and f.endswith(".py") or f.endswith("_25.pyc") or f.endswith(
- "_26.pyc") or f.endswith("_27.pyc")) and not f.startswith("_"):
- data = open(join(pfolder, f))
- content = data.read()
- data.close()
-
- if f.endswith("_25.pyc") and version_info[0:2] != (2, 5):
- continue
- elif f.endswith("_26.pyc") and version_info[0:2] != (2, 6):
- continue
- elif f.endswith("_27.pyc") and version_info[0:2] != (2, 7):
- continue
-
- name = f[:-3]
- if name[-1] == ".": name = name[:-4]
-
- version = self.VERSION.findall(content)
- if version:
- version = float(version[0][1])
- else:
- version = 0
-
- # home contains plugins from pyload root
- if home and name in home:
- if home[name]['v'] >= version:
- continue
-
- if name in IGNORE or (folder, name) in IGNORE:
- continue
-
- plugins[name] = {}
- plugins[name]['v'] = version
-
- module = f.replace(".pyc", "").replace(".py", "")
-
- # the plugin is loaded from user directory
- plugins[name]['user'] = True if home else False
- plugins[name]['name'] = module
-
- if pattern:
- pattern = self.PATTERN.findall(content)
-
- if pattern:
- pattern = pattern[0][1]
- else:
- pattern = "^unmachtable$"
-
- plugins[name]['pattern'] = pattern
-
- try:
- plugins[name]['re'] = re.compile(pattern)
- except:
- self.log.error(_("%s has a invalid pattern.") % name)
-
-
- # internals have no config
- if folder == "internal":
- self.core.config.deleteConfig(name)
- continue
-
- config = self.CONFIG.findall(content)
- if config:
- config = literal_eval(config[0].strip().replace("\n", "").replace("\r", ""))
- desc = self.DESC.findall(content)
- desc = desc[0][1] if desc else ""
-
- if type(config[0]) == tuple:
- config = [list(x) for x in config]
- else:
- config = [list(config)]
-
- if folder == "hooks":
- append = True
- for item in config:
- if item[0] == "activated": append = False
-
- # activated flag missing
- if append: config.append(["activated", "bool", "Activated", False])
-
- try:
- self.core.config.addPluginConfig(name, config, desc)
- except:
- self.log.error("Invalid config in %s: %s" % (name, config))
-
- elif folder == "hooks": #force config creation
- desc = self.DESC.findall(content)
- desc = desc[0][1] if desc else ""
- config = (["activated", "bool", "Activated", False],)
-
- try:
- self.core.config.addPluginConfig(name, config, desc)
- except:
- self.log.error("Invalid config in %s: %s" % (name, config))
-
- if not home:
- temp = self.parse(folder, pattern, plugins)
- plugins.update(temp)
-
- return plugins
-
-
- def parseUrls(self, urls):
- """parse plugins for given list of urls"""
-
- last = None
- res = [] # tupels of (url, plugin)
-
- for url in urls:
- if type(url) not in (str, unicode, buffer): continue
- found = False
-
- if last and last[1]['re'].match(url):
- res.append((url, last[0]))
- continue
-
- for name, value in chain(self.crypterPlugins.iteritems(), self.hosterPlugins.iteritems(),
- self.containerPlugins.iteritems()):
- if value['re'].match(url):
- res.append((url, name))
- last = (name, value)
- found = True
- break
-
- if not found:
- res.append((url, "BasePlugin"))
-
- return res
-
- def findPlugin(self, name, pluginlist=("hoster", "crypter", "container")):
- for ptype in pluginlist:
- if name in self.plugins[ptype]:
- return self.plugins[ptype][name], ptype
- return None, None
-
- def getPlugin(self, name, original=False):
- """return plugin module from hoster|decrypter|container"""
- plugin, type = self.findPlugin(name)
-
- if not plugin:
- self.log.warning("Plugin %s not found." % name)
- plugin = self.hosterPlugins['BasePlugin']
-
- if "new_module" in plugin and not original:
- return plugin['new_module']
-
- return self.loadModule(type, name)
-
- def getPluginName(self, name):
- """ used to obtain new name if other plugin was injected"""
- plugin, type = self.findPlugin(name)
-
- if "new_name" in plugin:
- return plugin['new_name']
-
- return name
-
- def loadModule(self, type, name):
- """ Returns loaded module for plugin
-
- :param type: plugin type, subfolder of module.plugins
- :param name:
- """
- plugins = self.plugins[type]
- if name in plugins:
- if "module" in plugins[name]: return plugins[name]['module']
- try:
- module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]['name']), globals(), locals(),
- plugins[name]['name'])
- plugins[name]['module'] = module #cache import, maybe unneeded
- return module
- except Exception, e:
- self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)})
- if self.core.debug:
- print_exc()
-
- def loadClass(self, type, name):
- """Returns the class of a plugin with the same name"""
- module = self.loadModule(type, name)
- if module: return getattr(module, name)
-
- def getAccountPlugins(self):
- """return list of account plugin names"""
- return self.accountPlugins.keys()
-
- def find_module(self, fullname, path=None):
- #redirecting imports if necesarry
- if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #seperate pyload plugins
- if fullname.startswith(self.USERROOT): user = 1
- else: user = 0 #used as bool and int
-
- split = fullname.split(".")
- if len(split) != 4 - user: return
- type, name = split[2 - user:4 - user]
-
- if type in self.plugins and name in self.plugins[type]:
- #userplugin is a newer version
- if not user and self.plugins[type][name]['user']:
- return self
- #imported from userdir, but pyloads is newer
- if user and not self.plugins[type][name]['user']:
- return self
-
-
- def load_module(self, name, replace=True):
- if name not in sys.modules: #could be already in modules
- if replace:
- if self.ROOT in name:
- newname = name.replace(self.ROOT, self.USERROOT)
- else:
- newname = name.replace(self.USERROOT, self.ROOT)
- else: newname = name
-
- base, plugin = newname.rsplit(".", 1)
-
- self.log.debug("Redirected import %s -> %s" % (name, newname))
-
- module = __import__(newname, globals(), locals(), [plugin])
- #inject under new an old name
- sys.modules[name] = module
- sys.modules[newname] = module
-
- return sys.modules[name]
-
-
- def reloadPlugins(self, type_plugins):
- """ reloads and reindexes plugins """
- if not type_plugins: return False
-
- self.log.debug("Request reload of plugins: %s" % type_plugins)
-
- as_dict = {}
- for t,n in type_plugins:
- if t in as_dict:
- as_dict[t].append(n)
- else:
- as_dict[t] = [n]
-
- # we do not reload hooks or internals, would cause to much side effects
- if "hooks" in as_dict or "internal" in as_dict:
- return False
-
- for type in as_dict.iterkeys():
- for plugin in as_dict[type]:
- if plugin in self.plugins[type]:
- if "module" in self.plugins[type][plugin]:
- self.log.debug("Reloading %s" % plugin)
- reload(self.plugins[type][plugin]['module'])
-
- #index creation
- self.plugins['crypter'] = self.crypterPlugins = self.parse("crypter", pattern=True)
- self.plugins['container'] = self.containerPlugins = self.parse("container", pattern=True)
- self.plugins['hoster'] = self.hosterPlugins = self.parse("hoster", pattern=True)
- self.plugins['captcha'] = self.captchaPlugins = self.parse("captcha")
- self.plugins['accounts'] = self.accountPlugins = self.parse("accounts")
-
- if "accounts" in as_dict: #accounts needs to be reloaded
- self.core.accountManager.initPlugins()
- self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
-
- return True
-
-
-
-if __name__ == "__main__":
- _ = lambda x: x
- pypath = "/home/christian/Projekte/pyload-0.4/module/plugins"
-
- from time import time
-
- p = PluginManager(None)
-
- a = time()
-
- test = ["http://www.youtube.com/watch?v=%s" % x for x in xrange(0, 100)]
- print p.parseUrls(test)
-
- b = time()
-
- print b - a, "s"
diff --git a/module/plugins/ReCaptcha.py b/module/plugins/ReCaptcha.py
deleted file mode 100644
index 143ecfb83..000000000
--- a/module/plugins/ReCaptcha.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-
-class ReCaptcha():
-
- def __init__(self, plugin):
- self.plugin = plugin
-
- def challenge(self, id):
- js = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", get={"k":id}, cookies=True)
-
- try:
- challenge = re.search("challenge : '(.*?)',", js).group(1)
- server = re.search("server : '(.*?)',", js).group(1)
- except:
- self.plugin.fail("recaptcha error")
- result = self.result(server,challenge)
-
- return challenge, result
-
- def result(self, server, challenge):
- return self.plugin.decryptCaptcha("%simage"%server, get={"c":challenge}, cookies=True, imgtype="jpg")
diff --git a/module/plugins/accounts/AlldebridCom.py b/module/plugins/accounts/AlldebridCom.py
deleted file mode 100644
index dbf4ad800..000000000
--- a/module/plugins/accounts/AlldebridCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import xml.dom.minidom as dom
-
-from time import time
-from urllib import urlencode
-
-from BeautifulSoup import BeautifulSoup
-
-from module.plugins.Account import Account
-
-
-class AlldebridCom(Account):
- __name__ = "AlldebridCom"
- __type__ = "account"
- __version__ = "0.22"
-
- __description__ = """AllDebrid.com account plugin"""
- __author_name__ = "Andy Voigt"
- __author_mail__ = "spamsales@online.de"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- page = req.load("http://www.alldebrid.com/account/")
- soup = BeautifulSoup(page)
- #Try to parse expiration date directly from the control panel page (better accuracy)
- try:
- time_text = soup.find('div', attrs={'class': 'remaining_time_text'}).strong.string
- self.logDebug("Account expires in: %s" % time_text)
- p = re.compile('\d+')
- exp_data = p.findall(time_text)
- exp_time = time() + int(exp_data[0]) * 24 * 60 * 60 + int(
- exp_data[1]) * 60 * 60 + (int(exp_data[2]) - 1) * 60
- #Get expiration date from API
- except:
- data = self.getAccountData(user)
- page = req.load("http://www.alldebrid.com/api.php?action=info_user&login=%s&pw=%s" % (user,
- data['password']))
- self.logDebug(page)
- xml = dom.parseString(page)
- exp_time = time() + int(xml.getElementsByTagName("date")[0].childNodes[0].nodeValue) * 24 * 60 * 60
- account_info = {"validuntil": exp_time, "trafficleft": -1}
- return account_info
-
- def login(self, user, data, req):
- urlparams = urlencode({'action': 'login', 'login_login': user, 'login_password': data['password']})
- page = req.load("http://www.alldebrid.com/register/?%s" % urlparams)
-
- if "This login doesn't exist" in page:
- self.wrongPassword()
-
- if "The password is not valid" in page:
- self.wrongPassword()
-
- if "Invalid captcha" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/BayfilesCom.py b/module/plugins/accounts/BayfilesCom.py
deleted file mode 100644
index 7c4708f7d..000000000
--- a/module/plugins/accounts/BayfilesCom.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class BayfilesCom(Account):
- __name__ = "BayfilesCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Bayfiles.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- for _ in xrange(2):
- response = json_loads(req.load("http://api.bayfiles.com/v1/account/info"))
- self.logDebug(response)
- if not response['error']:
- break
- self.logWarning(response['error'])
- self.relogin(user)
-
- return {"premium": bool(response['premium']), "trafficleft": -1,
- "validuntil": response['expires'] if response['expires'] >= int(time()) else -1}
-
- def login(self, user, data, req):
- response = json_loads(req.load("http://api.bayfiles.com/v1/account/login/%s/%s" % (user, data['password'])))
- self.logDebug(response)
- if response['error']:
- self.logError(response['error'])
- self.wrongPassword()
diff --git a/module/plugins/accounts/BitshareCom.py b/module/plugins/accounts/BitshareCom.py
deleted file mode 100644
index 272bbeb6e..000000000
--- a/module/plugins/accounts/BitshareCom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class BitshareCom(Account):
- __name__ = "BitshareCom"
- __type__ = "account"
- __version__ = "0.12"
-
- __description__ = """Bitshare account plugin"""
- __author_name__ = "Paul King"
- __author_mail__ = None
-
-
- def loadAccountInfo(self, user, req):
- page = req.load("http://bitshare.com/mysettings.html")
-
- if "\"http://bitshare.com/myupgrade.html\">Free" in page:
- return {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- if not '<input type="checkbox" name="directdownload" checked="checked" />' in page:
- self.logWarning(_("Activate direct Download in your Bitshare Account"))
-
- return {"validuntil": -1, "trafficleft": -1, "premium": True}
-
- def login(self, user, data, req):
- page = req.load("http://bitshare.com/login.html",
- post={"user": user, "password": data['password'], "submit": "Login"}, cookies=True)
- if "login" in req.lastEffectiveURL:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CramitIn.py b/module/plugins/accounts/CramitIn.py
deleted file mode 100644
index 34aa3ab40..000000000
--- a/module/plugins/accounts/CramitIn.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class CramitIn(XFSPAccount):
- __name__ = "CramitIn"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Cramit.in account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- MAIN_PAGE = "http://cramit.in/"
diff --git a/module/plugins/accounts/CyberlockerCh.py b/module/plugins/accounts/CyberlockerCh.py
deleted file mode 100644
index 729975fb0..000000000
--- a/module/plugins/accounts/CyberlockerCh.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-from module.plugins.internal.SimpleHoster import parseHtmlForm
-
-
-class CyberlockerCh(XFSPAccount):
- __name__ = "CyberlockerCh"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Cyberlocker.ch account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- MAIN_PAGE = "http://cyberlocker.ch/"
-
-
- def login(self, user, data, req):
- html = req.load(self.MAIN_PAGE + 'login.html', decode=True)
-
- action, inputs = parseHtmlForm('name="FL"', html)
- if not inputs:
- inputs = {"op": "login",
- "redirect": self.MAIN_PAGE}
-
- inputs.update({"login": user,
- "password": data['password']})
-
- # Without this a 403 Forbidden is returned
- req.http.lastURL = self.MAIN_PAGE + 'login.html'
- html = req.load(self.MAIN_PAGE, post=inputs, decode=True)
-
- if 'Incorrect Login or Password' in html or '>Error<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/CzshareCom.py b/module/plugins/accounts/CzshareCom.py
deleted file mode 100644
index 6f2ee641e..000000000
--- a/module/plugins/accounts/CzshareCom.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-import re
-
-from module.plugins.Account import Account
-
-
-class CzshareCom(Account):
- __name__ = "CzshareCom"
- __type__ = "account"
- __version__ = "0.14"
-
- __description__ = """Czshare.com account plugin, now Sdilej.cz"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([0-9 ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://sdilej.cz/prehled_kreditu/")
-
- m = re.search(self.CREDIT_LEFT_PATTERN, html)
- if m is None:
- return {"validuntil": 0, "trafficleft": 0}
- else:
- credits = float(m.group(1).replace(' ', '').replace(',', '.'))
- credits = credits * 1024 ** {'KiB': 0, 'MiB': 1, 'GiB': 2}[m.group(2)]
- validuntil = mktime(strptime(m.group(3), '%d.%m.%y %H:%M'))
- return {"validuntil": validuntil, "trafficleft": credits}
-
- def login(self, user, data, req):
- html = req.load('https://sdilej.cz/index.php', post={
- "Prihlasit": "Prihlasit",
- "login-password": data['password'],
- "login-name": user
- })
-
- if '<div class="login' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/DebridItaliaCom.py b/module/plugins/accounts/DebridItaliaCom.py
deleted file mode 100644
index f4441c356..000000000
--- a/module/plugins/accounts/DebridItaliaCom.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class DebridItaliaCom(Account):
- __name__ = "DebridItaliaCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Debriditalia.com account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- WALID_UNTIL_PATTERN = r"Premium valid till: (?P<D>[^|]+) \|"
-
-
- def loadAccountInfo(self, user, req):
- if 'Account premium not activated' in self.html:
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
- m = re.search(self.WALID_UNTIL_PATTERN, self.html)
- if m:
- validuntil = int(time.mktime(time.strptime(m.group('D'), "%d/%m/%Y %H:%M")))
- return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
- else:
- self.logError('Unable to retrieve account information - Plugin may be out of date')
-
- def login(self, user, data, req):
- self.html = req.load("http://debriditalia.com/login.php",
- get={"u": user, "p": data['password']})
- if 'NO' in self.html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/DepositfilesCom.py b/module/plugins/accounts/DepositfilesCom.py
deleted file mode 100644
index 01f1906f4..000000000
--- a/module/plugins/accounts/DepositfilesCom.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import strptime, mktime
-
-from module.plugins.Account import Account
-
-
-class DepositfilesCom(Account):
- __name__ = "DepositfilesCom"
- __type__ = "account"
- __version__ = "0.3"
-
- __description__ = """Depositfiles.com account plugin"""
- __author_name__ = ("mkaay", "stickell", "Walter Purcaro")
- __author_mail__ = ("mkaay@mkaay.de", "l.stickell@yahoo.it", "vuolter@gmail.com")
-
-
- def loadAccountInfo(self, user, req):
- src = req.load("https://dfiles.eu/de/gold/")
- validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", src).group(1)
-
- validuntil = int(mktime(strptime(validuntil, "%Y-%m-%d %H:%M:%S")))
-
- return {"validuntil": validuntil, "trafficleft": -1}
-
- def login(self, user, data, req):
- src = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
- post={"login": user, "password": data['password']})
- if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in src:
- self.wrongPassword()
diff --git a/module/plugins/accounts/EasybytezCom.py b/module/plugins/accounts/EasybytezCom.py
deleted file mode 100644
index 3b8517686..000000000
--- a/module/plugins/accounts/EasybytezCom.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import mktime, strptime, gmtime
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import parseHtmlForm
-from module.utils import parseFileSize
-
-
-class EasybytezCom(Account):
- __name__ = "EasybytezCom"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """EasyBytez.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- VALID_UNTIL_PATTERN = r'Premium account expire:</TD><TD><b>([^<]+)</b>'
- TRAFFIC_LEFT_PATTERN = r'<TR><TD>Traffic available today:</TD><TD><b>(?P<S>[^<]+)</b>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.easybytez.com/?op=my_account", decode=True)
-
- validuntil = trafficleft = None
- premium = False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- try:
- self.logDebug("Expire date: " + m.group(1))
- validuntil = mktime(strptime(m.group(1), "%d %B %Y"))
- except Exception, e:
- self.logError(e)
- if validuntil > mktime(gmtime()):
- premium = True
- trafficleft = -1
- else:
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = m.group(1)
- if "Unlimited" in trafficleft:
- trafficleft = -1
- else:
- trafficleft = parseFileSize(trafficleft) / 1024
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- html = req.load('http://www.easybytez.com/login.html', decode=True)
- action, inputs = parseHtmlForm('name="FL"', html)
- inputs.update({"login": user,
- "password": data['password'],
- "redirect": "http://www.easybytez.com/"})
-
- html = req.load(action, post=inputs, decode=True)
-
- if 'Incorrect Login or Password' in html or '>Error<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/EgoFilesCom.py b/module/plugins/accounts/EgoFilesCom.py
deleted file mode 100644
index 41b58c4e7..000000000
--- a/module/plugins/accounts/EgoFilesCom.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.utils import parseFileSize
-
-
-class EgoFilesCom(Account):
- __name__ = "EgoFilesCom"
- __type__ = "account"
- __version__ = "0.2"
-
- __description__ = """Egofiles.com account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- PREMIUM_ACCOUNT_PATTERN = '<br/>\s*Premium: (?P<P>[^/]*) / Traffic left: (?P<T>[\d.]*) (?P<U>\w*)\s*\\n\s*<br/>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://egofiles.com")
- if 'You are logged as a Free User' in html:
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
- m = re.search(self.PREMIUM_ACCOUNT_PATTERN, html)
- if m:
- validuntil = int(time.mktime(time.strptime(m.group('P'), "%Y-%m-%d %H:%M:%S")))
- trafficleft = parseFileSize(m.group('T'), m.group('U')) / 1024
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
- else:
- self.logError('Unable to retrieve account information - Plugin may be out of date')
-
- def login(self, user, data, req):
- # Set English language
- req.load("https://egofiles.com/ajax/lang.php?lang=en", just_header=True)
-
- html = req.load("http://egofiles.com/ajax/register.php",
- post={"log": 1,
- "loginV": user,
- "passV": data['password']})
- if 'Login successful' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/EuroshareEu.py b/module/plugins/accounts/EuroshareEu.py
deleted file mode 100644
index f37693206..000000000
--- a/module/plugins/accounts/EuroshareEu.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-import re
-
-from module.plugins.Account import Account
-
-
-class EuroshareEu(Account):
- __name__ = "EuroshareEu"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Euroshare.eu account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- self.relogin(user)
- html = req.load("http://euroshare.eu/customer-zone/settings/")
-
- m = re.search('id="input_expire_date" value="(\d+\.\d+\.\d+ \d+:\d+)"', html)
- if m is None:
- premium, validuntil = False, -1
- else:
- premium = True
- validuntil = mktime(strptime(m.group(1), "%d.%m.%Y %H:%M"))
-
- return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
-
- def login(self, user, data, req):
-
- html = req.load('http://euroshare.eu/customer-zone/login/', post={
- "trvale": "1",
- "login": user,
- "password": data['password']
- }, decode=True)
-
- if u">Nesprávne prihlasovacie meno alebo heslo" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FastixRu.py b/module/plugins/accounts/FastixRu.py
deleted file mode 100644
index 7e46ccd05..000000000
--- a/module/plugins/accounts/FastixRu.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FastixRu(Account):
- __name__ = "FastixRu"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Fastix account plugin"""
- __author_name__ = "Massimo Rosamilia"
- __author_mail__ = "max@spiritix.eu"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- page = req.load("http://fastix.ru/api_v2/?apikey=%s&sub=getaccountdetails" % (data['api']))
- page = json_loads(page)
- points = page['points']
- kb = float(points)
- kb = kb * 1024 ** 2 / 1000
- if points > 0:
- account_info = {"validuntil": -1, "trafficleft": kb}
- else:
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
- return account_info
-
- def login(self, user, data, req):
- page = req.load("http://fastix.ru/api_v2/?sub=get_apikey&email=%s&password=%s" % (user, data['password']))
- api = json_loads(page)
- api = api['apikey']
- data['api'] = api
- if "error_code" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FastshareCz.py b/module/plugins/accounts/FastshareCz.py
deleted file mode 100644
index a968be19e..000000000
--- a/module/plugins/accounts/FastshareCz.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Account import Account
-from module.utils import parseFileSize
-
-
-class FastshareCz(Account):
- __name__ = "FastshareCz"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Fastshare.cz account plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- CREDIT_PATTERN = r'(?:Kredit|Credit)\s*</td>\s*<td[^>]*>([\d. \w]+)&nbsp;'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.fastshare.cz/user", decode=True)
-
- m = re.search(self.CREDIT_PATTERN, html)
- if m:
- trafficleft = parseFileSize(m.group(1)) / 1024
- premium = True if trafficleft else False
- else:
- trafficleft = None
- premium = False
-
- return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- req.load('http://www.fastshare.cz/login') # Do not remove or it will not login
- html = req.load('http://www.fastshare.cz/sql.php', post={
- "heslo": data['password'],
- "login": user
- }, decode=True)
-
- if u'>Špatné uşivatelské jméno nebo heslo.<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/File4safeCom.py b/module/plugins/accounts/File4safeCom.py
deleted file mode 100644
index aa7894e98..000000000
--- a/module/plugins/accounts/File4safeCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class File4safeCom(XFSPAccount):
- __name__ = "File4safeCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """File4safe.com account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- MAIN_PAGE = "http://file4safe.com/"
-
- LOGIN_FAIL_PATTERN = r'input_login'
- PREMIUM_PATTERN = r'Extend Premium'
diff --git a/module/plugins/accounts/FilecloudIo.py b/module/plugins/accounts/FilecloudIo.py
deleted file mode 100644
index ec98cf9b2..000000000
--- a/module/plugins/accounts/FilecloudIo.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FilecloudIo(Account):
- __name__ = "FilecloudIo"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """FilecloudIo account plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
-
- def loadAccountInfo(self, user, req):
- # It looks like the first API request always fails, so we retry 5 times, it should work on the second try
- for _ in xrange(5):
- rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
- post={"username": user, "password": self.accounts[user]['password']})
- rep = json_loads(rep)
- if rep['status'] == 'ok':
- break
- elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password':
- self.logError("Wrong username or password")
- return {"valid": False, "premium": False}
- else:
- return {"premium": False}
-
- akey = rep['akey']
- self.accounts[user]['akey'] = akey # Saved for hoster plugin
- rep = req.load("http://api.filecloud.io/api-fetch_account_details.api",
- post={"akey": akey})
- rep = json_loads(rep)
-
- if rep['is_premium'] == 1:
- return {"validuntil": int(rep['premium_until']), "trafficleft": -1}
- else:
- return {"premium": False}
-
- def login(self, user, data, req):
- req.cj.setCookie("secure.filecloud.io", "lang", "en")
- html = req.load('https://secure.filecloud.io/user-login.html')
-
- if not hasattr(self, "form_data"):
- self.form_data = {}
-
- self.form_data['username'] = user
- self.form_data['password'] = data['password']
-
- html = req.load('https://secure.filecloud.io/user-login_p.html',
- post=self.form_data,
- multipart=True)
-
- self.logged_in = True if "you have successfully logged in - filecloud.io" in html else False
- self.form_data = {}
diff --git a/module/plugins/accounts/FilefactoryCom.py b/module/plugins/accounts/FilefactoryCom.py
deleted file mode 100644
index 84d80cab7..000000000
--- a/module/plugins/accounts/FilefactoryCom.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import mktime, strptime
-
-from pycurl import REFERER
-
-from module.plugins.Account import Account
-
-
-class FilefactoryCom(Account):
- __name__ = "FilefactoryCom"
- __type__ = "account"
- __version__ = "0.14"
-
- __description__ = """Filefactory.com account plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- VALID_UNTIL_PATTERN = r'Premium valid until: <strong>(?P<d>\d{1,2})\w{1,2} (?P<m>\w{3}), (?P<y>\d{4})</strong>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.filefactory.com/account/")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- premium = True
- validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<d> \g<m> \g<y>', m.group(0))
- validuntil = mktime(strptime(validuntil, "%d %b %Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
- def login(self, user, data, req):
- req.http.c.setopt(REFERER, "http://www.filefactory.com/member/login.php")
-
- html = req.load("http://www.filefactory.com/member/signin.php", post={
- "loginEmail": user,
- "loginPassword": data['password'],
- "Submit": "Sign In"})
-
- if req.lastEffectiveURL != "http://www.filefactory.com/account/":
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilejungleCom.py b/module/plugins/accounts/FilejungleCom.py
deleted file mode 100644
index 72e275d4f..000000000
--- a/module/plugins/accounts/FilejungleCom.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-
-
-class FilejungleCom(Account):
- __name__ = "FilejungleCom"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Filejungle.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- login_timeout = 60
-
- URL = "http://filejungle.com/"
- TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ [A-Za-z]+ \d+)<br'
- LOGIN_FAILED_PATTERN = r'<span htmlfor="loginUser(Name|Password)" generated="true" class="fail_info">'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load(self.URL + "dashboard.php")
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- premium = True
- validuntil = mktime(strptime(m.group(1), "%d %b %Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
- def login(self, user, data, req):
- html = req.load(self.URL + "login.php", post={
- "loginUserName": user,
- "loginUserPassword": data['password'],
- "loginFormSubmit": "Login",
- "recaptcha_challenge_field": "",
- "recaptcha_response_field": "",
- "recaptcha_shortencode_field": ""})
-
- if re.search(self.LOGIN_FAILED_PATTERN, html):
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilerNet.py b/module/plugins/accounts/FilerNet.py
deleted file mode 100644
index 2e50298d7..000000000
--- a/module/plugins/accounts/FilerNet.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-from module.utils import parseFileSize
-
-
-class FilerNet(Account):
- __name__ = "FilerNet"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Filer.net account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- TOKEN_PATTERN = r'_csrf_token" value="([^"]+)" />'
- WALID_UNTIL_PATTERN = r"Der Premium-Zugang ist gÃŒltig bis (.+)\.\s*</td>"
- TRAFFIC_PATTERN = r'Traffic</th>\s*<td>([^<]+)</td>'
- FREE_PATTERN = r'Account Status</th>\s*<td>\s*Free'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("https://filer.net/profile")
-
- # Free user
- if re.search(self.FREE_PATTERN, html):
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
- until = re.search(self.WALID_UNTIL_PATTERN, html)
- traffic = re.search(self.TRAFFIC_PATTERN, html)
- if until and traffic:
- validuntil = int(time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S")))
- trafficleft = parseFileSize(traffic.group(1)) / 1024
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
- else:
- self.logError('Unable to retrieve account information - Plugin may be out of date')
- return {"premium": False, "validuntil": None, "trafficleft": None}
-
- def login(self, user, data, req):
- html = req.load("https://filer.net/login")
- token = re.search(self.TOKEN_PATTERN, html).group(1)
- html = req.load("https://filer.net/login_check",
- post={"_username": user, "_password": data['password'],
- "_remember_me": "on", "_csrf_token": token, "_target_path": "https://filer.net/"})
- if 'Logout' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FilerioCom.py b/module/plugins/accounts/FilerioCom.py
deleted file mode 100644
index 544a7f3a6..000000000
--- a/module/plugins/accounts/FilerioCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class FilerioCom(XFSPAccount):
- __name__ = "FilerioCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """FileRio.in account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- MAIN_PAGE = "http://filerio.in/"
diff --git a/module/plugins/accounts/FilesMailRu.py b/module/plugins/accounts/FilesMailRu.py
deleted file mode 100644
index 5ece67140..000000000
--- a/module/plugins/accounts/FilesMailRu.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class FilesMailRu(Account):
- __name__ = "FilesMailRu"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Filesmail.ru account plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def loadAccountInfo(self, user, req):
- return {"validuntil": None, "trafficleft": None}
-
- def login(self, user, data, req):
- user, domain = user.split("@")
-
- page = req.load("http://swa.mail.ru/cgi-bin/auth", None,
- {"Domain": domain, "Login": user, "Password": data['password'],
- "Page": "http://files.mail.ru/"}, cookies=True)
-
- if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in page: # @TODO seems not to work
- self.wrongPassword()
diff --git a/module/plugins/accounts/FileserveCom.py b/module/plugins/accounts/FileserveCom.py
deleted file mode 100644
index 5be5e8d04..000000000
--- a/module/plugins/accounts/FileserveCom.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FileserveCom(Account):
- __name__ = "FileserveCom"
- __type__ = "account"
- __version__ = "0.2"
-
- __description__ = """Fileserve.com account plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
-
- page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(page)
-
- if res['type'] == "premium":
- validuntil = mktime(strptime(res['expireTime'], "%Y-%m-%d %H:%M:%S"))
- return {"trafficleft": res['traffic'], "validuntil": validuntil}
- else:
- return {"premium": False, "trafficleft": None, "validuntil": None}
-
- def login(self, user, data, req):
- page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
- "submit": "Submit+Query"})
- res = json_loads(page)
-
- if not res['type']:
- self.wrongPassword()
-
- #login at fileserv page
- req.load("http://www.fileserve.com/login.php",
- post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
- "loginFormSubmit": "Login"})
diff --git a/module/plugins/accounts/FourSharedCom.py b/module/plugins/accounts/FourSharedCom.py
deleted file mode 100644
index 5e37e54f3..000000000
--- a/module/plugins/accounts/FourSharedCom.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FourSharedCom(Account):
- __name__ = "FourSharedCom"
- __type__ = "account"
- __version__ = "0.03"
- __description__ = """FourShared.com account plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- def loadAccountInfo(self, user, req):
- # Free mode only for now
- return {"premium": False}
-
- def login(self, user, data, req):
- req.cj.setCookie("4shared.com", "4langcookie", "en")
- response = req.load('http://www.4shared.com/web/login',
- post={"login": user,
- "password": data['password'],
- "remember": "on",
- "_remember": "on",
- "returnTo": "http://www.4shared.com/account/home.jsp"})
-
- if 'Please log in to access your 4shared account' in response:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreakshareCom.py b/module/plugins/accounts/FreakshareCom.py
deleted file mode 100644
index dfa5f4541..000000000
--- a/module/plugins/accounts/FreakshareCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import strptime, mktime
-
-from module.plugins.Account import Account
-
-
-class FreakshareCom(Account):
- __name__ = "FreakshareCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Freakshare.com account plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def loadAccountInfo(self, user, req):
- page = req.load("http://freakshare.com/")
-
- validuntil = r"ltig bis:</td>\s*<td><b>([0-9 \-:.]+)</b></td>"
- validuntil = re.search(validuntil, page, re.MULTILINE)
- validuntil = validuntil.group(1).strip()
- validuntil = mktime(strptime(validuntil, "%d.%m.%Y - %H:%M"))
-
- traffic = r"Traffic verbleibend:</td>\s*<td>([^<]+)"
- traffic = re.search(traffic, page, re.MULTILINE)
- traffic = traffic.group(1).strip()
- traffic = self.parseTraffic(traffic)
-
- return {"validuntil": validuntil, "trafficleft": traffic}
-
- def login(self, user, data, req):
- page = req.load("http://freakshare.com/login.html", None,
- {"submit": "Login", "user": user, "pass": data['password']}, cookies=True)
-
- if "Falsche Logindaten!" in page or "Wrong Username or Password!" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/FreeWayMe.py b/module/plugins/accounts/FreeWayMe.py
deleted file mode 100644
index fe5a79949..000000000
--- a/module/plugins/accounts/FreeWayMe.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class FreeWayMe(Account):
- __name__ = "FreeWayMe"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """FreeWayMe account plugin"""
- __author_name__ = "Nicolas Giese"
- __author_mail__ = "james@free-way.me"
-
-
- def loadAccountInfo(self, user, req):
- status = self.getAccountStatus(user, req)
- if not status:
- return False
- self.logDebug(status)
-
- account_info = {"validuntil": -1, "premium": False}
- if status['premium'] == "Free":
- account_info['trafficleft'] = int(status['guthaben']) * 1024
- elif status['premium'] == "Spender":
- account_info['trafficleft'] = -1
- elif status['premium'] == "Flatrate":
- account_info = {"validuntil": int(status['Flatrate']),
- "trafficleft": -1,
- "premium": True}
-
- return account_info
-
- def getpw(self, user):
- return self.accounts[user]['password']
-
- def login(self, user, data, req):
- status = self.getAccountStatus(user, req)
-
- # Check if user and password are valid
- if not status:
- self.wrongPassword()
-
- def getAccountStatus(self, user, req):
- answer = req.load("https://www.free-way.me/ajax/jd.php",
- get={"id": 4, "user": user, "pass": self.accounts[user]['password']})
- self.logDebug("login: %s" % answer)
- if answer == "Invalid login":
- self.wrongPassword()
- return False
- return json_loads(answer)
diff --git a/module/plugins/accounts/FshareVn.py b/module/plugins/accounts/FshareVn.py
deleted file mode 100644
index 78714f238..000000000
--- a/module/plugins/accounts/FshareVn.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-from pycurl import REFERER
-import re
-
-from module.plugins.Account import Account
-
-
-class FshareVn(Account):
- __name__ = "FshareVn"
- __type__ = "account"
- __version__ = "0.07"
-
- __description__ = """Fshare.vn account plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- VALID_UNTIL_PATTERN = ur'<dt>Thời hạn dùng:</dt>\s*<dd>([^<]+)</dd>'
- LIFETIME_PATTERN = ur'<dt>Lần đăng nhập trước:</dt>\s*<dd>[^<]+</dd>'
- TRAFFIC_LEFT_PATTERN = ur'<dt>Tổng Dung Lượng Tài Khoản</dt>\s*<dd[^>]*>([0-9.]+) ([kKMG])B</dd>'
- DIRECT_DOWNLOAD_PATTERN = ur'<input type="checkbox"\s*([^=>]*)[^>]*/>Kích hoạt download trực tiếp</dt>'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.fshare.vn/account_info.php", decode=True)
-
- if re.search(self.LIFETIME_PATTERN, html):
- self.logDebug("Lifetime membership detected")
- trafficleft = self.getTrafficLeft()
- return {"validuntil": -1, "trafficleft": trafficleft, "premium": True}
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- premium = True
- validuntil = mktime(strptime(m.group(1), '%I:%M:%S %p %d-%m-%Y'))
- trafficleft = self.getTrafficLeft()
- else:
- premium = False
- validuntil = None
- trafficleft = None
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- req.http.c.setopt(REFERER, "https://www.fshare.vn/login.php")
-
- html = req.load('https://www.fshare.vn/login.php', post={
- "login_password": data['password'],
- "login_useremail": user,
- "url_refe": "http://www.fshare.vn/index.php"
- }, referer=True, decode=True)
-
- if not re.search(r'<img\s+alt="VIP"', html):
- self.wrongPassword()
-
- def getTrafficLeft(self):
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- return float(m.group(1)) * 1024 ** {'k': 0, 'K': 0, 'M': 1, 'G': 2}[m.group(2)] if m else 0
diff --git a/module/plugins/accounts/Ftp.py b/module/plugins/accounts/Ftp.py
deleted file mode 100644
index 2e60874a9..000000000
--- a/module/plugins/accounts/Ftp.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class Ftp(Account):
- __name__ = "Ftp"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Ftp dummy account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- login_timeout = info_threshold = 1000000
diff --git a/module/plugins/accounts/HellshareCz.py b/module/plugins/accounts/HellshareCz.py
deleted file mode 100644
index 9207cddab..000000000
--- a/module/plugins/accounts/HellshareCz.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.plugins.Account import Account
-
-
-class HellshareCz(Account):
- __name__ = "HellshareCz"
- __type__ = "account"
- __version__ = "0.14"
-
- __description__ = """Hellshare.cz account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- CREDIT_LEFT_PATTERN = r'<div class="credit-link">\s*<table>\s*<tr>\s*<th>(\d+|\d\d\.\d\d\.)</th>'
-
-
- def loadAccountInfo(self, user, req):
- self.relogin(user)
- html = req.load("http://www.hellshare.com/")
-
- m = re.search(self.CREDIT_LEFT_PATTERN, html)
- if m is None:
- trafficleft = None
- validuntil = None
- premium = False
- else:
- credit = m.group(1)
- premium = True
- try:
- if "." in credit:
- #Time-based account
- vt = [int(x) for x in credit.split('.')[:2]]
- lt = time.localtime()
- year = lt.tm_year + int(vt[1] < lt.tm_mon or (vt[1] == lt.tm_mon and vt[0] < lt.tm_mday))
- validuntil = time.mktime(time.strptime("%s%d 23:59:59" % (credit, year), "%d.%m.%Y %H:%M:%S"))
- trafficleft = -1
- else:
- #Traffic-based account
- trafficleft = int(credit) * 1024
- validuntil = -1
- except Exception, e:
- self.logError('Unable to parse credit info', e)
- validuntil = -1
- trafficleft = -1
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- html = req.load('http://www.hellshare.com/')
- if req.lastEffectiveURL != 'http://www.hellshare.com/':
- #Switch to English
- self.logDebug('Switch lang - URL: %s' % req.lastEffectiveURL)
- json = req.load("%s?do=locRouter-show" % req.lastEffectiveURL)
- hash = re.search(r"(--[0-9a-f]+-)", json).group(1)
- self.logDebug('Switch lang - HASH: %s' % hash)
- html = req.load('http://www.hellshare.com/%s/' % hash)
-
- if re.search(self.CREDIT_LEFT_PATTERN, html):
- self.logDebug('Already logged in')
- return
-
- html = req.load('http://www.hellshare.com/login?do=loginForm-submit', post={
- "login": "Log in",
- "password": data['password'],
- "username": user,
- "perm_login": "on"
- })
-
- if "<p>You input a wrong user name or wrong password</p>" in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/HotfileCom.py b/module/plugins/accounts/HotfileCom.py
deleted file mode 100644
index cffbbab8f..000000000
--- a/module/plugins/accounts/HotfileCom.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import strptime, mktime
-import hashlib
-
-from module.plugins.Account import Account
-
-
-class HotfileCom(Account):
- __name__ = "HotfileCom"
- __type__ = "account"
- __version__ = "0.2"
-
- __description__ = """Hotfile.com account plugin"""
- __author_name__ = ("mkaay", "JoKoT3")
- __author_mail__ = ("mkaay@mkaay.de", "jokot3@gmail.com")
-
-
- def loadAccountInfo(self, user, req):
- resp = self.apiCall("getuserinfo", user=user)
- if resp.startswith("."):
- self.core.debug("HotfileCom API Error: %s" % resp)
- raise Exception
- info = {}
- for p in resp.split("&"):
- key, value = p.split("=")
- info[key] = value
-
- if info['is_premium'] == '1':
- info['premium_until'] = info['premium_until'].replace("T", " ")
- zone = info['premium_until'][19:]
- info['premium_until'] = info['premium_until'][:19]
- zone = int(zone[:3])
-
- validuntil = int(mktime(strptime(info['premium_until'], "%Y-%m-%d %H:%M:%S"))) + (zone * 60 * 60)
- tmp = {"validuntil": validuntil, "trafficleft": -1, "premium": True}
-
- elif info['is_premium'] == '0':
- tmp = {"premium": False}
-
- return tmp
-
- def apiCall(self, method, post={}, user=None):
- if user:
- data = self.getAccountData(user)
- else:
- user, data = self.selectAccount()
-
- req = self.getAccountRequest(user)
-
- digest = req.load("http://api.hotfile.com/", post={"action": "getdigest"})
- h = hashlib.md5()
- h.update(data['password'])
- hp = h.hexdigest()
- h = hashlib.md5()
- h.update(hp)
- h.update(digest)
- pwhash = h.hexdigest()
-
- post.update({"action": method})
- post.update({"username": user, "passwordmd5dig": pwhash, "digest": digest})
- resp = req.load("http://api.hotfile.com/", post=post)
- req.close()
- return resp
-
- def login(self, user, data, req):
- cj = self.getAccountCookies(user)
- cj.setCookie("hotfile.com", "lang", "en")
- req.load("http://hotfile.com/", cookies=True)
- page = req.load("http://hotfile.com/login.php", post={"returnto": "/", "user": user, "pass": data['password']},
- cookies=True)
-
- if "Bad username/password" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/Http.py b/module/plugins/accounts/Http.py
deleted file mode 100644
index 3b64fe8da..000000000
--- a/module/plugins/accounts/Http.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class Http(Account):
- __name__ = "Http"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Http dummy account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- login_timeout = info_threshold = 1000000
diff --git a/module/plugins/accounts/LetitbitNet.py b/module/plugins/accounts/LetitbitNet.py
deleted file mode 100644
index c849f9d2d..000000000
--- a/module/plugins/accounts/LetitbitNet.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-# from module.common.json_layer import json_loads, json_dumps
-
-
-class LetitbitNet(Account):
- __name__ = "LetitbitNet"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Letitbit.net account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def loadAccountInfo(self, user, req):
- ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
- # api_key = self.accounts[user]['password']
- # json_data = [api_key, ['key/info']]
- # api_rep = req.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
- # self.logDebug('API Key Info: ' + api_rep)
- # api_rep = json_loads(api_rep)
- #
- # if api_rep['status'] == 'FAIL':
- # self.logWarning(api_rep['data'])
- # return {'valid': False, 'premium': False}
-
- return {"premium": True}
-
- def login(self, user, data, req):
- # API_KEY is the username and the PREMIUM_KEY is the password
- self.logInfo('You must use your API KEY as username and the PREMIUM KEY as password.')
diff --git a/module/plugins/accounts/LinksnappyCom.py b/module/plugins/accounts/LinksnappyCom.py
deleted file mode 100644
index a03357e25..000000000
--- a/module/plugins/accounts/LinksnappyCom.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from hashlib import md5
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class LinksnappyCom(Account):
- __name__ = "LinksnappyCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Linksnappy.com account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- r = req.load('http://gen.linksnappy.com/lseAPI.php',
- get={'act': 'USERDETAILS', 'username': user, 'password': md5(data['password']).hexdigest()})
- self.logDebug("JSON data: " + r)
- j = json_loads(r)
-
- if j['error']:
- return {"premium": False}
-
- validuntil = j['return']['expire']
- if validuntil == 'lifetime':
- validuntil = -1
- elif validuntil == 'expired':
- return {"premium": False}
- else:
- validuntil = float(validuntil)
-
- if 'trafficleft' not in j['return'] or isinstance(j['return']['trafficleft'], str):
- trafficleft = -1
- else:
- trafficleft = int(j['return']['trafficleft']) * 1024
-
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
- def login(self, user, data, req):
- r = req.load('http://gen.linksnappy.com/lseAPI.php',
- get={'act': 'USERDETAILS', 'username': user, 'password': md5(data['password']).hexdigest()})
-
- if 'Invalid Account Details' in r:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MegaDebridEu.py b/module/plugins/accounts/MegaDebridEu.py
deleted file mode 100644
index 9c5603989..000000000
--- a/module/plugins/accounts/MegaDebridEu.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class MegaDebridEu(Account):
- __name__ = "MegaDebridEu"
- __type__ = "account"
- __version__ = "0.2"
-
- __description__ = """mega-debrid.eu account plugin"""
- __author_name__ = "D.Ducatel"
- __author_mail__ = "dducatel@je-geek.fr"
-
- # Define the base URL of MegaDebrid api
- API_URL = "https://www.mega-debrid.eu/api.php"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- jsonResponse = req.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- response = json_loads(jsonResponse)
-
- if response['response_code'] == "ok":
- return {"premium": True, "validuntil": float(response['vip_end']), "status": True}
- else:
- self.logError(response)
- return {"status": False, "premium": False}
-
- def login(self, user, data, req):
- jsonResponse = req.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- response = json_loads(jsonResponse)
- if response['response_code'] != "ok":
- self.wrongPassword()
diff --git a/module/plugins/accounts/MegasharesCom.py b/module/plugins/accounts/MegasharesCom.py
deleted file mode 100644
index 7c4777706..000000000
--- a/module/plugins/accounts/MegasharesCom.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-
-
-class MegasharesCom(Account):
- __name__ = "MegasharesCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Megashares.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- VALID_UNTIL_PATTERN = r'<p class="premium_info_box">Period Ends: (\w{3} \d{1,2}, \d{4})</p>'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://d01.megashares.com/myms.php", decode=True)
-
- premium = False if '>Premium Upgrade<' in html else True
-
- validuntil = trafficleft = -1
- try:
- timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
- self.logDebug(timestr)
- validuntil = mktime(strptime(timestr, "%b %d, %Y"))
- except Exception, e:
- self.logError(e)
-
- return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
-
- def login(self, user, data, req):
- html = req.load('http://d01.megashares.com/myms_login.php', post={
- "httpref": "",
- "myms_login": "Login",
- "mymslogin_name": user,
- "mymspassword": data['password']
- }, decode=True)
-
- if not '<span class="b ml">%s</span>' % user in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MovReelCom.py b/module/plugins/accounts/MovReelCom.py
deleted file mode 100644
index 34862c4ef..000000000
--- a/module/plugins/accounts/MovReelCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class MovReelCom(XFSPAccount):
- __name__ = "MovReelCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Movreel.com account plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- login_timeout = 60
- info_threshold = 30
-
- MAIN_PAGE = "http://movreel.com/"
-
- TRAFFIC_LEFT_PATTERN = r'Traffic.*?<b>([^<]+)</b>'
- LOGIN_FAIL_PATTERN = r'<b[^>]*>Incorrect Login or Password</b><br>'
diff --git a/module/plugins/accounts/MultishareCz.py b/module/plugins/accounts/MultishareCz.py
deleted file mode 100644
index fc13bac69..000000000
--- a/module/plugins/accounts/MultishareCz.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-#from time import mktime, strptime
-#from pycurl import REFERER
-import re
-from module.utils import parseFileSize
-
-
-class MultishareCz(Account):
- __name__ = "MultishareCz"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """Multishare.cz account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[0-9,]+)&nbsp;(?P<U>\w+)</strong>'
- ACCOUNT_INFO_PATTERN = r'<input type="hidden" id="(u_ID|u_hash)" name="[^"]*" value="([^"]+)">'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://www.multishare.cz/profil/", decode=True)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- trafficleft = parseFileSize(m.group('S'), m.group('U')) / 1024 if m else 0
- self.premium = True if trafficleft else False
-
- html = req.load("http://www.multishare.cz/", decode=True)
- mms_info = dict(re.findall(self.ACCOUNT_INFO_PATTERN, html))
-
- return dict(mms_info, **{"validuntil": -1, "trafficleft": trafficleft})
-
- def login(self, user, data, req):
- html = req.load('http://www.multishare.cz/html/prihlaseni_process.php', post={
- "akce": "Přihlásit",
- "heslo": data['password'],
- "jmeno": user
- }, decode=True)
-
- if '<div class="akce-chyba akce">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/MyfastfileCom.py b/module/plugins/accounts/MyfastfileCom.py
deleted file mode 100644
index 8db9c9ade..000000000
--- a/module/plugins/accounts/MyfastfileCom.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import time
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class MyfastfileCom(Account):
- __name__ = "MyfastfileCom"
- __type__ = "account"
- __version__ = "0.02"
- __description__ = """Myfastfile.com account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- def loadAccountInfo(self, user, req):
- if 'days_left' in self.json_data:
- validuntil = int(time() + self.json_data['days_left'] * 24 * 60 * 60)
- return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
- else:
- self.logError('Unable to get account information')
-
- def login(self, user, data, req):
- # Password to use is the API-Password written in http://myfastfile.com/myaccount
- html = req.load("http://myfastfile.com/api.php",
- get={"user": user, "pass": data['password']})
- self.logDebug('JSON data: ' + html)
- self.json_data = json_loads(html)
- if self.json_data['status'] != 'ok':
- self.logError('Invalid login. The password to use is the API-Password you find in your "My Account" page')
- self.wrongPassword()
diff --git a/module/plugins/accounts/NetloadIn.py b/module/plugins/accounts/NetloadIn.py
deleted file mode 100755
index 12c5556fb..000000000
--- a/module/plugins/accounts/NetloadIn.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import time
-
-from module.plugins.Account import Account
-
-
-class NetloadIn(Account):
- __name__ = "NetloadIn"
- __type__ = "account"
- __version__ = "0.22"
-
- __description__ = """Netload.in account plugin"""
- __author_name__ = ("RaNaN", "CryNickSystems")
- __author_mail__ = ("RaNaN@pyload.org", "webmaster@pcProfil.de")
-
-
- def loadAccountInfo(self, user, req):
- page = req.load("http://netload.in/index.php?id=2&lang=de")
- left = r">(\d+) (Tag|Tage), (\d+) Stunden<"
- left = re.search(left, page)
- if left:
- validuntil = time() + int(left.group(1)) * 24 * 60 * 60 + int(left.group(3)) * 60 * 60
- trafficleft = -1
- premium = True
- else:
- validuntil = None
- premium = False
- trafficleft = None
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- page = req.load("http://netload.in/index.php", None,
- {"txtuser": user, "txtpass": data['password'], "txtcheck": "login", "txtlogin": "Login"},
- cookies=True)
- if "password or it might be invalid!" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/OboomCom.py b/module/plugins/accounts/OboomCom.py
deleted file mode 100644
index a37759f9a..000000000
--- a/module/plugins/accounts/OboomCom.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from module.lib.beaker.crypto.pbkdf2 import PBKDF2
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class OboomCom(Account):
- __name__ = "OboomCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Oboom.com account plugin"""
- __author_name__ = "stanley"
- __author_mail__ = "stanley.foerster@gmail.com"
-
-
- def loadAccountData(self, user, req):
- passwd = self.getAccountData(user)['password']
- salt = passwd[::-1]
- pbkdf2 = PBKDF2(passwd, salt, 1000).hexread(16)
- result = json_loads(req.load("https://www.oboom.com/1.0/login", get={"auth": user, "pass": pbkdf2}))
- if not result[0] == 200:
- self.logWarning("Failed to log in: %s" % result[1])
- self.wrongPassword()
- return result[1]
-
- def loadAccountInfo(self, name, req):
- accountData = self.loadAccountData(name, req)
- userData = accountData['user']
-
- if "premium_unix" in userData:
- validUntilUtc = int(userData['premium_unix'])
- if validUntilUtc > int(time.time()):
- premium = True
- validUntil = validUntilUtc
- traffic = userData['traffic']
- trafficLeft = traffic['current']
- maxTraffic = traffic['max']
- session = accountData['session']
- return {"premium": premium,
- "validuntil": validUntil,
- "trafficleft": trafficLeft / 1024,
- "maxtraffic": maxTraffic / 1024,
- "session": session
- }
- return {"premium": False, "validuntil": -1}
-
- def login(self, user, data, req):
- self.loadAccountData(user, req)
diff --git a/module/plugins/accounts/OneFichierCom.py b/module/plugins/accounts/OneFichierCom.py
deleted file mode 100644
index 43dd1c2b6..000000000
--- a/module/plugins/accounts/OneFichierCom.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import strptime, mktime
-from pycurl import REFERER
-
-from module.plugins.Account import Account
-
-
-class OneFichierCom(Account):
- __name__ = "OneFichierCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """1fichier.com account plugin"""
- __author_name__ = "Elrick69"
- __author_mail__ = "elrick69[AT]rocketmail[DOT]com"
-
- VALID_UNTIL_PATTERN = r'You are a premium user until (?P<d>\d{2})/(?P<m>\d{2})/(?P<y>\d{4})'
-
-
- def loadAccountInfo(self, user, req):
-
- html = req.load("http://1fichier.com/console/abo.pl")
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
-
- if m:
- premium = True
- validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<d>/\g<m>/\g<y>', m.group(0))
- validuntil = int(mktime(strptime(validuntil, "%d/%m/%Y")))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
- def login(self, user, data, req):
-
- req.http.c.setopt(REFERER, "http://1fichier.com/login.pl?lg=en")
-
- html = req.load("http://1fichier.com/login.pl?lg=en", post={
- "mail": user,
- "pass": data['password'],
- "Login": "Login"})
-
- if r'<div class="error_message">Invalid username or password.</div>' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/OverLoadMe.py b/module/plugins/accounts/OverLoadMe.py
deleted file mode 100644
index 34f684cb1..000000000
--- a/module/plugins/accounts/OverLoadMe.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class OverLoadMe(Account):
- __name__ = "OverLoadMe"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Over-Load.me account plugin"""
- __author_name__ = "marley"
- __author_mail__ = "marley@over-load.me"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- page = req.load("https://api.over-load.me/account.php", get={"user": user, "auth": data['password']}).strip()
- data = json_loads(page)
-
- # Check for premium
- if data['membership'] == "Free":
- return {"premium": False}
-
- account_info = {"validuntil": data['expirationunix'], "trafficleft": -1}
- return account_info
-
- def login(self, user, data, req):
- jsondata = req.load("https://api.over-load.me/account.php",
- get={"user": user, "auth": data['password']}).strip()
- data = json_loads(jsondata)
-
- if data['err'] == 1:
- self.wrongPassword()
diff --git a/module/plugins/accounts/PremiumTo.py b/module/plugins/accounts/PremiumTo.py
deleted file mode 100644
index 5678f8210..000000000
--- a/module/plugins/accounts/PremiumTo.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class PremiumTo(Account):
- __name__ = "PremiumTo"
- __type__ = "account"
- __version__ = "0.04"
- __description__ = """Premium.to account plugin"""
- __author_name__ = ("RaNaN", "zoidberg", "stickell")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- def loadAccountInfo(self, user, req):
- api_r = req.load("http://premium.to/api/straffic.php",
- get={'username': self.username, 'password': self.password})
- traffic = sum(map(int, api_r.split(';')))
-
- return {"trafficleft": int(traffic) / 1024, "validuntil": -1}
-
- def login(self, user, data, req):
- self.username = user
- self.password = data['password']
- authcode = req.load("http://premium.to/api/getauthcode.php?username=%s&password=%s" % (
- user, self.password)).strip()
-
- if "wrong username" in authcode:
- self.wrongPassword()
diff --git a/module/plugins/accounts/PremiumizeMe.py b/module/plugins/accounts/PremiumizeMe.py
deleted file mode 100644
index dcf8b8f20..000000000
--- a/module/plugins/accounts/PremiumizeMe.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-from module.common.json_layer import json_loads
-
-
-class PremiumizeMe(Account):
- __name__ = "PremiumizeMe"
- __type__ = "account"
- __version__ = "0.11"
-
- __description__ = """Premiumize.me account plugin"""
- __author_name__ = "Florian Franzen"
- __author_mail__ = "FlorianFranzen@gmail.com"
-
-
- def loadAccountInfo(self, user, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
- self.logDebug(status)
-
- # Parse account info
- account_info = {"validuntil": float(status['result']['expires']),
- "trafficleft": max(0, status['result']['trafficleft_bytes'] / 1024)}
-
- if status['result']['type'] == 'free':
- account_info['premium'] = False
-
- return account_info
-
- def login(self, user, data, req):
- # Get user data from premiumize.me
- status = self.getAccountStatus(user, req)
-
- # Check if user and password are valid
- if status['status'] != 200:
- self.wrongPassword()
-
- def getAccountStatus(self, user, req):
- # Use premiumize.me API v1 (see https://secure.premiumize.me/?show=api)
- # to retrieve account info and return the parsed json answer
- answer = req.load(
- "https://api.premiumize.me/pm-api/v1.php?method=accountstatus&params[login]=%s&params[pass]=%s" % (
- user, self.accounts[user]['password']))
- return json_loads(answer)
diff --git a/module/plugins/accounts/QuickshareCz.py b/module/plugins/accounts/QuickshareCz.py
deleted file mode 100644
index fcaf14e92..000000000
--- a/module/plugins/accounts/QuickshareCz.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Account import Account
-from module.utils import parseFileSize
-
-
-class QuickshareCz(Account):
- __name__ = "QuickshareCz"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Quickshare.cz account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.quickshare.cz/premium", decode=True)
-
- m = re.search(r'Stav kreditu: <strong>(.+?)</strong>', html)
- if m:
- trafficleft = parseFileSize(m.group(1)) / 1024
- premium = True if trafficleft else False
- else:
- trafficleft = None
- premium = False
-
- return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- html = req.load('http://www.quickshare.cz/html/prihlaseni_process.php', post={
- "akce": u'Přihlásit',
- "heslo": data['password'],
- "jmeno": user
- }, decode=True)
-
- if u'>TakovÜ uşivatel neexistuje.<' in html or u'>Špatné heslo.<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/RPNetBiz.py b/module/plugins/accounts/RPNetBiz.py
deleted file mode 100644
index 358f6ffca..000000000
--- a/module/plugins/accounts/RPNetBiz.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RPNetBiz(Account):
- __name__ = "RPNetBiz"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """RPNet.biz account plugin"""
- __author_name__ = "Dman"
- __author_mail__ = "dmanugm@gmail.com"
-
-
- def loadAccountInfo(self, user, req):
- # Get account information from rpnet.biz
- response = self.getAccountStatus(user, req)
- try:
- if response['accountInfo']['isPremium']:
- # Parse account info. Change the trafficleft later to support per host info.
- account_info = {"validuntil": int(response['accountInfo']['premiumExpiry']),
- "trafficleft": -1, "premium": True}
- else:
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
-
- except KeyError:
- #handle wrong password exception
- account_info = {"validuntil": None, "trafficleft": None, "premium": False}
-
- return account_info
-
- def login(self, user, data, req):
- # Get account information from rpnet.biz
- response = self.getAccountStatus(user, req)
-
- # If we have an error in the response, we have wrong login information
- if 'error' in response:
- self.wrongPassword()
-
- def getAccountStatus(self, user, req):
- # Using the rpnet API, check if valid premium account
- response = req.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": self.accounts[user]['password'],
- "action": "showAccountInformation"})
- self.logDebug("JSON data: %s" % response)
-
- return json_loads(response)
diff --git a/module/plugins/accounts/RapidgatorNet.py b/module/plugins/accounts/RapidgatorNet.py
deleted file mode 100644
index 849933a87..000000000
--- a/module/plugins/accounts/RapidgatorNet.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class RapidgatorNet(Account):
- __name__ = "RapidgatorNet"
- __type__ = "account"
- __version__ = "0.04"
-
- __description__ = """Rapidgator.net account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- API_URL = 'http://rapidgator.net/api/user'
-
-
- def loadAccountInfo(self, user, req):
- try:
- sid = self.getAccountData(user).get('SID')
- assert sid
-
- json = req.load("%s/info?sid=%s" % (self.API_URL, sid))
- self.logDebug("API:USERINFO", json)
- json = json_loads(json)
-
- if json['response_status'] == 200:
- if "reset_in" in json['response']:
- self.scheduleRefresh(user, json['response']['reset_in'])
-
- return {"validuntil": json['response']['expire_date'],
- "trafficleft": int(json['response']['traffic_left']) / 1024,
- "premium": True}
- else:
- self.logError(json['response_details'])
- except Exception, e:
- self.logError(e)
-
- return {"validuntil": None, "trafficleft": None, "premium": False}
-
- def login(self, user, data, req):
- try:
- json = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
- self.logDebug("API:LOGIN", json)
- json = json_loads(json)
-
- if json['response_status'] == 200:
- data['SID'] = str(json['response']['session_id'])
- return
- else:
- self.logError(json['response_details'])
- except Exception, e:
- self.logError(e)
-
- self.wrongPassword()
diff --git a/module/plugins/accounts/RapidshareCom.py b/module/plugins/accounts/RapidshareCom.py
deleted file mode 100644
index dc4f09ee1..000000000
--- a/module/plugins/accounts/RapidshareCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class RapidshareCom(Account):
- __name__ = "RapidshareCom"
- __type__ = "account"
- __version__ = "0.22"
-
- __description__ = """Rapidshare.com account plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
- api_param_prem = {"sub": "getaccountdetails", "type": "prem", "login": user,
- "password": data['password'], "withcookie": 1}
- src = req.load(api_url_base, cookies=False, get=api_param_prem)
- if src.startswith("ERROR"):
- raise Exception(src)
- fields = src.split("\n")
- info = {}
- for t in fields:
- if not t.strip():
- continue
- k, v = t.split("=")
- info[k] = v
-
- validuntil = int(info['billeduntil'])
- premium = True if validuntil else False
-
- tmp = {"premium": premium, "validuntil": validuntil, "trafficleft": -1, "maxtraffic": -1}
-
- return tmp
-
- def login(self, user, data, req):
- api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
- api_param_prem = {"sub": "getaccountdetails", "type": "prem", "login": user,
- "password": data['password'], "withcookie": 1}
- src = req.load(api_url_base, cookies=False, get=api_param_prem)
- if src.startswith("ERROR"):
- raise Exception(src + "### Note you have to use your account number for login, instead of name.")
- fields = src.split("\n")
- info = {}
- for t in fields:
- if not t.strip():
- continue
- k, v = t.split("=")
- info[k] = v
- cj = self.getAccountCookies(user)
- cj.setCookie("rapidshare.com", "enc", info['cookie'])
diff --git a/module/plugins/accounts/RarefileNet.py b/module/plugins/accounts/RarefileNet.py
deleted file mode 100644
index c8eae79a8..000000000
--- a/module/plugins/accounts/RarefileNet.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class RarefileNet(XFSPAccount):
- __name__ = "RarefileNet"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """RareFile.net account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- MAIN_PAGE = "http://rarefile.net/"
diff --git a/module/plugins/accounts/RealdebridCom.py b/module/plugins/accounts/RealdebridCom.py
deleted file mode 100644
index 9d1939c60..000000000
--- a/module/plugins/accounts/RealdebridCom.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import xml.dom.minidom as dom
-
-from module.plugins.Account import Account
-
-
-class RealdebridCom(Account):
- __name__ = "RealdebridCom"
- __type__ = "account"
- __version__ = "0.43"
-
- __description__ = """Real-Debrid.com account plugin"""
- __author_name__ = "Devirex Hazzard"
- __author_mail__ = "naibaf_11@yahoo.de"
-
-
- def loadAccountInfo(self, user, req):
- if self.pin_code:
- return {"premium": False}
- page = req.load("https://real-debrid.com/api/account.php")
- xml = dom.parseString(page)
- account_info = {"validuntil": int(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
- "trafficleft": -1}
-
- return account_info
-
- def login(self, user, data, req):
- self.pin_code = False
- page = req.load("https://real-debrid.com/ajax/login.php", get={"user": user, "pass": data['password']})
- if "Your login informations are incorrect" in page:
- self.wrongPassword()
- elif "PIN Code required" in page:
- self.logWarning('PIN code required. Please login to https://real-debrid.com using the PIN or disable the double authentication in your control panel on https://real-debrid.com.')
- self.pin_code = True
diff --git a/module/plugins/accounts/RehostTo.py b/module/plugins/accounts/RehostTo.py
deleted file mode 100644
index f15230f83..000000000
--- a/module/plugins/accounts/RehostTo.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class RehostTo(Account):
- __name__ = "RehostTo"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Rehost.to account plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAccountData(user)
- page = req.load("http://rehost.to/api.php?cmd=login&user=%s&pass=%s" % (user, data['password']))
- data = [x.split("=") for x in page.split(",")]
- ses = data[0][1]
- long_ses = data[1][1]
-
- page = req.load("http://rehost.to/api.php?cmd=get_premium_credits&long_ses=%s" % long_ses)
- traffic, valid = page.split(",")
-
- account_info = {"trafficleft": int(traffic) * 1024,
- "validuntil": int(valid),
- "long_ses": long_ses,
- "ses": ses}
-
- return account_info
-
- def login(self, user, data, req):
- page = req.load("http://rehost.to/api.php?cmd=login&user=%s&pass=%s" % (user, data['password']))
-
- if "Login failed." in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/RyushareCom.py b/module/plugins/accounts/RyushareCom.py
deleted file mode 100644
index 7fb373ca7..000000000
--- a/module/plugins/accounts/RyushareCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class RyushareCom(XFSPAccount):
- __name__ = "RyushareCom"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Ryushare.com account plugin"""
- __author_name__ = ("zoidberg", "trance4us")
- __author_mail__ = ("zoidberg@mujmail.cz", "")
-
- MAIN_PAGE = "http://ryushare.com/"
-
-
- def login(self, user, data, req):
- req.lastURL = "http://ryushare.com/login.python"
- html = req.load("http://ryushare.com/login.python",
- post={"login": user, "password": data['password'], "op": "login"})
- if 'Incorrect Login or Password' in html or '>Error<' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/ShareRapidCom.py b/module/plugins/accounts/ShareRapidCom.py
deleted file mode 100644
index 50077b1fb..000000000
--- a/module/plugins/accounts/ShareRapidCom.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import mktime, strptime
-from module.plugins.Account import Account
-
-
-class ShareRapidCom(Account):
- __name__ = "ShareRapidCom"
- __type__ = "account"
- __version__ = "0.34"
-
- __description__ = """MegaRapid.cz account plugin"""
- __author_name__ = ("MikyWoW", "zoidberg")
- __author_mail__ = ("mikywow@seznam.cz", "zoidberg@mujmail.cz")
-
- login_timeout = 60
-
-
- def loadAccountInfo(self, user, req):
- src = req.load("http://megarapid.cz/mujucet/", decode=True)
-
- m = re.search(ur'<td>Max. počet paralelních stahování: </td><td>(\d+)', src)
- if m:
- data = self.getAccountData(user)
- data['options']['limitDL'] = [int(m.group(1))]
-
- m = re.search(ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>', src)
- if m:
- validuntil = mktime(strptime(m.group(1), "%d.%m.%Y - %H:%M"))
- return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
-
- m = re.search(r'<tr><td>Kredit</td><td>(.*?) GiB', src)
- if m:
- trafficleft = float(m.group(1)) * (1 << 20)
- return {"premium": True, "trafficleft": trafficleft, "validuntil": -1}
-
- return {"premium": False, "trafficleft": None, "validuntil": None}
-
- def login(self, user, data, req):
- htm = req.load("http://megarapid.cz/prihlaseni/", cookies=True)
- if "Heslo:" in htm:
- start = htm.index('id="inp_hash" name="hash" value="')
- htm = htm[start + 33:]
- hashes = htm[0:32]
- htm = req.load("http://megarapid.cz/prihlaseni/",
- post={"hash": hashes,
- "login": user,
- "pass1": data['password'],
- "remember": 0,
- "sbmt": u"Přihlásit"}, cookies=True)
diff --git a/module/plugins/accounts/ShareonlineBiz.py b/module/plugins/accounts/ShareonlineBiz.py
deleted file mode 100644
index b9ff0096c..000000000
--- a/module/plugins/accounts/ShareonlineBiz.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-
-
-class ShareonlineBiz(Account):
- __name__ = "ShareonlineBiz"
- __type__ = "account"
- __version__ = "0.24"
-
- __description__ = """Share-online.biz account plugin"""
- __author_name__ = ("mkaay", "zoidberg")
- __author_mail__ = ("mkaay@mkaay.de", "zoidberg@mujmail.cz")
-
-
- def getUserAPI(self, user, req):
- return req.load("http://api.share-online.biz/account.php",
- {"username": user, "password": self.accounts[user]['password'], "act": "userDetails"})
-
- def loadAccountInfo(self, user, req):
- src = self.getUserAPI(user, req)
-
- info = {}
- for line in src.splitlines():
- if "=" in line:
- key, value = line.split("=")
- info[key] = value
- self.logDebug(info)
-
- if "dl" in info and info['dl'].lower() != "not_available":
- req.cj.setCookie("share-online.biz", "dl", info['dl'])
- if "a" in info and info['a'].lower() != "not_available":
- req.cj.setCookie("share-online.biz", "a", info['a'])
-
- return {"validuntil": int(info['expire_date']) if "expire_date" in info else -1,
- "trafficleft": -1,
- "premium": True if ("dl" in info or "a" in info) and (info['group'] != "Sammler") else False}
-
- def login(self, user, data, req):
- src = self.getUserAPI(user, req)
- if "EXCEPTION" in src:
- self.wrongPassword()
diff --git a/module/plugins/accounts/SimplyPremiumCom.py b/module/plugins/accounts/SimplyPremiumCom.py
deleted file mode 100644
index b0a62f83b..000000000
--- a/module/plugins/accounts/SimplyPremiumCom.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.Account import Account
-
-
-class SimplyPremiumCom(Account):
- __name__ = "SimplyPremiumCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """Simply-Premium.com account plugin"""
- __author_name__ = "EvolutionClip"
- __author_mail__ = "evolutionclip@live.de"
-
-
- def loadAccountInfo(self, user, req):
- json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
- self.logDebug("JSON data: " + json_data)
- json_data = json_loads(json_data)
-
- if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
- return {"premium": False}
-
- #Time package
- validuntil = float(json_data['result']['timeend'])
- #Traffic package
- # {"trafficleft": int(traffic) / 1024, "validuntil": -1}
- #trafficleft = int(json_data['result']['traffic'] / 1024)
-
- #return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
- return {"premium": True, "validuntil": validuntil}
-
- def login(self, user, data, req):
- req.cj.setCookie("simply-premium.com", "lang", "EN")
-
- if data['password'] == '' or data['password'] == '0':
- post_data = {"key": user}
- else:
- post_data = {"login_name": user, "login_pass": data['password']}
-
- html = req.load("http://www.simply-premium.com/login.php", post=post_data)
-
- if 'logout' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/SimplydebridCom.py b/module/plugins/accounts/SimplydebridCom.py
deleted file mode 100644
index 70f2d39b9..000000000
--- a/module/plugins/accounts/SimplydebridCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-
-
-class SimplydebridCom(Account):
- __name__ = "SimplydebridCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Simply-Debrid.com account plugin"""
- __author_name__ = "Kagenoshin"
- __author_mail__ = "kagenoshin@gmx.ch"
-
-
- def loadAccountInfo(self, user, req):
- get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
- response = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- data = [x.strip() for x in response.split(";")]
- if str(data[0]) != "1":
- return {"premium": False}
- else:
- return {"trafficleft": -1, "validuntil": mktime(strptime(str(data[2]), "%d/%m/%Y"))}
-
- def login(self, user, data, req):
- self.loginname = user
- self.password = data['password']
- get_data = {'login': 1, 'u': self.loginname, 'p': self.password}
- response = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
- if response != "02: loggin success":
- self.wrongPassword()
diff --git a/module/plugins/accounts/StahnuTo.py b/module/plugins/accounts/StahnuTo.py
deleted file mode 100644
index 6d9c3e924..000000000
--- a/module/plugins/accounts/StahnuTo.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-from module.utils import parseFileSize
-
-
-class StahnuTo(Account):
- __name__ = "StahnuTo"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """StahnuTo account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://www.stahnu.to/")
-
- m = re.search(r'>VIP: (\d+.*)<', html)
- trafficleft = parseFileSize(m.group(1)) * 1024 if m else 0
-
- return {"premium": trafficleft > (512 * 1024), "trafficleft": trafficleft, "validuntil": -1}
-
- def login(self, user, data, req):
- html = req.load("http://www.stahnu.to/login.php", post={
- "username": user,
- "password": data['password'],
- "submit": "Login"})
-
- if not '<a href="logout.php">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/TurbobitNet.py b/module/plugins/accounts/TurbobitNet.py
deleted file mode 100644
index a477b06c0..000000000
--- a/module/plugins/accounts/TurbobitNet.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-
-
-class TurbobitNet(Account):
- __name__ = "TurbobitNet"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """TurbobitNet account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- html = req.load("http://turbobit.net")
-
- m = re.search(r'<u>Turbo Access</u> to ([0-9.]+)', html)
- if m:
- premium = True
- validuntil = mktime(strptime(m.group(1), "%d.%m.%Y"))
- else:
- premium = False
- validuntil = -1
-
- return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
-
- def login(self, user, data, req):
- req.cj.setCookie("turbobit.net", "user_lang", "en")
-
- html = req.load("http://turbobit.net/user/login", post={
- "user[login]": user,
- "user[pass]": data['password'],
- "user[submit]": "Login"})
-
- if not '<div class="menu-item user-name">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UlozTo.py b/module/plugins/accounts/UlozTo.py
deleted file mode 100644
index 6f0d0ae7d..000000000
--- a/module/plugins/accounts/UlozTo.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class UlozTo(Account):
- __name__ = "UlozTo"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """Uloz.to account plugin"""
- __author_name__ = ("zoidberg", "pulpe")
- __author_mail__ = "zoidberg@mujmail.cz"
-
- TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a href="/kredit" title="[^"]*?GB = ([0-9.]+) MB"'
-
-
- def loadAccountInfo(self, user, req):
- #this cookie gets lost somehow after each request
- self.phpsessid = req.cj.getCookie("ULOSESSID")
- html = req.load("http://www.ulozto.net/", decode=True)
- req.cj.setCookie("www.ulozto.net", "ULOSESSID", self.phpsessid)
-
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- trafficleft = int(float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048) if m else 0
- self.premium = True if trafficleft else False
-
- return {"validuntil": -1, "trafficleft": trafficleft}
-
- def login(self, user, data, req):
- login_page = req.load('http://www.ulozto.net/?do=web-login', decode=True)
- action = re.findall('<form action="(.+?)"', login_page)[1].replace('&amp;', '&')
- token = re.search('_token_" value="(.+?)"', login_page).group(1)
-
- html = req.load('http://www.ulozto.net'+action, post={
- "_token_": token,
- "login": "Submit",
- "password": data['password'],
- "username": user
- }, decode=True)
-
- if '<div class="flash error">' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UnrestrictLi.py b/module/plugins/accounts/UnrestrictLi.py
deleted file mode 100644
index a65f19c5b..000000000
--- a/module/plugins/accounts/UnrestrictLi.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Account import Account
-from module.common.json_layer import json_loads
-
-
-class UnrestrictLi(Account):
- __name__ = "UnrestrictLi"
- __type__ = "account"
- __version__ = "0.03"
-
- __description__ = """Unrestrict.li account plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def loadAccountInfo(self, user, req):
- json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json')
- self.logDebug("JSON data: " + json_data)
- json_data = json_loads(json_data)
-
- if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
- return {"premium": False}
-
- validuntil = json_data['result']['expires']
- trafficleft = int(json_data['result']['traffic'] / 1024)
-
- return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
-
- def login(self, user, data, req):
- req.cj.setCookie("unrestrict.li", "lang", "EN")
- html = req.load("https://unrestrict.li/sign_in")
-
- if 'solvemedia' in html:
- self.logError("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry")
- return
-
- post_data = {"username": user, "password": data['password'],
- "remember_me": "remember", "signin": "Sign in"}
- html = req.load("https://unrestrict.li/sign_in", post=post_data)
-
- if 'sign_out' not in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadedTo.py b/module/plugins/accounts/UploadedTo.py
deleted file mode 100644
index 9db496bbc..000000000
--- a/module/plugins/accounts/UploadedTo.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import time
-
-from module.plugins.Account import Account
-
-
-class UploadedTo(Account):
- __name__ = "UploadedTo"
- __type__ = "account"
- __version__ = "0.26"
-
- __description__ = """Uploaded.to account plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def loadAccountInfo(self, user, req):
-
- req.load("http://uploaded.net/language/en")
- html = req.load("http://uploaded.net/me")
-
- premium = '<a href="register"><em>Premium</em>' in html or '<em>Premium</em></th>' in html
-
- if premium:
- raw_traffic = re.search(r'<th colspan="2"><b class="cB">([^<]+)', html).group(1).replace('.', '')
- raw_valid = re.search(r"<td>Duration:</td>\s*<th>([^<]+)", html, re.MULTILINE).group(1).strip()
-
- traffic = int(self.parseTraffic(raw_traffic))
-
- if raw_valid == "unlimited":
- validuntil = -1
- else:
- raw_valid = re.findall(r"(\d+) (Week|weeks|days|day|hours|hour)", raw_valid)
- validuntil = time()
- for n, u in raw_valid:
- validuntil += int(n) * 60 * 60 * {"Week": 168, "weeks": 168, "days": 24,
- "day": 24, "hours": 1, "hour": 1}[u]
-
- return {"validuntil": validuntil, "trafficleft": traffic, "maxtraffic": 50 * 1024 * 1024}
- else:
- return {"premium": False, "validuntil": -1}
-
- def login(self, user, data, req):
-
- req.load("http://uploaded.net/language/en")
- req.cj.setCookie("uploaded.net", "lang", "en")
-
- page = req.load("http://uploaded.net/io/login", post={"id": user, "pw": data['password'], "_": ""})
-
- if "User and password do not match!" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadheroCom.py b/module/plugins/accounts/UploadheroCom.py
deleted file mode 100644
index 20f209268..000000000
--- a/module/plugins/accounts/UploadheroCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import datetime
-import time
-
-from module.plugins.Account import Account
-
-
-class UploadheroCom(Account):
- __name__ = "UploadheroCom"
- __type__ = "account"
- __version__ = "0.2"
-
- __description__ = """Uploadhero.co account plugin"""
- __author_name__ = "mcmyst"
- __author_mail__ = "mcmyst@hotmail.fr"
-
-
- def loadAccountInfo(self, user, req):
- premium_pattern = re.compile('Il vous reste <span class="bleu">([0-9]+)</span> jours premium.')
-
- data = self.getAccountData(user)
- page = req.load("http://uploadhero.co/my-account")
-
- if premium_pattern.search(page):
- end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(page).group(1)))
- end_date = time.mktime(future.timetuple())
- account_info = {"validuntil": end_date, "trafficleft": -1, "premium": True}
- else:
- account_info = {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- return account_info
-
- def login(self, user, data, req):
- page = req.load("http://uploadhero.co/lib/connexion.php",
- post={"pseudo_login": user, "password_login": data['password']})
-
- if "mot de passe invalide" in page:
- self.wrongPassword()
diff --git a/module/plugins/accounts/UploadingCom.py b/module/plugins/accounts/UploadingCom.py
deleted file mode 100644
index 416a29b1e..000000000
--- a/module/plugins/accounts/UploadingCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import time, strptime, mktime
-import re
-
-from module.plugins.Account import Account
-
-
-class UploadingCom(Account):
- __name__ = "UploadingCom"
- __type__ = "account"
- __version__ = "0.1"
-
- __description__ = """Uploading.com account plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def loadAccountInfo(self, user, req):
- src = req.load("http://uploading.com/")
- premium = True
- if "UPGRADE TO PREMIUM" in src:
- return {"validuntil": -1, "trafficleft": -1, "premium": False}
-
- m = re.search("Valid Until:(.*?)<", src)
- if m:
- validuntil = int(mktime(strptime(m.group(1).strip(), "%b %d, %Y")))
- else:
- validuntil = -1
-
- return {"validuntil": validuntil, "trafficleft": -1, "premium": True}
-
- def login(self, user, data, req):
- req.cj.setCookie("uploading.com", "lang", "1")
- req.cj.setCookie("uploading.com", "language", "1")
- req.cj.setCookie("uploading.com", "setlang", "en")
- req.cj.setCookie("uploading.com", "_lang", "en")
- req.load("http://uploading.com/")
- req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time() * 1000),
- post={"email": user, "password": data['password'], "remember": "on"})
diff --git a/module/plugins/accounts/UptoboxCom.py b/module/plugins/accounts/UptoboxCom.py
deleted file mode 100644
index 60de213ae..000000000
--- a/module/plugins/accounts/UptoboxCom.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.XFSPAccount import XFSPAccount
-
-
-class UptoboxCom(XFSPAccount):
- __name__ = "UptoboxCom"
- __type__ = "account"
- __version__ = "0.02"
-
- __description__ = """DDLStorage.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- MAIN_PAGE = "http://uptobox.com/"
-
- VALID_UNTIL_PATTERN = r'>Premium.[Aa]ccount expire: ([^<]+)</strong>'
diff --git a/module/plugins/accounts/YibaishiwuCom.py b/module/plugins/accounts/YibaishiwuCom.py
deleted file mode 100644
index be62d3f40..000000000
--- a/module/plugins/accounts/YibaishiwuCom.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Account import Account
-
-
-class YibaishiwuCom(Account):
- __name__ = "YibaishiwuCom"
- __type__ = "account"
- __version__ = "0.01"
-
- __description__ = """115.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- ACCOUNT_INFO_PATTERN = r'var USER_PERMISSION = {(.*?)}'
-
-
- def loadAccountInfo(self, user, req):
- #self.relogin(user)
- html = req.load("http://115.com/", decode=True)
-
- m = re.search(self.ACCOUNT_INFO_PATTERN, html, re.S)
- premium = True if (m and 'is_vip: 1' in m.group(1)) else False
- validuntil = trafficleft = (-1 if m else 0)
- return dict({"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium})
-
- def login(self, user, data, req):
- html = req.load('http://passport.115.com/?ac=login', post={
- "back": "http://www.115.com/",
- "goto": "http://115.com/",
- "login[account]": user,
- "login[passwd]": data['password']
- }, decode=True)
-
- if not 'var USER_PERMISSION = {' in html:
- self.wrongPassword()
diff --git a/module/plugins/accounts/ZeveraCom.py b/module/plugins/accounts/ZeveraCom.py
deleted file mode 100644
index db1ebd4ae..000000000
--- a/module/plugins/accounts/ZeveraCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-
-
-class ZeveraCom(Account):
- __name__ = "ZeveraCom"
- __type__ = "account"
- __version__ = "0.21"
-
- __description__ = """Zevera.com account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def loadAccountInfo(self, user, req):
- data = self.getAPIData(req)
- if data == "No traffic":
- account_info = {"trafficleft": 0, "validuntil": 0, "premium": False}
- else:
- account_info = {
- "trafficleft": int(data['availabletodaytraffic']) * 1024,
- "validuntil": mktime(strptime(data['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S")),
- "premium": True
- }
- return account_info
-
- def login(self, user, data, req):
- self.loginname = user
- self.password = data['password']
- if self.getAPIData(req) == "No traffic":
- self.wrongPassword()
-
- def getAPIData(self, req, just_header=False, **kwargs):
- get_data = {
- 'cmd': 'accountinfo',
- 'login': self.loginname,
- 'pass': self.password
- }
- get_data.update(kwargs)
-
- response = req.load("http://www.zevera.com/jDownloader.ashx", get=get_data,
- decode=True, just_header=just_header)
- self.logDebug(response)
-
- if ':' in response:
- if not just_header:
- response = response.replace(',', '\n')
- return dict((y.strip().lower(), z.strip()) for (y, z) in
- [x.split(':', 1) for x in response.splitlines() if ':' in x])
- else:
- return response
diff --git a/module/plugins/captcha/GigasizeCom.py b/module/plugins/captcha/GigasizeCom.py
deleted file mode 100644
index add3ffc57..000000000
--- a/module/plugins/captcha/GigasizeCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha import OCR
-
-
-class GigasizeCom(OCR):
- __name__ = "GigasizeCom"
- __type__ = "ocr"
- __version__ = "0.1"
-
- __description__ = """Gigasize.com ocr plugin"""
- __author_name__ = "pyLoad Team"
- __author_mail__ = "admin@pyload.org"
-
-
- def __init__(self):
- OCR.__init__(self)
-
- def get_captcha(self, image):
- self.load_image(image)
- self.threshold(2.8)
- self.run_tesser(True, False, False, True)
- return self.result_captcha
diff --git a/module/plugins/captcha/LinksaveIn.py b/module/plugins/captcha/LinksaveIn.py
deleted file mode 100644
index dd5ac7b98..000000000
--- a/module/plugins/captcha/LinksaveIn.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from PIL import Image
-from glob import glob
-from os import sep
-from os.path import abspath, dirname
-
-from module.plugins.captcha import OCR
-
-
-class LinksaveIn(OCR):
- __name__ = "LinksaveIn"
- __type__ = "ocr"
- __version__ = "0.1"
-
- __description__ = """Linksave.in ocr plugin"""
- __author_name__ = "pyLoad Team"
- __author_mail__ = "admin@pyload.org"
-
-
- def __init__(self):
- OCR.__init__(self)
- self.data_dir = dirname(abspath(__file__)) + sep + "LinksaveIn" + sep
-
- def load_image(self, image):
- im = Image.open(image)
- frame_nr = 0
-
- lut = im.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- new = Image.new("RGB", im.size)
- npix = new.load()
- while True:
- try:
- im.seek(frame_nr)
- except EOFError:
- break
- frame = im.copy()
- pix = frame.load()
- for x in xrange(frame.size[0]):
- for y in xrange(frame.size[1]):
- if lut[pix[x, y]] != (0,0,0):
- npix[x, y] = lut[pix[x, y]]
- frame_nr += 1
- new.save(self.data_dir+"unblacked.png")
- self.image = new.copy()
- self.pixels = self.image.load()
- self.result_captcha = ''
-
- def get_bg(self):
- stat = {}
- cstat = {}
- img = self.image.convert("P")
- for bgpath in glob(self.data_dir+"bg/*.gif"):
- stat[bgpath] = 0
- bg = Image.open(bgpath)
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- for x in xrange(bg.size[0]):
- for y in xrange(bg.size[1]):
- rgb_bg = bglut[bgpix[x, y]]
- rgb_c = lut[pix[x, y]]
- try:
- cstat[rgb_c] += 1
- except:
- cstat[rgb_c] = 1
- if rgb_bg == rgb_c:
- stat[bgpath] += 1
- max_p = 0
- bg = ""
- for bgpath, value in stat.items():
- if max_p < value:
- bg = bgpath
- max_p = value
- return bg
-
- def substract_bg(self, bgpath):
- bg = Image.open(bgpath)
- img = self.image.convert("P")
-
- bglut = bg.resize((256, 1))
- bglut.putdata(range(256))
- bglut = list(bglut.convert("RGB").getdata())
-
- lut = img.resize((256, 1))
- lut.putdata(range(256))
- lut = list(lut.convert("RGB").getdata())
-
- bgpix = bg.load()
- pix = img.load()
- orgpix = self.image.load()
- for x in xrange(bg.size[0]):
- for y in xrange(bg.size[1]):
- rgb_bg = bglut[bgpix[x, y]]
- rgb_c = lut[pix[x, y]]
- if rgb_c == rgb_bg:
- orgpix[x, y] = (255,255,255)
-
- def eval_black_white(self):
- new = Image.new("RGB", (140, 75))
- pix = new.load()
- orgpix = self.image.load()
- thresh = 4
- for x in xrange(new.size[0]):
- for y in xrange(new.size[1]):
- rgb = orgpix[x, y]
- r, g, b = rgb
- pix[x, y] = (255,255,255)
- if r > max(b, g)+thresh:
- pix[x, y] = (0,0,0)
- if g < min(r, b):
- pix[x, y] = (0,0,0)
- if g > max(r, b)+thresh:
- pix[x, y] = (0,0,0)
- if b > max(r, g)+thresh:
- pix[x, y] = (0,0,0)
- self.image = new
- self.pixels = self.image.load()
-
- def get_captcha(self, image):
- self.load_image(image)
- bg = self.get_bg()
- self.substract_bg(bg)
- self.eval_black_white()
- self.to_greyscale()
- self.image.save(self.data_dir+"cleaned_pass1.png")
- self.clean(4)
- self.clean(4)
- self.image.save(self.data_dir+"cleaned_pass2.png")
- letters = self.split_captcha_letters()
- final = ""
- for n, letter in enumerate(letters):
- self.image = letter
- self.image.save(ocr.data_dir+"letter%d.png" % n)
- self.run_tesser(True, True, False, False)
- final += self.result_captcha
-
- return final
diff --git a/module/plugins/captcha/NetloadIn.py b/module/plugins/captcha/NetloadIn.py
deleted file mode 100644
index cb6cb9264..000000000
--- a/module/plugins/captcha/NetloadIn.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha import OCR
-
-
-class NetloadIn(OCR):
- __name__ = "NetloadIn"
- __type__ = "ocr"
- __version__ = "0.1"
-
- __description__ = """Netload.in ocr plugin"""
- __author_name__ = "pyLoad Team"
- __author_mail__ = "admin@pyload.org"
-
-
- def __init__(self):
- OCR.__init__(self)
-
- def get_captcha(self, image):
- self.load_image(image)
- self.to_greyscale()
- self.clean(3)
- self.clean(3)
- self.run_tesser(True, True, False, False)
-
- self.result_captcha = self.result_captcha.replace(" ", "")[:4] # cut to 4 numbers
-
- return self.result_captcha
diff --git a/module/plugins/captcha/ShareonlineBiz.py b/module/plugins/captcha/ShareonlineBiz.py
deleted file mode 100644
index aab4e9da0..000000000
--- a/module/plugins/captcha/ShareonlineBiz.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.captcha import OCR
-
-
-class ShareonlineBiz(OCR):
- __name__ = "ShareonlineBiz"
- __type__ = "ocr"
- __version__ = "0.1"
-
- __description__ = """Shareonline.biz ocr plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def __init__(self):
- OCR.__init__(self)
-
- def get_captcha(self, image):
- self.load_image(image)
- self.to_greyscale()
- self.image = self.image.resize((160, 50))
- self.pixels = self.image.load()
- self.threshold(1.85)
- #self.eval_black_white(240)
- #self.derotate_by_average()
-
- letters = self.split_captcha_letters()
-
- final = ""
- for letter in letters:
- self.image = letter
- self.run_tesser(True, True, False, False)
- final += self.result_captcha
-
- return final
-
- #tesseract at 60%
diff --git a/module/plugins/captcha/captcha.py b/module/plugins/captcha/captcha.py
deleted file mode 100644
index cc07f50cf..000000000
--- a/module/plugins/captcha/captcha.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import GifImagePlugin
-import Image
-import JpegImagePlugin
-import PngImagePlugin
-import TiffImagePlugin
-import logging
-import os
-import subprocess
-#import tempfile
-
-from os.path import abspath, join
-
-
-class OCR(object):
- __name__ = "OCR"
- __type__ = "ocr"
- __version__ = "0.1"
-
- __description__ = """OCR base plugin"""
- __author_name__ = "pyLoad Team"
- __author_mail__ = "admin@pyload.org"
-
-
- def __init__(self):
- self.logger = logging.getLogger("log")
-
- def load_image(self, image):
- self.image = Image.open(image)
- self.pixels = self.image.load()
- self.result_captcha = ''
-
- def unload(self):
- """delete all tmp images"""
- pass
-
- def threshold(self, value):
- self.image = self.image.point(lambda a: a * value + 10)
-
- def run(self, command):
- """Run a command"""
-
- popen = subprocess.Popen(command, bufsize = -1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- popen.wait()
- output = popen.stdout.read() +" | "+ popen.stderr.read()
- popen.stdout.close()
- popen.stderr.close()
- self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output))
-
- def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True):
- #self.logger.debug("create tmp tif")
-
- #tmp = tempfile.NamedTemporaryFile(suffix=".tif")
- tmp = open(join("tmp", "tmpTif_%s.tif" % self.__name__), "wb")
- tmp.close()
- #self.logger.debug("create tmp txt")
- #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
- tmpTxt = open(join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb")
- tmpTxt.close()
-
- self.logger.debug("save tiff")
- self.image.save(tmp.name, 'TIFF')
-
- if os.name == "nt":
- tessparams = [join(pypath, "tesseract", "tesseract.exe")]
- else:
- tessparams = ["tesseract"]
-
- tessparams.extend( [abspath(tmp.name), abspath(tmpTxt.name).replace(".txt", "")] )
-
- if subset and (digits or lowercase or uppercase):
- #self.logger.debug("create temp subset config")
- #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
- tmpSub = open(join("tmp", "tmpSub_%s.subset" % self.__name__), "wb")
- tmpSub.write("tessedit_char_whitelist ")
- if digits:
- tmpSub.write("0123456789")
- if lowercase:
- tmpSub.write("abcdefghijklmnopqrstuvwxyz")
- if uppercase:
- tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
- tmpSub.write("\n")
- tessparams.append("nobatch")
- tessparams.append(abspath(tmpSub.name))
- tmpSub.close()
-
- self.logger.debug("run tesseract")
- self.run(tessparams)
- self.logger.debug("read txt")
-
- try:
- with open(tmpTxt.name, 'r') as f:
- self.result_captcha = f.read().replace("\n", "")
- except:
- self.result_captcha = ""
-
- self.logger.debug(self.result_captcha)
- try:
- os.remove(tmp.name)
- os.remove(tmpTxt.name)
- if subset and (digits or lowercase or uppercase):
- os.remove(tmpSub.name)
- except:
- pass
-
- def get_captcha(self, name):
- raise NotImplementedError
-
- def to_greyscale(self):
- if self.image.mode != 'L':
- self.image = self.image.convert('L')
-
- self.pixels = self.image.load()
-
- def eval_black_white(self, limit):
- self.pixels = self.image.load()
- w, h = self.image.size
- for x in xrange(w):
- for y in xrange(h):
- if self.pixels[x, y] > limit:
- self.pixels[x, y] = 255
- else:
- self.pixels[x, y] = 0
-
- def clean(self, allowed):
- pixels = self.pixels
-
- w, h = self.image.size
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 255:
- continue
- # No point in processing white pixels since we only want to remove black pixel
- count = 0
-
- try:
- if pixels[x-1, y-1] != 255:
- count += 1
- if pixels[x-1, y] != 255:
- count += 1
- if pixels[x-1, y + 1] != 255:
- count += 1
- if pixels[x, y + 1] != 255:
- count += 1
- if pixels[x + 1, y + 1] != 255:
- count += 1
- if pixels[x + 1, y] != 255:
- count += 1
- if pixels[x + 1, y-1] != 255:
- count += 1
- if pixels[x, y-1] != 255:
- count += 1
- except:
- pass
-
- # not enough neighbors are dark pixels so mark this pixel
- # to be changed to white
- if count < allowed:
- pixels[x, y] = 1
-
- # second pass: this time set all 1's to 255 (white)
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 1:
- pixels[x, y] = 255
-
- self.pixels = pixels
-
- def derotate_by_average(self):
- """rotate by checking each angle and guess most suitable"""
-
- w, h = self.image.size
- pixels = self.pixels
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 155
-
- highest = {}
- counts = {}
-
- for angle in xrange(-45, 45):
-
- tmpimage = self.image.rotate(angle)
-
- pixels = tmpimage.load()
-
- w, h = self.image.size
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 255
-
-
- count = {}
-
- for x in xrange(w):
- count[x] = 0
- for y in xrange(h):
- if pixels[x, y] == 155:
- count[x] += 1
-
- sum = 0
- cnt = 0
-
- for x in count.values():
- if x != 0:
- sum += x
- cnt += 1
-
- avg = sum / cnt
- counts[angle] = cnt
- highest[angle] = 0
- for x in count.values():
- if x > highest[angle]:
- highest[angle] = x
-
- highest[angle] = highest[angle] - avg
-
- hkey = 0
- hvalue = 0
-
- for key, value in highest.iteritems():
- if value > hvalue:
- hkey = key
- hvalue = value
-
- self.image = self.image.rotate(hkey)
- pixels = self.image.load()
-
- for x in xrange(w):
- for y in xrange(h):
- if pixels[x, y] == 0:
- pixels[x, y] = 255
-
- if pixels[x, y] == 155:
- pixels[x, y] = 0
-
- self.pixels = pixels
-
- def split_captcha_letters(self):
- captcha = self.image
- started = False
- letters = []
- width, height = captcha.size
- bottomY, topY = 0, height
- pixels = captcha.load()
-
- for x in xrange(width):
- black_pixel_in_col = False
- for y in xrange(height):
- if pixels[x, y] != 255:
- if not started:
- started = True
- firstX = x
- lastX = x
-
- if y > bottomY:
- bottomY = y
- if y < topY:
- topY = y
- if x > lastX:
- lastX = x
-
- black_pixel_in_col = True
-
- if black_pixel_in_col is False and started is True:
- rect = (firstX, topY, lastX, bottomY)
- new_captcha = captcha.crop(rect)
-
- w, h = new_captcha.size
- if w > 5 and h > 5:
- letters.append(new_captcha)
-
- started = False
- bottomY, topY = 0, height
-
- return letters
-
- def correct(self, values, var=None):
- if var:
- result = var
- else:
- result = self.result_captcha
-
- for key, item in values.iteritems():
-
- if key.__class__ == str:
- result = result.replace(key, item)
- else:
- for expr in key:
- result = result.replace(expr, item)
-
- if var:
- return result
- else:
- self.result_captcha = result
diff --git a/module/plugins/container/CCF.py b/module/plugins/container/CCF.py
deleted file mode 100644
index ee92beb9a..000000000
--- a/module/plugins/container/CCF.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from os import makedirs
-from os.path import exists
-from urllib2 import build_opener
-
-from module.lib.MultipartPostHandler import MultipartPostHandler
-
-from module.plugins.Container import Container
-from module.utils import save_join
-
-
-class CCF(Container):
- __name__ = "CCF"
- __version__ = "0.2"
-
- __pattern__ = r'.+\.ccf'
-
- __description__ = """CCF container decrypter plugin"""
- __author_name__ = "Willnix"
- __author_mail__ = "Willnix@pyload.org"
-
-
- def decrypt(self, pyfile):
-
- infile = pyfile.url.replace("\n", "")
-
- opener = build_opener(MultipartPostHandler)
- params = {"src": "ccf",
- "filename": "test.ccf",
- "upload": open(infile, "rb")}
- tempdlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', params).read()
-
- download_folder = self.config['general']['download_folder']
-
- tempdlc_name = save_join(download_folder, "tmp_%s.dlc" % pyfile.name)
- tempdlc = open(tempdlc_name, "w")
- tempdlc.write(re.search(r'<dlc>(.*)</dlc>', tempdlc_content, re.DOTALL).group(1))
- tempdlc.close()
-
- self.urls = [tempdlc_name]
diff --git a/module/plugins/container/LinkList.py b/module/plugins/container/LinkList.py
deleted file mode 100644
index 7e418bd67..000000000
--- a/module/plugins/container/LinkList.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import codecs
-
-from module.plugins.Container import Container
-from module.utils import fs_encode
-
-
-class LinkList(Container):
- __name__ = "LinkList"
- __version__ = "0.12"
-
- __pattern__ = r'.+\.txt'
- __config__ = [("clear", "bool", "Clear Linklist after adding", False),
- ("encoding", "string", "File encoding (default utf-8)", "")]
-
- __description__ = """Read link lists in txt format"""
- __author_name__ = ("spoob", "jeix")
- __author_mail__ = ("spoob@pyload.org", "jeix@hasnomail.com")
-
-
- def decrypt(self, pyfile):
- try:
- file_enc = codecs.lookup(self.getConfig("encoding")).name
- except:
- file_enc = "utf-8"
-
- print repr(pyfile.url)
- print pyfile.url
-
- file_name = fs_encode(pyfile.url)
-
- txt = codecs.open(file_name, 'r', file_enc)
- links = txt.readlines()
- curPack = "Parsed links from %s" % pyfile.name
-
- packages = {curPack:[],}
-
- for link in links:
- link = link.strip()
- if not link:
- continue
-
- if link.startswith(";"):
- continue
- if link.startswith("[") and link.endswith("]"):
- # new package
- curPack = link[1:-1]
- packages[curPack] = []
- continue
- packages[curPack].append(link)
- txt.close()
-
- # empty packages fix
-
- delete = []
-
- for key,value in packages.iteritems():
- if not value:
- delete.append(key)
-
- for key in delete:
- del packages[key]
-
- if self.getConfig("clear"):
- try:
- txt = open(file_name, 'wb')
- txt.close()
- except:
- self.logWarning(_("LinkList could not be cleared."))
-
- for name, links in packages.iteritems():
- self.packages.append((name, links, name))
diff --git a/module/plugins/container/RSDF.py b/module/plugins/container/RSDF.py
deleted file mode 100644
index c35efacc6..000000000
--- a/module/plugins/container/RSDF.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import base64
-import binascii
-import re
-
-from module.plugins.Container import Container
-
-
-class RSDF(Container):
- __name__ = "RSDF"
- __version__ = "0.22"
-
- __pattern__ = r'.+\.rsdf'
-
- __description__ = """RSDF container decrypter plugin"""
- __author_name__ = ("RaNaN", "spoob")
- __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org")
-
-
- def decrypt(self, pyfile):
-
- from Crypto.Cipher import AES
-
- infile = pyfile.url.replace("\n", "")
- Key = binascii.unhexlify('8C35192D964DC3182C6F84F3252239EB4A320D2500000000')
-
- IV = binascii.unhexlify('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
- IV_Cipher = AES.new(Key, AES.MODE_ECB)
- IV = IV_Cipher.encrypt(IV)
-
- obj = AES.new(Key, AES.MODE_CFB, IV)
-
- rsdf = open(infile, 'r')
-
- data = rsdf.read()
- rsdf.close()
-
- if re.search(r"<title>404 - Not Found</title>", data) is None:
- data = binascii.unhexlify(''.join(data.split()))
- data = data.splitlines()
-
- for link in data:
- if not link:
- continue
- link = base64.b64decode(link)
- link = obj.decrypt(link)
- decryptedUrl = link.replace('CCF: ', '')
- self.urls.append(decryptedUrl)
-
- self.log.debug("%s: adding package %s with %d links" % (self.__name__,pyfile.package().name,len(links)))
diff --git a/module/plugins/crypter/BitshareComFolder.py b/module/plugins/crypter/BitshareComFolder.py
deleted file mode 100644
index 219dabce8..000000000
--- a/module/plugins/crypter/BitshareComFolder.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class BitshareComFolder(SimpleCrypter):
- __name__ = "BitshareComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
-
- __description__ = """Bitshare.com folder decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<a href="(http://bitshare.com/files/.+)">.+</a></td>'
- TITLE_PATTERN = r'View public folder "(?P<title>.+)"</h1>'
diff --git a/module/plugins/crypter/C1neonCom.py b/module/plugins/crypter/C1neonCom.py
deleted file mode 100644
index 829ed63d6..000000000
--- a/module/plugins/crypter/C1neonCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class C1neonCom(DeadCrypter):
- __name__ = "C1neonCom"
- __type__ = "crypter"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?c1neon.com/.*?'
-
- __description__ = """C1neon.com decrypter plugin"""
- __author_name__ = "godofdream"
- __author_mail__ = "soilfiction@gmail.com"
diff --git a/module/plugins/crypter/ChipDe.py b/module/plugins/crypter/ChipDe.py
deleted file mode 100644
index a75cc5e0e..000000000
--- a/module/plugins/crypter/ChipDe.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class ChipDe(Crypter):
- __name__ = "ChipDe"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?chip.de/video/.*\.html'
-
- __description__ = """Chip.de decrypter plugin"""
- __author_name__ = "4Christopher"
- __author_mail__ = "4Christopher@gmx.de"
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- try:
- f = re.search(r'"(http://video.chip.de/\d+?/.*)"', self.html)
- except:
- self.fail('Failed to find the URL')
- else:
- self.urls = [f.group(1)]
- self.logDebug('The file URL is %s' % self.urls[0])
diff --git a/module/plugins/crypter/CrockoComFolder.py b/module/plugins/crypter/CrockoComFolder.py
deleted file mode 100644
index 56abeac29..000000000
--- a/module/plugins/crypter/CrockoComFolder.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class CrockoComFolder(SimpleCrypter):
- __name__ = "CrockoComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?crocko.com/f/.*'
-
- __description__ = """Crocko.com folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'<td class="last"><a href="([^"]+)">download</a>'
diff --git a/module/plugins/crypter/CryptItCom.py b/module/plugins/crypter/CryptItCom.py
deleted file mode 100644
index 66c5e7ca7..000000000
--- a/module/plugins/crypter/CryptItCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class CryptItCom(DeadCrypter):
- __name__ = "CryptItCom"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/[\w]+'
-
- __description__ = """Crypt-it.com decrypter plugin"""
- __author_name__ = "jeix"
- __author_mail__ = "jeix@hasnomail.de"
diff --git a/module/plugins/crypter/CzshareComFolder.py b/module/plugins/crypter/CzshareComFolder.py
deleted file mode 100644
index 64affc867..000000000
--- a/module/plugins/crypter/CzshareComFolder.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class CzshareComFolder(Crypter):
- __name__ = "CzshareComFolder"
- __type__ = "crypter"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.*'
-
- __description__ = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FOLDER_PATTERN = r'<tr class="subdirectory">\s*<td>\s*<table>(.*?)</table>'
- LINK_PATTERN = r'<td class="col2"><a href="([^"]+)">info</a></td>'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if m is None:
- self.fail("Parse error (FOLDER)")
-
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
- if not self.urls:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/DDLMusicOrg.py b/module/plugins/crypter/DDLMusicOrg.py
deleted file mode 100644
index f5e7203d3..000000000
--- a/module/plugins/crypter/DDLMusicOrg.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import sleep
-
-from module.plugins.Crypter import Crypter
-
-
-class DDLMusicOrg(Crypter):
- __name__ = "DDLMusicOrg"
- __type__ = "crypter"
- __version__ = "0.3"
-
- __pattern__ = r'http://(?:www\.)?ddl-music\.org/captcha/ddlm_cr\d\.php\?\d+\?\d+'
-
- __description__ = """Ddl-music.org decrypter plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def setup(self):
- self.multiDL = False
-
- def decrypt(self, pyfile):
- html = self.req.load(pyfile.url, cookies=True)
-
- if re.search(r"Wer dies nicht rechnen kann", html) is not None:
- self.offline()
-
- math = re.search(r"(\d+) ([\+-]) (\d+) =\s+<inp", self.html)
- id = re.search(r"name=\"id\" value=\"(\d+)\"", self.html).group(1)
- linknr = re.search(r"name=\"linknr\" value=\"(\d+)\"", self.html).group(1)
-
- solve = ""
- if math.group(2) == "+":
- solve = int(math.group(1)) + int(math.group(3))
- else:
- solve = int(math.group(1)) - int(math.group(3))
- sleep(3)
- htmlwithlink = self.req.load(pyfile.url, cookies=True,
- post={"calc%s" % linknr: solve, "send%s" % linknr: "Send", "id": id,
- "linknr": linknr})
- m = re.search(r"<form id=\"ff\" action=\"(.*?)\" method=\"post\">", htmlwithlink)
- if m:
- self.urls = [m.group(1)]
- else:
- self.retry()
diff --git a/module/plugins/crypter/DailymotionBatch.py b/module/plugins/crypter/DailymotionBatch.py
deleted file mode 100644
index e43d4e1fd..000000000
--- a/module/plugins/crypter/DailymotionBatch.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urlparse import urljoin
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-
-class DailymotionBatch(Crypter):
- __name__ = "DailymotionBatch"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
-
- __description__ = """Dailymotion.com channel & playlist decrypter"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
- def api_response(self, ref, req=None):
- url = urljoin("https://api.dailymotion.com/", ref)
- page = self.load(url, get=req)
- return json_loads(page)
-
- def getPlaylistInfo(self, id):
- ref = "playlist/" + id
- req = {"fields": "name,owner.screenname"}
- playlist = self.api_response(ref, req)
-
- if "error" in playlist:
- return
-
- name = playlist['name']
- owner = playlist['owner.screenname']
- return name, owner
-
- def _getPlaylists(self, user_id, page=1):
- ref = "user/%s/playlists" % user_id
- req = {"fields": "id", "page": page, "limit": 100}
- user = self.api_response(ref, req)
-
- if "error" in user:
- return
-
- for playlist in user['list']:
- yield playlist['id']
-
- if user['has_more']:
- for item in self._getPlaylists(user_id, page + 1):
- yield item
-
- def getPlaylists(self, user_id):
- return [(id,) + self.getPlaylistInfo(id) for id in self._getPlaylists(user_id)]
-
- def _getVideos(self, id, page=1):
- ref = "playlist/%s/videos" % id
- req = {"fields": "url", "page": page, "limit": 100}
- playlist = self.api_response(ref, req)
-
- if "error" in playlist:
- return
-
- for video in playlist['list']:
- yield video['url']
-
- if playlist['has_more']:
- for item in self._getVideos(id, page + 1):
- yield item
-
- def getVideos(self, playlist_id):
- return list(self._getVideos(playlist_id))[::-1]
-
- def decrypt(self, pyfile):
- m = re.match(self.__pattern__, pyfile.url)
- m_id = m.group("ID")
- m_type = m.group("TYPE")
-
- if m_type == "playlist":
- self.logDebug("Url recognized as Playlist")
- p_info = self.getPlaylistInfo(m_id)
- playlists = [(m_id,) + p_info] if p_info else None
- else:
- self.logDebug("Url recognized as Channel")
- playlists = self.getPlaylists(m_id)
- self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), m_id))
-
- if not playlists:
- self.fail("No playlist available")
-
- for p_id, p_name, p_owner in playlists:
- p_videos = self.getVideos(p_id)
- p_folder = save_join(self.config['general']['download_folder'], p_owner, p_name)
- self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
- self.packages.append((p_name, p_videos, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
diff --git a/module/plugins/crypter/DataHuFolder.py b/module/plugins/crypter/DataHuFolder.py
deleted file mode 100644
index 4f6116100..000000000
--- a/module/plugins/crypter/DataHuFolder.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class DataHuFolder(SimpleCrypter):
- __name__ = "DataHuFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?data.hu/dir/\w+'
-
- __description__ = """Data.hu folder decrypter plugin"""
- __author_name__ = ("crash", "stickell")
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r"<a href='(http://data\.hu/get/.+)' target='_blank'>\1</a>"
- TITLE_PATTERN = ur'<title>(?P<title>.+) Let\xf6lt\xe9se</title>'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: # Password protected
- password = self.getPassword()
- if password is '':
- self.fail("No password specified, please set right password on Add package form and retry")
- self.logDebug('The folder is password protected', 'Using password: ' + password)
- self.html = self.load(pyfile.url, post={'mappa_pass': password}, decode=True)
- if u'Hib\xe1s jelsz\xf3' in self.html: # Wrong password
- self.fail("Incorrect password, please set right password on Add package form and retry")
-
- package_name, folder_name = self.getPackageNameAndFolder()
-
- package_links = re.findall(self.LINK_PATTERN, self.html)
- self.logDebug('Package has %d links' % len(package_links))
-
- if package_links:
- self.packages = [(package_name, package_links, folder_name)]
- else:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/DdlstorageComFolder.py b/module/plugins/crypter/DdlstorageComFolder.py
deleted file mode 100644
index 3b1dc6dd6..000000000
--- a/module/plugins/crypter/DdlstorageComFolder.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
-
-
-class DdlstorageComFolder(DeadCrypter):
- __name__ = "DdlstorageComFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+'
-
- __description__ = """DDLStorage.com folder decrypter plugin"""
- __author_name__ = ("godofdream", "stickell")
- __author_mail__ = ("soilfiction@gmail.com", "l.stickell@yahoo.it")
-
-
-getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/module/plugins/crypter/DepositfilesComFolder.py b/module/plugins/crypter/DepositfilesComFolder.py
deleted file mode 100644
index b7c273f0b..000000000
--- a/module/plugins/crypter/DepositfilesComFolder.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class DepositfilesComFolder(SimpleCrypter):
- __name__ = "DepositfilesComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?depositfiles.com/folders/\w+'
-
- __description__ = """Depositfiles.com folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'<div class="progressName"[^>]*>\s*<a href="([^"]+)" title="[^"]*" target="_blank">'
diff --git a/module/plugins/crypter/Dereferer.py b/module/plugins/crypter/Dereferer.py
deleted file mode 100644
index 21529ddfd..000000000
--- a/module/plugins/crypter/Dereferer.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.Crypter import Crypter
-
-
-class Dereferer(Crypter):
- __name__ = "Dereferer"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'https?://([^/]+)/.*?(?P<url>(ht|f)tps?(://|%3A%2F%2F).*)'
-
- __description__ = """Crypter for dereferers"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def decrypt(self, pyfile):
- link = re.match(self.__pattern__, pyfile.url).group('url')
- self.urls = [unquote(link).rstrip('+')]
diff --git a/module/plugins/crypter/DlProtectCom.py b/module/plugins/crypter/DlProtectCom.py
deleted file mode 100644
index 4c958437a..000000000
--- a/module/plugins/crypter/DlProtectCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from base64 import urlsafe_b64encode
-from time import time
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class DlProtectCom(SimpleCrypter):
- __name__ = "DlProtectCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?dl-protect\.com/((en|fr)/)?(?P<ID>\w+)'
-
- __description__ = """Dl-protect.com decrypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- OFFLINE_PATTERN = r'>Unfortunately, the link you are looking for is not found'
-
-
- def getLinks(self):
- # Direct link with redirect
- if not re.match(r"http://(?:www\.)?dl-protect\.com", self.req.http.lastEffectiveURL):
- return [self.req.http.lastEffectiveURL]
-
- #id = re.match(self.__pattern__, self.pyfile.url).group("ID")
- key = re.search(r'name="id_key" value="(.+?)"', self.html).group(1)
-
- post_req = {"id_key": key, "submitform": ""}
-
- if self.OFFLINE_PATTERN in self.html:
- self.offline()
- elif ">Please click on continue to see the content" in self.html:
- post_req.update({"submitform": "Continue"})
- else:
- mstime = int(round(time() * 1000))
- b64time = "_" + urlsafe_b64encode(str(mstime)).replace("=", "%3D")
-
- post_req.update({"i": b64time, "submitform": "Decrypt+link"})
-
- if ">Password :" in self.html:
- post_req['pwd'] = self.getPassword()
-
- if ">Security Code" in self.html:
- captcha_id = re.search(r'/captcha\.php\?uid=(.+?)"', self.html).group(1)
- captcha_url = "http://www.dl-protect.com/captcha.php?uid=" + captcha_id
- captcha_code = self.decryptCaptcha(captcha_url, imgtype="gif")
-
- post_req['secure'] = captcha_code
-
- self.html = self.load(self.pyfile.url, post=post_req)
-
- for errmsg in (">The password is incorrect", ">The security code is incorrect"):
- if errmsg in self.html:
- self.fail(errmsg[1:])
-
- pattern = r'<a href="([^/].+?)" target="_blank">'
- return re.findall(pattern, self.html)
diff --git a/module/plugins/crypter/DontKnowMe.py b/module/plugins/crypter/DontKnowMe.py
deleted file mode 100644
index 23fbb8d52..000000000
--- a/module/plugins/crypter/DontKnowMe.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.Crypter import Crypter
-
-
-class DontKnowMe(Crypter):
- __name__ = "DontKnowMe"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?dontknow.me/at/\?.+$'
-
- __description__ = """DontKnow.me decrypter plugin"""
- __author_name__ = "selaux"
- __author_mail__ = None
-
- LINK_PATTERN = r'http://dontknow.me/at/\?(.+)$'
-
-
- def decrypt(self, pyfile):
- link = re.findall(self.LINK_PATTERN, pyfile.url)[0]
- self.urls = [unquote(link)]
diff --git a/module/plugins/crypter/DuckCryptInfo.py b/module/plugins/crypter/DuckCryptInfo.py
deleted file mode 100644
index e7a5a59e9..000000000
--- a/module/plugins/crypter/DuckCryptInfo.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.lib.BeautifulSoup import BeautifulSoup
-
-from module.plugins.Crypter import Crypter
-
-
-class DuckCryptInfo(Crypter):
- __name__ = "DuckCryptInfo"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?duckcrypt.info/(folder|wait|link)/(\w+)/?(\w*)'
-
- __description__ = """DuckCrypt.info decrypter plugin"""
- __author_name__ = "godofdream"
- __author_mail__ = "soilfiction@gmail.com"
-
- TIMER_PATTERN = r'<span id="timer">(.*)</span>'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
- # seems we don't need to wait
- #src = self.req.load(str(url))
- #m = re.search(self.TIMER_PATTERN, src)
- #if m:
- # self.logDebug("Sleeping for" % m.group(1))
- # self.setWait(int(m.group(1)) ,False)
- m = re.match(self.__pattern__, url)
- if m is None:
- self.fail('Weird error in link')
- if str(m.group(1)) == "link":
- self.handleLink(url)
- else:
- self.handleFolder(m)
-
- def handleFolder(self, m):
- src = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
- m = re.match(self.__pattern__, src)
- self.logDebug("Redirectet to " + str(m.group(0)))
- src = self.load(str(m.group(0)))
- soup = BeautifulSoup(src)
- cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
- self.logDebug("Redirectet to " + str(cryptlinks))
- if not cryptlinks:
- self.fail('no links m - (Plugin out of date?)')
- for clink in cryptlinks:
- if clink.find("a"):
- self.handleLink(clink.find("a")['href'])
-
- def handleLink(self, url):
- src = self.load(url)
- soup = BeautifulSoup(src)
- self.urls = [soup.find("iframe")['src']]
- if not self.urls:
- self.logDebug('no links m - (Plugin out of date?)')
diff --git a/module/plugins/crypter/DuploadOrgFolder.py b/module/plugins/crypter/DuploadOrgFolder.py
deleted file mode 100644
index 6f764f687..000000000
--- a/module/plugins/crypter/DuploadOrgFolder.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class DuploadOrgFolder(SimpleCrypter):
- __name__ = "DuploadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/folder/\d+/'
-
- __description__ = """Dupload.org folder decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<td style="[^"]+"><a href="(http://[^"]+)" target="_blank">[^<]+</a></td>'
diff --git a/module/plugins/crypter/EasybytezComFolder.py b/module/plugins/crypter/EasybytezComFolder.py
deleted file mode 100644
index 7832bef5f..000000000
--- a/module/plugins/crypter/EasybytezComFolder.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class EasybytezComFolder(SimpleCrypter):
- __name__ = "EasybytezComFolder"
- __type__ = "crypter"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?easybytez\.com/users/(?P<ID>\d+/\d+)'
-
- __description__ = """Easybytez.com decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- URL_REPLACEMENTS = [(__pattern__, r"http://www.easybytez.com/users/\g<ID>?per_page=10000")]
-
- LINK_PATTERN = r'<td><a href="(http://www\.easybytez\.com/\w+)" target="_blank">.+(?:</a>)?</td>'
- TITLE_PATTERN = r'<Title>Files of \d+: (?P<title>.+) folder</Title>'
diff --git a/module/plugins/crypter/EmbeduploadCom.py b/module/plugins/crypter/EmbeduploadCom.py
deleted file mode 100644
index 6b876ed7f..000000000
--- a/module/plugins/crypter/EmbeduploadCom.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-from module.network.HTTPRequest import BadHeader
-
-
-class EmbeduploadCom(Crypter):
- __name__ = "EmbeduploadCom"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?embedupload.com/\?d=.*'
- __config__ = [("preferedHoster", "str", "Prefered hoster list (bar-separated) ", "embedupload"),
- ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
-
- __description__ = """EmbedUpload.com decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'<div id="([^"]+)"[^>]*>\s*<a href="([^"]+)" target="_blank" (?:class="DownloadNow"|style="color:red")>'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- tmp_links = []
-
- m = re.findall(self.LINK_PATTERN, self.html)
- if m:
- prefered_set = set(self.getConfig("preferedHoster").split('|'))
- prefered_set = map(lambda s: s.lower().split('.')[0], prefered_set)
- print "PF", prefered_set
- tmp_links.extend([x[1] for x in m if x[0] in prefered_set])
- self.urls = self.getLocation(tmp_links)
-
- if not self.urls:
- ignored_set = set(self.getConfig("ignoredHoster").split('|'))
- ignored_set = map(lambda s: s.lower().split('.')[0], ignored_set)
- print "IG", ignored_set
- tmp_links.extend([x[1] for x in m if x[0] not in ignored_set])
- self.urls = self.getLocation(tmp_links)
-
- if not self.urls:
- self.fail('Could not extract any links')
-
- def getLocation(self, tmp_links):
- new_links = []
- for link in tmp_links:
- try:
- header = self.load(link, just_header=True)
- if "location" in header:
- new_links.append(header['location'])
- except BadHeader:
- pass
- return new_links
diff --git a/module/plugins/crypter/FilebeerInfoFolder.py b/module/plugins/crypter/FilebeerInfoFolder.py
deleted file mode 100644
index 0743dcb0f..000000000
--- a/module/plugins/crypter/FilebeerInfoFolder.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class FilebeerInfoFolder(DeadCrypter):
- __name__ = "FilebeerInfoFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?filebeer\.info/(\d+~f).*'
-
- __description__ = """Filebeer.info folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
diff --git a/module/plugins/crypter/FilecloudIoFolder.py b/module/plugins/crypter/FilecloudIoFolder.py
deleted file mode 100644
index 9ec950061..000000000
--- a/module/plugins/crypter/FilecloudIoFolder.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FilecloudIoFolder(SimpleCrypter):
- __name__ = "FilecloudIoFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
-
- __description__ = """Filecloud.io folder decrypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- LINK_PATTERN = r'href="(http://filecloud.io/\w+)" title'
- TITLE_PATTERN = r'>(?P<title>.+?) - filecloud.io<'
diff --git a/module/plugins/crypter/FilefactoryComFolder.py b/module/plugins/crypter/FilefactoryComFolder.py
deleted file mode 100644
index 562c56732..000000000
--- a/module/plugins/crypter/FilefactoryComFolder.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FilefactoryComFolder(SimpleCrypter):
- __name__ = "FilefactoryComFolder"
- __type__ = "crypter"
- __version__ = "0.2"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
-
- __description__ = """Filefactory.com folder decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<td><a href="([^"]+)">'
- TITLE_PATTERN = r'<h1>Files in <span>(?P<title>.+)</span></h1>'
- PAGES_PATTERN = r'data-paginator-totalPages="(?P<pages>\d+)"'
-
- SH_COOKIES = [('.filefactory.com', 'locale', 'en_US.utf8')]
-
-
- def loadPage(self, page_n):
- return self.load(self.pyfile.url, get={'page': page_n})
diff --git a/module/plugins/crypter/FilerNetFolder.py b/module/plugins/crypter/FilerNetFolder.py
deleted file mode 100644
index 9951661b5..000000000
--- a/module/plugins/crypter/FilerNetFolder.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FilerNetFolder(SimpleCrypter):
- __name__ = "FilerNetFolder"
- __type__ = "crypter"
- __version__ = "0.3"
-
- __pattern__ = r'https?://filer\.net/folder/\w{16}'
-
- __description__ = """Filer.net decrypter plugin"""
- __author_name_ = ("nath_schwarz", "stickell")
- __author_mail_ = ("nathan.notwhite@gmail.com", "l.stickell@yahoo.it")
-
- LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
- TITLE_PATTERN = r'<h3>(?P<title>.+) - <small'
-
-
- def getLinks(self):
- return ['http://filer.net%s' % link for link in re.findall(self.LINK_PATTERN, self.html)]
diff --git a/module/plugins/crypter/FileserveComFolder.py b/module/plugins/crypter/FileserveComFolder.py
deleted file mode 100644
index 2db6baf0e..000000000
--- a/module/plugins/crypter/FileserveComFolder.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class FileserveComFolder(Crypter):
- __name__ = "FileserveComFolder"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?fileserve.com/list/\w+'
-
- __description__ = """FileServe.com folder decrypter plugin"""
- __author_name__ = "fionnc"
- __author_mail__ = "fionnc@gmail.com"
-
- FOLDER_PATTERN = r'<table class="file_list">(.*?)</table>'
- LINK_PATTERN = r'<a href="([^"]+)" class="sheet_icon wbold">'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- new_links = []
-
- folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if folder is None:
- self.fail("Parse error (FOLDER)")
-
- new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1)))
-
- if new_links:
- self.urls = [map(lambda s: "http://fileserve.com%s" % s, new_links)]
- else:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/FilestubeCom.py b/module/plugins/crypter/FilestubeCom.py
deleted file mode 100644
index f0aaaa579..000000000
--- a/module/plugins/crypter/FilestubeCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FilestubeCom(SimpleCrypter):
- __name__ = "FilestubeCom"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
-
- __description__ = """Filestube.com decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
- TITLE_PATTERN = r'<h1\s*> (?P<title>.+) download\s*</h1>'
diff --git a/module/plugins/crypter/FiletramCom.py b/module/plugins/crypter/FiletramCom.py
deleted file mode 100644
index 7052955cf..000000000
--- a/module/plugins/crypter/FiletramCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FiletramCom(SimpleCrypter):
- __name__ = "FiletramCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?filetram.com/[^/]+/.+'
-
- __description__ = """Filetram.com decrypter plugin"""
- __author_name__ = ("igel", "stickell")
- __author_mail__ = ("igelkun@myopera.com", "l.stickell@yahoo.it")
-
- LINK_PATTERN = r'\s+(http://.+)'
- TITLE_PATTERN = r'<title>(?P<title>[^<]+) - Free Download[^<]*</title>'
diff --git a/module/plugins/crypter/FiredriveComFolder.py b/module/plugins/crypter/FiredriveComFolder.py
deleted file mode 100644
index a94d0847f..000000000
--- a/module/plugins/crypter/FiredriveComFolder.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FiredriveComFolder(SimpleCrypter):
- __name__ = "FiredriveComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
-
- __description__ = """Firedrive.com folder decrypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- LINK_PATTERN = r'<div class="pf_item pf_(file|folder).+?public=\'(.+?)\''
- TITLE_PATTERN = r'>Shared Folder "(?P<title>.+)" | Firedrive<'
- OFFLINE_PATTERN = r'class="sad_face_image"|>No such page here.<'
- TEMP_OFFLINE_PATTERN = r'>(File Temporarily Unavailable|Server Error. Try again later)'
-
-
- def getLinks(self):
- return map(lambda x: "http://www.firedrive.com/%s/%s" %
- ("share" if x[0] == "folder" else "file", x[1]),
- re.findall(self.LINK_PATTERN, self.html))
diff --git a/module/plugins/crypter/FourChanOrg.py b/module/plugins/crypter/FourChanOrg.py
deleted file mode 100644
index c497fa799..000000000
--- a/module/plugins/crypter/FourChanOrg.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599)
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class FourChanOrg(Crypter):
- __name__ = "FourChanOrg"
- __type__ = "crypter"
- __version__ = "0.3"
-
- __pattern__ = r'http://(?:www\.)?boards\.4chan.org/\w+/res/(\d+)'
-
- __description__ = """4chan.org folder decrypter plugin"""
- __author_name__ = None
- __author_mail__ = None
-
-
- def decrypt(self, pyfile):
- pagehtml = self.load(pyfile.url)
- images = set(re.findall(r'(images\.4chan\.org/[^/]*/src/[^"<]*)', pagehtml))
- self.urls = ["http://" + image for image in images]
diff --git a/module/plugins/crypter/FreakhareComFolder.py b/module/plugins/crypter/FreakhareComFolder.py
deleted file mode 100644
index 2a6877891..000000000
--- a/module/plugins/crypter/FreakhareComFolder.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FreakhareComFolder(SimpleCrypter):
- __name__ = "FreakhareComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.com/folder/.+'
-
- __description__ = """Freakhare.com folder decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<a href="(http://freakshare.com/files/[^"]+)" target="_blank">'
- TITLE_PATTERN = r'Folder:</b> (?P<title>.+)'
- PAGES_PATTERN = r'Pages: +(?P<pages>\d+)'
-
-
- def loadPage(self, page_n):
- if not hasattr(self, 'f_id') and not hasattr(self, 'f_md5'):
- m = re.search(r'http://freakshare.com/\?x=folder&f_id=(\d+)&f_md5=(\w+)', self.html)
- if m:
- self.f_id = m.group(1)
- self.f_md5 = m.group(2)
- return self.load('http://freakshare.com/', get={'x': 'folder',
- 'f_id': self.f_id,
- 'f_md5': self.f_md5,
- 'entrys': '20',
- 'page': page_n - 1,
- 'order': ''}, decode=True)
diff --git a/module/plugins/crypter/FreetexthostCom.py b/module/plugins/crypter/FreetexthostCom.py
deleted file mode 100644
index bd8a90ce5..000000000
--- a/module/plugins/crypter/FreetexthostCom.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FreetexthostCom(SimpleCrypter):
- __name__ = "FreetexthostCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?freetexthost\.com/\w+'
-
- __description__ = """Freetexthost.com decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def getLinks(self):
- m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.DOTALL)
- if m is None:
- self.fail('Unable to extract links | Plugin may be out-of-date')
- links = m.group(1)
- return links.strip().split("<br />\r\n")
diff --git a/module/plugins/crypter/FshareVnFolder.py b/module/plugins/crypter/FshareVnFolder.py
deleted file mode 100644
index 5976ff6d5..000000000
--- a/module/plugins/crypter/FshareVnFolder.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class FshareVnFolder(SimpleCrypter):
- __name__ = "FshareVnFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?fshare.vn/folder/.*'
-
- __description__ = """Fshare.vn folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'<li class="w_80pc"><a href="([^"]+)" target="_blank">'
diff --git a/module/plugins/crypter/GooGl.py b/module/plugins/crypter/GooGl.py
deleted file mode 100644
index 1d9c2801f..000000000
--- a/module/plugins/crypter/GooGl.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter
-from module.common.json_layer import json_loads
-
-
-class GooGl(Crypter):
- __name__ = "GooGl"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?goo\.gl/\w+'
-
- __description__ = """Goo.gl decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- API_URL = "https://www.googleapis.com/urlshortener/v1/url"
-
-
- def decrypt(self, pyfile):
- rep = self.load(self.API_URL, get={'shortUrl': pyfile.url})
- self.logDebug('JSON data: ' + rep)
- rep = json_loads(rep)
-
- if 'longUrl' in rep:
- self.urls = [rep['longUrl']]
- else:
- self.fail('Unable to expand shortened link')
diff --git a/module/plugins/crypter/HoerbuchIn.py b/module/plugins/crypter/HoerbuchIn.py
deleted file mode 100644
index 924ce5d3a..000000000
--- a/module/plugins/crypter/HoerbuchIn.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.lib.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
-
-from module.plugins.Crypter import Crypter
-
-
-class HoerbuchIn(Crypter):
- __name__ = "HoerbuchIn"
- __type__ = "crypter"
- __version__ = "0.6"
-
- __pattern__ = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out.php\?.+|protection/folder_\d+\.html)'
-
- __description__ = """Hoerbuch.in decrypter plugin"""
- __author_name__ = ("spoob", "mkaay")
- __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de")
-
- article = re.compile("http://(?:www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/")
- protection = re.compile("http://(?:www\.)?hoerbuch\.in/protection/folder_\d+.html")
-
-
- def decrypt(self, pyfile):
- self.pyfile = pyfile
-
- if self.article.match(pyfile.url):
- src = self.load(pyfile.url)
- soup = BeautifulSoup(src, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
-
- abookname = soup.find("a", attrs={"rel": "bookmark"}).text
- for a in soup.findAll("a", attrs={"href": self.protection}):
- package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1])
- links = self.decryptFolder(a['href'])
-
- self.packages.append((package, links, package))
- else:
- self.urls = self.decryptFolder(pyfile.url)
-
- def decryptFolder(self, url):
- m = self.protection.search(url)
- if m is None:
- self.fail("Bad URL")
- url = m.group(0)
-
- self.pyfile.url = url
- src = self.req.load(url, post={"viewed": "adpg"})
-
- links = []
- pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
- for hoster, lid in pattern.findall(src):
- self.req.lastURL = url
- self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid))
- links.append(self.req.lastEffectiveURL)
-
- return links
diff --git a/module/plugins/crypter/HotfileFolderCom.py b/module/plugins/crypter/HotfileFolderCom.py
deleted file mode 100644
index 3efd8fc87..000000000
--- a/module/plugins/crypter/HotfileFolderCom.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class HotfileFolderCom(Crypter):
- __name__ = "HotfileFolderCom"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?hotfile.com/list/\w+/\w+'
-
- __description__ = """Hotfile.com folder decrypter plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- name = re.findall(
- r'<img src="/i/folder.gif" width="23" height="14" style="margin-bottom: -2px;" />([^<]+)', html,
- re.MULTILINE)[0].replace("/", "")
- new_links = re.findall(r'href="(http://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+[^"]+)', html)
-
- new_links = [x[0] for x in new_links]
-
- self.packages = [(name, new_links, name)]
diff --git a/module/plugins/crypter/ILoadTo.py b/module/plugins/crypter/ILoadTo.py
deleted file mode 100644
index d7818570a..000000000
--- a/module/plugins/crypter/ILoadTo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class ILoadTo(DeadCrypter):
- __name__ = "ILoadTo"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?iload\.to/go/\d+-[\w\.-]+/'
-
- __description__ = """Iload.to decrypter plugin"""
- __author_name__ = "hzpz"
- __author_mail__ = None
diff --git a/module/plugins/crypter/ImgurComAlbum.py b/module/plugins/crypter/ImgurComAlbum.py
deleted file mode 100644
index eb1f4441a..000000000
--- a/module/plugins/crypter/ImgurComAlbum.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-from module.utils import uniqify
-
-
-class ImgurComAlbum(SimpleCrypter):
- __name__ = "ImgurComAlbum"
- __type__ = "crypter"
- __version__ = "0.4"
-
- __pattern__ = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
-
- __description__ = """Imgur.com decrypter plugin"""
- __author_name_ = "nath_schwarz"
- __author_mail_ = "nathan.notwhite@gmail.com"
-
- TITLE_PATTERN = r'(?P<title>.+) - Imgur'
- LINK_PATTERN = r'i\.imgur\.com/\w{7}s?\.(?:jpeg|jpg|png|gif|apng)'
-
-
- def getLinks(self):
- f = lambda url: "http://" + re.sub(r'(\w{7})s\.', r'\1.', url)
- return uniqify(map(f, re.findall(self.LINK_PATTERN, self.html)))
diff --git a/module/plugins/crypter/LetitbitNetFolder.py b/module/plugins/crypter/LetitbitNetFolder.py
deleted file mode 100644
index 0297d2007..000000000
--- a/module/plugins/crypter/LetitbitNetFolder.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class LetitbitNetFolder(Crypter):
- __name__ = "LetitbitNetFolder"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?letitbit.net/folder/\w+'
-
- __description__ = """Letitbit.net folder decrypter plugin"""
- __author_name__ = ("DHMH", "z00nx")
- __author_mail__ = ("webmaster@pcProfil.de", "z00nx0@gmail.com")
-
- FOLDER_PATTERN = r'<table>(.*)</table>'
- LINK_PATTERN = r'<a href="([^"]+)" target="_blank">'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if folder is None:
- self.fail("Parse error (FOLDER)")
-
- self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
-
- if not self.urls:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/LinkSaveIn.py b/module/plugins/crypter/LinkSaveIn.py
deleted file mode 100644
index 060a434d2..000000000
--- a/module/plugins/crypter/LinkSaveIn.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# * cnl2 and web links are skipped if JS is not available (instead of failing the package)
-# * only best available link source is used (priority: cnl2>rsdf>ccf>dlc>web
-
-import base64
-import binascii
-import re
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-from module.unescape import unescape
-
-
-class LinkSaveIn(Crypter):
- __name__ = "LinkSaveIn"
- __type__ = "crypter"
- __version__ = "2.01"
-
- __pattern__ = r'http://(?:www\.)?linksave.in/(?P<id>\w+)$'
-
- __description__ = """LinkSave.in decrypter plugin"""
- __author_name__ = "fragonib"
- __author_mail__ = "fragonib[AT]yahoo[DOT]es"
-
- # Constants
- _JK_KEY_ = "jk"
- _CRYPTED_KEY_ = "crypted"
- HOSTER_NAME = "linksave.in"
-
-
- def setup(self):
- self.html = None
- self.fileid = None
- self.captcha = False
- self.package = None
- self.preferred_sources = ["cnl2", "rsdf", "ccf", "dlc", "web"]
-
- def decrypt(self, pyfile):
- # Init
- self.package = pyfile.package()
- self.fileid = re.match(self.__pattern__, pyfile.url).group('id')
- self.req.cj.setCookie(self.HOSTER_NAME, "Linksave_Language", "english")
-
- # Request package
- self.html = self.load(pyfile.url)
- if not self.isOnline():
- self.offline()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleErrors()
-
- # Get package name and folder
- (package_name, folder_name) = self.getPackageInfo()
-
- # Extract package links
- package_links = []
- for type_ in self.preferred_sources:
- package_links.extend(self.handleLinkSource(type_))
- if package_links: # use only first source which provides links
- break
- package_links = set(package_links)
-
- # Pack
- if package_links:
- self.packages = [(package_name, package_links, folder_name)]
- else:
- self.fail('Could not extract any links')
-
- def isOnline(self):
- if "<big>Error 404 - Folder not found!</big>" in self.html:
- self.logDebug("File not found")
- return False
- return True
-
- def isPasswordProtected(self):
- if re.search(r'''<input.*?type="password"''', self.html):
- self.logDebug("Links are password protected")
- return True
-
- def isCaptchaProtected(self):
- if "<b>Captcha:</b>" in self.html:
- self.logDebug("Links are captcha protected")
- return True
- return False
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
- self.logDebug("Submitting password [%s] for protected links" % password)
- post = {"id": self.fileid, "besucherpasswort": password, 'login': 'submit'}
- self.html = self.load(self.pyfile.url, post=post)
-
- def unlockCaptchaProtection(self):
- captcha_hash = re.search(r'name="hash" value="([^"]+)', self.html).group(1)
- captcha_url = re.search(r'src=".(/captcha/cap.php\?hsh=[^"]+)', self.html).group(1)
- captcha_code = self.decryptCaptcha("http://linksave.in" + captcha_url, forceUser=True)
- self.html = self.load(self.pyfile.url, post={"id": self.fileid, "hash": captcha_hash, "code": captcha_code})
-
- def getPackageInfo(self):
- name = self.pyfile.package().name
- folder = self.pyfile.package().folder
- self.logDebug("Defaulting to pyfile name [%s] and folder [%s] for package" % (name, folder))
- return name, folder
-
- def handleErrors(self):
- if "The visitorpassword you have entered is wrong" in self.html:
- self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
- self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
-
- if self.captcha:
- if "Wrong code. Please retry" in self.html:
- self.logDebug("Invalid captcha, retrying")
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
- def handleLinkSource(self, type_):
- if type_ == "cnl2":
- return self.handleCNL2()
- elif type_ in ("rsdf", "ccf", "dlc"):
- return self.handleContainer(type_)
- elif type_ == "web":
- return self.handleWebLinks()
- else:
- self.fail('unknown source type "%s" (this is probably a bug)' % type_)
-
- def handleWebLinks(self):
- package_links = []
- self.logDebug("Search for Web links")
- if not self.js:
- self.logDebug("no JS -> skip Web links")
- else:
- #@TODO: Gather paginated web links
- pattern = r'<a href="http://linksave\.in/(\w{43})"'
- ids = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Web links" % len(ids))
- for i, weblink_id in enumerate(ids):
- try:
- webLink = "http://linksave.in/%s" % weblink_id
- self.logDebug("Decrypting Web link %d, %s" % (i + 1, webLink))
- fwLink = "http://linksave.in/fw-%s" % weblink_id
- response = self.load(fwLink)
- jscode = re.findall(r'<script type="text/javascript">(.*)</script>', response)[-1]
- jseval = self.js.eval("document = { write: function(e) { return e; } }; %s" % jscode)
- dlLink = re.search(r'http://linksave\.in/dl-\w+', jseval).group(0)
- self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
- response = self.load(dlLink)
- link = unescape(re.search(r'<iframe src="(.+?)"', response).group(1))
- package_links.append(link)
- except Exception, detail:
- self.logDebug("Error decrypting Web link %s, %s" % (webLink, detail))
- return package_links
-
- def handleContainer(self, type_):
- package_links = []
- type_ = type_.lower()
- self.logDebug('Seach for %s Container links' % type_.upper())
- if not type_.isalnum(): # check to prevent broken re-pattern (cnl2,rsdf,ccf,dlc,web are all alpha-numeric)
- self.fail('unknown container type "%s" (this is probably a bug)' % type_)
- pattern = r"\('%s_link'\).href=unescape\('(.*?\.%s)'\)" % (type_, type_)
- containersLinks = re.findall(pattern, self.html)
- self.logDebug("Found %d %s Container links" % (len(containersLinks), type_.upper()))
- for containerLink in containersLinks:
- link = "http://linksave.in/%s" % unescape(containerLink)
- package_links.append(link)
- return package_links
-
- def handleCNL2(self):
- package_links = []
- self.logDebug("Search for CNL2 links")
- if not self.js:
- self.logDebug("no JS -> skip CNL2 links")
- elif 'cnl2_load' in self.html:
- try:
- (vcrypted, vjk) = self._getCipherParams()
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except:
- self.fail("Unable to decrypt CNL2 links")
- return package_links
-
- def _getCipherParams(self):
- # Get jk
- jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._JK_KEY_
- vjk = re.findall(jk_re, self.html)
-
- # Get crypted
- crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._CRYPTED_KEY_
- vcrypted = re.findall(crypted_re, self.html)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decode crypted
- crypted = base64.standard_b64decode(crypted)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted)
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = text.split("\n")
- links = filter(lambda x: x != "", links)
-
- # Log and return
- self.logDebug("Package has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/LinkdecrypterCom.py b/module/plugins/crypter/LinkdecrypterCom.py
deleted file mode 100644
index 21f05b962..000000000
--- a/module/plugins/crypter/LinkdecrypterCom.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class LinkdecrypterCom(Crypter):
- __name__ = "LinkdecrypterCom"
- __type__ = "crypter"
- __version__ = "0.27"
-
- __pattern__ = None
-
- __description__ = """Linkdecrypter.com"""
- __author_name__ = ("zoidberg", "flowlee")
- __author_mail__ = ("zoidberg@mujmail.cz", "")
-
- TEXTAREA_PATTERN = r'<textarea name="links" wrap="off" readonly="1" class="caja_des">(.+)</textarea>'
- PASSWORD_PATTERN = r'<input type="text" name="password"'
- CAPTCHA_PATTERN = r'<img class="captcha" src="(.+?)"(.*?)>'
- REDIR_PATTERN = r'<i>(Click <a href="./">here</a> if your browser does not redirect you).</i>'
-
-
- def decrypt(self, pyfile):
-
- self.passwords = self.getPassword().splitlines()
-
- # API not working anymore
- self.urls = self.decryptHTML()
- if not self.urls:
- self.fail('Could not extract any links')
-
- def decryptAPI(self):
-
- get_dict = {"t": "link", "url": self.pyfile.url, "lcache": "1"}
- self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
- if self.html.startswith('http://'):
- return self.html.splitlines()
-
- if self.html == 'INTERRUPTION(PASSWORD)':
- for get_dict['pass'] in self.passwords:
- self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
- if self.html.startswith('http://'):
- return self.html.splitlines()
-
- self.logError('API', self.html)
- if self.html == 'INTERRUPTION(PASSWORD)':
- self.fail("No or incorrect password")
-
- return None
-
- def decryptHTML(self):
-
- retries = 5
-
- post_dict = {"link_cache": "on", "pro_links": self.pyfile.url, "modo_links": "text"}
- self.html = self.load('http://linkdecrypter.com/', post=post_dict, cookies=True, decode=True)
-
- while self.passwords or retries:
- m = re.search(self.TEXTAREA_PATTERN, self.html, flags=re.DOTALL)
- if m:
- return [x for x in m.group(1).splitlines() if '[LINK-ERROR]' not in x]
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- captcha_url = 'http://linkdecrypter.com/' + m.group(1)
- result_type = "positional" if "getPos" in m.group(2) else "textual"
-
- m = re.search(r"<p><i><b>([^<]+)</b></i></p>", self.html)
- msg = m.group(1) if m else ""
- self.logInfo("Captcha protected link", result_type, msg)
-
- captcha = self.decryptCaptcha(captcha_url, result_type=result_type)
- if result_type == "positional":
- captcha = "%d|%d" % captcha
- self.html = self.load('http://linkdecrypter.com/', post={"captcha": captcha}, decode=True)
- retries -= 1
-
- elif self.PASSWORD_PATTERN in self.html:
- if self.passwords:
- password = self.passwords.pop(0)
- self.logInfo("Password protected link, trying " + password)
- self.html = self.load('http://linkdecrypter.com/', post={'password': password}, decode=True)
- else:
- self.fail("No or incorrect password")
-
- else:
- retries -= 1
- self.html = self.load('http://linkdecrypter.com/', cookies=True, decode=True)
-
- return None
diff --git a/module/plugins/crypter/LixIn.py b/module/plugins/crypter/LixIn.py
deleted file mode 100644
index cdf87eeb2..000000000
--- a/module/plugins/crypter/LixIn.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-
-
-class LixIn(Crypter):
- __name__ = "LixIn"
- __type__ = "crypter"
- __version__ = "0.22"
-
- __pattern__ = r'http://(www.)?lix.in/(?P<id>.*)'
-
- __description__ = """Lix.in decrypter plugin"""
- __author_name__ = "spoob"
- __author_mail__ = "spoob@pyload.org"
-
- CAPTCHA_PATTERN = r'<img src="(?P<image>captcha_img.php\?.*?)"'
- SUBMIT_PATTERN = r"value='continue.*?'"
- LINK_PATTERN = r'name="ifram" src="(?P<link>.*?)"'
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
-
- m = re.match(self.__pattern__, url)
- if m is None:
- self.fail("couldn't identify file id")
-
- id = m.group("id")
- self.logDebug("File id is %s" % id)
-
- self.html = self.req.load(url, decode=True)
-
- m = re.search(self.SUBMIT_PATTERN, self.html)
- if m is None:
- self.fail("link doesn't seem valid")
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- for _ in xrange(5):
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- self.logDebug("trying captcha")
- captcharesult = self.decryptCaptcha("http://lix.in/" + m.group("image"))
- self.html = self.req.load(url, decode=True,
- post={"capt": captcharesult, "submit": "submit", "tiny": id})
- else:
- self.logDebug("no captcha/captcha solved")
- else:
- self.html = self.req.load(url, decode=True, post={"submit": "submit", "tiny": id})
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.fail("can't find destination url")
- else:
- self.urls = [m.group("link")]
- self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/module/plugins/crypter/LofCc.py b/module/plugins/crypter/LofCc.py
deleted file mode 100644
index 8a5cd86ee..000000000
--- a/module/plugins/crypter/LofCc.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class LofCc(DeadCrypter):
- __name__ = "LofCc"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?lof.cc/(.*)'
-
- __description__ = """Lof.cc decrypter plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
diff --git a/module/plugins/crypter/MBLinkInfo.py b/module/plugins/crypter/MBLinkInfo.py
deleted file mode 100644
index 4fc066e57..000000000
--- a/module/plugins/crypter/MBLinkInfo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class MBLinkInfo(DeadCrypter):
- __name__ = "MBLinkInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
-
- __description__ = """MBLink.info decrypter plugin"""
- __author_name__ = ("Gummibaer", "stickell")
- __author_mail__ = ("Gummibaer@wiki-bierkiste.de", "l.stickell@yahoo.it")
diff --git a/module/plugins/crypter/MediafireComFolder.py b/module/plugins/crypter/MediafireComFolder.py
deleted file mode 100644
index bb7c90722..000000000
--- a/module/plugins/crypter/MediafireComFolder.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-from module.plugins.hoster.MediafireCom import checkHTMLHeader
-from module.common.json_layer import json_loads
-
-
-class MediafireComFolder(Crypter):
- __name__ = "MediafireComFolder"
- __type__ = "crypter"
- __version__ = "0.14"
-
- __pattern__ = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))'
-
- __description__ = """Mediafire.com folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FOLDER_KEY_PATTERN = r"var afI= '(\w+)';"
- FILE_URL_PATTERN = r'<meta property="og:url" content="http://www.mediafire.com/\?(\w+)"/>'
-
-
- def decrypt(self, pyfile):
- url, result = checkHTMLHeader(pyfile.url)
- self.logDebug('Location (%d): %s' % (result, url))
-
- if result == 0:
- # load and parse html
- html = self.load(pyfile.url)
- m = re.search(self.FILE_URL_PATTERN, html)
- if m:
- # file page
- self.urls.append("http://www.mediafire.com/file/%s" % m.group(1))
- else:
- # folder page
- m = re.search(self.FOLDER_KEY_PATTERN, html)
- if m:
- folder_key = m.group(1)
- self.logDebug("FOLDER KEY: %s" % folder_key)
-
- json_resp = json_loads(self.load(
- "http://www.mediafire.com/api/folder/get_info.php?folder_key=%s&response_format=json&version=1" % folder_key))
- #self.logInfo(json_resp)
- if json_resp['response']['result'] == "Success":
- for link in json_resp['response']['folder_info']['files']:
- self.urls.append("http://www.mediafire.com/file/%s" % link['quickkey'])
- else:
- self.fail(json_resp['response']['message'])
- elif result == 1:
- self.offline()
- else:
- self.urls.append(url)
-
- if not self.urls:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/Movie2kTo.py b/module/plugins/crypter/Movie2kTo.py
deleted file mode 100644
index ba201621f..000000000
--- a/module/plugins/crypter/Movie2kTo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class Movie2kTo(DeadCrypter):
- __name__ = "Movie2kTo"
- __type__ = "crypter"
- __version__ = "0.51"
-
- __pattern__ = r'http://(?:www\.)?movie2k\.to/(.*)\.html'
-
- __description__ = """Movie2k.to decrypter plugin"""
- __author_name__ = "4Christopher"
- __author_mail__ = "4Christopher@gmx.de"
diff --git a/module/plugins/crypter/MultiUpOrg.py b/module/plugins/crypter/MultiUpOrg.py
deleted file mode 100644
index ffeaa6f04..000000000
--- a/module/plugins/crypter/MultiUpOrg.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from urlparse import urljoin
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class MultiUpOrg(SimpleCrypter):
- __name__ = "MultiUpOrg"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
-
- __description__ = """MultiUp.org crypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- TITLE_PATTERN = r'<title>.*(Project|Projet|ownload|élécharger) (?P<title>.+?) (\(|- )'
-
-
- def getLinks(self):
- m_type = re.match(self.__pattern__, self.pyfile.url).group("TYPE")
-
- if m_type == "project":
- pattern = r'\n(http://www\.multiup\.org/(?:en|fr)/download/.*)'
- else:
- pattern = r'style="width:97%;text-align:left".*\n.*href="(.*)"'
- if m_type == "download":
- dl_pattern = r'href="(.*)">.*\n.*<h5>DOWNLOAD</h5>'
- miror_page = urljoin("http://www.multiup.org", re.search(dl_pattern, self.html).group(1))
- self.html = self.load(miror_page)
-
- return re.findall(pattern, self.html)
diff --git a/module/plugins/crypter/MultiloadCz.py b/module/plugins/crypter/MultiloadCz.py
deleted file mode 100644
index 7d43ec729..000000000
--- a/module/plugins/crypter/MultiloadCz.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class MultiloadCz(Crypter):
- __name__ = "MultiloadCz"
- __type__ = "crypter"
- __version__ = "0.4"
-
- __pattern__ = r'http://(?:[^/]*\.)?multiload.cz/(stahnout|slozka)/.*'
- __config__ = [("usedHoster", "str", "Prefered hoster list (bar-separated) ", ""),
- ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
-
- __description__ = """Multiload.cz decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FOLDER_PATTERN = r'<form action="" method="get"><textarea[^>]*>([^>]*)</textarea></form>'
- LINK_PATTERN = r'<p class="manager-server"><strong>([^<]+)</strong></p><p class="manager-linky"><a href="([^"]+)">'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
-
- if re.match(self.__pattern__, pyfile.url).group(1) == "slozka":
- m = re.search(self.FOLDER_PATTERN, self.html)
- if m:
- self.urls.extend(m.group(1).split())
- else:
- m = re.findall(self.LINK_PATTERN, self.html)
- if m:
- prefered_set = set(self.getConfig("usedHoster").split('|'))
- self.urls.extend([x[1] for x in m if x[0] in prefered_set])
-
- if not self.urls:
- ignored_set = set(self.getConfig("ignoredHoster").split('|'))
- self.urls.extend([x[1] for x in m if x[0] not in ignored_set])
-
- if not self.urls:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/MultiuploadCom.py b/module/plugins/crypter/MultiuploadCom.py
deleted file mode 100644
index 96cd734e4..000000000
--- a/module/plugins/crypter/MultiuploadCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from time import time
-
-from module.plugins.Crypter import Crypter
-from module.common.json_layer import json_loads
-
-
-class MultiuploadCom(Crypter):
- __name__ = "MultiuploadCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?multiupload.com/(\w+)'
- __config__ = [("preferedHoster", "str", "Prefered hoster list (bar-separated) ", "multiupload"),
- ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
-
- __description__ = """MultiUpload.com decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- ML_LINK_PATTERN = r'<div id="downloadbutton_" style=""><a href="([^"]+)"'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
- m = re.search(self.ML_LINK_PATTERN, self.html)
- ml_url = m.group(1) if m else None
-
- json_list = json_loads(self.load("http://multiupload.com/progress/", get={
- "d": re.match(self.__pattern__, pyfile.url).group(1),
- "r": str(int(time() * 1000))
- }))
-
- prefered_set = map(lambda s: s.lower().split('.')[0], set(self.getConfig("preferedHoster").split('|')))
-
- if ml_url and 'multiupload' in prefered_set:
- self.urls.append(ml_url)
-
- for link in json_list:
- if link['service'].lower() in prefered_set and int(link['status']) and not int(link['deleted']):
- url = self.getLocation(link['url'])
- if url:
- self.urls.append(url)
-
- if not self.urls:
- ignored_set = map(lambda s: s.lower().split('.')[0], set(self.getConfig("ignoredHoster").split('|')))
-
- if 'multiupload' not in ignored_set:
- self.urls.append(ml_url)
-
- for link in json_list:
- if link['service'].lower() not in ignored_set and int(link['status']) and not int(link['deleted']):
- url = self.getLocation(link['url'])
- if url:
- self.urls.append(url)
-
- if not self.urls:
- self.fail('Could not extract any links')
-
- def getLocation(self, url):
- header = self.load(url, just_header=True)
- return header['location'] if "location" in header else None
diff --git a/module/plugins/crypter/NCryptIn.py b/module/plugins/crypter/NCryptIn.py
deleted file mode 100644
index 1b7b8b3d4..000000000
--- a/module/plugins/crypter/NCryptIn.py
+++ /dev/null
@@ -1,303 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import base64
-import binascii
-import re
-
-from Crypto.Cipher import AES
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class NCryptIn(Crypter):
- __name__ = "NCryptIn"
- __type__ = "crypter"
- __version__ = "1.32"
-
- __pattern__ = r'http://(?:www\.)?ncrypt.in/(?P<type>folder|link|frame)-([^/\?]+)'
-
- __description__ = """NCrypt.in decrypter plugin"""
- __author_name__ = ("fragonib", "stickell")
- __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "l.stickell@yahoo.it")
-
- JK_KEY = "jk"
- CRYPTED_KEY = "crypted"
-
- NAME_PATTERN = r'<meta name="description" content="(?P<N>[^"]+)"'
-
-
- def setup(self):
- self.package = None
- self.html = None
- self.cleanedHtml = None
- self.links_source_order = ["cnl2", "rsdf", "ccf", "dlc", "web"]
- self.protection_type = None
-
- def decrypt(self, pyfile):
- # Init
- self.package = pyfile.package()
- package_links = []
- package_name = self.package.name
- folder_name = self.package.folder
-
- # Deal with single links
- if self.isSingleLink():
- package_links.extend(self.handleSingleLink())
-
- # Deal with folders
- else:
-
- # Request folder home
- self.html = self.requestFolderHome()
- self.cleanedHtml = self.removeHtmlCrap(self.html)
- if not self.isOnline():
- self.offline()
-
- # Check for folder protection
- if self.isProtected():
- self.html = self.unlockProtection()
- self.cleanedHtml = self.removeHtmlCrap(self.html)
- self.handleErrors()
-
- # Prepare package name and folder
- (package_name, folder_name) = self.getPackageInfo()
-
- # Extract package links
- for link_source_type in self.links_source_order:
- package_links.extend(self.handleLinkSource(link_source_type))
- if package_links: # use only first source which provides links
- break
- package_links = set(package_links)
-
- # Pack and return links
- if not package_links:
- self.fail('Could not extract any links')
- self.packages = [(package_name, package_links, folder_name)]
-
- def isSingleLink(self):
- link_type = re.match(self.__pattern__, self.pyfile.url).group('type')
- return link_type in ("link", "frame")
-
- def requestFolderHome(self):
- return self.load(self.pyfile.url, decode=True)
-
- def removeHtmlCrap(self, content):
- patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")',
- r'display:none;">(.*?)</(div|span)>',
- r'<div\s+class="jdownloader"(.*?)</div>',
- r'<table class="global">(.*?)</table>',
- r'<iframe\s+style="display:none(.*?)</iframe>')
- for pattern in patterns:
- rexpr = re.compile(pattern, re.DOTALL)
- content = re.sub(rexpr, "", content)
- return content
-
- def isOnline(self):
- if "Your folder does not exist" in self.cleanedHtml:
- self.logDebug("File not m")
- return False
- return True
-
- def isProtected(self):
- form = re.search(r'<form.*?name.*?protected.*?>(.*?)</form>', self.cleanedHtml, re.DOTALL)
- if form is not None:
- content = form.group(1)
- for keyword in ("password", "captcha"):
- if keyword in content:
- self.protection_type = keyword
- self.logDebug("Links are %s protected" % self.protection_type)
- return True
- return False
-
- def getPackageInfo(self):
- m = re.search(self.NAME_PATTERN, self.html)
- if m:
- name = folder = m.group('N').strip()
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
- else:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not m, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
- return name, folder
-
- def unlockProtection(self):
-
- postData = {}
-
- form = re.search(r'<form name="protected"(.*?)</form>', self.cleanedHtml, re.DOTALL).group(1)
-
- # Submit package password
- if "password" in form:
- password = self.getPassword()
- self.logDebug("Submitting password [%s] for protected links" % password)
- postData['password'] = password
-
- # Resolve anicaptcha
- if "anicaptcha" in form:
- self.logDebug("Captcha protected")
- captchaUri = re.search(r'src="(/temp/anicaptcha/[^"]+)', form).group(1)
- captcha = self.decryptCaptcha("http://ncrypt.in" + captchaUri)
- self.logDebug("Captcha resolved [%s]" % captcha)
- postData['captcha'] = captcha
-
- # Resolve recaptcha
- if "recaptcha" in form:
- self.logDebug("ReCaptcha protected")
- captcha_key = re.search(r'\?k=(.*?)"', form).group(1)
- self.logDebug("Resolving ReCaptcha with key [%s]" % captcha_key)
- recaptcha = ReCaptcha(self)
- challenge, code = recaptcha.challenge(captcha_key)
- postData['recaptcha_challenge_field'] = challenge
- postData['recaptcha_response_field'] = code
-
- # Resolve circlecaptcha
- if "circlecaptcha" in form:
- self.logDebug("CircleCaptcha protected")
- captcha_img_url = "http://ncrypt.in/classes/captcha/circlecaptcha.php"
- coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
- postData['circle.x'] = coords[0]
- postData['circle.y'] = coords[1]
-
- # Unlock protection
- postData['submit_protected'] = 'Continue to folder'
- return self.load(self.pyfile.url, post=postData, decode=True)
-
- def handleErrors(self):
- if self.protection_type == "password":
- if "This password is invalid!" in self.cleanedHtml:
- self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
- self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
-
- if self.protection_type == "captcha":
- if "The securitycheck was wrong!" in self.cleanedHtml:
- self.logDebug("Invalid captcha, retrying")
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
- def handleLinkSource(self, link_source_type):
- # Check for JS engine
- require_js_engine = link_source_type in ("cnl2", "rsdf", "ccf", "dlc")
- if require_js_engine and not self.js:
- self.logDebug("No JS engine available, skip %s links" % link_source_type)
- return []
-
- # Select suitable handler
- if link_source_type == 'single':
- return self.handleSingleLink()
- if link_source_type == 'cnl2':
- return self.handleCNL2()
- elif link_source_type in ("rsdf", "ccf", "dlc"):
- return self.handleContainer(link_source_type)
- elif link_source_type == "web":
- return self.handleWebLinks()
- else:
- self.fail('unknown source type "%s" (this is probably a bug)' % link_source_type)
-
- def handleSingleLink(self):
-
- self.logDebug("Handling Single link")
- package_links = []
-
- # Decrypt single link
- decrypted_link = self.decryptLink(self.pyfile.url)
- if decrypted_link:
- package_links.append(decrypted_link)
-
- return package_links
-
- def handleCNL2(self):
-
- self.logDebug("Handling CNL2 links")
- package_links = []
-
- if 'cnl2_output' in self.cleanedHtml:
- try:
- (vcrypted, vjk) = self._getCipherParams()
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except:
- self.fail("Unable to decrypt CNL2 links")
-
- return package_links
-
- def handleContainers(self):
-
- self.logDebug("Handling Container links")
- package_links = []
-
- pattern = r"/container/(rsdf|dlc|ccf)/([a-z0-9]+)"
- containersLinks = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Container links" % len(containersLinks))
- for containerLink in containersLinks:
- link = "http://ncrypt.in/container/%s/%s.%s" % (containerLink[0], containerLink[1], containerLink[0])
- package_links.append(link)
-
- return package_links
-
- def handleWebLinks(self):
-
- self.logDebug("Handling Web links")
- pattern = r"(http://ncrypt\.in/link-.*?=)"
- links = re.findall(pattern, self.html)
-
- package_links = []
- self.logDebug("Decrypting %d Web links" % len(links))
- for i, link in enumerate(links):
- self.logDebug("Decrypting Web link %d, %s" % (i + 1, link))
- decrypted_link = self.decrypt(link)
- if decrypted_link:
- package_links.append(decrypted_link)
-
- return package_links
-
- def decryptLink(self, link):
- try:
- url = link.replace("link-", "frame-")
- link = self.load(url, just_header=True)['location']
- return link
- except Exception, detail:
- self.logDebug("Error decrypting link %s, %s" % (link, detail))
-
- def _getCipherParams(self):
-
- pattern = r'<input.*?name="%s".*?value="(.*?)"'
-
- # Get jk
- jk_re = pattern % NCryptIn.JK_KEY
- vjk = re.findall(jk_re, self.html)
-
- # Get crypted
- crypted_re = pattern % NCryptIn.CRYPTED_KEY
- vcrypted = re.findall(crypted_re, self.html)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decode crypted
- crypted = base64.standard_b64decode(crypted)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted)
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = text.split("\n")
- links = filter(lambda x: x != "", links)
-
- # Log and return
- self.logDebug("Block has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/NetfolderIn.py b/module/plugins/crypter/NetfolderIn.py
deleted file mode 100644
index 1c337391c..000000000
--- a/module/plugins/crypter/NetfolderIn.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class NetfolderIn(SimpleCrypter):
- __name__ = "NetfolderIn"
- __type__ = "crypter"
- __version__ = "0.6"
-
- __pattern__ = r'http://(?:www\.)?netfolder.in/((?P<id1>\w+)/\w+|folder.php\?folder_id=(?P<id2>\w+))'
-
- __description__ = """NetFolder.in decrypter plugin"""
- __author_name__ = ("RaNaN", "fragonib")
- __author_mail__ = ("RaNaN@pyload.org", "fragonib[AT]yahoo[DOT]es")
-
- TITLE_PATTERN = r'<div class="Text">Inhalt des Ordners <span(.*)>(?P<title>.+)</span></div>'
-
-
- def decrypt(self, pyfile):
- # Request package
- self.html = self.load(pyfile.url)
-
- # Check for password protection
- if self.isPasswordProtected():
- self.html = self.submitPassword()
- if not self.html:
- self.fail("Incorrect password, please set right password on Add package form and retry")
-
- # Get package name and folder
- (package_name, folder_name) = self.getPackageNameAndFolder()
-
- # Get package links
- package_links = self.getLinks()
-
- # Set package
- self.packages = [(package_name, package_links, folder_name)]
-
- def isPasswordProtected(self):
- if '<input type="password" name="password"' in self.html:
- self.logDebug("Links are password protected")
- return True
- return False
-
- def submitPassword(self):
- # Gather data
- try:
- m = re.match(self.__pattern__, self.pyfile.url)
- id = max(m.group('id1'), m.group('id2'))
- except AttributeError:
- self.logDebug("Unable to get package id from url [%s]" % self.pyfile.url)
- return
- url = "http://netfolder.in/folder.php?folder_id=" + id
- password = self.getPassword()
-
- # Submit package password
- post = {'password': password, 'save': 'Absenden'}
- self.logDebug("Submitting password [%s] for protected links with id [%s]" % (password, id))
- html = self.load(url, {}, post)
-
- # Check for invalid password
- if '<div class="InPage_Error">' in html:
- self.logDebug("Incorrect password, please set right password on Edit package form and retry")
- return None
-
- return html
-
- def getLinks(self):
- links = re.search(r'name="list" value="(.*?)"', self.html).group(1).split(",")
- self.logDebug("Package has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/NosvideoCom.py b/module/plugins/crypter/NosvideoCom.py
deleted file mode 100644
index a3bb11b16..000000000
--- a/module/plugins/crypter/NosvideoCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class NosvideoCom(SimpleCrypter):
- __name__ = "NosvideoCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
-
- __description__ = """Nosvideo.com decrypter plugin"""
- __author_name__ = "igel"
- __author_mail__ = "igelkun@myopera.com"
-
- LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload.com/\?d=\w+)"'
- TITLE_PATTERN = r'<[tT]itle>Watch (?P<title>.+)</[tT]itle>'
diff --git a/module/plugins/crypter/OneKhDe.py b/module/plugins/crypter/OneKhDe.py
deleted file mode 100644
index ba93278d5..000000000
--- a/module/plugins/crypter/OneKhDe.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.unescape import unescape
-from module.plugins.Crypter import Crypter
-
-
-class OneKhDe(Crypter):
- __name__ = "OneKhDe"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?1kh.de/f/'
-
- __description__ = """1kh.de decrypter plugin"""
- __author_name__ = "spoob"
- __author_mail__ = "spoob@pyload.org"
-
-
- def __init__(self, parent):
- Crypter.__init__(self, parent)
- self.parent = parent
- self.html = None
-
- def file_exists(self):
- """ returns True or False
- """
- return True
-
- def proceed(self, url, location):
- url = self.parent.url
- self.html = self.req.load(url)
- link_ids = re.findall(r"<a id=\"DownloadLink_(\d*)\" href=\"http://1kh.de/", self.html)
- for id in link_ids:
- new_link = unescape(
- re.search("width=\"100%\" src=\"(.*)\"></iframe>", self.req.load("http://1kh.de/l/" + id)).group(1))
- self.urls.append(new_link)
diff --git a/module/plugins/crypter/OronComFolder.py b/module/plugins/crypter/OronComFolder.py
deleted file mode 100755
index f0b2f943c..000000000
--- a/module/plugins/crypter/OronComFolder.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class OronComFolder(DeadCrypter):
- __name__ = "OronComFolder"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?oron.com/folder/\w+'
-
- __description__ = """Oron.com folder decrypter plugin"""
- __author_name__ = "DHMH"
- __author_mail__ = "webmaster@pcProfil.de"
diff --git a/module/plugins/crypter/PastebinCom.py b/module/plugins/crypter/PastebinCom.py
deleted file mode 100644
index 55aa2ee4a..000000000
--- a/module/plugins/crypter/PastebinCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class PastebinCom(SimpleCrypter):
- __name__ = "PastebinCom"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?pastebin\.com/\w+'
-
- __description__ = """Pastebin.com decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
- TITLE_PATTERN = r'<div class="paste_box_line1" title="(?P<title>[^"]+)">'
diff --git a/module/plugins/crypter/QuickshareCzFolder.py b/module/plugins/crypter/QuickshareCzFolder.py
deleted file mode 100644
index 9840b84df..000000000
--- a/module/plugins/crypter/QuickshareCzFolder.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class QuickshareCzFolder(Crypter):
- __name__ = "QuickshareCzFolder"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?quickshare.cz/slozka-\d+.*'
-
- __description__ = """Quickshare.cz folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FOLDER_PATTERN = r'<textarea[^>]*>(.*?)</textarea>'
- LINK_PATTERN = r'(http://www.quickshare.cz/\S+)'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if m is None:
- self.fail("Parse error (FOLDER)")
- self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
-
- if not self.urls:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/RSLayerCom.py b/module/plugins/crypter/RSLayerCom.py
deleted file mode 100644
index 7dda0beab..000000000
--- a/module/plugins/crypter/RSLayerCom.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class RSLayerCom(DeadCrypter):
- __name__ = "RSLayerCom"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?rs-layer.com/directory-'
-
- __description__ = """RS-Layer.com decrypter plugin"""
- __author_name__ = "hzpz"
- __author_mail__ = None
diff --git a/module/plugins/crypter/RelinkUs.py b/module/plugins/crypter/RelinkUs.py
deleted file mode 100644
index 1b120fc77..000000000
--- a/module/plugins/crypter/RelinkUs.py
+++ /dev/null
@@ -1,263 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import base64
-import binascii
-import re
-import os
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-
-
-class RelinkUs(Crypter):
- __name__ = "RelinkUs"
- __type__ = "crypter"
- __version__ = "3.0"
-
- __pattern__ = r'http://(?:www\.)?relink.us/(f/|((view|go).php\?id=))(?P<id>.+)'
-
- __description__ = """Relink.us decrypter plugin"""
- __author_name__ = "fragonib"
- __author_mail__ = "fragonib[AT]yahoo[DOT]es"
-
- # Constants
- PREFERRED_LINK_SOURCES = ["cnl2", "dlc", "web"]
-
- OFFLINE_TOKEN = r'<title>Tattooside'
- PASSWORD_TOKEN = r'container_password\.php'
- PASSWORD_ERROR_ROKEN = r'You have entered an incorrect password'
- PASSWORD_SUBMIT_URL = r'http://www\.relink\.us/container_password\.php'
- CAPTCHA_TOKEN = r'container_captcha\.php'
- CAPTCHA_ERROR_ROKEN = r'You have solved the captcha wrong'
- CAPTCHA_IMG_URL = r'http://www\.relink\.us/core/captcha/circlecaptcha\.php'
- CAPTCHA_SUBMIT_URL = r'http://www\.relink\.us/container_captcha\.php'
- FILE_TITLE_REGEX = r'<th>Title</th><td><i>(.*)</i></td></tr>'
- FILE_NOTITLE = r'No title'
-
- CNL2_FORM_REGEX = r'<form id="cnl_form-(.*?)</form>'
- CNL2_FORMINPUT_REGEX = r'<input.*?name="%s".*?value="(.*?)"'
- CNL2_JK_KEY = "jk"
- CNL2_CRYPTED_KEY = "crypted"
- DLC_LINK_REGEX = r'<a href=".*?" class="dlc_button" target="_blank">'
- DLC_DOWNLOAD_URL = r'http://www\.relink\.us/download\.php'
- WEB_FORWARD_REGEX = r"getFile\('(?P<link>.+)'\)"
- WEB_FORWARD_URL = r'http://www\.relink\.us/frame\.php'
- WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(?P<link>.+)"></iframe>'
-
-
- def setup(self):
- self.fileid = None
- self.package = None
- self.password = None
- self.html = None
- self.captcha = False
-
- def decrypt(self, pyfile):
- # Init
- self.initPackage(pyfile)
-
- # Request package
- self.requestPackage()
-
- # Check for online
- if not self.isOnline():
- self.offline()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleErrors()
-
- # Get package name and folder
- (package_name, folder_name) = self.getPackageInfo()
-
- # Extract package links
- package_links = []
- for sources in self.PREFERRED_LINK_SOURCES:
- package_links.extend(self.handleLinkSource(sources))
- if package_links: # use only first source which provides links
- break
- package_links = set(package_links)
-
- # Pack
- if package_links:
- self.packages = [(package_name, package_links, folder_name)]
- else:
- self.fail('Could not extract any links')
-
- def initPackage(self, pyfile):
- self.fileid = re.match(self.__pattern__, pyfile.url).group('id')
- self.package = pyfile.package()
- self.password = self.getPassword()
-
- def requestPackage(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- def isOnline(self):
- if self.OFFLINE_TOKEN in self.html:
- self.logDebug("File not found")
- return False
- return True
-
- def isPasswordProtected(self):
- if self.PASSWORD_TOKEN in self.html:
- self.logDebug("Links are password protected")
- return True
-
- def isCaptchaProtected(self):
- if self.CAPTCHA_TOKEN in self.html:
- self.logDebug("Links are captcha protected")
- return True
- return False
-
- def unlockPasswordProtection(self):
- self.logDebug("Submitting password [%s] for protected links" % self.password)
- passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
- passwd_data = {'id': self.fileid, 'password': self.password, 'pw': 'submit'}
- self.html = self.load(passwd_url, post=passwd_data, decode=True)
-
- def unlockCaptchaProtection(self):
- self.logDebug("Request user positional captcha resolving")
- captcha_img_url = self.CAPTCHA_IMG_URL + "?id=%s" % self.fileid
- coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
- captcha_post_url = self.CAPTCHA_SUBMIT_URL + "?id=%s" % self.fileid
- captcha_post_data = {'button.x': coords[0], 'button.y': coords[1], 'captcha': 'submit'}
- self.html = self.load(captcha_post_url, post=captcha_post_data, decode=True)
-
- def getPackageInfo(self):
- name = folder = None
-
- # Try to get info from web
- m = re.search(self.FILE_TITLE_REGEX, self.html)
- if m is not None:
- title = m.group(1).strip()
- if not self.FILE_NOTITLE in title:
- name = folder = title
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
-
- # Fallback to defaults
- if not name or not folder:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
-
- # Return package info
- return name, folder
-
- def handleErrors(self):
- if self.PASSWORD_ERROR_ROKEN in self.html:
- msg = "Incorrect password, please set right password on 'Edit package' form and retry"
- self.logDebug(msg)
- self.fail(msg)
-
- if self.captcha:
- if self.CAPTCHA_ERROR_ROKEN in self.html:
- self.logDebug("Invalid captcha, retrying")
- self.invalidCaptcha()
- self.retry()
- else:
- self.correctCaptcha()
-
- def handleLinkSource(self, source):
- if source == 'cnl2':
- return self.handleCNL2Links()
- elif source == 'dlc':
- return self.handleDLCLinks()
- elif source == 'web':
- return self.handleWEBLinks()
- else:
- self.fail('Unknown source [%s] (this is probably a bug)' % source)
-
- def handleCNL2Links(self):
- self.logDebug("Search for CNL2 links")
- package_links = []
- m = re.search(self.CNL2_FORM_REGEX, self.html, re.DOTALL)
- if m is not None:
- cnl2_form = m.group(1)
- try:
- (vcrypted, vjk) = self._getCipherParams(cnl2_form)
- for (crypted, jk) in zip(vcrypted, vjk):
- package_links.extend(self._getLinks(crypted, jk))
- except:
- self.logDebug("Unable to decrypt CNL2 links")
- return package_links
-
- def handleDLCLinks(self):
- self.logDebug('Search for DLC links')
- package_links = []
- m = re.search(self.DLC_LINK_REGEX, self.html)
- if m is not None:
- container_url = self.DLC_DOWNLOAD_URL + "?id=%s&dlc=1" % self.fileid
- self.logDebug("Downloading DLC container link [%s]" % container_url)
- try:
- dlc = self.load(container_url)
- dlc_filename = self.fileid + ".dlc"
- dlc_filepath = os.path.join(self.config['general']['download_folder'], dlc_filename)
- f = open(dlc_filepath, "wb")
- f.write(dlc)
- f.close()
- package_links.append(dlc_filepath)
- except:
- self.logDebug("Unable to download DLC container")
- return package_links
-
- def handleWEBLinks(self):
- self.logDebug("Search for WEB links")
- package_links = []
- fw_params = re.findall(self.WEB_FORWARD_REGEX, self.html)
- self.logDebug("Decrypting %d Web links" % len(fw_params))
- for index, fw_param in enumerate(fw_params):
- try:
- fw_url = self.WEB_FORWARD_URL + "?%s" % fw_param
- self.logDebug("Decrypting Web link %d, %s" % (index + 1, fw_url))
- fw_response = self.load(fw_url, decode=True)
- dl_link = re.search(self.WEB_LINK_REGEX, fw_response).group('link')
- package_links.append(dl_link)
- except Exception, detail:
- self.logDebug("Error decrypting Web link %s, %s" % (index, detail))
- self.setWait(4)
- self.wait()
- return package_links
-
- def _getCipherParams(self, cnl2_form):
- # Get jk
- jk_re = self.CNL2_FORMINPUT_REGEX % self.CNL2_JK_KEY
- vjk = re.findall(jk_re, cnl2_form, re.IGNORECASE)
-
- # Get crypted
- crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
- vcrypted = re.findall(crypted_re, cnl2_form, re.IGNORECASE)
-
- # Log and return
- self.logDebug("Detected %d crypted blocks" % len(vcrypted))
- return vcrypted, vjk
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decode crypted
- crypted = base64.standard_b64decode(crypted)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted)
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = text.split("\n")
- links = filter(lambda x: x != "", links)
-
- # Log and return
- self.logDebug("Package has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/SafelinkingNet.py b/module/plugins/crypter/SafelinkingNet.py
deleted file mode 100644
index e0c165705..000000000
--- a/module/plugins/crypter/SafelinkingNet.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION
-
-from module.lib.BeautifulSoup import BeautifulSoup
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.CaptchaService import SolveMedia
-
-
-class SafelinkingNet(Crypter):
- __name__ = "SafelinkingNet"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'https?://(?:www\.)?safelinking.net/([pd])/\w+'
-
- __description__ = """Safelinking.net decrypter plugin"""
- __author_name__ = "quareevo"
- __author_mail__ = "quareevo@arcor.de"
-
- SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w\.\-_]+)';"
-
-
- def decrypt(self, pyfile):
- url = pyfile.url
- if re.match(self.__pattern__, url).group(1) == "d":
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
- self.load(url)
- m = re.search("^Location: (.+)$", self.req.http.header, re.MULTILINE)
- if m:
- self.urls = [m.group(1)]
- else:
- self.fail("Couldn't find forwarded Link")
-
- else:
- password = ""
- postData = {"post-protect": "1"}
-
- self.html = self.load(url)
-
- if "link-password" in self.html:
- password = pyfile.package().password
- postData['link-password'] = password
-
- if "altcaptcha" in self.html:
- for _ in xrange(5):
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captchaKey = m.group(1)
- captcha = SolveMedia(self)
- captchaProvider = "Solvemedia"
- else:
- self.fail("Error parsing captcha")
-
- challenge, response = captcha.challenge(captchaKey)
- postData['adcopy_challenge'] = challenge
- postData['adcopy_response'] = response
-
- self.html = self.load(url, post=postData)
- if "The password you entered was incorrect" in self.html:
- self.fail("Incorrect Password")
- if not "The CAPTCHA code you entered was wrong" in self.html:
- break
-
- pyfile.package().password = ""
- soup = BeautifulSoup(self.html)
- scripts = soup.findAll("script")
- for s in scripts:
- if "d_links" in s.text:
- break
- m = re.search('d_links":(\[.*?\])', s.text)
- if m:
- linkDict = json_loads(m.group(1))
- for link in linkDict:
- if not "http://" in link['full']:
- self.urls.append("https://safelinking.net/d/" + link['full'])
- else:
- self.urls.append(link['full'])
diff --git a/module/plugins/crypter/SecuredIn.py b/module/plugins/crypter/SecuredIn.py
deleted file mode 100644
index 0b558c688..000000000
--- a/module/plugins/crypter/SecuredIn.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class SecuredIn(DeadCrypter):
- __name__ = "SecuredIn"
- __type__ = "crypter"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?secured\.in/download-[\d]+-[\w]{8}\.html'
-
- __description__ = """Secured.in decrypter plugin"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
diff --git a/module/plugins/crypter/SerienjunkiesOrg.py b/module/plugins/crypter/SerienjunkiesOrg.py
deleted file mode 100644
index fe4ee4e36..000000000
--- a/module/plugins/crypter/SerienjunkiesOrg.py
+++ /dev/null
@@ -1,324 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from time import sleep
-
-from module.lib.BeautifulSoup import BeautifulSoup
-
-from module.plugins.Crypter import Crypter
-from module.unescape import unescape
-
-
-class SerienjunkiesOrg(Crypter):
- __name__ = "SerienjunkiesOrg"
- __type__ = "crypter"
- __version__ = "0.39"
-
- __pattern__ = r'http://(?:www\.)?(serienjunkies.org|dokujunkies.org)/.*?'
- __config__ = [("changeNameSJ", "Packagename;Show;Season;Format;Episode", "Take SJ.org name", "Show"),
- ("changeNameDJ", "Packagename;Show;Format;Episode", "Take DJ.org name", "Show"),
- ("randomPreferred", "bool", "Randomize Preferred-List", False),
- ("hosterListMode", "OnlyOne;OnlyPreferred(One);OnlyPreferred(All);All",
- "Use for hosters (if supported)", "All"),
- ("hosterList", "str", "Preferred Hoster list (comma separated)",
- "RapidshareCom,UploadedTo,NetloadIn,FilefactoryCom,FreakshareNet,FilebaseTo,HotfileCom,DepositfilesCom,EasyshareCom,KickloadCom"),
- ("ignoreList", "str", "Ignored Hoster list (comma separated)", "MegauploadCom")]
-
- __description__ = """Serienjunkies.org decrypter plugin"""
- __author_name__ = ("mkaay", "godofdream")
- __author_mail__ = ("mkaay@mkaay.de", "soilfiction@gmail.com")
-
-
- def setup(self):
- self.multiDL = False
-
- def getSJSrc(self, url):
- src = self.req.load(str(url))
- if "This website is not available in your country" in src:
- self.fail("Not available in your country")
- if not src.find("Enter Serienjunkies") == -1:
- sleep(1)
- src = self.req.load(str(url))
- return src
-
- def handleShow(self, url):
- src = self.getSJSrc(url)
- soup = BeautifulSoup(src)
- packageName = self.pyfile.package().name
- if self.getConfig("changeNameSJ") == "Show":
- found = unescape(soup.find("h2").find("a").string.split(' &#8211;')[0])
- if found:
- packageName = found
-
- nav = soup.find("div", attrs={"id": "scb"})
-
- package_links = []
- for a in nav.findAll("a"):
- if self.getConfig("changeNameSJ") == "Show":
- package_links.append(a['href'])
- else:
- package_links.append(a['href'] + "#hasName")
- if self.getConfig("changeNameSJ") == "Show":
- self.packages.append((packageName, package_links, packageName))
- else:
- self.core.files.addLinks(package_links, self.pyfile.package().id)
-
- def handleSeason(self, url):
- src = self.getSJSrc(url)
- soup = BeautifulSoup(src)
- post = soup.find("div", attrs={"class": "post-content"})
- ps = post.findAll("p")
-
- seasonName = unescape(soup.find("a", attrs={"rel": "bookmark"}).string).replace("&#8211;", "-")
- groups = {}
- gid = -1
- for p in ps:
- if re.search("<strong>Sprache|<strong>Format", str(p)):
- var = p.findAll("strong")
- opts = {"Sprache": "", "Format": ""}
- for v in var:
- n = unescape(v.string).strip()
- n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n)
- if n.strip() not in opts:
- continue
- val = v.nextSibling
- if not val:
- continue
- val = val.replace("|", "").strip()
- val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val)
- opts[n.strip()] = val.strip()
- gid += 1
- groups[gid] = {}
- groups[gid]['ep'] = {}
- groups[gid]['opts'] = opts
- elif re.search("<strong>Download:", str(p)):
- parts = str(p).split("<br />")
- if re.search("<strong>", parts[0]):
- ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace(
- "&#8211;", "-")
- groups[gid]['ep'][ename] = {}
- parts.remove(parts[0])
- for part in parts:
- hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part)
- if hostername:
- hostername = hostername.group(1)
- groups[gid]['ep'][ename][hostername] = []
- links = re.findall('href="(.*?)"', part)
- for link in links:
- groups[gid]['ep'][ename][hostername].append(link + "#hasName")
-
- links = []
- for g in groups.values():
- for ename in g['ep']:
- links.extend(self.getpreferred(g['ep'][ename]))
- if self.getConfig("changeNameSJ") == "Episode":
- self.packages.append((ename, links, ename))
- links = []
- package = "%s (%s, %s)" % (seasonName, g['opts']['Format'], g['opts']['Sprache'])
- if self.getConfig("changeNameSJ") == "Format":
- self.packages.append((package, links, package))
- links = []
- if (self.getConfig("changeNameSJ") == "Packagename") or re.search("#hasName", url):
- self.core.files.addLinks(links, self.pyfile.package().id)
- elif (self.getConfig("changeNameSJ") == "Season") or not re.search("#hasName", url):
- self.packages.append((seasonName, links, seasonName))
-
- def handleEpisode(self, url):
- src = self.getSJSrc(url)
- if not src.find(
- "Du hast das Download-Limit &uuml;berschritten! Bitte versuche es sp&auml;ter nocheinmal.") == -1:
- self.fail(_("Downloadlimit reached"))
- else:
- soup = BeautifulSoup(src)
- form = soup.find("form")
- h1 = soup.find("h1")
-
- if h1.get("class") == "wrap":
- captchaTag = soup.find(attrs={"src": re.compile("^/secure/")})
- if not captchaTag:
- sleep(5)
- self.retry()
-
- captchaUrl = "http://download.serienjunkies.org" + captchaTag['src']
- result = self.decryptCaptcha(str(captchaUrl), imgtype="png")
- sinp = form.find(attrs={"name": "s"})
-
- self.req.lastURL = str(url)
- sj = self.load(str(url), post={'s': sinp['value'], 'c': result, 'action': "Download"})
-
- soup = BeautifulSoup(sj)
- rawLinks = soup.findAll(attrs={"action": re.compile("^http://download.serienjunkies.org/")})
-
- if not len(rawLinks) > 0:
- sleep(1)
- self.retry()
- return
-
- self.correctCaptcha()
-
- links = []
- for link in rawLinks:
- frameUrl = link['action'].replace("/go-", "/frame/go-")
- links.append(self.handleFrame(frameUrl))
- if re.search("#hasName", url) or ((self.getConfig("changeNameSJ") == "Packagename") and
- (self.getConfig("changeNameDJ") == "Packagename")):
- self.core.files.addLinks(links, self.pyfile.package().id)
- else:
- if h1.text[2] == "_":
- eName = h1.text[3:]
- else:
- eName = h1.text
- self.packages.append((eName, links, eName))
-
- def handleOldStyleLink(self, url):
- sj = self.req.load(str(url))
- soup = BeautifulSoup(sj)
- form = soup.find("form", attrs={"action": re.compile("^http://serienjunkies.org")})
- captchaTag = form.find(attrs={"src": re.compile("^/safe/secure/")})
- captchaUrl = "http://serienjunkies.org" + captchaTag['src']
- result = self.decryptCaptcha(str(captchaUrl))
- url = form['action']
- sinp = form.find(attrs={"name": "s"})
-
- self.req.load(str(url), post={'s': sinp['value'], 'c': result, 'dl.start': "Download"}, cookies=False,
- just_header=True)
- decrypted = self.req.lastEffectiveURL
- if decrypted == str(url):
- self.retry()
- self.core.files.addLinks([decrypted], self.pyfile.package().id)
-
- def handleFrame(self, url):
- self.req.load(str(url))
- return self.req.lastEffectiveURL
-
- def handleShowDJ(self, url):
- src = self.getSJSrc(url)
- soup = BeautifulSoup(src)
- post = soup.find("div", attrs={"id": "page_post"})
- ps = post.findAll("p")
- found = unescape(soup.find("h2").find("a").string.split(' &#8211;')[0])
- if found:
- seasonName = found
-
- groups = {}
- gid = -1
- for p in ps:
- if re.search("<strong>Sprache|<strong>Format", str(p)):
- var = p.findAll("strong")
- opts = {"Sprache": "", "Format": ""}
- for v in var:
- n = unescape(v.string).strip()
- n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n)
- if n.strip() not in opts:
- continue
- val = v.nextSibling
- if not val:
- continue
- val = val.replace("|", "").strip()
- val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val)
- opts[n.strip()] = val.strip()
- gid += 1
- groups[gid] = {}
- groups[gid]['ep'] = {}
- groups[gid]['opts'] = opts
- elif re.search("<strong>Download:", str(p)):
- parts = str(p).split("<br />")
- if re.search("<strong>", parts[0]):
- ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace(
- "&#8211;", "-")
- groups[gid]['ep'][ename] = {}
- parts.remove(parts[0])
- for part in parts:
- hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part)
- if hostername:
- hostername = hostername.group(1)
- groups[gid]['ep'][ename][hostername] = []
- links = re.findall('href="(.*?)"', part)
- for link in links:
- groups[gid]['ep'][ename][hostername].append(link + "#hasName")
-
- links = []
- for g in groups.values():
- for ename in g['ep']:
- links.extend(self.getpreferred(g['ep'][ename]))
- if self.getConfig("changeNameDJ") == "Episode":
- self.packages.append((ename, links, ename))
- links = []
- package = "%s (%s, %s)" % (seasonName, g['opts']['Format'], g['opts']['Sprache'])
- if self.getConfig("changeNameDJ") == "Format":
- self.packages.append((package, links, package))
- links = []
- if (self.getConfig("changeNameDJ") == "Packagename") or re.search("#hasName", url):
- self.core.files.addLinks(links, self.pyfile.package().id)
- elif (self.getConfig("changeNameDJ") == "Show") or not re.search("#hasName", url):
- self.packages.append((seasonName, links, seasonName))
-
- def handleCategoryDJ(self, url):
- package_links = []
- src = self.getSJSrc(url)
- soup = BeautifulSoup(src)
- content = soup.find("div", attrs={"id": "content"})
- for a in content.findAll("a", attrs={"rel": "bookmark"}):
- package_links.append(a['href'])
- self.core.files.addLinks(package_links, self.pyfile.package().id)
-
- def decrypt(self, pyfile):
- showPattern = re.compile("^http://serienjunkies.org/serie/(.*)/$")
- seasonPattern = re.compile("^http://serienjunkies.org/.*?/(.*)/$")
- episodePattern = re.compile("^http://download.serienjunkies.org/f-.*?.html(#hasName)?$")
- oldStyleLink = re.compile("^http://serienjunkies.org/safe/(.*)$")
- categoryPatternDJ = re.compile("^http://dokujunkies.org/.*?(.*)$")
- showPatternDJ = re.compile(r"^http://dokujunkies.org/.*?/(.*)\.html(#hasName)?$")
- framePattern = re.compile("^http://download.(serienjunkies.org|dokujunkies.org)/frame/go-.*?/$")
- url = pyfile.url
- if framePattern.match(url):
- self.packages.append((pyfile.package().name, [self.handleFrame(url)], pyfile.package().name))
- elif episodePattern.match(url):
- self.handleEpisode(url)
- elif oldStyleLink.match(url):
- self.handleOldStyleLink(url)
- elif showPattern.match(url):
- self.handleShow(url)
- elif showPatternDJ.match(url):
- self.handleShowDJ(url)
- elif seasonPattern.match(url):
- self.handleSeason(url)
- elif categoryPatternDJ.match(url):
- self.handleCategoryDJ(url)
-
- #selects the preferred hoster, after that selects any hoster (ignoring the one to ignore)
- def getpreferred(self, hosterlist):
-
- result = []
- preferredList = self.getConfig("hosterList").strip().lower().replace(
- '|', ',').replace('.', '').replace(';', ',').split(',')
- if (self.getConfig("randomPreferred") is True) and (
- self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]):
- random.shuffle(preferredList)
- # we don't want hosters be read two times
- hosterlist2 = hosterlist.copy()
-
- for preferred in preferredList:
- for Hoster in hosterlist:
- if preferred == Hoster.lower().replace('.', ''):
- for Part in hosterlist[Hoster]:
- self.logDebug("selected " + Part)
- result.append(str(Part))
- del (hosterlist2[Hoster])
- if self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]:
- return result
-
- ignorelist = self.getConfig("ignoreList").strip().lower().replace(
- '|', ',').replace('.', '').replace(';', ',').split(',')
- if self.getConfig('hosterListMode') in ["OnlyOne", "All"]:
- for Hoster in hosterlist2:
- if Hoster.strip().lower().replace('.', '') not in ignorelist:
- for Part in hosterlist2[Hoster]:
- self.logDebug("selected2 " + Part)
- result.append(str(Part))
-
- if self.getConfig('hosterListMode') == "OnlyOne":
- return result
- return result
diff --git a/module/plugins/crypter/ShareLinksBiz.py b/module/plugins/crypter/ShareLinksBiz.py
deleted file mode 100644
index 87bb16a50..000000000
--- a/module/plugins/crypter/ShareLinksBiz.py
+++ /dev/null
@@ -1,269 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import base64
-import binascii
-import re
-
-from Crypto.Cipher import AES
-from module.plugins.Crypter import Crypter
-
-
-class ShareLinksBiz(Crypter):
- __name__ = "ShareLinksBiz"
- __type__ = "crypter"
- __version__ = "1.13"
-
- __pattern__ = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
-
- __description__ = """Share-Links.biz decrypter plugin"""
- __author_name__ = "fragonib"
- __author_mail__ = "fragonib[AT]yahoo[DOT]es"
-
-
- def setup(self):
- self.baseUrl = None
- self.fileId = None
- self.package = None
- self.html = None
- self.captcha = False
-
- def decrypt(self, pyfile):
- # Init
- self.initFile(pyfile)
-
- # Request package
- url = self.baseUrl + '/' + self.fileId
- self.html = self.load(url, decode=True)
-
- # Unblock server (load all images)
- self.unblockServer()
-
- # Check for protection
- if self.isPasswordProtected():
- self.unlockPasswordProtection()
- self.handleErrors()
-
- if self.isCaptchaProtected():
- self.captcha = True
- self.unlockCaptchaProtection()
- self.handleErrors()
-
- # Extract package links
- package_links = []
- package_links.extend(self.handleWebLinks())
- package_links.extend(self.handleContainers())
- package_links.extend(self.handleCNL2())
- package_links = set(package_links)
-
- # Get package info
- package_name, package_folder = self.getPackageInfo()
-
- # Pack
- self.packages = [(package_name, package_links, package_folder)]
-
- def initFile(self, pyfile):
- url = pyfile.url
- if 's2l.biz' in url:
- url = self.load(url, just_header=True)['location']
- self.baseUrl = "http://www.%s.biz" % re.match(self.__pattern__, url).group(1)
- self.fileId = re.match(self.__pattern__, url).group('ID')
- self.package = pyfile.package()
-
- def isOnline(self):
- if "No usable content was found" in self.html:
- self.logDebug("File not found")
- return False
- return True
-
- def isPasswordProtected(self):
- if re.search(r'''<form.*?id="passwordForm".*?>''', self.html):
- self.logDebug("Links are protected")
- return True
- return False
-
- def isCaptchaProtected(self):
- if '<map id="captchamap"' in self.html:
- self.logDebug("Links are captcha protected")
- return True
- return False
-
- def unblockServer(self):
- imgs = re.findall(r"(/template/images/.*?\.gif)", self.html)
- for img in imgs:
- self.load(self.baseUrl + img)
-
- def unlockPasswordProtection(self):
- password = self.getPassword()
- self.logDebug("Submitting password [%s] for protected links" % password)
- post = {"password": password, 'login': 'Submit form'}
- url = self.baseUrl + '/' + self.fileId
- self.html = self.load(url, post=post, decode=True)
-
- def unlockCaptchaProtection(self):
- # Get captcha map
- captchaMap = self._getCaptchaMap()
- self.logDebug("Captcha map with [%d] positions" % len(captchaMap.keys()))
-
- # Request user for captcha coords
- m = re.search(r'<img src="/captcha.gif\?d=(.*?)&amp;PHPSESSID=(.*?)&amp;legend=1"', self.html)
- captchaUrl = self.baseUrl + '/captcha.gif?d=%s&PHPSESSID=%s' % (m.group(1), m.group(2))
- self.logDebug("Waiting user for correct position")
- coords = self.decryptCaptcha(captchaUrl, forceUser=True, imgtype="gif", result_type='positional')
- self.logDebug("Captcha resolved, coords [%s]" % str(coords))
-
- # Resolve captcha
- href = self._resolveCoords(coords, captchaMap)
- if href is None:
- self.logDebug("Invalid captcha resolving, retrying")
- self.invalidCaptcha()
- self.setWait(5, False)
- self.wait()
- self.retry()
- url = self.baseUrl + href
- self.html = self.load(url, decode=True)
-
- def _getCaptchaMap(self):
- mapp = {}
- for m in re.finditer(r'<area shape="rect" coords="(.*?)" href="(.*?)"', self.html):
- rect = eval('(' + m.group(1) + ')')
- href = m.group(2)
- mapp[rect] = href
- return mapp
-
- def _resolveCoords(self, coords, captchaMap):
- x, y = coords
- for rect, href in captchaMap.items():
- x1, y1, x2, y2 = rect
- if (x >= x1 and x <= x2) and (y >= y1 and y <= y2):
- return href
-
- def handleErrors(self):
- if "The inserted password was wrong" in self.html:
- self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
- self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
-
- if self.captcha:
- if "Your choice was wrong" in self.html:
- self.logDebug("Invalid captcha, retrying")
- self.invalidCaptcha()
- self.setWait(5)
- self.wait()
- self.retry()
- else:
- self.correctCaptcha()
-
- def getPackageInfo(self):
- name = folder = None
-
- # Extract from web package header
- title_re = r'<h2><img.*?/>(.*)</h2>'
- m = re.search(title_re, self.html, re.DOTALL)
- if m is not None:
- title = m.group(1).strip()
- if 'unnamed' not in title:
- name = folder = title
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
-
- # Fallback to defaults
- if not name or not folder:
- name = self.package.name
- folder = self.package.folder
- self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
-
- # Return package info
- return name, folder
-
- def handleWebLinks(self):
- package_links = []
- self.logDebug("Handling Web links")
-
- #@TODO: Gather paginated web links
- pattern = r"javascript:_get\('(.*?)', \d+, ''\)"
- ids = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Web links" % len(ids))
- for i, ID in enumerate(ids):
- try:
- self.logDebug("Decrypting Web link %d, [%s]" % (i + 1, ID))
- dwLink = self.baseUrl + "/get/lnk/" + ID
- response = self.load(dwLink)
- code = re.search(r'frm/(\d+)', response).group(1)
- fwLink = self.baseUrl + "/get/frm/" + code
- response = self.load(fwLink)
- jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', response,
- re.DOTALL).group(1)
- jscode = self.js.eval("f = %s" % jscode)
- jslauncher = "window=''; parent={frames:{Main:{location:{href:''}}},location:''}; %s; parent.frames.Main.location.href"
- dlLink = self.js.eval(jslauncher % jscode)
- self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
- package_links.append(dlLink)
- except Exception, detail:
- self.logDebug("Error decrypting Web link [%s], %s" % (ID, detail))
- return package_links
-
- def handleContainers(self):
- package_links = []
- self.logDebug("Handling Container links")
-
- pattern = r"javascript:_get\('(.*?)', 0, '(rsdf|ccf|dlc)'\)"
- containersLinks = re.findall(pattern, self.html)
- self.logDebug("Decrypting %d Container links" % len(containersLinks))
- for containerLink in containersLinks:
- link = "%s/get/%s/%s" % (self.baseUrl, containerLink[1], containerLink[0])
- package_links.append(link)
- return package_links
-
- def handleCNL2(self):
- package_links = []
- self.logDebug("Handling CNL2 links")
-
- if '/lib/cnl2/ClicknLoad.swf' in self.html:
- try:
- (crypted, jk) = self._getCipherParams()
- package_links.extend(self._getLinks(crypted, jk))
- except:
- self.fail("Unable to decrypt CNL2 links")
- return package_links
-
- def _getCipherParams(self):
- # Request CNL2
- code = re.search(r'ClicknLoad.swf\?code=(.*?)"', self.html).group(1)
- url = "%s/get/cnl2/%s" % (self.baseUrl, code)
- response = self.load(url)
- params = response.split(";;")
-
- # Get jk
- strlist = list(base64.standard_b64decode(params[1]))
- strlist.reverse()
- jk = ''.join(strlist)
-
- # Get crypted
- strlist = list(base64.standard_b64decode(params[2]))
- strlist.reverse()
- crypted = ''.join(strlist)
-
- # Log and return
- return crypted, jk
-
- def _getLinks(self, crypted, jk):
- # Get key
- jreturn = self.js.eval("%s f()" % jk)
- self.logDebug("JsEngine returns value [%s]" % jreturn)
- key = binascii.unhexlify(jreturn)
-
- # Decode crypted
- crypted = base64.standard_b64decode(crypted)
-
- # Decrypt
- Key = key
- IV = key
- obj = AES.new(Key, AES.MODE_CBC, IV)
- text = obj.decrypt(crypted)
-
- # Extract links
- text = text.replace("\x00", "").replace("\r", "")
- links = text.split("\n")
- links = filter(lambda x: x != "", links)
-
- # Log and return
- self.logDebug("Block has %d links" % len(links))
- return links
diff --git a/module/plugins/crypter/ShareRapidComFolder.py b/module/plugins/crypter/ShareRapidComFolder.py
deleted file mode 100644
index fd12ff96f..000000000
--- a/module/plugins/crypter/ShareRapidComFolder.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class ShareRapidComFolder(SimpleCrypter):
- __name__ = "ShareRapidComFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?((share(-?rapid\.(biz|com|cz|info|eu|net|org|pl|sk)|-(central|credit|free|net)\.cz|-ms\.net)|(s-?rapid|rapids)\.(cz|sk))|(e-stahuj|mediatack|premium-rapidshare|rapidshare-premium|qiuck)\.cz|kadzet\.com|stahuj-zdarma\.eu|strelci\.net|universal-share\.com)/(slozka/.+)'
-
- __description__ = """Share-Rapid.com folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'<td class="soubor"[^>]*><a href="([^"]+)">'
diff --git a/module/plugins/crypter/SpeedLoadOrgFolder.py b/module/plugins/crypter/SpeedLoadOrgFolder.py
deleted file mode 100644
index 092d3efe4..000000000
--- a/module/plugins/crypter/SpeedLoadOrgFolder.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class SpeedLoadOrgFolder(DeadCrypter):
- __name__ = "SpeedLoadOrgFolder"
- __type__ = "crypter"
- __version__ = "0.3"
-
- __pattern__ = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
-
- __description__ = """Speedload decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
diff --git a/module/plugins/crypter/StealthTo.py b/module/plugins/crypter/StealthTo.py
deleted file mode 100644
index 402724e89..000000000
--- a/module/plugins/crypter/StealthTo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class StealthTo(DeadCrypter):
- __name__ = "StealthTo"
- __type__ = "crypter"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?stealth\.to/folder/.+'
-
- __description__ = """Stealth.to decrypter plugin"""
- __author_name__ = "spoob"
- __author_mail__ = "spoob@pyload.org"
diff --git a/module/plugins/crypter/TnyCz.py b/module/plugins/crypter/TnyCz.py
deleted file mode 100644
index 3ae2c5ed2..000000000
--- a/module/plugins/crypter/TnyCz.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-import re
-
-
-class TnyCz(SimpleCrypter):
- __name__ = "TnyCz"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?tny\.cz/\w+'
-
- __description__ = """Tny.cz decrypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- TITLE_PATTERN = r'<title>(?P<title>.+) - .+</title>'
-
-
- def getLinks(self):
- m = re.search(r'<a id=\'save_paste\' href="(.+save\.php\?hash=.+)">', self.html)
- return re.findall(".+", self.load(m.group(1), decode=True)) if m else None
diff --git a/module/plugins/crypter/TrailerzoneInfo.py b/module/plugins/crypter/TrailerzoneInfo.py
deleted file mode 100644
index 42e45a819..000000000
--- a/module/plugins/crypter/TrailerzoneInfo.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class TrailerzoneInfo(DeadCrypter):
- __name__ = "TrailerzoneInfo"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?trailerzone.info/.*?'
-
- __description__ = """TrailerZone.info decrypter plugin"""
- __author_name__ = "godofdream"
- __author_mail__ = "soilfiction@gmail.com"
diff --git a/module/plugins/crypter/TurbobitNetFolder.py b/module/plugins/crypter/TurbobitNetFolder.py
deleted file mode 100644
index 0810e0ed1..000000000
--- a/module/plugins/crypter/TurbobitNetFolder.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-from module.common.json_layer import json_loads
-
-
-class TurbobitNetFolder(SimpleCrypter):
- __name__ = "TurbobitNetFolder"
- __type__ = "crypter"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
-
- __description__ = """Turbobit.net folder decrypter plugin"""
- __author_name__ = ("stickell", "Walter Purcaro")
- __author_mail__ = ("l.stickell@yahoo.it", "vuolter@gmail.com")
-
- TITLE_PATTERN = r"src='/js/lib/grid/icon/folder.png'> <span>(?P<title>.+?)</span>"
-
-
- def _getLinks(self, id, page=1):
- gridFile = self.load("http://turbobit.net/downloadfolder/gridFile",
- get={"rootId": id, "rows": 200, "page": page}, decode=True)
- grid = json_loads(gridFile)
-
- if grid['rows']:
- for i in grid['rows']:
- yield i['id']
- for id in self._getLinks(id, page + 1):
- yield id
- else:
- return
-
- def getLinks(self):
- id = re.match(self.__pattern__, self.pyfile.url).group("ID")
- fixurl = lambda id: "http://turbobit.net/%s.html" % id
- return map(fixurl, self._getLinks(id))
diff --git a/module/plugins/crypter/TusfilesNetFolder.py b/module/plugins/crypter/TusfilesNetFolder.py
deleted file mode 100644
index f51c2b715..000000000
--- a/module/plugins/crypter/TusfilesNetFolder.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import math
-import re
-from urlparse import urljoin
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class TusfilesNetFolder(SimpleCrypter):
- __name__ = "TusfilesNetFolder"
- __type__ = "crypter"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)/?'
-
- __description__ = """Tusfiles.net folder decrypter plugin"""
- __author_name__ = ("Walter Purcaro", "stickell")
- __author_mail__ = ("vuolter@gmail.com", "l.stickell@yahoo.it")
-
- LINK_PATTERN = r'<TD align=left><a href="(.*?)">'
- TITLE_PATTERN = r'<Title>.*?\: (?P<title>.+) folder</Title>'
- PAGES_PATTERN = r'>\((?P<pages>\d+) \w+\)<'
-
- URL_REPLACEMENTS = [(__pattern__, r'https://www.tusfiles.net/go/\g<ID>/')]
-
-
- def loadPage(self, page_n):
- return self.load(urljoin(self.pyfile.url, str(page_n)), decode=True)
-
- def handleMultiPages(self):
- pages = re.search(self.PAGES_PATTERN, self.html)
- if pages:
- pages = int(math.ceil(int(pages.group('pages')) / 25.0))
- else:
- return
-
- for p in xrange(2, pages + 1):
- self.html = self.loadPage(p)
- self.package_links += self.getLinks()
diff --git a/module/plugins/crypter/UlozToFolder.py b/module/plugins/crypter/UlozToFolder.py
deleted file mode 100644
index 06d30099c..000000000
--- a/module/plugins/crypter/UlozToFolder.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-from module.plugins.Crypter import Crypter
-
-
-class UlozToFolder(Crypter):
- __name__ = "UlozToFolder"
- __type__ = "crypter"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj.cz|zachowajto.pl)/(m|soubory)/.*'
-
- __description__ = """Uloz.to folder decrypter plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FOLDER_PATTERN = r'<ul class="profile_files">(.*?)</ul>'
- LINK_PATTERN = r'<br /><a href="/([^"]+)">[^<]+</a>'
- NEXT_PAGE_PATTERN = r'<a class="next " href="/([^"]+)">&nbsp;</a>'
-
-
- def decrypt(self, pyfile):
- html = self.load(pyfile.url)
-
- new_links = []
- for i in xrange(1, 100):
- self.logInfo("Fetching links from page %i" % i)
- m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
- if m is None:
- self.fail("Parse error (FOLDER)")
-
- new_links.extend(re.findall(self.LINK_PATTERN, m.group(1)))
- m = re.search(self.NEXT_PAGE_PATTERN, html)
- if m:
- html = self.load("http://ulozto.net/" + m.group(1))
- else:
- break
- else:
- self.logInfo("Limit of 99 pages reached, aborting")
-
- if new_links:
- self.urls = [map(lambda s: "http://ulozto.net/%s" % s, new_links)]
- else:
- self.fail('Could not extract any links')
diff --git a/module/plugins/crypter/UploadableChFolder.py b/module/plugins/crypter/UploadableChFolder.py
deleted file mode 100644
index b76076531..000000000
--- a/module/plugins/crypter/UploadableChFolder.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class UploadableChFolder(SimpleCrypter):
- __name__ = "UploadableChFolder"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?uploadable\.ch/list/\w+'
-
- __description__ = """ Uploadable.ch folder decrypter plugin """
- __author_name__ = ("guidobelix", "Walter Purcaro")
- __author_mail__ = ("guidobelix@hotmail.it", "vuolter@gmail.com")
-
-
- LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
- TITLE_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<title>.+?)</div>'
- OFFLINE_PATTERN = r'We are sorry... The URL you entered cannot be found on the server.'
- TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
diff --git a/module/plugins/crypter/UploadedToFolder.py b/module/plugins/crypter/UploadedToFolder.py
deleted file mode 100644
index 068412062..000000000
--- a/module/plugins/crypter/UploadedToFolder.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleCrypter import SimpleCrypter
-
-
-class UploadedToFolder(SimpleCrypter):
- __name__ = "UploadedToFolder"
- __type__ = "crypter"
- __version__ = "0.3"
-
- __pattern__ = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<id>\w+)'
-
- __description__ = """UploadedTo decrypter plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- PLAIN_PATTERN = r'<small class="date"><a href="(?P<plain>[\w/]+)" onclick='
- TITLE_PATTERN = r'<title>(?P<title>[^<]+)</title>'
-
-
- def decrypt(self, pyfile):
- self.html = self.load(pyfile.url)
-
- package_name, folder_name = self.getPackageNameAndFolder()
-
- m = re.search(self.PLAIN_PATTERN, self.html)
- if m:
- plain_link = 'http://uploaded.net/' + m.group('plain')
- else:
- self.fail('Parse error - Unable to find plain url list')
-
- self.html = self.load(plain_link)
- package_links = self.html.split('\n')[:-1]
- self.logDebug('Package has %d links' % len(package_links))
-
- self.packages = [(package_name, package_links, folder_name)]
diff --git a/module/plugins/crypter/WiiReloadedOrg.py b/module/plugins/crypter/WiiReloadedOrg.py
deleted file mode 100644
index cb02fbd2d..000000000
--- a/module/plugins/crypter/WiiReloadedOrg.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadCrypter import DeadCrypter
-
-
-class WiiReloadedOrg(DeadCrypter):
- __name__ = "WiiReloadedOrg"
- __type__ = "crypter"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+'
-
- __description__ = """Wii-Reloaded.org decrypter plugin"""
- __author_name__ = "hzpz"
- __author_mail__ = None
diff --git a/module/plugins/crypter/XupPl.py b/module/plugins/crypter/XupPl.py
deleted file mode 100644
index 5e5f511a8..000000000
--- a/module/plugins/crypter/XupPl.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter
-
-
-class XupPl(Crypter):
- __name__ = "XupPl"
- __type__ = "crypter"
- __version__ = "0.1"
-
- __pattern__ = r'https?://(?:[^/]*\.)?xup\.pl/.*'
-
- __description__ = """Xup.pl decrypter plugin"""
- __author_name__ = "z00nx"
- __author_mail__ = "z00nx0@gmail.com"
-
-
- def decrypt(self, pyfile):
- header = self.load(pyfile.url, just_header=True)
- if 'location' in header:
- self.urls = [header['location']]
- else:
- self.fail('Unable to find link')
diff --git a/module/plugins/crypter/YoutubeBatch.py b/module/plugins/crypter/YoutubeBatch.py
deleted file mode 100644
index 1caca16bd..000000000
--- a/module/plugins/crypter/YoutubeBatch.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urlparse import urljoin
-
-from module.common.json_layer import json_loads
-from module.plugins.Crypter import Crypter
-from module.utils import save_join
-
-API_URL = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
-
-
-class YoutubeBatch(Crypter):
- __name__ = "YoutubeBatch"
- __type__ = "crypter"
- __version__ = "1.00"
-
- __pattern__ = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
- __config__ = [("likes", "bool", "Grab user (channel) liked videos", False),
- ("favorites", "bool", "Grab user (channel) favorite videos", False),
- ("uploads", "bool", "Grab channel unplaylisted videos", True)]
-
- __description__ = """Youtube.com channel & playlist decrypter plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
- def api_response(self, ref, req):
- req.update({"key": API_KEY})
- url = urljoin("https://www.googleapis.com/youtube/v3/", ref)
- page = self.load(url, get=req)
- return json_loads(page)
-
- def getChannel(self, user):
- channels = self.api_response("channels", {"part": "id,snippet,contentDetails", "forUsername": user, "maxResults": "50"})
- if channels['items']:
- channel = channels['items'][0]
- return {"id": channel['id'],
- "title": channel['snippet']['title'],
- "relatedPlaylists": channel['contentDetails']['relatedPlaylists'],
- "user": user} # One lone channel for user?
-
- def getPlaylist(self, p_id):
- playlists = self.api_response("playlists", {"part": "snippet", "id": p_id})
- if playlists['items']:
- playlist = playlists['items'][0]
- return {"id": p_id,
- "title": playlist['snippet']['title'],
- "channelId": playlist['snippet']['channelId'],
- "channelTitle": playlist['snippet']['channelTitle']}
-
- def _getPlaylists(self, id, token=None):
- req = {"part": "id", "maxResults": "50", "channelId": id}
- if token:
- req.update({"pageToken": token})
-
- playlists = self.api_response("playlists", req)
-
- for playlist in playlists['items']:
- yield playlist['id']
-
- if "nextPageToken" in playlists:
- for item in self._getPlaylists(id, playlists['nextPageToken']):
- yield item
-
- def getPlaylists(self, ch_id):
- return map(self.getPlaylist, self._getPlaylists(ch_id))
-
- def _getVideosId(self, id, token=None):
- req = {"part": "contentDetails", "maxResults": "50", "playlistId": id}
- if token:
- req.update({"pageToken": token})
-
- playlist = self.api_response("playlistItems", req)
-
- for item in playlist['items']:
- yield item['contentDetails']['videoId']
-
- if "nextPageToken" in playlist:
- for item in self._getVideosId(id, playlist['nextPageToken']):
- yield item
-
- def getVideosId(self, p_id):
- return list(self._getVideosId(p_id))
-
- def decrypt(self, pyfile):
- m = re.match(self.__pattern__, pyfile.url)
- m_id = m.group("ID")
- m_type = m.group("TYPE")
-
- if m_type == "user":
- self.logDebug("Url recognized as Channel")
- user = m_id
- channel = self.getChannel(user)
-
- if channel:
- playlists = self.getPlaylists(channel['id'])
- self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), channel['title']))
-
- relatedplaylist = {p_name: self.getPlaylist(p_id) for p_name, p_id in channel['relatedPlaylists'].iteritems()}
- self.logDebug("Channel's related playlists found = %s" % relatedplaylist.keys())
-
- relatedplaylist['uploads']['title'] = "Unplaylisted videos"
- relatedplaylist['uploads']['checkDups'] = True #: checkDups flag
-
- for p_name, p_data in relatedplaylist.iteritems():
- if self.getConfig(p_name):
- p_data['title'] += " of " + user
- playlists.append(p_data)
- else:
- playlists = []
- else:
- self.logDebug("Url recognized as Playlist")
- playlists = [self.getPlaylist(m_id)]
-
- if not playlists:
- self.fail("No playlist available")
-
- addedvideos = []
- urlize = lambda x: "https://www.youtube.com/watch?v=" + x
- for p in playlists:
- p_name = p['title']
- p_videos = self.getVideosId(p['id'])
- p_folder = save_join(self.config['general']['download_folder'], p['channelTitle'], p_name)
- self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
-
- if not p_videos:
- continue
- elif "checkDups" in p:
- p_urls = [urlize(v_id) for v_id in p_videos if v_id not in addedvideos]
- self.logDebug("%s video\s available on playlist \"%s\" after duplicates cleanup" % (len(p_urls), p_name))
- else:
- p_urls = map(urlize, p_videos)
-
- self.packages.append((p_name, p_urls, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
-
- addedvideos.extend(p_videos)
diff --git a/module/plugins/hooks/AlldebridCom.py b/module/plugins/hooks/AlldebridCom.py
deleted file mode 100644
index 906875a69..000000000
--- a/module/plugins/hooks/AlldebridCom.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class AlldebridCom(MultiHoster):
- __name__ = "AlldebridCom"
- __type__ = "hook"
- __version__ = "0.13"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("https", "bool", "Enable HTTPS", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Alldebrid.com hook plugin"""
- __author_name__ = "Andy Voigt"
- __author_mail__ = "spamsales@online.de"
-
-
- def getHoster(self):
- https = "https" if self.getConfig("https") else "http"
- page = getURL(https + "://www.alldebrid.com/api.php?action=get_host").replace("\"", "").strip()
-
- return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/module/plugins/hooks/BypassCaptcha.py b/module/plugins/hooks/BypassCaptcha.py
deleted file mode 100644
index d62de24a7..000000000
--- a/module/plugins/hooks/BypassCaptcha.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from pycurl import FORM_FILE, LOW_SPEED_TIME
-from thread import start_new_thread
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook
-
-
-class BypassCaptchaException(Exception):
-
- def __init__(self, err):
- self.err = err
-
- def getCode(self):
- return self.err
-
- def __str__(self):
- return "<BypassCaptchaException %s>" % self.err
-
- def __repr__(self):
- return "<BypassCaptchaException %s>" % self.err
-
-
-class BypassCaptcha(Hook):
- __name__ = "BypassCaptcha"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("force", "bool", "Force BC even if client is connected", False),
- ("passkey", "password", "Passkey", "")]
-
- __description__ = """Send captchas to BypassCaptcha.com"""
- __author_name__ = ("RaNaN", "Godofdream", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "soilfcition@gmail.com", "zoidberg@mujmail.cz")
-
- PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32"
-
- SUBMIT_URL = "http://bypasscaptcha.com/upload.php"
- RESPOND_URL = "http://bypasscaptcha.com/check_value.php"
- GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php"
-
-
- def setup(self):
- self.info = {}
-
- def getCredits(self):
- response = getURL(self.GETCREDITS_URL, post={"key": self.getConfig("passkey")})
-
- data = dict([x.split(' ', 1) for x in response.splitlines()])
- return int(data['Left'])
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
-
- #raise timeout threshold
- req.c.setopt(LOW_SPEED_TIME, 80)
-
- try:
- response = req.load(self.SUBMIT_URL,
- post={"vendor_key": self.PYLOAD_KEY,
- "key": self.getConfig("passkey"),
- "gen_task_id": "1",
- "file": (FORM_FILE, captcha)},
- multipart=True)
- finally:
- req.close()
-
- data = dict([x.split(' ', 1) for x in response.splitlines()])
- if not data or "Value" not in data:
- raise BypassCaptchaException(response)
-
- result = data['Value']
- ticket = data['TaskId']
- self.logDebug("result %s : %s" % (ticket, result))
-
- return ticket, result
-
- def respond(self, ticket, success):
- try:
- response = getURL(self.RESPOND_URL, post={"task_id": ticket, "key": self.getConfig("passkey"),
- "cv": 1 if success else 0})
- except BadHeader, e:
- self.logError("Could not send response.", str(e))
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- start_new_thread(self.processCaptcha, (task,))
-
- else:
- self.logInfo("Your %s account has not enough credits" % self.__name__)
-
- def captchaCorrect(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- self.respond(task.data['ticket'], True)
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- self.respond(task.data['ticket'], False)
-
- def processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except BypassCaptchaException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/Captcha9kw.py b/module/plugins/hooks/Captcha9kw.py
deleted file mode 100755
index 1b7406edd..000000000
--- a/module/plugins/hooks/Captcha9kw.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import time
-
-from base64 import b64encode
-from thread import start_new_thread
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook
-
-
-class Captcha9kw(Hook):
- __name__ = "Captcha9kw"
- __type__ = "hook"
- __version__ = "0.09"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("force", "bool", "Force CT even if client is connected", True),
- ("https", "bool", "Enable HTTPS", False),
- ("confirm", "bool", "Confirm Captcha (Cost +6)", False),
- ("captchaperhour", "int", "Captcha per hour (max. 9999)", 9999),
- ("prio", "int", "Prio 1-10 (Cost +1-10)", 0),
- ("selfsolve", "bool",
- "If enabled and you have a 9kw client active only you will get your captcha to solve it (Selfsolve)",
- False),
- ("timeout", "int", "Timeout (max. 300)", 300),
- ("passkey", "password", "API key", "")]
-
- __description__ = """Send captchas to 9kw.eu"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
- API_URL = "://www.9kw.eu/index.cgi"
-
-
- def setup(self):
- self.API_URL = "https" + self.API_URL if self.getConfig("https") else "http" + self.API_URL
- self.info = {}
-
- def getCredits(self):
- response = getURL(self.API_URL, get={"apikey": self.getConfig("passkey"), "pyload": "1", "source": "pyload",
- "action": "usercaptchaguthaben"})
-
- if response.isdigit():
- self.logInfo(_("%s credits left") % response)
- self.info['credits'] = credits = int(response)
- return credits
- else:
- self.logError(response)
- return 0
-
- def processCaptcha(self, task):
- result = None
-
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
- data = b64encode(data)
- self.logDebug("%s : %s" % (task.captchaFile, data))
- if task.isPositional():
- mouse = 1
- else:
- mouse = 0
-
- response = getURL(self.API_URL, post={
- "apikey": self.getConfig("passkey"),
- "prio": self.getConfig("prio"),
- "confirm": self.getConfig("confirm"),
- "captchaperhour": self.getConfig("captchaperhour"),
- "maxtimeout": self.getConfig("timeout"),
- "selfsolve": self.getConfig("selfsolve"),
- "pyload": "1",
- "source": "pyload",
- "base64": "1",
- "mouse": mouse,
- "file-upload-01": data,
- "action": "usercaptchaupload"})
-
- if response.isdigit():
- self.logInfo(_("New CaptchaID from upload: %s : %s") % (response, task.captchaFile))
-
- for _ in xrange(1, 100, 1):
- response2 = getURL(self.API_URL, get={"apikey": self.getConfig("passkey"), "id": response,
- "pyload": "1", "source": "pyload",
- "action": "usercaptchacorrectdata"})
-
- if response2 != "":
- break
-
- time.sleep(3)
-
- result = response2
- task.data['ticket'] = response
- self.logInfo("result %s : %s" % (response, result))
- task.setResult(result)
- else:
- self.logError("Bad upload: %s" % response)
- return False
-
- def newCaptchaTask(self, task):
- if not task.isTextual() and not task.isPositional():
- return False
-
- if not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.setWaiting(self.getConfig("timeout"))
- start_new_thread(self.processCaptcha, (task,))
-
- else:
- self.logError(_("Your Captcha 9kw.eu Account has not enough credits"))
-
- def captchaCorrect(self, task):
- if "ticket" in task.data:
-
- try:
- response = getURL(self.API_URL,
- post={"action": "usercaptchacorrectback",
- "apikey": self.getConfig("passkey"),
- "api_key": self.getConfig("passkey"),
- "correct": "1",
- "pyload": "1",
- "source": "pyload",
- "id": task.data['ticket']})
- self.logInfo("Request correct: %s" % response)
-
- except BadHeader, e:
- self.logError("Could not send correct request.", str(e))
- else:
- self.logError("No CaptchaID for correct request (task %s) found." % task)
-
- def captchaInvalid(self, task):
- if "ticket" in task.data:
-
- try:
- response = getURL(self.API_URL,
- post={"action": "usercaptchacorrectback",
- "apikey": self.getConfig("passkey"),
- "api_key": self.getConfig("passkey"),
- "correct": "2",
- "pyload": "1",
- "source": "pyload",
- "id": task.data['ticket']})
- self.logInfo("Request refund: %s" % response)
-
- except BadHeader, e:
- self.logError("Could not send refund request.", str(e))
- else:
- self.logError("No CaptchaID for not correct request (task %s) found." % task)
diff --git a/module/plugins/hooks/CaptchaBrotherhood.py b/module/plugins/hooks/CaptchaBrotherhood.py
deleted file mode 100644
index e240cbbc0..000000000
--- a/module/plugins/hooks/CaptchaBrotherhood.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import StringIO
-import pycurl
-
-from PIL import Image
-from thread import start_new_thread
-from time import sleep
-from urllib import urlencode
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook
-
-
-class CaptchaBrotherhoodException(Exception):
-
- def __init__(self, err):
- self.err = err
-
- def getCode(self):
- return self.err
-
- def __str__(self):
- return "<CaptchaBrotherhoodException %s>" % self.err
-
- def __repr__(self):
- return "<CaptchaBrotherhoodException %s>" % self.err
-
-
-class CaptchaBrotherhood(Hook):
- __name__ = "CaptchaBrotherhood"
- __type__ = "hook"
- __version__ = "0.05"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("username", "str", "Username", ""),
- ("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Password", "")]
-
- __description__ = """Send captchas to CaptchaBrotherhood.com"""
- __author_name__ = ("RaNaN", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
-
- API_URL = "http://www.captchabrotherhood.com/"
-
-
- def setup(self):
- self.info = {}
-
- def getCredits(self):
- response = getURL(self.API_URL + "askCredits.aspx",
- get={"username": self.getConfig("username"), "password": self.getConfig("passkey")})
- if not response.startswith("OK"):
- raise CaptchaBrotherhoodException(response)
- else:
- credits = int(response[3:])
- self.logInfo(_("%d credits left") % credits)
- self.info['credits'] = credits
- return credits
-
- def submit(self, captcha, captchaType="file", match=None):
- try:
- img = Image.open(captcha)
- output = StringIO.StringIO()
- self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode)
- if img.format in ("GIF", "JPEG"):
- img.save(output, img.format)
- else:
- if img.mode != "RGB":
- img = img.convert("RGB")
- img.save(output, "JPEG")
- data = output.getvalue()
- output.close()
- except Exception, e:
- raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e)
-
- req = getRequest()
-
- url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL,
- urlencode({"username": self.getConfig("username"),
- "password": self.getConfig("passkey"),
- "captchaSource": "pyLoad",
- "timeout": "80"}))
-
- req.c.setopt(pycurl.URL, url)
- req.c.setopt(pycurl.POST, 1)
- req.c.setopt(pycurl.POSTFIELDS, data)
- req.c.setopt(pycurl.HTTPHEADER, ["Content-Type: text/html"])
-
- try:
- req.c.perform()
- response = req.getResponse()
- except Exception, e:
- raise CaptchaBrotherhoodException("Submit captcha image failed")
-
- req.close()
-
- if not response.startswith("OK"):
- raise CaptchaBrotherhoodException(response[1])
-
- ticket = response[3:]
-
- for _ in xrange(15):
- sleep(5)
- response = self.get_api("askCaptchaResult", ticket)
- if response.startswith("OK-answered"):
- return ticket, response[12:]
-
- raise CaptchaBrotherhoodException("No solution received in time")
-
- def get_api(self, api, ticket):
- response = getURL("%s%s.aspx" % (self.API_URL, api),
- get={"username": self.getConfig("username"),
- "password": self.getConfig("passkey"),
- "captchaID": ticket})
- if not response.startswith("OK"):
- raise CaptchaBrotherhoodException("Unknown response: %s" % response)
-
- return response
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig("username") or not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if self.getCredits() > 10:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- start_new_thread(self.processCaptcha, (task,))
- else:
- self.logInfo("Your CaptchaBrotherhood Account has not enough credits")
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- response = self.get_api("complainCaptcha", task.data['ticket'])
-
- def processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except CaptchaBrotherhoodException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/Checksum.py b/module/plugins/hooks/Checksum.py
deleted file mode 100644
index 013172899..000000000
--- a/module/plugins/hooks/Checksum.py
+++ /dev/null
@@ -1,175 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import hashlib
-import re
-import zlib
-
-from os import remove
-from os.path import getsize, isfile, splitext
-
-from module.plugins.Hook import Hook
-from module.utils import save_join, fs_encode
-
-
-def computeChecksum(local_file, algorithm):
- if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")):
- h = getattr(hashlib, algorithm)()
-
- with open(local_file, 'rb') as f:
- for chunk in iter(lambda: f.read(128 * h.block_size), b''):
- h.update(chunk)
-
- return h.hexdigest()
-
- elif algorithm in ("adler32", "crc32"):
- hf = getattr(zlib, algorithm)
- last = 0
-
- with open(local_file, 'rb') as f:
- for chunk in iter(lambda: f.read(8192), b''):
- last = hf(chunk, last)
-
- return "%x" % last
-
- else:
- return None
-
-
-class Checksum(Hook):
- __name__ = "Checksum"
- __type__ = "hook"
- __version__ = "0.13"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("check_checksum", "bool", "Check checksum? (If False only size will be verified)", True),
- ("check_action", "fail;retry;nothing", "What to do if check fails?", "retry"),
- ("max_tries", "int", "Number of retries", 2),
- ("retry_action", "fail;nothing", "What to do if all retries fail?", "fail"),
- ("wait_time", "int", "Time to wait before each retry (seconds)", 1)]
-
- __description__ = """Verify downloaded file size and checksum"""
- __author_name__ = ("zoidberg", "Walter Purcaro", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "vuolter@gmail.com", "l.stickell@yahoo.it")
-
- methods = {'sfv': 'crc32', 'crc': 'crc32', 'hash': 'md5'}
- regexps = {'sfv': r'^(?P<name>[^;].+)\s+(?P<hash>[0-9A-Fa-f]{8})$',
- 'md5': r'^(?P<name>[0-9A-Fa-f]{32}) (?P<file>.+)$',
- 'crc': r'filename=(?P<name>.+)\nsize=(?P<size>\d+)\ncrc32=(?P<hash>[0-9A-Fa-f]{8})$',
- 'default': r'^(?P<hash>[0-9A-Fa-f]+)\s+\*?(?P<name>.+)$'}
-
-
- def coreReady(self):
- if not self.getConfig("check_checksum"):
- self.logInfo("Checksum validation is disabled in plugin configuration")
-
- def setup(self):
- self.algorithms = sorted(
- getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse=True)
- self.algorithms.extend(["crc32", "adler32"])
- self.formats = self.algorithms + ["sfv", "crc", "hash"]
-
- def downloadFinished(self, pyfile):
- """
- Compute checksum for the downloaded file and compare it with the hash provided by the hoster.
- pyfile.plugin.check_data should be a dictionary which can contain:
- a) if known, the exact filesize in bytes (e.g. "size": 123456789)
- b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307")
- """
- if hasattr(pyfile.plugin, "check_data") and (isinstance(pyfile.plugin.check_data, dict)):
- data = pyfile.plugin.check_data.copy()
- elif hasattr(pyfile.plugin, "api_data") and (isinstance(pyfile.plugin.api_data, dict)):
- data = pyfile.plugin.api_data.copy()
- else:
- return
-
- self.logDebug(data)
-
- if not pyfile.plugin.lastDownload:
- self.checkFailed(pyfile, None, "No file downloaded")
-
- local_file = fs_encode(pyfile.plugin.lastDownload)
- #download_folder = self.config['general']['download_folder']
- #local_file = fs_encode(save_join(download_folder, pyfile.package().folder, pyfile.name))
-
- if not isfile(local_file):
- self.checkFailed(pyfile, None, "File does not exist")
-
- # validate file size
- if "size" in data:
- api_size = int(data['size'])
- file_size = getsize(local_file)
- if api_size != file_size:
- self.logWarning("File %s has incorrect size: %d B (%d expected)" % (pyfile.name, file_size, api_size))
- self.checkFailed(pyfile, local_file, "Incorrect file size")
- del data['size']
-
- # validate checksum
- if data and self.getConfig("check_checksum"):
- if "checksum" in data:
- data['md5'] = data['checksum']
-
- for key in self.algorithms:
- if key in data:
- checksum = computeChecksum(local_file, key.replace("-", "").lower())
- if checksum:
- if checksum == data[key].lower():
- self.logInfo('File integrity of "%s" verified by %s checksum (%s).' %
- (pyfile.name, key.upper(), checksum))
- break
- else:
- self.logWarning("%s checksum for file %s does not match (%s != %s)" %
- (key.upper(), pyfile.name, checksum, data[key]))
- self.checkFailed(pyfile, local_file, "Checksums do not match")
- else:
- self.logWarning("Unsupported hashing algorithm: %s" % key.upper())
- else:
- self.logWarning("Unable to validate checksum for file %s" % pyfile.name)
-
- def checkFailed(self, pyfile, local_file, msg):
- check_action = self.getConfig("check_action")
- if check_action == "retry":
- max_tries = self.getConfig("max_tries")
- retry_action = self.getConfig("retry_action")
- if pyfile.plugin.retries < max_tries:
- if local_file:
- remove(local_file)
- pyfile.plugin.retry(max_tries=max_tries, wait_time=self.getConfig("wait_time"), reason=msg)
- elif retry_action == "nothing":
- return
- elif check_action == "nothing":
- return
- pyfile.plugin.fail(reason=msg)
-
- def packageFinished(self, pypack):
- download_folder = save_join(self.config['general']['download_folder'], pypack.folder, "")
-
- for link in pypack.getChildren().itervalues():
- file_type = splitext(link['name'])[1][1:].lower()
- #self.logDebug(link, file_type)
-
- if file_type not in self.formats:
- continue
-
- hash_file = fs_encode(save_join(download_folder, link['name']))
- if not isfile(hash_file):
- self.logWarning("File not found: %s" % link['name'])
- continue
-
- with open(hash_file) as f:
- text = f.read()
-
- for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text):
- data = m.groupdict()
- self.logDebug(link['name'], data)
-
- local_file = fs_encode(save_join(download_folder, data['name']))
- algorithm = self.methods.get(file_type, file_type)
- checksum = computeChecksum(local_file, algorithm)
- if checksum == data['hash']:
- self.logInfo('File integrity of "%s" verified by %s checksum (%s).' %
- (data['name'], algorithm, checksum))
- else:
- self.logWarning("%s checksum for file %s does not match (%s != %s)" %
- (algorithm, data['name'], checksum, data['hash']))
diff --git a/module/plugins/hooks/ClickAndLoad.py b/module/plugins/hooks/ClickAndLoad.py
deleted file mode 100644
index 5c523caf7..000000000
--- a/module/plugins/hooks/ClickAndLoad.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import socket
-import thread
-
-from module.plugins.Hook import Hook
-
-
-class ClickAndLoad(Hook):
- __name__ = "ClickAndLoad"
- __type__ = "hook"
- __version__ = "0.22"
-
- __config__ = [("activated", "bool", "Activated", True),
- ("extern", "bool", "Allow external link adding", False)]
-
- __description__ = """Gives abillity to use jd's click and load. depends on webinterface"""
- __author_name__ = ("RaNaN", "mkaay")
- __author_mail__ = ("RaNaN@pyload.de", "mkaay@mkaay.de")
-
-
- def coreReady(self):
- self.port = int(self.config['webinterface']['port'])
- if self.config['webinterface']['activated']:
- try:
- if self.getConfig("extern"):
- ip = "0.0.0.0"
- else:
- ip = "127.0.0.1"
-
- thread.start_new_thread(proxy, (self, ip, self.port, 9666))
- except:
- self.logError("ClickAndLoad port already in use.")
-
-
-def proxy(self, *settings):
- thread.start_new_thread(server, (self,) + settings)
- lock = thread.allocate_lock()
- lock.acquire()
- lock.acquire()
-
-
-def server(self, *settings):
- try:
- dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- dock_socket.bind((settings[0], settings[2]))
- dock_socket.listen(5)
- while True:
- client_socket = dock_socket.accept()[0]
- server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server_socket.connect(("127.0.0.1", settings[1]))
- thread.start_new_thread(forward, (client_socket, server_socket))
- thread.start_new_thread(forward, (server_socket, client_socket))
- except socket.error, e:
- if hasattr(e, "errno"):
- errno = e.errno
- else:
- errno = e.args[0]
-
- if errno == 98:
- self.logWarning(_("Click'N'Load: Port 9666 already in use"))
- return
- thread.start_new_thread(server, (self,) + settings)
- except:
- thread.start_new_thread(server, (self,) + settings)
-
-
-def forward(source, destination):
- string = ' '
- while string:
- string = source.recv(1024)
- if string:
- destination.sendall(string)
- else:
- #source.shutdown(socket.SHUT_RD)
- destination.shutdown(socket.SHUT_WR)
diff --git a/module/plugins/hooks/DeathByCaptcha.py b/module/plugins/hooks/DeathByCaptcha.py
deleted file mode 100644
index 530395d32..000000000
--- a/module/plugins/hooks/DeathByCaptcha.py
+++ /dev/null
@@ -1,202 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-
-from base64 import b64encode
-from pycurl import FORM_FILE, HTTPHEADER
-from thread import start_new_thread
-from time import sleep
-
-from module.common.json_layer import json_loads
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getRequest
-from module.plugins.Hook import Hook
-
-
-class DeathByCaptchaException(Exception):
- DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials',
- 'invalid-credentials': 'Access denied, check your credentials',
- 'banned': 'Access denied, account is suspended',
- 'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA',
- 'invalid-captcha': 'CAPTCHA is not a valid image',
- 'service-overload': 'CAPTCHA was rejected due to service overload, try again later',
- 'invalid-request': 'Invalid request',
- 'timed-out': 'No CAPTCHA solution received in time'}
-
- def __init__(self, err):
- self.err = err
-
- def getCode(self):
- return self.err
-
- def getDesc(self):
- if self.err in self.DBC_ERRORS.keys():
- return self.DBC_ERRORS[self.err]
- else:
- return self.err
-
- def __str__(self):
- return "<DeathByCaptchaException %s>" % self.err
-
- def __repr__(self):
- return "<DeathByCaptchaException %s>" % self.err
-
-
-class DeathByCaptcha(Hook):
- __name__ = "DeathByCaptcha"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force DBC even if client is connected", False)]
-
- __description__ = """Send captchas to DeathByCaptcha.com"""
- __author_name__ = ("RaNaN", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
-
- API_URL = "http://api.dbcapi.me/api/"
-
-
- def setup(self):
- self.info = {}
-
- def call_api(self, api="captcha", post=False, multipart=False):
- req = getRequest()
- req.c.setopt(HTTPHEADER, ["Accept: application/json", "User-Agent: pyLoad %s" % self.core.version])
-
- if post:
- if not isinstance(post, dict):
- post = {}
- post.update({"username": self.getConfig("username"),
- "password": self.getConfig("passkey")})
-
- response = None
- try:
- json = req.load("%s%s" % (self.API_URL, api),
- post=post,
- multipart=multipart)
- self.logDebug(json)
- response = json_loads(json)
-
- if "error" in response:
- raise DeathByCaptchaException(response['error'])
- elif "status" not in response:
- raise DeathByCaptchaException(str(response))
-
- except BadHeader, e:
- if 403 == e.code:
- raise DeathByCaptchaException('not-logged-in')
- elif 413 == e.code:
- raise DeathByCaptchaException('invalid-captcha')
- elif 503 == e.code:
- raise DeathByCaptchaException('service-overload')
- elif e.code in (400, 405):
- raise DeathByCaptchaException('invalid-request')
- else:
- raise
-
- finally:
- req.close()
-
- return response
-
- def getCredits(self):
- response = self.call_api("user", True)
-
- if 'is_banned' in response and response['is_banned']:
- raise DeathByCaptchaException('banned')
- elif 'balance' in response and 'rate' in response:
- self.info.update(response)
- else:
- raise DeathByCaptchaException(response)
-
- def getStatus(self):
- response = self.call_api("status", False)
-
- if 'is_service_overloaded' in response and response['is_service_overloaded']:
- raise DeathByCaptchaException('service-overload')
-
- def submit(self, captcha, captchaType="file", match=None):
- #workaround multipart-post bug in HTTPRequest.py
- if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")):
- multipart = True
- data = (FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = "base64:" + b64encode(data)
-
- response = self.call_api("captcha", {"captchafile": data}, multipart)
-
- if "captcha" not in response:
- raise DeathByCaptchaException(response)
- ticket = response['captcha']
-
- for _ in xrange(24):
- sleep(5)
- response = self.call_api("captcha/%d" % ticket, False)
- if response['text'] and response['is_correct']:
- break
- else:
- raise DeathByCaptchaException('timed-out')
-
- result = response['text']
- self.logDebug("result %s : %s" % (ticket, result))
-
- return ticket, result
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig("username") or not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- try:
- self.getStatus()
- self.getCredits()
- except DeathByCaptchaException, e:
- self.logError(e.getDesc())
- return False
-
- balance, rate = self.info['balance'], self.info['rate']
- self.logInfo("Account balance: US$%.3f (%d captchas left at %.2f cents each)" % (balance / 100,
- balance // rate, rate))
-
- if balance > rate:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(180)
- start_new_thread(self.processCaptcha, (task,))
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- try:
- response = self.call_api("captcha/%d/report" % task.data['ticket'], True)
- except DeathByCaptchaException, e:
- self.logError(e.getDesc())
- except Exception, e:
- self.logError(e)
-
- def processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except DeathByCaptchaException, e:
- task.error = e.getCode()
- self.logError(e.getDesc())
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/DebridItaliaCom.py b/module/plugins/hooks/DebridItaliaCom.py
deleted file mode 100644
index 88efb6b2a..000000000
--- a/module/plugins/hooks/DebridItaliaCom.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class DebridItaliaCom(MultiHoster):
- __name__ = "DebridItaliaCom"
- __type__ = "hook"
- __version__ = "0.07"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Debriditalia.com hook plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def getHoster(self):
- return ["netload.in", "hotfile.com", "rapidshare.com", "multiupload.com",
- "uploading.com", "megashares.com", "crocko.com", "filepost.com",
- "bitshare.com", "share-links.biz", "putlocker.com", "uploaded.to",
- "speedload.org", "rapidgator.net", "likeupload.net", "cyberlocker.ch",
- "depositfiles.com", "extabit.com", "filefactory.com", "sharefiles.co",
- "ryushare.com", "tusfiles.net", "nowvideo.co", "cloudzer.net", "letitbit.net",
- "easybytez.com", "uptobox.com", "ddlstorage.com"]
diff --git a/module/plugins/hooks/DeleteFinished.py b/module/plugins/hooks/DeleteFinished.py
deleted file mode 100644
index bc926906f..000000000
--- a/module/plugins/hooks/DeleteFinished.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.database import style
-from module.plugins.Hook import Hook
-
-
-class DeleteFinished(Hook):
- __name__ = "DeleteFinished"
- __type__ = "hook"
- __version__ = "1.09"
-
- __config__ = [('activated', 'bool', 'Activated', 'False'),
- ('interval', 'int', 'Delete every (hours)', '72'),
- ('deloffline', 'bool', 'Delete packages with offline links', 'False')]
-
- __description__ = """Automatically delete all finished packages from queue"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
- ## overwritten methods ##
- def periodical(self):
- if not self.info['sleep']:
- deloffline = self.getConfig('deloffline')
- mode = '0,1,4' if deloffline else '0,4'
- msg = 'delete all finished packages in queue list (%s packages with offline links)'
- self.logInfo(msg % ('including' if deloffline else 'excluding'))
- self.deleteFinished(mode)
- self.info['sleep'] = True
- self.addEvent('packageFinished', self.wakeup)
-
- def pluginConfigChanged(self, plugin, name, value):
- if name == 'interval' and value != self.interval:
- self.interval = value * 3600
- self.initPeriodical()
-
- def unload(self):
- self.removeEvent('packageFinished', self.wakeup)
-
- def coreReady(self):
- self.info = {'sleep': True}
- interval = self.getConfig('interval')
- self.pluginConfigChanged('DeleteFinished', 'interval', interval)
- self.addEvent('packageFinished', self.wakeup)
-
- ## own methods ##
- @style.queue
- def deleteFinished(self, mode):
- self.c.execute('DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE package=packages.id AND status NOT IN (%s))' % mode)
- self.c.execute('DELETE FROM links WHERE NOT EXISTS(SELECT 1 FROM packages WHERE id=links.package)')
-
- def wakeup(self, pypack):
- self.removeEvent('packageFinished', self.wakeup)
- self.info['sleep'] = False
-
- ## event managing ##
- def addEvent(self, event, func):
- """Adds an event listener for event name"""
- if event in self.m.events:
- if func in self.m.events[event]:
- self.logDebug('Function already registered %s' % func)
- else:
- self.m.events[event].append(func)
- else:
- self.m.events[event] = [func]
-
- def setup(self):
- self.m = self.manager
- self.removeEvent = self.m.removeEvent
diff --git a/module/plugins/hooks/DownloadScheduler.py b/module/plugins/hooks/DownloadScheduler.py
deleted file mode 100644
index a3d70b3ab..000000000
--- a/module/plugins/hooks/DownloadScheduler.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import localtime
-
-from module.plugins.Hook import Hook
-
-
-class DownloadScheduler(Hook):
- __name__ = "DownloadScheduler"
- __type__ = "hook"
- __version__ = "0.21"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("timetable", "str", "List time periods as hh:mm full or number(kB/s)",
- "0:00 full, 7:00 250, 10:00 0, 17:00 150"),
- ("abort", "bool", "Abort active downloads when start period with speed 0", False)]
-
- __description__ = """Download Scheduler"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
-
- def setup(self):
- self.cb = None # callback to scheduler job; will be by removed hookmanager when hook unloaded
-
- def coreReady(self):
- self.updateSchedule()
-
- def updateSchedule(self, schedule=None):
- if schedule is None:
- schedule = self.getConfig("timetable")
-
- schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)",
- schedule.lower().replace("full", "-1").replace("none", "0"))
- if not schedule:
- self.logError("Invalid schedule")
- return
-
- t0 = localtime()
- now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X")
- schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now])
-
- self.logDebug("Schedule", schedule)
-
- for i, v in enumerate(schedule):
- if v[3] == "X":
- last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)]
- self.logDebug("Now/Last/Next", now, last, next)
-
- self.setDownloadSpeed(last[3])
-
- next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400
- self.core.scheduler.removeJob(self.cb)
- self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False)
-
- def setDownloadSpeed(self, speed):
- if speed == 0:
- abort = self.getConfig("abort")
- self.logInfo("Stopping download server. (Running downloads will %sbe aborted.)" % ('' if abort else 'not '))
- self.core.api.pauseServer()
- if abort:
- self.core.api.stopAllDownloads()
- else:
- self.core.api.unpauseServer()
-
- if speed > 0:
- self.logInfo("Setting download speed to %d kB/s" % speed)
- self.core.api.setConfigValue("download", "limit_speed", 1)
- self.core.api.setConfigValue("download", "max_speed", speed)
- else:
- self.logInfo("Setting download speed to FULL")
- self.core.api.setConfigValue("download", "limit_speed", 0)
- self.core.api.setConfigValue("download", "max_speed", -1)
diff --git a/module/plugins/hooks/EasybytezCom.py b/module/plugins/hooks/EasybytezCom.py
deleted file mode 100644
index da37297d9..000000000
--- a/module/plugins/hooks/EasybytezCom.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class EasybytezCom(MultiHoster):
- __name__ = "EasybytezCom"
- __type__ = "hook"
- __version__ = "0.03"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", "")]
-
- __description__ = """EasyBytez.com hook plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def getHoster(self):
- self.account = self.core.accountManager.getAccountPlugin(self.__name__)
- user = self.account.selectAccount()[0]
-
- try:
- req = self.account.getAccountRequest(user)
- page = req.load("http://www.easybytez.com")
-
- m = re.search(r'</textarea>\s*Supported sites:(.*)', page)
- return m.group(1).split(',')
- except Exception, e:
- self.logDebug(e)
- self.logWarning("Unable to load supported hoster list, using last known")
- return ["bitshare.com", "crocko.com", "ddlstorage.com", "depositfiles.com", "extabit.com", "hotfile.com",
- "mediafire.com", "netload.in", "rapidgator.net", "rapidshare.com", "uploading.com", "uload.to",
- "uploaded.to"]
diff --git a/module/plugins/hooks/Ev0InFetcher.py b/module/plugins/hooks/Ev0InFetcher.py
deleted file mode 100644
index c54c38bc6..000000000
--- a/module/plugins/hooks/Ev0InFetcher.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import mktime, time
-
-from module.lib import feedparser
-
-from module.plugins.Hook import Hook
-
-
-class Ev0InFetcher(Hook):
- __name__ = "Ev0InFetcher"
- __type__ = "hook"
- __version__ = "0.21"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("interval", "int", "Check interval in minutes", 10),
- ("queue", "bool", "Move new shows directly to Queue", False),
- ("shows", "str", "Shows to check for (comma seperated)", ""),
- ("quality", "xvid;x264;rmvb", "Video Format", "xvid"),
- ("hoster", "str", "Hoster to use (comma seperated)",
- "NetloadIn,RapidshareCom,MegauploadCom,HotfileCom")]
-
- __description__ = """Checks rss feeds for Ev0.in"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def setup(self):
- self.interval = self.getConfig("interval") * 60
-
- def filterLinks(self, links):
- results = self.core.pluginManager.parseUrls(links)
- sortedLinks = {}
-
- for url, hoster in results:
- if hoster not in sortedLinks:
- sortedLinks[hoster] = []
- sortedLinks[hoster].append(url)
-
- for h in self.getConfig("hoster").split(","):
- try:
- return sortedLinks[h.strip()]
- except:
- continue
- return []
-
-
- def periodical(self):
-
- def normalizefiletitle(filename):
- filename = filename.replace('.', ' ')
- filename = filename.replace('_', ' ')
- filename = filename.lower()
- return filename
-
- shows = [s.strip() for s in self.getConfig("shows").split(",")]
-
- feed = feedparser.parse("http://feeds.feedburner.com/ev0in/%s?format=xml" % self.getConfig("quality"))
-
- showStorage = {}
- for show in shows:
- showStorage[show] = int(self.getStorage("show_%s_lastfound" % show, 0))
-
- found = False
- for item in feed['items']:
- for show, lastfound in showStorage.iteritems():
- if show.lower() in normalizefiletitle(item['title']) and lastfound < int(mktime(item.date_parsed)):
- links = self.filterLinks(item['description'].split("<br />"))
- packagename = item['title'].encode("utf-8")
- self.logInfo("Ev0InFetcher: new episode '%s' (matched '%s')" % (packagename, show))
- self.core.api.addPackage(packagename, links, 1 if self.getConfig("queue") else 0)
- self.setStorage("show_%s_lastfound" % show, int(mktime(item.date_parsed)))
- found = True
- if not found:
- #self.logDebug("Ev0InFetcher: no new episodes found")
- pass
-
- for show, lastfound in self.getStorage().iteritems():
- if int(lastfound) > 0 and int(lastfound) + (3600 * 24 * 30) < int(time()):
- self.delStorage("show_%s_lastfound" % show)
- self.logDebug("Ev0InFetcher: cleaned '%s' record" % show)
diff --git a/module/plugins/hooks/ExpertDecoders.py b/module/plugins/hooks/ExpertDecoders.py
deleted file mode 100644
index c7ab80da0..000000000
--- a/module/plugins/hooks/ExpertDecoders.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-from base64 import b64encode
-from pycurl import LOW_SPEED_TIME
-from thread import start_new_thread
-from uuid import uuid4
-
-from module.network.HTTPRequest import BadHeader
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook
-
-
-class ExpertDecoders(Hook):
- __name__ = "ExpertDecoders"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("force", "bool", "Force CT even if client is connected", False),
- ("passkey", "password", "Access key", "")]
-
- __description__ = """Send captchas to expertdecoders.com"""
- __author_name__ = ("RaNaN", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
-
- API_URL = "http://www.fasttypers.org/imagepost.ashx"
-
-
- def setup(self):
- self.info = {}
-
- def getCredits(self):
- response = getURL(self.API_URL, post={"key": self.getConfig("passkey"), "action": "balance"})
-
- if response.isdigit():
- self.logInfo(_("%s credits left") % response)
- self.info['credits'] = credits = int(response)
- return credits
- else:
- self.logError(response)
- return 0
-
- def processCaptcha(self, task):
- task.data['ticket'] = ticket = uuid4()
- result = None
-
- with open(task.captchaFile, 'rb') as f:
- data = f.read()
- data = b64encode(data)
- #self.logDebug("%s: %s : %s" % (ticket, task.captchaFile, data))
-
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(LOW_SPEED_TIME, 80)
-
- try:
- result = req.load(self.API_URL, post={"action": "upload", "key": self.getConfig("passkey"),
- "file": data, "gen_task_id": ticket})
- finally:
- req.close()
-
- self.logDebug("result %s : %s" % (ticket, result))
- task.setResult(result)
-
- def newCaptchaTask(self, task):
- if not task.isTextual():
- return False
-
- if not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.setWaiting(100)
- start_new_thread(self.processCaptcha, (task,))
-
- else:
- self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
-
- def captchaInvalid(self, task):
- if "ticket" in task.data:
-
- try:
- response = getURL(self.API_URL, post={"action": "refund", "key": self.getConfig("passkey"),
- "gen_task_id": task.data['ticket']})
- self.logInfo("Request refund: %s" % response)
-
- except BadHeader, e:
- self.logError("Could not send refund request.", str(e))
diff --git a/module/plugins/hooks/ExternalScripts.py b/module/plugins/hooks/ExternalScripts.py
deleted file mode 100644
index 8d03d27d4..000000000
--- a/module/plugins/hooks/ExternalScripts.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import subprocess
-
-from os import listdir, access, X_OK, makedirs
-from os.path import join, exists, basename, abspath
-
-from module.plugins.Hook import Hook
-from module.utils import save_join
-
-
-class ExternalScripts(Hook):
- __name__ = "ExternalScripts"
- __type__ = "hook"
- __version__ = "0.23"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """Run external scripts"""
- __author_name__ = ("mkaay", "RaNaN", "spoob")
- __author_mail__ = ("mkaay@mkaay.de", "ranan@pyload.org", "spoob@pyload.org")
-
- event_list = ["unrarFinished", "allDownloadsFinished", "allDownloadsProcessed"]
-
-
- def setup(self):
- self.scripts = {}
-
- folders = ["download_preparing", "download_finished", "package_finished",
- "before_reconnect", "after_reconnect", "unrar_finished",
- "all_dls_finished", "all_dls_processed"]
-
- for folder in folders:
- self.scripts[folder] = []
-
- self.initPluginType(folder, join(pypath, 'scripts', folder))
- self.initPluginType(folder, join('scripts', folder))
-
- for script_type, names in self.scripts.iteritems():
- if names:
- self.logInfo((_("Installed scripts for %s: ") % script_type) + ", ".join([basename(x) for x in names]))
-
- def initPluginType(self, folder, path):
- if not exists(path):
- try:
- makedirs(path)
- except:
- self.logDebug("Script folder %s not created" % folder)
- return
-
- for f in listdir(path):
- if f.startswith("#") or f.startswith(".") or f.startswith("_") or f.endswith("~") or f.endswith(".swp"):
- continue
-
- if not access(join(path, f), X_OK):
- self.logWarning(_("Script not executable:") + " %s/%s" % (folder, f))
-
- self.scripts[folder].append(join(path, f))
-
- def callScript(self, script, *args):
- try:
- cmd = [script] + [str(x) if not isinstance(x, basestring) else x for x in args]
- self.logDebug("Executing %(script)s: %(cmd)s" % {"script": abspath(script), "cmd": " ".join(cmd)})
- #output goes to pyload
- subprocess.Popen(cmd, bufsize=-1)
- except Exception, e:
- self.logError(_("Error in %(script)s: %(error)s") % {"script": basename(script), "error": str(e)})
-
- def downloadPreparing(self, pyfile):
- for script in self.scripts['download_preparing']:
- self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.id)
-
- def downloadFinished(self, pyfile):
- for script in self.scripts['download_finished']:
- self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.name,
- save_join(self.config['general']['download_folder'],
- pyfile.package().folder, pyfile.name), pyfile.id)
-
- def packageFinished(self, pypack):
- for script in self.scripts['package_finished']:
- folder = self.config['general']['download_folder']
- folder = save_join(folder, pypack.folder)
-
- self.callScript(script, pypack.name, folder, pypack.password, pypack.id)
-
- def beforeReconnecting(self, ip):
- for script in self.scripts['before_reconnect']:
- self.callScript(script, ip)
-
- def afterReconnecting(self, ip):
- for script in self.scripts['after_reconnect']:
- self.callScript(script, ip)
-
- def unrarFinished(self, folder, fname):
- for script in self.scripts['unrar_finished']:
- self.callScript(script, folder, fname)
-
- def allDownloadsFinished(self):
- for script in self.scripts['all_dls_finished']:
- self.callScript(script)
-
- def allDownloadsProcessed(self):
- for script in self.scripts['all_dls_processed']:
- self.callScript(script)
diff --git a/module/plugins/hooks/ExtractArchive.py b/module/plugins/hooks/ExtractArchive.py
deleted file mode 100644
index 144829459..000000000
--- a/module/plugins/hooks/ExtractArchive.py
+++ /dev/null
@@ -1,320 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import sys
-
-from copy import copy
-from os import remove, chmod, makedirs
-from os.path import exists, basename, isfile, isdir
-from traceback import print_exc
-
-# monkey patch bug in python 2.6 and lower
-# http://bugs.python.org/issue6122 , http://bugs.python.org/issue1236 , http://bugs.python.org/issue1731717
-if sys.version_info < (2, 7) and os.name != "nt":
- import errno
- from subprocess import Popen
-
- def _eintr_retry_call(func, *args):
- while True:
- try:
- return func(*args)
- except OSError, e:
- if e.errno == errno.EINTR:
- continue
- raise
-
- # unsued timeout option for older python version
- def wait(self, timeout=0):
- """Wait for child process to terminate. Returns returncode
- attribute."""
- if self.returncode is None:
- try:
- pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
- except OSError, e:
- if e.errno != errno.ECHILD:
- raise
- # This happens if SIGCLD is set to be ignored or waiting
- # for child processes has otherwise been disabled for our
- # process. This child is dead, we can't get the status.
- sts = 0
- self._handle_exitstatus(sts)
- return self.returncode
-
- Popen.wait = wait
-
-if os.name != "nt":
- from grp import getgrnam
- from os import chown
- from pwd import getpwnam
-
-from module.plugins.Hook import Hook, threaded, Expose
-from module.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword
-from module.utils import save_join, fs_encode
-
-
-class ExtractArchive(Hook):
- """
- Provides: unrarFinished (folder, filename)
- """
- __name__ = "ExtractArchive"
- __type__ = "hook"
- __version__ = "0.16"
-
- __config__ = [("activated", "bool", "Activated", True),
- ("fullpath", "bool", "Extract full path", True),
- ("overwrite", "bool", "Overwrite files", True),
- ("passwordfile", "file", "password file", "unrar_passwords.txt"),
- ("deletearchive", "bool", "Delete archives when done", False),
- ("subfolder", "bool", "Create subfolder for each package", False),
- ("destination", "folder", "Extract files to", ""),
- ("excludefiles", "str", "Exclude files from unpacking (seperated by ;)", ""),
- ("recursive", "bool", "Extract archives in archvies", True),
- ("queue", "bool", "Wait for all downloads to be finished", True),
- ("renice", "int", "CPU Priority", 0)]
-
- __description__ = """Extract different kind of archives"""
- __author_name__ = ("pyLoad Team", "AndroKev")
- __author_mail__ = ("admin@pyload.org", "@pyloadforum")
-
- event_list = ["allDownloadsProcessed"]
-
-
- def setup(self):
- self.plugins = []
- self.passwords = []
- names = []
-
- for p in ("UnRar", "UnZip"):
- try:
- module = self.core.pluginManager.loadModule("internal", p)
- klass = getattr(module, p)
- if klass.checkDeps():
- names.append(p)
- self.plugins.append(klass)
-
- except OSError, e:
- if e.errno == 2:
- self.logInfo(_("No %s installed") % p)
- else:
- self.logWarning(_("Could not activate %s") % p, str(e))
- if self.core.debug:
- print_exc()
-
- except Exception, e:
- self.logWarning(_("Could not activate %s") % p, str(e))
- if self.core.debug:
- print_exc()
-
- if names:
- self.logInfo(_("Activated") + " " + " ".join(names))
- else:
- self.logInfo(_("No Extract plugins activated"))
-
- # queue with package ids
- self.queue = []
-
- @Expose
- def extractPackage(self, id):
- """ Extract package with given id"""
- self.manager.startThread(self.extract, [id])
-
- def packageFinished(self, pypack):
- if self.getConfig("queue"):
- self.logInfo(_("Package %s queued for later extracting") % pypack.name)
- self.queue.append(pypack.id)
- else:
- self.manager.startThread(self.extract, [pypack.id])
-
- @threaded
- def allDownloadsProcessed(self, thread):
- local = copy(self.queue)
- del self.queue[:]
- self.extract(local, thread)
-
- def extract(self, ids, thread=None):
- # reload from txt file
- self.reloadPasswords()
-
- # dl folder
- dl = self.config['general']['download_folder']
-
- extracted = []
-
- #iterate packages -> plugins -> targets
- for pid in ids:
- p = self.core.files.getPackage(pid)
- self.logInfo(_("Check package %s") % p.name)
- if not p:
- continue
-
- # determine output folder
- out = save_join(dl, p.folder, "")
- # force trailing slash
-
- if self.getConfig("destination") and self.getConfig("destination").lower() != "none":
-
- out = save_join(dl, p.folder, self.getConfig("destination"), "")
- #relative to package folder if destination is relative, otherwise absolute path overwrites them
-
- if self.getConfig("subfolder"):
- out = save_join(out, fs_encode(p.folder))
-
- if not exists(out):
- makedirs(out)
-
- files_ids = [(save_join(dl, p.folder, x['name']), x['id']) for x in p.getChildren().itervalues()]
- matched = False
-
- # check as long there are unseen files
- while files_ids:
- new_files_ids = []
-
- for plugin in self.plugins:
- targets = plugin.getTargets(files_ids)
- if targets:
- self.logDebug("Targets for %s: %s" % (plugin.__name__, targets))
- matched = True
- for target, fid in targets:
- if target in extracted:
- self.logDebug(basename(target), "skipped")
- continue
- extracted.append(target) # prevent extracting same file twice
-
- klass = plugin(self, target, out, self.getConfig("fullpath"), self.getConfig("overwrite"), self.getConfig("excludefiles"),
- self.getConfig("renice"))
- klass.init()
-
- self.logInfo(basename(target), _("Extract to %s") % out)
- new_files = self.startExtracting(klass, fid, p.password.strip().splitlines(), thread)
- self.logDebug("Extracted: %s" % new_files)
- self.setPermissions(new_files)
-
- for file in new_files:
- if not exists(file):
- self.logDebug("new file %s does not exists" % file)
- continue
- if self.getConfig("recursive") and isfile(file):
- new_files_ids.append((file, fid)) # append as new target
-
- files_ids = new_files_ids # also check extracted files
-
- if not matched:
- self.logInfo(_("No files found to extract"))
-
- def startExtracting(self, plugin, fid, passwords, thread):
- pyfile = self.core.files.getFile(fid)
- if not pyfile:
- return []
-
- pyfile.setCustomStatus(_("extracting"))
- thread.addActive(pyfile) # keep this file until everything is done
-
- try:
- progress = lambda x: pyfile.setProgress(x)
- success = False
-
- if not plugin.checkArchive():
- plugin.extract(progress)
- success = True
- else:
- self.logInfo(basename(plugin.file), _("Password protected"))
- self.logDebug("Passwords: %s" % str(passwords))
-
- pwlist = copy(self.getPasswords())
- #remove already supplied pws from list (only local)
- for pw in passwords:
- if pw in pwlist:
- pwlist.remove(pw)
-
- for pw in passwords + pwlist:
- try:
- self.logDebug("Try password: %s" % pw)
- if plugin.checkPassword(pw):
- plugin.extract(progress, pw)
- self.addPassword(pw)
- success = True
- break
- except WrongPassword:
- self.logDebug("Password was wrong")
-
- if not success:
- self.logError(basename(plugin.file), _("Wrong password"))
- return []
-
- if self.core.debug:
- self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles()))
-
- if self.getConfig("deletearchive"):
- files = plugin.getDeleteFiles()
- self.logInfo(_("Deleting %s files") % len(files))
- for f in files:
- if exists(f):
- remove(f)
- else:
- self.logDebug("%s does not exists" % f)
-
- self.logInfo(basename(plugin.file), _("Extracting finished"))
- self.manager.dispatchEvent("unrarFinished", plugin.out, plugin.file)
-
- return plugin.getExtractedFiles()
-
- except ArchiveError, e:
- self.logError(basename(plugin.file), _("Archive Error"), str(e))
- except CRCError:
- self.logError(basename(plugin.file), _("CRC Mismatch"))
- except Exception, e:
- if self.core.debug:
- print_exc()
- self.logError(basename(plugin.file), _("Unknown Error"), str(e))
-
- return []
-
- @Expose
- def getPasswords(self):
- """ List of saved passwords """
- return self.passwords
-
- def reloadPasswords(self):
- pwfile = self.getConfig("passwordfile")
- if not exists(pwfile):
- open(pwfile, "wb").close()
-
- passwords = []
- f = open(pwfile, "rb")
- for pw in f.read().splitlines():
- passwords.append(pw)
- f.close()
-
- self.passwords = passwords
-
- @Expose
- def addPassword(self, pw):
- """ Adds a password to saved list"""
- pwfile = self.getConfig("passwordfile")
-
- if pw in self.passwords:
- self.passwords.remove(pw)
- self.passwords.insert(0, pw)
-
- f = open(pwfile, "wb")
- for pw in self.passwords:
- f.write(pw + "\n")
- f.close()
-
- def setPermissions(self, files):
- for f in files:
- if not exists(f):
- continue
- try:
- if self.config['permission']['change_file']:
- if isfile(f):
- chmod(f, int(self.config['permission']['file'], 8))
- elif isdir(f):
- chmod(f, int(self.config['permission']['folder'], 8))
-
- if self.config['permission']['change_dl'] and os.name != "nt":
- uid = getpwnam(self.config['permission']['user'])[2]
- gid = getgrnam(self.config['permission']['group'])[2]
- chown(f, uid, gid)
- except Exception, e:
- self.logWarning(_("Setting User and Group failed"), e)
diff --git a/module/plugins/hooks/FastixRu.py b/module/plugins/hooks/FastixRu.py
deleted file mode 100644
index 879dba277..000000000
--- a/module/plugins/hooks/FastixRu.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class FastixRu(MultiHoster):
- __name__ = "FastixRu"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Fastix.ru hook plugin"""
- __author_name__ = "Massimo Rosamilia"
- __author_mail__ = "max@spiritix.eu"
-
-
- def getHoster(self):
- page = getURL(
- "http://fastix.ru/api_v2/?apikey=5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y&sub=allowed_sources")
- host_list = json_loads(page)
- host_list = host_list['allow']
- return host_list
diff --git a/module/plugins/hooks/FreeWayMe.py b/module/plugins/hooks/FreeWayMe.py
deleted file mode 100644
index 12d58ac8a..000000000
--- a/module/plugins/hooks/FreeWayMe.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class FreeWayMe(MultiHoster):
- __name__ = "FreeWayMe"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """FreeWay.me hook plugin"""
- __author_name__ = "Nicolas Giese"
- __author_mail__ = "james@free-way.me"
-
-
- def getHoster(self):
- hostis = getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
- self.logDebug("hosters: %s" % hostis)
- return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/module/plugins/hooks/HotFolder.py b/module/plugins/hooks/HotFolder.py
deleted file mode 100644
index 4c81292e3..000000000
--- a/module/plugins/hooks/HotFolder.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import time
-
-from os import listdir, makedirs
-from os.path import exists, isfile, join
-from shutil import move
-
-from module.plugins.Hook import Hook
-
-
-class HotFolder(Hook):
- __name__ = "HotFolder"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("folder", "str", "Folder to observe", "container"),
- ("watch_file", "bool", "Observe link file", False),
- ("keep", "bool", "Keep added containers", True),
- ("file", "str", "Link file", "links.txt")]
-
- __description__ = """Observe folder and file for changes and add container and links"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.de"
-
-
- def setup(self):
- self.interval = 10
-
- def periodical(self):
- if not exists(join(self.getConfig("folder"), "finished")):
- makedirs(join(self.getConfig("folder"), "finished"))
-
- if self.getConfig("watch_file"):
-
- if not exists(self.getConfig("file")):
- f = open(self.getConfig("file"), "wb")
- f.close()
-
- f = open(self.getConfig("file"), "rb")
- content = f.read().strip()
- f.close()
- f = open(self.getConfig("file"), "wb")
- f.close()
- if content:
- name = "%s_%s.txt" % (self.getConfig("file"), time.strftime("%H-%M-%S_%d%b%Y"))
-
- f = open(join(self.getConfig("folder"), "finished", name), "wb")
- f.write(content)
- f.close()
-
- self.core.api.addPackage(f.name, [f.name], 1)
-
- for f in listdir(self.getConfig("folder")):
- path = join(self.getConfig("folder"), f)
-
- if not isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
- continue
-
- newpath = join(self.getConfig("folder"), "finished", f if self.getConfig("keep") else "tmp_" + f)
- move(path, newpath)
-
- self.logInfo(_("Added %s from HotFolder") % f)
- self.core.api.addPackage(f, [newpath], 1)
diff --git a/module/plugins/hooks/IRCInterface.py b/module/plugins/hooks/IRCInterface.py
deleted file mode 100644
index 7ebc0275f..000000000
--- a/module/plugins/hooks/IRCInterface.py
+++ /dev/null
@@ -1,404 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import socket
-import time
-
-from pycurl import FORM_FILE
-from select import select
-from threading import Thread
-from time import sleep
-from traceback import print_exc
-
-from module.Api import PackageDoesNotExists, FileDoesNotExists
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook
-from module.utils import formatSize
-
-
-class IRCInterface(Thread, Hook):
- __name__ = "IRCInterface"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("host", "str", "IRC-Server Address", "Enter your server here!"),
- ("port", "int", "IRC-Server Port", 6667),
- ("ident", "str", "Clients ident", "pyload-irc"),
- ("realname", "str", "Realname", "pyload-irc"),
- ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"),
- ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"),
- ("info_file", "bool", "Inform about every file finished", False),
- ("info_pack", "bool", "Inform about every package finished", True),
- ("captcha", "bool", "Send captcha requests", True)]
-
- __description__ = """Connect to irc and let owner perform different tasks"""
- __author_name__ = "Jeix"
- __author_mail__ = "Jeix@hasnomail.com"
-
-
- def __init__(self, core, manager):
- Thread.__init__(self)
- Hook.__init__(self, core, manager)
- self.setDaemon(True)
- # self.sm = core.server_methods
- self.api = core.api # todo, only use api
-
- def coreReady(self):
- self.abort = False
- self.more = []
- self.new_package = {}
-
- self.start()
-
- def packageFinished(self, pypack):
- try:
- if self.getConfig("info_pack"):
- self.response(_("Package finished: %s") % pypack.name)
- except:
- pass
-
- def downloadFinished(self, pyfile):
- try:
- if self.getConfig("info_file"):
- self.response(
- _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname})
- except:
- pass
-
- def newCaptchaTask(self, task):
- if self.getConfig("captcha") and task.isTextual():
- task.handler.append(self)
- task.setWaiting(60)
-
- page = getURL("http://www.freeimagehosting.net/upload.php",
- post={"attached": (FORM_FILE, task.captchaFile)}, multipart=True)
-
- url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).group(1)
- self.response(_("New Captcha Request: %s") % url)
- self.response(_("Answer with 'c %s text on the captcha'") % task.id)
-
- def run(self):
- # connect to IRC etc.
- self.sock = socket.socket()
- host = self.getConfig("host")
- self.sock.connect((host, self.getConfig("port")))
- nick = self.getConfig("nick")
- self.sock.send("NICK %s\r\n" % nick)
- self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick))
- for t in self.getConfig("owner").split():
- if t.strip().startswith("#"):
- self.sock.send("JOIN %s\r\n" % t.strip())
- self.logInfo("pyLoad IRC: Connected to %s!" % host)
- self.logInfo("pyLoad IRC: Switching to listening mode!")
- try:
- self.main_loop()
-
- except IRCError, ex:
- self.sock.send("QUIT :byebye\r\n")
- print_exc()
- self.sock.close()
-
- def main_loop(self):
- readbuffer = ""
- while True:
- sleep(1)
- fdset = select([self.sock], [], [], 0)
- if self.sock not in fdset[0]:
- continue
-
- if self.abort:
- raise IRCError("quit")
-
- readbuffer += self.sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- line = line.rstrip()
- first = line.split()
-
- if first[0] == "PING":
- self.sock.send("PONG %s\r\n" % first[1])
-
- if first[0] == "ERROR":
- raise IRCError(line)
-
- msg = line.split(None, 3)
- if len(msg) < 4:
- continue
-
- msg = {
- "origin": msg[0][1:],
- "action": msg[1],
- "target": msg[2],
- "text": msg[3][1:]
- }
-
- self.handle_events(msg)
-
- def handle_events(self, msg):
- if not msg['origin'].split("!", 1)[0] in self.getConfig("owner").split():
- return
-
- if msg['target'].split("!", 1)[0] != self.getConfig("nick"):
- return
-
- if msg['action'] != "PRIVMSG":
- return
-
- # HANDLE CTCP ANTI FLOOD/BOT PROTECTION
- if msg['text'] == "\x01VERSION\x01":
- self.logDebug("Sending CTCP VERSION.")
- self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
- return
- elif msg['text'] == "\x01TIME\x01":
- self.logDebug("Sending CTCP TIME.")
- self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
- return
- elif msg['text'] == "\x01LAG\x01":
- self.logDebug("Received CTCP LAG.") # don't know how to answer
- return
-
- trigger = "pass"
- args = None
-
- try:
- temp = msg['text'].split()
- trigger = temp[0]
- if len(temp) > 1:
- args = temp[1:]
- except:
- pass
-
- handler = getattr(self, "event_%s" % trigger, self.event_pass)
- try:
- res = handler(args)
- for line in res:
- self.response(line, msg['origin'])
- except Exception, e:
- self.logError("pyLoad IRC: " + repr(e))
-
- def response(self, msg, origin=""):
- if origin == "":
- for t in self.getConfig("owner").split():
- self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg))
- else:
- self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg))
-
- #### Events
-
- def event_pass(self, args):
- return []
-
- def event_status(self, args):
- downloads = self.api.statusDownloads()
- if not downloads:
- return ["INFO: There are no active downloads currently."]
-
- temp_progress = ""
- lines = ["ID - Name - Status - Speed - ETA - Progress"]
- for data in downloads:
-
- if data.status == 5:
- temp_progress = data.format_wait
- else:
- temp_progress = "%d%% (%s)" % (data.percent, data.format_size)
-
- lines.append("#%d - %s - %s - %s - %s - %s" %
- (
- data.fid,
- data.name,
- data.statusmsg,
- "%s/s" % formatSize(data.speed),
- "%s" % data.format_eta,
- temp_progress
- ))
- return lines
-
- def event_queue(self, args):
- ps = self.api.getQueueData()
-
- if not ps:
- return ["INFO: There are no packages in queue."]
-
- lines = []
- for pack in ps:
- lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
-
- return lines
-
- def event_collector(self, args):
- ps = self.api.getCollectorData()
- if not ps:
- return ["INFO: No packages in collector!"]
-
- lines = []
- for pack in ps:
- lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
-
- return lines
-
- def event_info(self, args):
- if not args:
- return ["ERROR: Use info like this: info <id>"]
-
- info = None
- try:
- info = self.api.getFileData(int(args[0]))
-
- except FileDoesNotExists:
- return ["ERROR: Link doesn't exists."]
-
- return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)]
-
- def event_packinfo(self, args):
- if not args:
- return ["ERROR: Use packinfo like this: packinfo <id>"]
-
- lines = []
- pack = None
- try:
- pack = self.api.getPackageData(int(args[0]))
-
- except PackageDoesNotExists:
- return ["ERROR: Package doesn't exists."]
-
- id = args[0]
-
- self.more = []
-
- lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links)))
- for pyfile in pack.links:
- self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size,
- pyfile.statusmsg, pyfile.plugin))
-
- if len(self.more) < 6:
- lines.extend(self.more)
- self.more = []
- else:
- lines.extend(self.more[:6])
- self.more = self.more[6:]
- lines.append("%d more links do display." % len(self.more))
-
- return lines
-
- def event_more(self, args):
- if not self.more:
- return ["No more information to display."]
-
- lines = self.more[:6]
- self.more = self.more[6:]
- lines.append("%d more links do display." % len(self.more))
-
- return lines
-
- def event_start(self, args):
- self.api.unpauseServer()
- return ["INFO: Starting downloads."]
-
- def event_stop(self, args):
- self.api.pauseServer()
- return ["INFO: No new downloads will be started."]
-
- def event_add(self, args):
- if len(args) < 2:
- return ['ERROR: Add links like this: "add <packagename|id> links". ',
- "This will add the link <link> to to the package <package> / the package with id <id>!"]
-
- pack = args[0].strip()
- links = [x.strip() for x in args[1:]]
-
- count_added = 0
- count_failed = 0
- try:
- id = int(pack)
- pack = self.api.getPackageData(id)
- if not pack:
- return ["ERROR: Package doesn't exists."]
-
- #TODO add links
-
- return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack['name'], id)]
-
- except:
- # create new package
- id = self.api.addPackage(pack, links, 1)
- return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))]
-
- def event_del(self, args):
- if len(args) < 2:
- return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
-
- if args[0] == "-p":
- ret = self.api.deletePackages(map(int, args[1:]))
- return ["INFO: Deleted %d packages!" % len(args[1:])]
-
- elif args[0] == "-l":
- ret = self.api.delLinks(map(int, args[1:]))
- return ["INFO: Deleted %d links!" % len(args[1:])]
-
- else:
- return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
-
- def event_push(self, args):
- if not args:
- return ["ERROR: Push package to queue like this: push <package id>"]
-
- id = int(args[0])
- try:
- info = self.api.getPackageInfo(id)
- except PackageDoesNotExists:
- return ["ERROR: Package #%d does not exist." % id]
-
- self.api.pushToQueue(id)
- return ["INFO: Pushed package #%d to queue." % id]
-
- def event_pull(self, args):
- if not args:
- return ["ERROR: Pull package from queue like this: pull <package id>."]
-
- id = int(args[0])
- if not self.api.getPackageData(id):
- return ["ERROR: Package #%d does not exist." % id]
-
- self.api.pullFromQueue(id)
- return ["INFO: Pulled package #%d from queue to collector." % id]
-
- def event_c(self, args):
- """ captcha answer """
- if not args:
- return ["ERROR: Captcha ID missing."]
-
- task = self.core.captchaManager.getTaskByID(args[0])
- if not task:
- return ["ERROR: Captcha Task with ID %s does not exists." % args[0]]
-
- task.setResult(" ".join(args[1:]))
- return ["INFO: Result %s saved." % " ".join(args[1:])]
-
- def event_help(self, args):
- lines = ["The following commands are available:",
- "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)",
- "queue Shows all packages in the queue",
- "collector Shows all packages in collector",
- "del -p|-l <id> [...] Deletes all packages|links with the ids specified",
- "info <id> Shows info of the link with id <id>",
- "packinfo <id> Shows info of the package with id <id>",
- "more Shows more info when the result was truncated",
- "start Starts all downloads",
- "stop Stops the download (but not abort active downloads)",
- "push <id> Push package to queue",
- "pull <id> Pull package from queue",
- "status Show general download status",
- "help Shows this help message"]
- return lines
-
-
-class IRCError(Exception):
-
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return repr(self.value)
diff --git a/module/plugins/hooks/ImageTyperz.py b/module/plugins/hooks/ImageTyperz.py
deleted file mode 100644
index d9d4e547e..000000000
--- a/module/plugins/hooks/ImageTyperz.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-
-from base64 import b64encode
-from pycurl import FORM_FILE, LOW_SPEED_TIME
-from thread import start_new_thread
-
-from module.network.RequestFactory import getURL, getRequest
-from module.plugins.Hook import Hook
-
-
-class ImageTyperzException(Exception):
-
- def __init__(self, err):
- self.err = err
-
- def getCode(self):
- return self.err
-
- def __str__(self):
- return "<ImageTyperzException %s>" % self.err
-
- def __repr__(self):
- return "<ImageTyperzException %s>" % self.err
-
-
-class ImageTyperz(Hook):
- __name__ = "ImageTyperz"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("username", "str", "Username", ""),
- ("passkey", "password", "Password", ""),
- ("force", "bool", "Force IT even if client is connected", False)]
-
- __description__ = """Send captchas to ImageTyperz.com"""
- __author_name__ = ("RaNaN", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
-
- SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx"
- RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx"
- GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx"
-
-
- def setup(self):
- self.info = {}
-
- def getCredits(self):
- response = getURL(self.GETCREDITS_URL, post={"action": "REQUESTBALANCE", "username": self.getConfig("username"),
- "password": self.getConfig("passkey")})
-
- if response.startswith('ERROR'):
- raise ImageTyperzException(response)
-
- try:
- balance = float(response)
- except:
- raise ImageTyperzException("invalid response")
-
- self.logInfo("Account balance: $%s left" % response)
- return balance
-
- def submit(self, captcha, captchaType="file", match=None):
- req = getRequest()
- #raise timeout threshold
- req.c.setopt(LOW_SPEED_TIME, 80)
-
- try:
- #workaround multipart-post bug in HTTPRequest.py
- if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")):
- multipart = True
- data = (FORM_FILE, captcha)
- else:
- multipart = False
- with open(captcha, 'rb') as f:
- data = f.read()
- data = b64encode(data)
-
- response = req.load(self.SUBMIT_URL, post={"action": "UPLOADCAPTCHA",
- "username": self.getConfig("username"),
- "password": self.getConfig("passkey"), "file": data},
- multipart=multipart)
- finally:
- req.close()
-
- if response.startswith("ERROR"):
- raise ImageTyperzException(response)
- else:
- data = response.split('|')
- if len(data) == 2:
- ticket, result = data
- else:
- raise ImageTyperzException("Unknown response %s" % response)
-
- return ticket, result
-
- def newCaptchaTask(self, task):
- if "service" in task.data:
- return False
-
- if not task.isTextual():
- return False
-
- if not self.getConfig("username") or not self.getConfig("passkey"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if self.getCredits() > 0:
- task.handler.append(self)
- task.data['service'] = self.__name__
- task.setWaiting(100)
- start_new_thread(self.processCaptcha, (task,))
-
- else:
- self.logInfo("Your %s account has not enough credits" % self.__name__)
-
- def captchaInvalid(self, task):
- if task.data['service'] == self.__name__ and "ticket" in task.data:
- response = getURL(self.RESPOND_URL, post={"action": "SETBADIMAGE", "username": self.getConfig("username"),
- "password": self.getConfig("passkey"),
- "imageid": task.data['ticket']})
-
- if response == "SUCCESS":
- self.logInfo("Bad captcha solution received, requested refund")
- else:
- self.logError("Bad captcha solution received, refund request failed", response)
-
- def processCaptcha(self, task):
- c = task.captchaFile
- try:
- ticket, result = self.submit(c)
- except ImageTyperzException, e:
- task.error = e.getCode()
- return
-
- task.data['ticket'] = ticket
- task.setResult(result)
diff --git a/module/plugins/hooks/LinkdecrypterCom.py b/module/plugins/hooks/LinkdecrypterCom.py
deleted file mode 100644
index 1aa8f7ca1..000000000
--- a/module/plugins/hooks/LinkdecrypterCom.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Hook
-from module.utils import remove_chars
-
-
-class LinkdecrypterCom(Hook):
- __name__ = "LinkdecrypterCom"
- __type__ = "hook"
- __version__ = "0.19"
-
- __config__ = [("activated", "bool", "Activated", False)]
-
- __description__ = """Linkdecrypter.com hook plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def coreReady(self):
- try:
- self.loadPatterns()
- except Exception, e:
- self.logError(e)
-
- def loadPatterns(self):
- page = getURL("http://linkdecrypter.com/")
- m = re.search(r'<b>Supported\(\d+\)</b>: <i>([^+<]*)', page)
- if m is None:
- self.logError(_("Crypter list not found"))
- return
-
- builtin = [name.lower() for name in self.core.pluginManager.crypterPlugins.keys()]
- builtin.append("downloadserienjunkiesorg")
-
- crypter_pattern = re.compile("(\w[\w.-]+)")
- online = []
- for crypter in m.group(1).split(', '):
- m = re.match(crypter_pattern, crypter)
- if m and remove_chars(m.group(1), "-.") not in builtin:
- online.append(m.group(1).replace(".", "\\."))
-
- if not online:
- self.logError(_("Crypter list is empty"))
- return
-
- regexp = r"https?://([^.]+\.)*?(%s)/.*" % "|".join(online)
-
- dict = self.core.pluginManager.crypterPlugins[self.__name__]
- dict['pattern'] = regexp
- dict['re'] = re.compile(regexp)
-
- self.logDebug("REGEXP: " + regexp)
diff --git a/module/plugins/hooks/LinksnappyCom.py b/module/plugins/hooks/LinksnappyCom.py
deleted file mode 100644
index 20da68632..000000000
--- a/module/plugins/hooks/LinksnappyCom.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class LinksnappyCom(MultiHoster):
- __name__ = "LinksnappyCom"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Linksnappy.com hook plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def getHoster(self):
- json_data = getURL('http://gen.linksnappy.com/lseAPI.php?act=FILEHOSTS')
- json_data = json_loads(json_data)
-
- return json_data['return'].keys()
diff --git a/module/plugins/hooks/MegaDebridEu.py b/module/plugins/hooks/MegaDebridEu.py
deleted file mode 100644
index 605b04f21..000000000
--- a/module/plugins/hooks/MegaDebridEu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class MegaDebridEu(MultiHoster):
- __name__ = "MegaDebridEu"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False)]
-
- __description__ = """mega-debrid.eu hook plugin"""
- __author_name__ = "D.Ducatel"
- __author_mail__ = "dducatel@je-geek.fr"
-
-
- def getHoster(self):
- reponse = getURL('http://www.mega-debrid.eu/api.php?action=getHosters')
- json_data = json_loads(reponse)
-
- if json_data['response_code'] == "ok":
- host_list = [element[0] for element in json_data['hosters']]
- else:
- self.logError("Unable to retrieve hoster list")
- host_list = list()
-
- return host_list
diff --git a/module/plugins/hooks/MergeFiles.py b/module/plugins/hooks/MergeFiles.py
deleted file mode 100644
index a859092fb..000000000
--- a/module/plugins/hooks/MergeFiles.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import traceback
-
-from module.plugins.Hook import Hook, threaded
-from module.utils import save_join, fs_encode
-
-
-class MergeFiles(Hook):
- __name__ = "MergeFiles"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("activated", "bool", "Activated", False)]
-
- __description__ = """Merges parts splitted with hjsplit"""
- __author_name__ = "and9000"
- __author_mail__ = "me@has-no-mail.com"
-
- BUFFER_SIZE = 4096
-
-
- def setup(self):
- # nothing to do
- pass
-
- @threaded
- def packageFinished(self, pack):
- files = {}
- fid_dict = {}
- for fid, data in pack.getChildren().iteritems():
- if re.search("\.[0-9]{3}$", data['name']):
- if data['name'][:-4] not in files:
- files[data['name'][:-4]] = []
- files[data['name'][:-4]].append(data['name'])
- files[data['name'][:-4]].sort()
- fid_dict[data['name']] = fid
-
- download_folder = self.config['general']['download_folder']
-
- if self.config['general']['folder_per_package']:
- download_folder = save_join(download_folder, pack.folder)
-
- for name, file_list in files.iteritems():
- self.logInfo("Starting merging of %s" % name)
- final_file = open(save_join(download_folder, name), "wb")
-
- for splitted_file in file_list:
- self.logDebug("Merging part %s" % splitted_file)
- pyfile = self.core.files.getFile(fid_dict[splitted_file])
- pyfile.setStatus("processing")
- try:
- s_file = open(os.path.join(download_folder, splitted_file), "rb")
- size_written = 0
- s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file)))
- while True:
- f_buffer = s_file.read(self.BUFFER_SIZE)
- if f_buffer:
- final_file.write(f_buffer)
- size_written += self.BUFFER_SIZE
- pyfile.setProgress((size_written * 100) / s_file_size)
- else:
- break
- s_file.close()
- self.logDebug("Finished merging part %s" % splitted_file)
- except Exception, e:
- print traceback.print_exc()
- finally:
- pyfile.setProgress(100)
- pyfile.setStatus("finished")
- pyfile.release()
-
- final_file.close()
- self.logInfo("Finished merging of %s" % name)
diff --git a/module/plugins/hooks/MultiHome.py b/module/plugins/hooks/MultiHome.py
deleted file mode 100644
index e2167b65e..000000000
--- a/module/plugins/hooks/MultiHome.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from time import time
-
-from module.plugins.Hook import Hook
-
-
-class MultiHome(Hook):
- __name__ = "MultiHome"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("interfaces", "str", "Interfaces", "None")]
-
- __description__ = """Ip address changer"""
- __author_name__ = "mkaay"
- __author_mail__ = "mkaay@mkaay.de"
-
-
- def setup(self):
- self.register = {}
- self.interfaces = []
- self.parseInterfaces(self.getConfig("interfaces").split(";"))
- if not self.interfaces:
- self.parseInterfaces([self.config['download']['interface']])
- self.setConfig("interfaces", self.toConfig())
-
- def toConfig(self):
- return ";".join([i.adress for i in self.interfaces])
-
- def parseInterfaces(self, interfaces):
- for interface in interfaces:
- if not interface or str(interface).lower() == "none":
- continue
- self.interfaces.append(Interface(interface))
-
- def coreReady(self):
- requestFactory = self.core.requestFactory
- oldGetRequest = requestFactory.getRequest
-
- def getRequest(pluginName, account=None):
- iface = self.bestInterface(pluginName, account)
- if iface:
- iface.useFor(pluginName, account)
- requestFactory.iface = lambda: iface.adress
- self.logDebug("Multihome: using address: " + iface.adress)
- return oldGetRequest(pluginName, account)
-
- requestFactory.getRequest = getRequest
-
- def bestInterface(self, pluginName, account):
- best = None
- for interface in self.interfaces:
- if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account):
- best = interface
- return best
-
-
-class Interface(object):
-
- def __init__(self, adress):
- self.adress = adress
- self.history = {}
-
- def lastPluginAccess(self, pluginName, account):
- if (pluginName, account) in self.history:
- return self.history[(pluginName, account)]
- return 0
-
- def useFor(self, pluginName, account):
- self.history[(pluginName, account)] = time()
-
- def __repr__(self):
- return "<Interface - %s>" % self.adress
diff --git a/module/plugins/hooks/MultishareCz.py b/module/plugins/hooks/MultishareCz.py
deleted file mode 100644
index 9249106d6..000000000
--- a/module/plugins/hooks/MultishareCz.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class MultishareCz(MultiHoster):
- __name__ = "MultishareCz"
- __type__ = "hook"
- __version__ = "0.04"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", "uloz.to")]
-
- __description__ = """MultiShare.cz hook plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="([^"]+)"></td>\s*<td class="stav">[^>]*?alt="OK"'
-
-
- def getHoster(self):
- page = getURL("http://www.multishare.cz/monitoring/")
- return re.findall(self.HOSTER_PATTERN, page)
diff --git a/module/plugins/hooks/MyfastfileCom.py b/module/plugins/hooks/MyfastfileCom.py
deleted file mode 100644
index bbf665b08..000000000
--- a/module/plugins/hooks/MyfastfileCom.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class MyfastfileCom(MultiHoster):
- __name__ = "MyfastfileCom"
- __type__ = "hook"
- __version__ = "0.02"
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
- __description__ = """Myfastfile.com hook plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- def getHoster(self):
- json_data = getURL('http://myfastfile.com/api.php?hosts', decode=True)
- self.logDebug('JSON data: ' + json_data)
- json_data = json_loads(json_data)
-
- return json_data['hosts']
diff --git a/module/plugins/hooks/OverLoadMe.py b/module/plugins/hooks/OverLoadMe.py
deleted file mode 100644
index 5be0b8482..000000000
--- a/module/plugins/hooks/OverLoadMe.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class OverLoadMe(MultiHoster):
- __name__ = "OverLoadMe"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("https", "bool", "Enable HTTPS", True),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 12)]
-
- __description__ = """Over-Load.me hook plugin"""
- __author_name__ = "marley"
- __author_mail__ = "marley@over-load.me"
-
-
- def getHoster(self):
- https = "https" if self.getConfig("https") else "http"
- page = getURL(https + "://api.over-load.me/hoster.php",
- get={"auth": "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}
- ).replace("\"", "").strip()
- self.logDebug("Hosterlist: %s" % page)
-
- return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/module/plugins/hooks/PremiumTo.py b/module/plugins/hooks/PremiumTo.py
deleted file mode 100644
index 0572dab34..000000000
--- a/module/plugins/hooks/PremiumTo.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class PremiumTo(MultiHoster):
- __name__ = "PremiumTo"
- __type__ = "hook"
- __version__ = "0.04"
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for downloads from supported hosters:", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", "")]
- __description__ = """Premium.to hook plugin"""
- __author_name__ = ("RaNaN", "zoidberg", "stickell")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- def getHoster(self):
- page = getURL("http://premium.to/api/hosters.php",
- get={'username': self.account.username, 'password': self.account.password})
- return [x.strip() for x in page.replace("\"", "").split(";")]
-
- def coreReady(self):
- self.account = self.core.accountManager.getAccountPlugin("PremiumTo")
-
- user = self.account.selectAccount()[0]
-
- if not user:
- self.logError(_("Please add your premium.to account first and restart pyLoad"))
- return
-
- return MultiHoster.coreReady(self)
diff --git a/module/plugins/hooks/PremiumizeMe.py b/module/plugins/hooks/PremiumizeMe.py
deleted file mode 100644
index e7291ece9..000000000
--- a/module/plugins/hooks/PremiumizeMe.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class PremiumizeMe(MultiHoster):
- __name__ = "PremiumizeMe"
- __type__ = "hook"
- __version__ = "0.12"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Premiumize.me hook plugin"""
- __author_name__ = "Florian Franzen"
- __author_mail__ = "FlorianFranzen@gmail.com"
-
-
- def getHoster(self):
- # If no accounts are available there will be no hosters available
- if not self.account or not self.account.canUse():
- return []
-
- # Get account data
- (user, data) = self.account.selectAccount()
-
- # Get supported hosters list from premiumize.me using the
- # json API v1 (see https://secure.premiumize.me/?show=api)
- answer = getURL("https://api.premiumize.me/pm-api/v1.php?method=hosterlist&params[login]=%s&params[pass]=%s" % (
- user, data['password']))
- data = json_loads(answer)
-
- # If account is not valid thera are no hosters available
- if data['status'] != 200:
- return []
-
- # Extract hosters from json file
- return data['result']['hosterlist']
-
- def coreReady(self):
- # Get account plugin and check if there is a valid account available
- self.account = self.core.accountManager.getAccountPlugin("PremiumizeMe")
- if not self.account.canUse():
- self.account = None
- self.logError(_("Please add a valid premiumize.me account first and restart pyLoad."))
- return
-
- # Run the overwriten core ready which actually enables the multihoster hook
- return MultiHoster.coreReady(self)
diff --git a/module/plugins/hooks/RPNetBiz.py b/module/plugins/hooks/RPNetBiz.py
deleted file mode 100644
index 9b9b7cda9..000000000
--- a/module/plugins/hooks/RPNetBiz.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class RPNetBiz(MultiHoster):
- __name__ = "RPNetBiz"
- __type__ = "hook"
- __version__ = "0.1"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """RPNet.biz hook plugin"""
- __author_name__ = "Dman"
- __author_mail__ = "dmanugm@gmail.com"
-
-
- def getHoster(self):
- # No hosts supported if no account
- if not self.account or not self.account.canUse():
- return []
-
- # Get account data
- (user, data) = self.account.selectAccount()
-
- response = getURL("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": data['password'], "action": "showHosterList"})
- hoster_list = json_loads(response)
-
- # If account is not valid thera are no hosters available
- if 'error' in hoster_list:
- return []
-
- # Extract hosters from json file
- return hoster_list['hosters']
-
- def coreReady(self):
- # Get account plugin and check if there is a valid account available
- self.account = self.core.accountManager.getAccountPlugin("RPNetBiz")
- if not self.account.canUse():
- self.account = None
- self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
- return
-
- # Run the overwriten core ready which actually enables the multihoster hook
- return MultiHoster.coreReady(self)
diff --git a/module/plugins/hooks/RealdebridCom.py b/module/plugins/hooks/RealdebridCom.py
deleted file mode 100644
index 87902ac7f..000000000
--- a/module/plugins/hooks/RealdebridCom.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class RealdebridCom(MultiHoster):
- __name__ = "RealdebridCom"
- __type__ = "hook"
- __version__ = "0.43"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("https", "bool", "Enable HTTPS", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Real-Debrid.com hook plugin"""
- __author_name__ = "Devirex Hazzard"
- __author_mail__ = "naibaf_11@yahoo.de"
-
-
- def getHoster(self):
- https = "https" if self.getConfig("https") else "http"
- page = getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
-
- return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/module/plugins/hooks/RehostTo.py b/module/plugins/hooks/RehostTo.py
deleted file mode 100644
index d504bb83b..000000000
--- a/module/plugins/hooks/RehostTo.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class RehostTo(MultiHoster):
- __name__ = "RehostTo"
- __type__ = "hook"
- __version__ = "0.43"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
-
- __description__ = """Rehost.to hook plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def getHoster(self):
- page = getURL("http://rehost.to/api.php?cmd=get_supported_och_dl&long_ses=%s" % self.long_ses)
- return [x.strip() for x in page.replace("\"", "").split(",")]
-
- def coreReady(self):
- self.account = self.core.accountManager.getAccountPlugin("RehostTo")
-
- user = self.account.selectAccount()[0]
-
- if not user:
- self.logError("Rehost.to: " + _("Please add your rehost.to account first and restart pyLoad"))
- return
-
- data = self.account.getAccountInfo(user)
- self.ses = data['ses']
- self.long_ses = data['long_ses']
-
- return MultiHoster.coreReady(self)
diff --git a/module/plugins/hooks/RestartFailed.py b/module/plugins/hooks/RestartFailed.py
deleted file mode 100644
index 3aef6f8cd..000000000
--- a/module/plugins/hooks/RestartFailed.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hook import Hook
-
-
-class RestartFailed(Hook):
- __name__ = "RestartFailed"
- __type__ = "hook"
- __version__ = "1.55"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("interval", "int", "Check interval in minutes", 90)]
-
- __description__ = """Periodically restart all failed downloads in queue"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- MIN_INTERVAL = 15 * 60 #: 15m minimum check interval (value is in seconds)
-
- event_list = ["pluginConfigChanged"]
-
-
- def pluginConfigChanged(self, plugin, name, value):
- if name == "interval":
- interval = value * 60
- if self.MIN_INTERVAL <= interval != self.interval:
- self.core.scheduler.removeJob(self.cb)
- self.interval = interval
- self.initPeriodical()
- else:
- self.logDebug("Invalid interval value, kept current")
-
- def periodical(self):
- self.logInfo("Restart failed downloads")
- self.api.restartFailed()
-
- def setup(self):
- self.api = self.core.api
- self.interval = self.MIN_INTERVAL
-
- def coreReady(self):
- self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
diff --git a/module/plugins/hooks/SimplyPremiumCom.py b/module/plugins/hooks/SimplyPremiumCom.py
deleted file mode 100644
index a946d391e..000000000
--- a/module/plugins/hooks/SimplyPremiumCom.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class SimplyPremiumCom(MultiHoster):
- __name__ = "SimplyPremiumCom"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", "False"),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", "False"),
- ("interval", "int", "Reload interval in hours (0 to disable)", "24")]
-
- __description__ = """Simply-Premium.com hook plugin"""
- __author_name__ = "EvolutionClip"
- __author_mail__ = "evolutionclip@live.de"
-
-
- def getHoster(self):
- json_data = getURL('http://www.simply-premium.com/api/hosts.php?format=json&online=1')
- json_data = json_loads(json_data)
-
- host_list = [element['regex'] for element in json_data['result']]
-
- return host_list
diff --git a/module/plugins/hooks/SimplydebridCom.py b/module/plugins/hooks/SimplydebridCom.py
deleted file mode 100644
index ab13f312e..000000000
--- a/module/plugins/hooks/SimplydebridCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class SimplydebridCom(MultiHoster):
- __name__ = "SimplydebridCom"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", "")]
-
- __description__ = """Simply-Debrid.com hook plugin"""
- __author_name__ = "Kagenoshin"
- __author_mail__ = "kagenoshin@gmx.ch"
-
-
- def getHoster(self):
- page = getURL("http://simply-debrid.com/api.php?list=1")
- return [x.strip() for x in page.rstrip(';').replace("\"", "").split(";")]
diff --git a/module/plugins/hooks/UnSkipOnFail.py b/module/plugins/hooks/UnSkipOnFail.py
deleted file mode 100644
index f25c5e2b4..000000000
--- a/module/plugins/hooks/UnSkipOnFail.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from os.path import basename
-
-from module.PyFile import PyFile
-from module.plugins.Hook import Hook
-from module.utils import fs_encode
-
-
-class UnSkipOnFail(Hook):
- __name__ = "UnSkipOnFail"
- __type__ = "hook"
- __version__ = "0.01"
-
- __config__ = [("activated", "bool", "Activated", True)]
-
- __description__ = """When a download fails, restart skipped duplicates"""
- __author_name__ = "hagg"
- __author_mail__ = None
-
-
- def downloadFailed(self, pyfile):
- pyfile_name = basename(pyfile.name)
- pid = pyfile.package().id
- msg = 'look for skipped duplicates for %s (pid:%s)...'
- self.logInfo(msg % (pyfile_name, pid))
- dups = self.findDuplicates(pyfile)
- for link in dups:
- # check if link is "skipped"(=4)
- if link.status == 4:
- lpid = link.packageID
- self.logInfo('restart "%s" (pid:%s)...' % (pyfile_name, lpid))
- self.setLinkStatus(link, "queued")
-
- def findDuplicates(self, pyfile):
- """ Search all packages for duplicate links to "pyfile".
- Duplicates are links that would overwrite "pyfile".
- To test on duplicity the package-folder and link-name
- of twolinks are compared (basename(link.name)).
- So this method returns a list of all links with equal
- package-folders and filenames as "pyfile", but except
- the data for "pyfile" iotselöf.
- It does MOT check the link's status.
- """
- dups = []
- pyfile_name = fs_encode(basename(pyfile.name))
- # get packages (w/o files, as most file data is useless here)
- queue = self.core.api.getQueue()
- for package in queue:
- # check if package-folder equals pyfile's package folder
- if fs_encode(package.folder) == fs_encode(pyfile.package().folder):
- # now get packaged data w/ files/links
- pdata = self.core.api.getPackageData(package.pid)
- if pdata.links:
- for link in pdata.links:
- link_name = fs_encode(basename(link.name))
- # check if link name collides with pdata's name
- if link_name == pyfile_name:
- # at last check if it is not pyfile itself
- if link.fid != pyfile.id:
- dups.append(link)
- return dups
-
- def setLinkStatus(self, link, new_status):
- """ Change status of "link" to "new_status".
- "link" has to be a valid FileData object,
- "new_status" has to be a valid status name
- (i.e. "queued" for this Plugin)
- It creates a temporary PyFile object using
- "link" data, changes its status, and tells
- the core.files-manager to save its data.
- """
- pyfile = PyFile(self.core.files,
- link.fid,
- link.url,
- link.name,
- link.size,
- link.status,
- link.error,
- link.plugin,
- link.packageID,
- link.order)
- pyfile.setStatus(new_status)
- self.core.files.save()
- pyfile.release()
diff --git a/module/plugins/hooks/UnrestrictLi.py b/module/plugins/hooks/UnrestrictLi.py
deleted file mode 100644
index ee5d79269..000000000
--- a/module/plugins/hooks/UnrestrictLi.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class UnrestrictLi(MultiHoster):
- __name__ = "UnrestrictLi"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", ""),
- ("unloadFailing", "bool", "Revert to standard download if download fails", False),
- ("interval", "int", "Reload interval in hours (0 to disable)", 24),
- ("history", "bool", "Delete History", False)]
-
- __description__ = """Unrestrict.li hook plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def getHoster(self):
- json_data = getURL('http://unrestrict.li/api/jdownloader/hosts.php?format=json')
- json_data = json_loads(json_data)
-
- host_list = [element['host'] for element in json_data['result']]
-
- return host_list
diff --git a/module/plugins/hooks/UpdateManager.py b/module/plugins/hooks/UpdateManager.py
deleted file mode 100644
index 546e6e6e8..000000000
--- a/module/plugins/hooks/UpdateManager.py
+++ /dev/null
@@ -1,281 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import sys
-
-from operator import itemgetter
-from os import path, remove, stat
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hook import Expose, Hook, threaded
-from module.utils import save_join
-
-
-class UpdateManager(Hook):
- __name__ = "UpdateManager"
- __type__ = "hook"
- __version__ = "0.35"
-
- __config__ = [("activated", "bool", "Activated", True),
- ("mode", "pyLoad + plugins;plugins only", "Check updates for", "pyLoad + plugins"),
- ("interval", "int", "Check interval in hours", 8),
- ("reloadplugins", "bool", "Monitor plugins for code changes (debug mode only)", True),
- ("nodebugupdate", "bool", "Don't check for updates in debug mode", True)]
-
- __description__ = """ Check for updates """
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
- event_list = ["pluginConfigChanged"]
-
- SERVER_URL = "http://updatemanager.pyload.org"
- MIN_INTERVAL = 3 * 60 * 60 #: 3h minimum check interval (value is in seconds)
-
-
- def pluginConfigChanged(self, plugin, name, value):
- if name == "interval":
- interval = value * 60 * 60
- if self.MIN_INTERVAL <= interval != self.interval:
- self.core.scheduler.removeJob(self.cb)
- self.interval = interval
- self.initPeriodical()
- else:
- self.logDebug("Invalid interval value, kept current")
- elif name == "reloadplugins":
- if self.cb2:
- self.core.scheduler.removeJob(self.cb2)
- if value is True and self.core.debug:
- self.periodical2()
-
- def coreReady(self):
- self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
- x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins"))
- self.core.scheduler.addJob(10, x, threaded=False)
-
- def unload(self):
- self.pluginConfigChanged(self.__name__, "reloadplugins", False)
-
- def setup(self):
- self.cb2 = None
- self.interval = self.MIN_INTERVAL
- self.updating = False
- self.info = {'pyload': False, 'version': None, 'plugins': False}
- self.mtimes = {} #: store modification time for each plugin
-
- def periodical2(self):
- if not self.updating:
- self.autoreloadPlugins()
- self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False)
-
- @Expose
- def autoreloadPlugins(self):
- """ reload and reindex all modified plugins """
- modules = filter(
- lambda m: m and (m.__name__.startswith("module.plugins.") or
- m.__name__.startswith("userplugins.")) and
- m.__name__.count(".") >= 2, sys.modules.itervalues()
- )
-
- reloads = []
-
- for m in modules:
- root, type, name = m.__name__.rsplit(".", 2)
- id = (type, name)
- if type in self.core.pluginManager.plugins:
- f = m.__file__.replace(".pyc", ".py")
- if not path.isfile(f):
- continue
-
- mtime = stat(f).st_mtime
-
- if id not in self.mtimes:
- self.mtimes[id] = mtime
- elif self.mtimes[id] < mtime:
- reloads.append(id)
- self.mtimes[id] = mtime
-
- return True if self.core.pluginManager.reloadPlugins(reloads) else False
-
- def periodical(self):
- if not self.info['pyload'] and not (self.getConfig("nodebugupdate") and self.core.debug):
- self.updateThread()
-
- def server_request(self):
- try:
- return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
- except:
- self.logWarning(_("Unable to contact server to get updates"))
-
- @threaded
- def updateThread(self):
- self.updating = True
- status = self.update(onlyplugin=self.getConfig("mode") == "plugins only")
- if status == 2:
- self.core.api.restart()
- else:
- self.updating = False
-
- @Expose
- def updatePlugins(self):
- """ simple wrapper for calling plugin update quickly """
- return self.update(onlyplugin=True)
-
- @Expose
- def update(self, onlyplugin=False):
- """ check for updates """
- data = self.server_request()
- if not data:
- exitcode = 0
- elif data[0] == "None":
- self.logInfo(_("No new pyLoad version available"))
- updates = data[1:]
- exitcode = self._updatePlugins(updates)
- elif onlyplugin:
- exitcode = 0
- else:
- newversion = data[0]
- self.logInfo(_("*** New pyLoad Version %s available ***") % newversion)
- self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
- exitcode = 3
- self.info['pyload'] = True
- self.info['version'] = newversion
- return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available
-
- def _updatePlugins(self, updates):
- """ check for plugin updates """
-
- if self.info['plugins']:
- return False #: plugins were already updated
-
- updated = []
-
- vre = re.compile(r'__version__.*=.*("|\')([0-9.]+)')
- url = updates[0]
- schema = updates[1].split('|')
- if "BLACKLIST" in updates:
- blacklist = updates[updates.index('BLACKLIST') + 1:]
- updates = updates[2:updates.index('BLACKLIST')]
- else:
- blacklist = None
- updates = updates[2:]
-
- upgradable = sorted(map(lambda x: dict(zip(schema, x.split('|'))), updates), key=itemgetter("type", "name"))
- for plugin in upgradable:
- filename = plugin['name']
- prefix = plugin['type']
- version = plugin['version']
-
- if filename.endswith(".pyc"):
- name = filename[:filename.find("_")]
- else:
- name = filename.replace(".py", "")
-
- #@TODO: obsolete after 0.4.10
- if prefix.endswith("s"):
- type = prefix[:-1]
- else:
- type = prefix
-
- plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
-
- oldver = float(plugins[name]['v']) if name in plugins else None
- newver = float(version)
-
- if not oldver:
- msg = "New [%(type)s] %(name)s (v%(newver)s)"
- elif newver > oldver:
- msg = "New version of [%(type)s] %(name)s (v%(oldver)s -> v%(newver)s)"
- else:
- continue
-
- self.logInfo(_(msg) % {
- 'type': type,
- 'name': name,
- 'oldver': oldver,
- 'newver': newver,
- })
-
- try:
- content = getURL(url % plugin)
- m = vre.search(content)
- if m and m.group(2) == version:
- f = open(save_join("userplugins", prefix, filename), "wb")
- f.write(content)
- f.close()
- updated.append((prefix, name))
- else:
- raise Exception, _("Version mismatch")
- except Exception, e:
- self.logError(_("Error updating plugin %s") % filename, str(e))
-
- if blacklist:
- blacklisted = sorted(map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist))
-
- # Always protect UpdateManager from self-removing
- try:
- blacklisted.remove(("hook", "UpdateManager"))
- except:
- pass
-
- removed = self.removePlugins(blacklisted)
- for t, n in removed:
- self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % {
- 'type': t,
- 'name': n,
- })
-
- if updated:
- reloaded = self.core.pluginManager.reloadPlugins(updated)
- if reloaded:
- self.logInfo(_("Plugins updated and reloaded"))
- exitcode = 1
- else:
- self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***"))
- self.info['plugins'] = True
- exitcode = 2
- else:
- self.logInfo(_("No plugin updates available"))
- exitcode = 0
-
- return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required
-
- @Expose
- def removePlugins(self, type_plugins):
- """ delete plugins from disk """
-
- if not type_plugins:
- return
-
- self.logDebug("Request deletion of plugins: %s" % type_plugins)
-
- removed = []
-
- for type, name in type_plugins:
- err = False
- file = name + ".py"
-
- for root in ("userplugins", path.join(pypath, "module", "plugins")):
-
- filename = save_join(root, type, file)
- try:
- remove(filename)
- except Exception, e:
- self.logDebug("Error deleting \"%s\"" % path.basename(filename), str(e))
- err = True
-
- filename += "c"
- if path.isfile(filename):
- try:
- if type == "hook":
- self.manager.deactivateHook(name)
- remove(filename)
- except Exception, e:
- self.logDebug("Error deleting \"%s\"" % path.basename(filename), str(e))
- err = True
-
- if not err:
- id = (type, name)
- removed.append(id)
-
- return removed #: return a list of the plugins successfully removed
diff --git a/module/plugins/hooks/WindowsPhoneToastNotify.py b/module/plugins/hooks/WindowsPhoneToastNotify.py
deleted file mode 100644
index 01570b966..000000000
--- a/module/plugins/hooks/WindowsPhoneToastNotify.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import httplib
-import time
-
-from module.plugins.Hook import Hook
-
-
-class WindowsPhoneToastNotify(Hook):
- __name__ = "WindowsPhoneToastNotify"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("force", "bool", "Force even if client is connected", False),
- ("pushId", "str", "pushId", ""),
- ("pushUrl", "str", "pushUrl", ""),
- ("pushTimeout", "int", "Timeout between notifications in seconds", 0)]
-
- __description__ = """Send push notifications to Windows Phone"""
- __author_name__ = "Andy Voigt"
- __author_mail__ = "phone-support@hotmail.de"
-
-
- def setup(self):
- self.info = {}
-
- def getXmlData(self):
- myxml = ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
- "<wp:Toast> <wp:Text1>Pyload Mobile</wp:Text1> <wp:Text2>Captcha waiting!</wp:Text2> "
- "</wp:Toast> </wp:Notification>")
- return myxml
-
- def doRequest(self):
- URL = self.getConfig("pushUrl")
- request = self.getXmlData()
- webservice = httplib.HTTP(URL)
- webservice.putrequest("POST", self.getConfig("pushId"))
- webservice.putheader("Host", URL)
- webservice.putheader("Content-type", "text/xml")
- webservice.putheader("X-NotificationClass", "2")
- webservice.putheader("X-WindowsPhone-Target", "toast")
- webservice.putheader("Content-length", "%d" % len(request))
- webservice.endheaders()
- webservice.send(request)
- webservice.close()
- self.setStorage("LAST_NOTIFY", time.time())
-
- def newCaptchaTask(self, task):
- if not self.getConfig("pushId") or not self.getConfig("pushUrl"):
- return False
-
- if self.core.isClientConnected() and not self.getConfig("force"):
- return False
-
- if (time.time() - float(self.getStorage("LAST_NOTIFY", 0))) < self.getConf("pushTimeout"):
- return False
-
- self.doRequest()
diff --git a/module/plugins/hooks/XFileSharingPro.py b/module/plugins/hooks/XFileSharingPro.py
deleted file mode 100644
index eb0376921..000000000
--- a/module/plugins/hooks/XFileSharingPro.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-
-
-class XFileSharingPro(Hook):
- __name__ = "XFileSharingPro"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", True),
- ("loadDefault", "bool", "Include default (built-in) hoster list", True),
- ("includeList", "str", "Include hosters (comma separated)", ""),
- ("excludeList", "str", "Exclude hosters (comma separated)", "")]
-
- __description__ = """XFileSharingPro hook plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def coreReady(self):
- self.loadPattern()
-
- def loadPattern(self):
- hosterList = self.getConfigSet('includeList')
- excludeList = self.getConfigSet('excludeList')
-
- if self.getConfig('loadDefault'):
- hosterList |= set((
- #WORKING HOSTERS:
- "aieshare.com", "asixfiles.com", "banashare.com", "cyberlocker.ch", "eyesfile.co", "eyesfile.com",
- "fileband.com", "filedwon.com", "filedownloads.org", "hipfile.com", "kingsupload.com", "mlfat4arab.com",
- "netuploaded.com", "odsiebie.pl", "q4share.com", "ravishare.com", "uptobox.com", "verzend.be",
- "xvidstage.com", "thefile.me", "sharesix.com", "hostingbulk.com",
- #NOT TESTED:
- "bebasupload.com", "boosterking.com", "divxme.com", "filevelocity.com", "glumbouploads.com",
- "grupload.com", "heftyfile.com", "host4desi.com", "laoupload.com", "linkzhost.com", "movreel.com",
- "rockdizfile.com", "limfile.com", "share76.com", "sharebeast.com", "sharehut.com", "sharerun.com",
- "shareswift.com", "sharingonline.com", "6ybh-upload.com", "skipfile.com", "spaadyshare.com",
- "space4file.com", "uploadbaz.com", "uploadc.com", "uploaddot.com", "uploadfloor.com", "uploadic.com",
- "uploadville.com", "vidbull.com", "zalaa.com", "zomgupload.com", "kupload.org", "movbay.org",
- "multishare.org", "omegave.org", "toucansharing.org", "uflinq.org", "banicrazy.info", "flowhot.info",
- "upbrasil.info", "shareyourfilez.biz", "bzlink.us", "cloudcache.cc", "fileserver.cc", "farshare.to",
- "filemaze.ws", "filehost.ws", "filestock.ru", "moidisk.ru", "4up.im", "100shared.com", "sharesix.com",
- "thefile.me", "filenuke.com", "sharerepo.com", "mightyupload.com",
- #WRONG FILE NAME:
- "sendmyway.com", "upchi.co.il",
- #NOT WORKING:
- "amonshare.com", "imageporter.com", "file4safe.com",
- #DOWN OR BROKEN:
- "ddlanime.com", "fileforth.com", "loombo.com", "goldfile.eu", "putshare.com"
- ))
-
- hosterList -= (excludeList)
- hosterList -= set(('', u''))
-
- if not hosterList:
- self.unload()
- return
-
- regexp = r"http://(?:[^/]*\.)?(%s)/\w{12}" % ("|".join(sorted(hosterList)).replace('.', '\.'))
- #self.logDebug(regexp)
-
- dict = self.core.pluginManager.hosterPlugins['XFileSharingPro']
- dict['pattern'] = regexp
- dict['re'] = re.compile(regexp)
- self.logDebug("Pattern loaded - handling %d hosters" % len(hosterList))
-
- def getConfigSet(self, option):
- s = self.getConfig(option).lower().replace('|', ',').replace(';', ',')
- return set([x.strip() for x in s.split(',')])
-
- def unload(self):
- dict = self.core.pluginManager.hosterPlugins['XFileSharingPro']
- dict['pattern'] = r'^unmatchable$'
- dict['re'] = re.compile(r'^unmatchable$')
diff --git a/module/plugins/hooks/XMPPInterface.py b/module/plugins/hooks/XMPPInterface.py
deleted file mode 100644
index c4a94a8bc..000000000
--- a/module/plugins/hooks/XMPPInterface.py
+++ /dev/null
@@ -1,233 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from pyxmpp import streamtls
-from pyxmpp.all import JID, Message
-from pyxmpp.interface import implements
-from pyxmpp.interfaces import *
-from pyxmpp.jabber.client import JabberClient
-
-from module.plugins.hooks.IRCInterface import IRCInterface
-
-
-class XMPPInterface(IRCInterface, JabberClient):
- __name__ = "XMPPInterface"
- __type__ = "hook"
- __version__ = "0.11"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("jid", "str", "Jabber ID", "user@exmaple-jabber-server.org"),
- ("pw", "str", "Password", ""),
- ("tls", "bool", "Use TLS", False),
- ("owners", "str", "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"),
- ("info_file", "bool", "Inform about every file finished", False),
- ("info_pack", "bool", "Inform about every package finished", True),
- ("captcha", "bool", "Send captcha requests", True)]
-
- __description__ = """Connect to jabber and let owner perform different tasks"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- implements(IMessageHandlersProvider)
-
- def __init__(self, core, manager):
- IRCInterface.__init__(self, core, manager)
-
- self.jid = JID(self.getConfig("jid"))
- password = self.getConfig("pw")
-
- # if bare JID is provided add a resource -- it is required
- if not self.jid.resource:
- self.jid = JID(self.jid.node, self.jid.domain, "pyLoad")
-
- if self.getConfig("tls"):
- tls_settings = streamtls.TLSSettings(require=True, verify_peer=False)
- auth = ("sasl:PLAIN", "sasl:DIGEST-MD5")
- else:
- tls_settings = None
- auth = ("sasl:DIGEST-MD5", "digest")
-
- # setup client with provided connection information
- # and identity data
- JabberClient.__init__(self, self.jid, password,
- disco_name="pyLoad XMPP Client", disco_type="bot",
- tls_settings=tls_settings, auth_methods=auth)
-
- self.interface_providers = [
- VersionHandler(self),
- self,
- ]
-
- def coreReady(self):
- self.new_package = {}
-
- self.start()
-
- def packageFinished(self, pypack):
- try:
- if self.getConfig("info_pack"):
- self.announce(_("Package finished: %s") % pypack.name)
- except:
- pass
-
- def downloadFinished(self, pyfile):
- try:
- if self.getConfig("info_file"):
- self.announce(
- _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname})
- except:
- pass
-
- def run(self):
- # connect to IRC etc.
- self.connect()
- try:
- self.loop()
- except Exception, ex:
- self.logError("pyLoad XMPP: %s" % str(ex))
-
- def stream_state_changed(self, state, arg):
- """This one is called when the state of stream connecting the component
- to a server changes. This will usually be used to let the user
- know what is going on."""
- self.logDebug("pyLoad XMPP: *** State changed: %s %r ***" % (state, arg))
-
- def disconnected(self):
- self.logDebug("pyLoad XMPP: Client was disconnected")
-
- def stream_closed(self, stream):
- self.logDebug("pyLoad XMPP: Stream was closed | %s" % stream)
-
- def stream_error(self, err):
- self.logDebug("pyLoad XMPP: Stream Error: %s" % err)
-
- def get_message_handlers(self):
- """Return list of (message_type, message_handler) tuples.
-
- The handlers returned will be called when matching message is received
- in a client session."""
- return [("normal", self.message)]
-
- def message(self, stanza):
- """Message handler for the component."""
- subject = stanza.get_subject()
- body = stanza.get_body()
- t = stanza.get_type()
- self.logDebug(u'pyLoad XMPP: Message from %s received.' % (unicode(stanza.get_from(),)))
- self.logDebug(u'pyLoad XMPP: Body: %s Subject: %s Type: %s' % (body, subject, t))
-
- if t == "headline":
- # 'headline' messages should never be replied to
- return True
- if subject:
- subject = u"Re: " + subject
-
- to_jid = stanza.get_from()
- from_jid = stanza.get_to()
-
- #j = JID()
- to_name = to_jid.as_utf8()
- from_name = from_jid.as_utf8()
-
- names = self.getConfig("owners").split(";")
-
- if to_name in names or to_jid.node + "@" + to_jid.domain in names:
- messages = []
-
- trigger = "pass"
- args = None
-
- try:
- temp = body.split()
- trigger = temp[0]
- if len(temp) > 1:
- args = temp[1:]
- except:
- pass
-
- handler = getattr(self, "event_%s" % trigger, self.event_pass)
- try:
- res = handler(args)
- for line in res:
- m = Message(
- to_jid=to_jid,
- from_jid=from_jid,
- stanza_type=stanza.get_type(),
- subject=subject,
- body=line)
-
- messages.append(m)
- except Exception, e:
- self.logError("pyLoad XMPP: " + repr(e))
-
- return messages
-
- else:
- return True
-
- def response(self, msg, origin=""):
- return self.announce(msg)
-
- def announce(self, message):
- """ send message to all owners"""
- for user in self.getConfig("owners").split(";"):
- self.logDebug("pyLoad XMPP: Send message to %s" % user)
-
- to_jid = JID(user)
-
- m = Message(from_jid=self.jid,
- to_jid=to_jid,
- stanza_type="chat",
- body=message)
-
- stream = self.get_stream()
- if not stream:
- self.connect()
- stream = self.get_stream()
-
- stream.send(m)
-
- def beforeReconnecting(self, ip):
- self.disconnect()
-
- def afterReconnecting(self, ip):
- self.connect()
-
-
-class VersionHandler(object):
- """Provides handler for a version query.
-
- This class will answer version query and announce 'jabber:iq:version' namespace
- in the client's disco#info results."""
-
- implements(IIqHandlersProvider, IFeaturesProvider)
-
- def __init__(self, client):
- """Just remember who created this."""
- self.client = client
-
- def get_features(self):
- """Return namespace which should the client include in its reply to a
- disco#info query."""
- return ["jabber:iq:version"]
-
- def get_iq_get_handlers(self):
- """Return list of tuples (element_name, namespace, handler) describing
- handlers of <iq type='get'/> stanzas"""
- return [("query", "jabber:iq:version", self.get_version)]
-
- def get_iq_set_handlers(self):
- """Return empty list, as this class provides no <iq type='set'/> stanza handler."""
- return []
-
- def get_version(self, iq):
- """Handler for jabber:iq:version queries.
-
- jabber:iq:version queries are not supported directly by PyXMPP, so the
- XML node is accessed directly through the libxml2 API. This should be
- used very carefully!"""
- iq = iq.make_result_response()
- q = iq.new_query("jabber:iq:version")
- q.newTextChild(q.ns(), "name", "Echo component")
- q.newTextChild(q.ns(), "version", "1.0")
- return iq
diff --git a/module/plugins/hooks/ZeveraCom.py b/module/plugins/hooks/ZeveraCom.py
deleted file mode 100644
index 0d5e23118..000000000
--- a/module/plugins/hooks/ZeveraCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.MultiHoster import MultiHoster
-
-
-class ZeveraCom(MultiHoster):
- __name__ = "ZeveraCom"
- __type__ = "hook"
- __version__ = "0.02"
-
- __config__ = [("activated", "bool", "Activated", False),
- ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
- ("hosterList", "str", "Hoster list (comma separated)", "")]
-
- __description__ = """Real-Debrid.com hook plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def getHoster(self):
- page = getURL("http://www.zevera.com/jDownloader.ashx?cmd=gethosters")
- return [x.strip() for x in page.replace("\"", "").split(",")]
diff --git a/module/plugins/hoster/AlldebridCom.py b/module/plugins/hoster/AlldebridCom.py
deleted file mode 100644
index 7201f1929..000000000
--- a/module/plugins/hoster/AlldebridCom.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import randrange
-from urllib import unquote
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class AlldebridCom(Hoster):
- __name__ = "AlldebridCom"
- __type__ = "hoster"
- __version__ = "0.34"
-
- __pattern__ = r'https?://(?:[^/]*\.)?alldebrid\..*'
-
- __description__ = """Alldebrid.com hoster plugin"""
- __author_name__ = "Andy Voigt"
- __author_mail__ = "spamsales@online.de"
-
-
- def getFilename(self, url):
- try:
- name = unquote(url.rsplit("/", 1)[1])
- except IndexError:
- name = "Unknown_Filename..."
- if name.endswith("..."): # incomplete filename, append random stuff
- name += "%s.tmp" % randrange(100, 999)
- return name
-
- def setup(self):
- self.chunkLimit = 16
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "AllDebrid")
- self.fail("No AllDebrid account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- password = self.getPassword().splitlines()
- password = "" if not password else password[0]
-
- url = "http://www.alldebrid.com/service.php?link=%s&json=true&pw=%s" % (pyfile.url, password)
- page = self.load(url)
- data = json_loads(page)
-
- self.logDebug("Json data: %s" % str(data))
-
- if data['error']:
- if data['error'] == "This link isn't available on the hoster website.":
- self.offline()
- else:
- self.logWarning(data['error'])
- self.tempOffline()
- else:
- if pyfile.name and not pyfile.name.endswith('.tmp'):
- pyfile.name = data['filename']
- pyfile.size = parseFileSize(data['filesize'])
- new_url = data['link']
-
- if self.getConfig("https"):
- new_url = new_url.replace("http://", "https://")
- else:
- new_url = new_url.replace("https://", "http://")
-
- if new_url != pyfile.url:
- self.logDebug("New URL: %s" % new_url)
-
- if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
- #only use when name wasnt already set
- pyfile.name = self.getFilename(new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload({"error": "<title>An error occured while processing your request</title>",
- "empty": re.compile(r"^$")})
-
- if check == "error":
- self.retry(wait_time=60, reason="An error occured while generating link.")
- elif check == "empty":
- self.retry(wait_time=60, reason="Downloaded File was empty.")
diff --git a/module/plugins/hoster/BasePlugin.py b/module/plugins/hoster/BasePlugin.py
deleted file mode 100644
index 54d789054..000000000
--- a/module/plugins/hoster/BasePlugin.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from re import match, search
-from urllib import unquote
-from urlparse import urlparse
-
-from module.network.HTTPRequest import BadHeader
-from module.plugins.Hoster import Hoster
-from module.utils import html_unescape, remove_chars
-
-
-class BasePlugin(Hoster):
- __name__ = "BasePlugin"
- __type__ = "hoster"
- __version__ = "0.20"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """Base Plugin when any other didnt fit"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- """main function"""
-
- #debug part, for api exerciser
- if pyfile.url.startswith("DEBUG_API"):
- self.multiDL = False
- return
-
- # self.__name__ = "NetloadIn"
- # pyfile.name = "test"
- # self.html = self.load("http://localhost:9000/short")
- # self.download("http://localhost:9000/short")
- # self.api = self.load("http://localhost:9000/short")
- # self.decryptCaptcha("http://localhost:9000/captcha")
- #
- # if pyfile.url == "79":
- # self.core.api.addPackage("test", [str(i) for i in xrange(80)], 1)
- #
- # return
- if pyfile.url.startswith("http"):
-
- try:
- self.downloadFile(pyfile)
- except BadHeader, e:
- if e.code in (401, 403):
- self.logDebug("Auth required")
-
- account = self.core.accountManager.getAccountPlugin('Http')
- servers = [x['login'] for x in account.getAllAccounts()]
- server = urlparse(pyfile.url).netloc
-
- if server in servers:
- self.logDebug("Logging on to %s" % server)
- self.req.addAuth(account.accounts[server]['password'])
- else:
- for pwd in pyfile.package().password.splitlines():
- if ":" in pwd:
- self.req.addAuth(pwd.strip())
- break
- else:
- self.fail(_("Authorization required (username:password)"))
-
- self.downloadFile(pyfile)
- else:
- raise
-
- else:
- self.fail("No Plugin matched and not a downloadable url.")
-
- def downloadFile(self, pyfile):
- url = pyfile.url
-
- for _ in xrange(5):
- header = self.load(url, just_header=True)
-
- # self.load does not raise a BadHeader on 404 responses, do it here
- if 'code' in header and header['code'] == 404:
- raise BadHeader(404)
-
- if 'location' in header:
- self.logDebug("Location: " + header['location'])
- base = match(r'https?://[^/]+', url).group(0)
- if header['location'].startswith("http"):
- url = header['location']
- elif header['location'].startswith("/"):
- url = base + unquote(header['location'])
- else:
- url = '%s/%s' % (base, unquote(header['location']))
- else:
- break
-
- name = html_unescape(unquote(urlparse(url).path.split("/")[-1]))
-
- if 'content-disposition' in header:
- self.logDebug("Content-Disposition: " + header['content-disposition'])
- m = search("filename(?P<type>=|\*=(?P<enc>.+)'')(?P<name>.*)", header['content-disposition'])
- if m:
- disp = m.groupdict()
- self.logDebug(disp)
- if not disp['enc']:
- disp['enc'] = 'utf-8'
- name = remove_chars(disp['name'], "\"';").strip()
- name = unicode(unquote(name), disp['enc'])
-
- if not name:
- name = url
- pyfile.name = name
- self.logDebug("Filename: %s" % pyfile.name)
- self.download(url, disposition=True)
diff --git a/module/plugins/hoster/BayfilesCom.py b/module/plugins/hoster/BayfilesCom.py
deleted file mode 100644
index dc7e8cbf0..000000000
--- a/module/plugins/hoster/BayfilesCom.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BayfilesCom(SimpleHoster):
- __name__ = "BayfilesCom"
- __type__ = "hoster"
- __version__ = "0.07"
-
- __pattern__ = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>[a-zA-Z0-9]+/[a-zA-Z0-9]+/[^/]+)'
-
- __description__ = """Bayfiles.com hoster plugin"""
- __author_name__ = ("zoidberg", "Walter Purcaro")
- __author_mail__ = ("zoidberg@mujmail.cz", "vuolter@gmail.com")
-
- FILE_INFO_PATTERN = r'<p title="(?P<N>[^"]+)">[^<]*<strong>(?P<S>[0-9., ]+)(?P<U>[kKMG])i?B</strong></p>'
- OFFLINE_PATTERN = r'(<p>The requested file could not be found.</p>|<title>404 Not Found</title>)'
-
- WAIT_PATTERN = r'>Your IP [0-9.]* has recently downloaded a file\. Upgrade to premium or wait (\d+) minutes\.<'
- VARS_PATTERN = r'var vfid = (\d+);\s*var delay = (\d+);'
- FREE_LINK_PATTERN = r"javascript:window.location.href = '([^']+)';"
- PREMIUM_LINK_PATTERN = r'(?:<a class="highlighted-btn" href="|(?=http://s\d+\.baycdn\.com/dl/))(.*?)"'
-
-
- def handleFree(self):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(int(m.group(1)) * 60)
- self.retry()
-
- # Get download token
- m = re.search(self.VARS_PATTERN, self.html)
- if m is None:
- self.parseError('VARS')
- vfid, delay = m.groups()
-
- response = json_loads(self.load('http://bayfiles.com/ajax_download', get={
- "_": time() * 1000,
- "action": "startTimer",
- "vfid": vfid}, decode=True))
-
- if not "token" in response or not response['token']:
- self.fail('No token')
-
- self.wait(int(delay))
-
- self.html = self.load('http://bayfiles.com/ajax_download', get={
- "token": response['token'],
- "action": "getLink",
- "vfid": vfid})
-
- # Get final link and download
- m = re.search(self.FREE_LINK_PATTERN, self.html)
- if m is None:
- self.parseError("Free link")
- self.startDownload(m.group(1))
-
- def handlePremium(self):
- m = re.search(self.PREMIUM_LINK_PATTERN, self.html)
- if m is None:
- self.parseError("Premium link")
- self.startDownload(m.group(1))
-
- def startDownload(self, url):
- self.logDebug("%s URL: %s" % ("Premium" if self.premium else "Free", url))
- self.download(url)
- # check download
- check = self.checkDownload({
- "waitforfreeslots": re.compile(r"<title>BayFiles</title>"),
- "notfound": re.compile(r"<title>404 Not Found</title>")
- })
- if check == "waitforfreeslots":
- self.retry(30, 5 * 60, "Wait for free slot")
- elif check == "notfound":
- self.retry(30, 5 * 60, "404 Not found")
-
-
-getInfo = create_getInfo(BayfilesCom)
diff --git a/module/plugins/hoster/BezvadataCz.py b/module/plugins/hoster/BezvadataCz.py
deleted file mode 100644
index 7156db473..000000000
--- a/module/plugins/hoster/BezvadataCz.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BezvadataCz(SimpleHoster):
- __name__ = "BezvadataCz"
- __type__ = "hoster"
- __version__ = "0.24"
-
- __pattern__ = r'http://(?:www\.)?bezvadata.cz/stahnout/.*'
-
- __description__ = """BezvaData.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
- FILE_SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
- OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
-
- def handleFree(self):
- #download button
- m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
- if m is None:
- self.parseError("page1 URL")
- url = "http://bezvadata.cz%s" % m.group(1)
-
- #captcha form
- self.html = self.load(url)
- self.checkErrors()
- for _ in xrange(5):
- action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
- if not inputs:
- self.parseError("FreeForm")
-
- m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
- if m is None:
- self.parseError("captcha img")
-
- #captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url
- self.load, proper_load = self.loadcaptcha, self.load
- try:
- inputs['captcha'] = self.decryptCaptcha(m.group(1), imgtype='png')
- finally:
- self.load = proper_load
-
- if '<img src="data:image/png;base64' in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("No valid captcha code entered")
-
- #download url
- self.html = self.load("http://bezvadata.cz%s" % action, post=inputs)
- self.checkErrors()
- m = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html)
- if m is None:
- self.parseError("page2 URL")
- url = "http://bezvadata.cz%s" % m.group(1)
- self.logDebug("DL URL %s" % url)
-
- #countdown
- m = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html)
- wait_time = (int(m.group(1)) * 60 + int(m.group(2)) + 1) if m else 120
- self.wait(wait_time, False)
-
- self.download(url)
-
- def checkErrors(self):
- if 'images/button-download-disable.png' in self.html:
- self.longWait(5 * 60, 24) # parallel dl limit
- elif '<div class="infobox' in self.html:
- self.tempOffline()
-
- def loadcaptcha(self, data, *args, **kwargs):
- return data.decode("base64")
-
-
-getInfo = create_getInfo(BezvadataCz)
diff --git a/module/plugins/hoster/BillionuploadsCom.py b/module/plugins/hoster/BillionuploadsCom.py
deleted file mode 100644
index c55b9073c..000000000
--- a/module/plugins/hoster/BillionuploadsCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class BillionuploadsCom(XFileSharingPro):
- __name__ = "BillionuploadsCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?billionuploads.com/\w{12}'
-
- __description__ = """Billionuploads.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "billionuploads.com"
-
- FILE_NAME_PATTERN = r'<b>Filename:</b>(?P<N>.*?)<br>'
- FILE_SIZE_PATTERN = r'<b>Size:</b>(?P<S>.*?)<br>'
-
-
-getInfo = create_getInfo(BillionuploadsCom)
diff --git a/module/plugins/hoster/BitshareCom.py b/module/plugins/hoster/BitshareCom.py
deleted file mode 100644
index 3c84ce5da..000000000
--- a/module/plugins/hoster/BitshareCom.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import with_statement
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class BitshareCom(SimpleHoster):
- __name__ = "BitshareCom"
- __type__ = "hoster"
- __version__ = "0.50"
-
- __pattern__ = r'http://(?:www\.)?bitshare\.com/(files/(?P<id1>[a-zA-Z0-9]+)(/(?P<name>.*?)\.html)?|\?f=(?P<id2>[a-zA-Z0-9]+))'
-
- __description__ = """Bitshare.com hoster plugin"""
- __author_name__ = ("Paul King", "fragonib")
- __author_mail__ = ("", "fragonib[AT]yahoo[DOT]es")
-
- FILE_INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.]+) (?P<U>\w+)</h1>'
- OFFLINE_PATTERN = r'(>We are sorry, but the requested file was not found in our database|>Error - File not available<|The file was deleted either by the uploader, inactivity or due to copyright claim)'
-
- FILE_AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
- CAPTCHA_KEY_PATTERN = r'http://api\.recaptcha\.net/challenge\?k=(.*?) '
- TRAFFIC_USED_UP = r'Your Traffic is used up for today. Upgrade to premium to continue!'
-
-
- def setup(self):
- self.req.cj.setCookie(".bitshare.com", "language_selection", "EN")
- self.multiDL = self.premium
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if self.premium:
- self.account.relogin(self.user)
-
- self.pyfile = pyfile
-
- # File id
- m = re.match(self.__pattern__, pyfile.url)
- self.file_id = max(m.group('id1'), m.group('id2'))
- self.logDebug("File id is [%s]" % self.file_id)
-
- # Load main page
- self.html = self.load(pyfile.url, ref=False, decode=True)
-
- # Check offline
- if re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
-
- # Check Traffic used up
- if re.search(self.TRAFFIC_USED_UP, self.html):
- self.logInfo("Your Traffic is used up for today")
- self.wait(30 * 60, True)
- self.retry()
-
- # File name
- m = re.match(self.__pattern__, pyfile.url)
- name1 = m.group('name') if m else None
- m = re.search(self.FILE_INFO_PATTERN, self.html)
- name2 = m.group('N') if m else None
- pyfile.name = max(name1, name2)
-
- # Ajax file id
- self.ajaxid = re.search(self.FILE_AJAXID_PATTERN, self.html).group(1)
- self.logDebug("File ajax id is [%s]" % self.ajaxid)
-
- # This may either download our file or forward us to an error page
- url = self.getDownloadUrl()
- self.logDebug("Downloading file with url [%s]" % url)
- self.download(url)
-
- check = self.checkDownload({"404": ">404 Not Found<", "Error": ">Error occured<"})
- if check == "404":
- self.retry(3, 60, 'Error 404')
- elif check == "error":
- self.retry(5, 5 * 60, "Bitshare host : Error occured")
-
- def getDownloadUrl(self):
- # Return location if direct download is active
- if self.premium:
- header = self.load(self.pyfile.url, cookies=True, just_header=True)
- if 'location' in header:
- return header['location']
-
- # Get download info
- self.logDebug("Getting download info")
- response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "generateID", "ajaxid": self.ajaxid})
- self.handleErrors(response, ':')
- parts = response.split(":")
- filetype = parts[0]
- wait = int(parts[1])
- captcha = int(parts[2])
- self.logDebug("Download info [type: '%s', waiting: %d, captcha: %d]" % (filetype, wait, captcha))
-
- # Waiting
- if wait > 0:
- self.logDebug("Waiting %d seconds." % wait)
- if wait < 120:
- self.wait(wait, False)
- else:
- self.wait(wait - 55, True)
- self.retry()
-
- # Resolve captcha
- if captcha == 1:
- self.logDebug("File is captcha protected")
- id = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
- # Try up to 3 times
- for i in xrange(3):
- self.logDebug("Resolving ReCaptcha with key [%s], round %d" % (id, i + 1))
- recaptcha = ReCaptcha(self)
- challenge, code = recaptcha.challenge(id)
- response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "validateCaptcha", "ajaxid": self.ajaxid,
- "recaptcha_challenge_field": challenge, "recaptcha_response_field": code})
- if self.handleCaptchaErrors(response):
- break
-
- # Get download URL
- self.logDebug("Getting download url")
- response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
- post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
- self.handleErrors(response, '#')
- url = response.split("#")[-1]
-
- return url
-
- def handleErrors(self, response, separator):
- self.logDebug("Checking response [%s]" % response)
- if "ERROR:Session timed out" in response:
- self.retry()
- elif "ERROR" in response:
- msg = response.split(separator)[-1]
- self.fail(msg)
-
- def handleCaptchaErrors(self, response):
- self.logDebug("Result of captcha resolving [%s]" % response)
- if "SUCCESS" in response:
- self.correctCaptcha()
- return True
- elif "ERROR:SESSION ERROR" in response:
- self.retry()
- self.logDebug("Wrong captcha")
- self.invalidCaptcha()
-
-
-getInfo = create_getInfo(BitshareCom)
diff --git a/module/plugins/hoster/BoltsharingCom.py b/module/plugins/hoster/BoltsharingCom.py
deleted file mode 100644
index d038c50ca..000000000
--- a/module/plugins/hoster/BoltsharingCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class BoltsharingCom(DeadHoster):
- __name__ = "BoltsharingCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?boltsharing.com/\w{12}'
-
- __description__ = """Boltsharing.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(BoltsharingCom)
diff --git a/module/plugins/hoster/CatShareNet.py b/module/plugins/hoster/CatShareNet.py
deleted file mode 100644
index 4bbdfce89..000000000
--- a/module/plugins/hoster/CatShareNet.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class CatShareNet(SimpleHoster):
- __name__ = "CatShareNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?catshare.net/\w{16}.*'
-
- __description__ = """CatShare.net hoster plugin"""
- __author_name__ = "z00nx"
- __author_mail__ = "z00nx0@gmail.com"
-
- FILE_INFO_PATTERN = r'<h3 class="pull-left"[^>]+>(?P<N>.*)</h3>\s+<h3 class="pull-right"[^>]+>(?P<S>.*)</h3>'
- OFFLINE_PATTERN = r'Podany plik zosta'
-
- SECONDS_PATTERN = r'var\s+count\s+=\s+(\d+);'
-
- RECAPTCHA_KEY = "6Lfln9kSAAAAANZ9JtHSOgxUPB9qfDFeLUI_QMEy"
-
-
- def handleFree(self):
- m = re.search(self.SECONDS_PATTERN, self.html)
- seconds = int(m.group(1))
- self.logDebug("Seconds found", seconds)
- self.wait(seconds + 1)
- recaptcha = ReCaptcha(self)
- challenge, code = recaptcha.challenge(self.RECAPTCHA_KEY)
- post_data = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": code}
- self.download(self.pyfile.url, post=post_data)
- check = self.checkDownload({"html": re.compile("\A<!DOCTYPE html PUBLIC")})
- if check == "html":
- self.logDebug("Wrong captcha entered")
- self.invalidCaptcha()
- self.retry()
-
-
-getInfo = create_getInfo(CatShareNet)
diff --git a/module/plugins/hoster/CloudzerNet.py b/module/plugins/hoster/CloudzerNet.py
deleted file mode 100644
index 72332c56f..000000000
--- a/module/plugins/hoster/CloudzerNet.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class CloudzerNet(DeadHoster):
- __name__ = "CloudzerNet"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+'
-
- __description__ = """Cloudzer.net hoster plugin"""
- __author_name__ = ("gs", "z00nx", "stickell")
- __author_mail__ = ("I-_-I-_-I@web.de", "z00nx0@gmail.com", "l.stickell@yahoo.it")
-
-
-getInfo = create_getInfo(CloudzerNet)
diff --git a/module/plugins/hoster/CramitIn.py b/module/plugins/hoster/CramitIn.py
deleted file mode 100644
index 6a872ab34..000000000
--- a/module/plugins/hoster/CramitIn.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class CramitIn(XFileSharingPro):
- __name__ = "CramitIn"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?cramit.in/\w{12}'
-
- __description__ = """Cramit.in hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "cramit.in"
-
- FILE_INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
- LINK_PATTERN = r'href="(http://cramit.in/file_download/.*?)"'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
-
-getInfo = create_getInfo(CramitIn)
diff --git a/module/plugins/hoster/CrockoCom.py b/module/plugins/hoster/CrockoCom.py
deleted file mode 100644
index 80d9b3d61..000000000
--- a/module/plugins/hoster/CrockoCom.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class CrockoCom(SimpleHoster):
- __name__ = "CrockoCom"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'http://(?:www\.)?(crocko|easy-share).com/\w+'
-
- __description__ = """Crocko hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
- FILE_SIZE_PATTERN = r'<span class="tip1"><span class="inner">(?P<S>[^<]+)</span></span>'
- OFFLINE_PATTERN = r"<h1>Sorry,<br />the page you're looking for <br />isn't here.</h1>|File not found"
-
- CAPTCHA_URL_PATTERN = re.compile(r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';")
- CAPTCHA_KEY_PATTERN = re.compile(r'Recaptcha.create\("([^"]+)"')
-
- FORM_PATTERN = r'<form method="post" action="([^"]+)">(.*?)</form>'
- FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?[^>]*>'
-
- FILE_NAME_REPLACEMENTS = [(r'<[^>]*>', '')]
-
-
- def handleFree(self):
- if "You need Premium membership to download this file." in self.html:
- self.fail("You need Premium membership to download this file.")
-
- for _ in xrange(5):
- m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
- if m:
- url, wait_time = 'http://crocko.com' + m.group(1), m.group(2)
- self.wait(wait_time)
- self.html = self.load(url)
- else:
- break
-
- m = re.search(self.CAPTCHA_KEY_PATTERN, self.html)
- if m is None:
- self.parseError('Captcha KEY')
- captcha_key = m.group(1)
-
- m = re.search(self.FORM_PATTERN, self.html, re.DOTALL)
- if m is None:
- self.parseError('ACTION')
- action, form = m.groups()
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
-
- recaptcha = ReCaptcha(self)
-
- for _ in xrange(5):
- inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(captcha_key)
- self.download(action, post=inputs)
-
- check = self.checkDownload({
- "captcha_err": self.CAPTCHA_KEY_PATTERN
- })
-
- if check == "captcha_err":
- self.invalidCaptcha()
- else:
- break
- else:
- self.fail('No valid captcha solution received')
-
-
-getInfo = create_getInfo(CrockoCom)
diff --git a/module/plugins/hoster/CyberlockerCh.py b/module/plugins/hoster/CyberlockerCh.py
deleted file mode 100644
index bfab72b7c..000000000
--- a/module/plugins/hoster/CyberlockerCh.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class CyberlockerCh(DeadHoster):
- __name__ = "CyberlockerCh"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?cyberlocker\.ch/\w+'
-
- __description__ = """Cyberlocker.ch hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
-getInfo = create_getInfo(CyberlockerCh)
diff --git a/module/plugins/hoster/CzshareCom.py b/module/plugins/hoster/CzshareCom.py
deleted file mode 100644
index 4f581651b..000000000
--- a/module/plugins/hoster/CzshareCom.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://czshare.com/5278880/random.bin
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import parseFileSize
-
-
-class CzshareCom(SimpleHoster):
- __name__ = "CzshareCom"
- __type__ = "hoster"
- __version__ = "0.94"
-
- __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download.php\?).*'
-
- __description__ = """CZshare.com hoster plugin, now Sdilej.cz"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=[^>]*>(?P<N>[^<]+)</a>'
- FILE_SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[0-9., ]+)(?P<U>[kKMG])i?B\s*</div>'
- OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
-
- FILE_SIZE_REPLACEMENTS = [(' ', '')]
- FILE_URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
-
- SH_CHECK_TRAFFIC = True
-
- FREE_URL_PATTERN = r'<a href="([^"]+)" class="page-download">[^>]*alt="([^"]+)" /></a>'
- FREE_FORM_PATTERN = r'<form action="download.php" method="post">\s*<img src="captcha.php" id="captcha" />(.*?)</form>'
- PREMIUM_FORM_PATTERN = r'<form action="/profi_down.php" method="post">(.*?)</form>'
- FORM_INPUT_PATTERN = r'<input[^>]* name="([^"]+)" value="([^"]+)"[^>]*/>'
- MULTIDL_PATTERN = r"<p><font color='red'>Z[^<]*PROFI.</font></p>"
- USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([0-9., ]+)([kKMG]i?B)</strong>\s*</div><!-- .credit -->'
-
-
- def checkTrafficLeft(self):
- # check if user logged in
- m = re.search(self.USER_CREDIT_PATTERN, self.html)
- if m is None:
- self.account.relogin(self.user)
- self.html = self.load(self.pyfile.url, cookies=True, decode=True)
- m = re.search(self.USER_CREDIT_PATTERN, self.html)
- if m is None:
- return False
-
- # check user credit
- try:
- credit = parseFileSize(m.group(1).replace(' ', ''), m.group(2))
- self.logInfo("Premium download for %i KiB of Credit" % (self.pyfile.size / 1024))
- self.logInfo("User %s has %i KiB left" % (self.user, credit / 1024))
- if credit < self.pyfile.size:
- self.logInfo("Not enough credit to download file %s" % self.pyfile.name)
- return False
- except Exception, e:
- # let's continue and see what happens...
- self.logError('Parse error (CREDIT): %s' % e)
-
- return True
-
- def handlePremium(self):
- # parse download link
- try:
- form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.DOTALL).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- except Exception, e:
- self.logError("Parse error (FORM): %s" % e)
- self.resetAccount()
-
- # download the file, destination is determined by pyLoad
- self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
- self.checkDownloadedFile()
-
- def handleFree(self):
- # get free url
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m is None:
- self.parseError('Free URL')
- parsed_url = "http://sdilej.cz" + m.group(1)
- self.logDebug("PARSED_URL:" + parsed_url)
-
- # get download ticket and parse html
- self.html = self.load(parsed_url, cookies=True, decode=True)
- if re.search(self.MULTIDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
-
- try:
- form = re.search(self.FREE_FORM_PATTERN, self.html, re.DOTALL).group(1)
- inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
- self.pyfile.size = int(inputs['size'])
- except Exception, e:
- self.logError(e)
- self.parseError('Form')
-
- # get and decrypt captcha
- captcha_url = 'http://sdilej.cz/captcha.php'
- for _ in xrange(5):
- inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
- self.html = self.load(parsed_url, cookies=True, post=inputs, decode=True)
- if u"<li>ZadanÜ ověřovací kód nesouhlasí!</li>" in self.html:
- self.invalidCaptcha()
- elif re.search(self.MULTIDL_PATTERN, self.html):
- self.longWait(5 * 60, 12)
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("No valid captcha code entered")
-
- m = re.search("countdown_number = (\d+);", self.html)
- self.setWait(int(m.group(1)) if m else 50)
-
- # download the file, destination is determined by pyLoad
- self.logDebug("WAIT URL", self.req.lastEffectiveURL)
- m = re.search("free_wait.php\?server=(.*?)&(.*)", self.req.lastEffectiveURL)
- if m is None:
- self.parseError('Download URL')
-
- url = "http://%s/download.php?%s" % (m.group(1), m.group(2))
-
- self.wait()
- self.download(url)
- self.checkDownloadedFile()
-
- def checkDownloadedFile(self):
- # check download
- check = self.checkDownload({
- "tempoffline": re.compile(r"^Soubor je do.*asn.* nedostupn.*$"),
- "credit": re.compile(r"^Nem.*te dostate.*n.* kredit.$"),
- "multi_dl": re.compile(self.MULTIDL_PATTERN),
- "captcha_err": "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
- })
-
- if check == "tempoffline":
- self.fail("File not available - try later")
- if check == "credit":
- self.resetAccount()
- elif check == "multi_dl":
- self.longWait(5 * 60, 12)
- elif check == "captcha_err":
- self.invalidCaptcha()
- self.retry()
-
-
-getInfo = create_getInfo(CzshareCom)
diff --git a/module/plugins/hoster/DailymotionCom.py b/module/plugins/hoster/DailymotionCom.py
deleted file mode 100644
index 79b7acb45..000000000
--- a/module/plugins/hoster/DailymotionCom.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.PyFile import statusMap
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-
-
-def getInfo(urls):
- result = [] #: [ .. (name, size, status, url) .. ]
- regex = re.compile(DailymotionCom.__pattern__)
- apiurl = "https://api.dailymotion.com/video/"
- request = {"fields": "access_error,status,title"}
- for url in urls:
- id = regex.search(url).group("ID")
- page = getURL(apiurl + id, get=request)
- info = json_loads(page)
-
- if "title" in info:
- name = info['title'] + ".mp4"
- else:
- name = url
-
- if "error" in info or info['access_error']:
- status = "offline"
- else:
- status = info['status']
- if status in ("ready", "published"):
- status = "online"
- elif status in ("waiting", "processing"):
- status = "temp. offline"
- else:
- status = "offline"
-
- result.append((name, 0, statusMap[status], url))
- return result
-
-
-class DailymotionCom(Hoster):
- __name__ = "DailymotionCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'https?://(?:www\.)?dailymotion\.com/.*?video/(?P<ID>[\w^_]+)'
- __config__ = [("quality", "Lowest;LD 144p;LD 240p;SD 384p;HQ 480p;HD 720p;HD 1080p;Highest", "Quality", "Highest")]
-
- __description__ = """Dailymotion.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def getStreams(self):
- streams = []
- for result in re.finditer(r"\"(?P<URL>http:\\/\\/www.dailymotion.com\\/cdn\\/H264-(?P<QF>.*?)\\.*?)\"",
- self.html):
- url = result.group("URL")
- qf = result.group("QF")
- link = url.replace("\\", "")
- quality = tuple(int(x) for x in qf.split("x"))
- streams.append((quality, link))
- return sorted(streams, key=lambda x: x[0][::-1])
-
- def getQuality(self):
- q = self.getConfig("quality")
- if q == "Lowest":
- quality = 0
- elif q == "Highest":
- quality = -1
- else:
- quality = int(q.rsplit(" ")[1][:-1])
- return quality
-
- def getLink(self, streams, quality):
- if quality > 0:
- for x, s in reversed([item for item in enumerate(streams)]):
- qf = s[0][1]
- if qf <= quality:
- idx = x
- break
- else:
- idx = 0
- else:
- idx = quality
-
- s = streams[idx]
- self.logInfo("Download video quality %sx%s" % s[0])
- return s[1]
-
- def checkInfo(self, pyfile):
- pyfile.name, pyfile.size, pyfile.status, pyfile.url = getInfo([pyfile.url])[0]
- if pyfile.status == 1:
- self.offline()
- elif pyfile.status == 6:
- self.tempOffline()
-
- def process(self, pyfile):
- self.checkInfo(pyfile)
-
- id = re.match(self.__pattern__, pyfile.url).group("ID")
- self.html = self.load("http://www.dailymotion.com/embed/video/" + id, decode=True)
-
- streams = self.getStreams()
- quality = self.getQuality()
- link = self.getLink(streams, quality)
-
- self.download(link)
diff --git a/module/plugins/hoster/DataHu.py b/module/plugins/hoster/DataHu.py
deleted file mode 100644
index 3dc01eef3..000000000
--- a/module/plugins/hoster/DataHu.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://data.hu/get/6381232/random.bin
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DataHu(SimpleHoster):
- __name__ = "DataHu"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?data.hu/get/\w+'
-
- __description__ = """Data.hu hoster plugin"""
- __author_name__ = ("crash", "stickell")
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
- OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
- LINK_PATTERN = r'<div class="download_box_button"><a href="([^"]+)">'
-
-
- def handleFree(self):
- self.resumeDownload = True
- self.html = self.load(self.pyfile.url, decode=True)
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- url = m.group(1)
- self.logDebug('Direct link: ' + url)
- else:
- self.parseError('Unable to get direct link')
-
- self.download(url, disposition=True)
-
-
-getInfo = create_getInfo(DataHu)
diff --git a/module/plugins/hoster/DataportCz.py b/module/plugins/hoster/DataportCz.py
deleted file mode 100644
index 56b2c2398..000000000
--- a/module/plugins/hoster/DataportCz.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DataportCz(SimpleHoster):
- __name__ = "DataportCz"
- __type__ = "hoster"
- __version__ = "0.37"
-
- __pattern__ = r'http://(?:www\.)?dataport.cz/file/(.*)'
-
- __description__ = """Dataport.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
- FILE_SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.dataport.cz/file/\1')]
-
- CAPTCHA_URL_PATTERN = r'<section id="captcha_bg">\s*<img src="(.*?)"'
- FREE_SLOTS_PATTERN = ur'Počet volnÜch slotů: <span class="darkblue">(\d+)</span><br />'
-
-
- def handleFree(self):
- captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
-
- for _ in xrange(60):
- action, inputs = self.parseHtmlForm('free_download_form')
- self.logDebug(action, inputs)
- if not action or not inputs:
- self.parseError('free_download_form')
-
- if "captchaId" in inputs and inputs['captchaId'] in captchas:
- inputs['captchaCode'] = captchas[inputs['captchaId']]
- else:
- self.parseError('captcha')
-
- self.html = self.download("http://www.dataport.cz%s" % action, post=inputs)
-
- check = self.checkDownload({"captcha": 'alert("\u0160patn\u011b opsan\u00fd k\u00f3d z obr\u00e1zu");',
- "slot": 'alert("Je n\u00e1m l\u00edto, ale moment\u00e1ln\u011b nejsou'})
- if check == "captcha":
- self.parseError('invalid captcha')
- elif check == "slot":
- self.logDebug("No free slots - wait 60s and retry")
- self.wait(60, False)
- self.html = self.load(self.pyfile.url, decode=True)
- continue
- else:
- break
-
-
-create_getInfo(DataportCz)
diff --git a/module/plugins/hoster/DateiTo.py b/module/plugins/hoster/DateiTo.py
deleted file mode 100644
index ff8c430ee..000000000
--- a/module/plugins/hoster/DateiTo.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DateiTo(SimpleHoster):
- __name__ = "DateiTo"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
-
- __description__ = """Datei.to hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
- FILE_SIZE_PATTERN = r'Dateigr&ouml;&szlig;e:</td>\s*<td colspan="2">(?P<S>.*?)</'
- OFFLINE_PATTERN = r'>Datei wurde nicht gefunden<|>Bitte wÀhle deine Datei aus... <'
- PARALELL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
-
- WAIT_PATTERN = r'countdown\({seconds: (\d+)'
- DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
- RECAPTCHA_KEY_PATTERN = r'Recaptcha.create\("(.*?)"'
-
-
- def handleFree(self):
- url = 'http://datei.to/ajax/download.php'
- data = {'P': 'I', 'ID': self.file_info['ID']}
-
- recaptcha = ReCaptcha(self)
-
- for _ in xrange(10):
- self.logDebug("URL", url, "POST", data)
- self.html = self.load(url, post=data)
- self.checkErrors()
-
- if url.endswith('download.php') and 'P' in data:
- if data['P'] == 'I':
- self.doWait()
-
- elif data['P'] == 'IV':
- break
-
- m = re.search(self.DATA_PATTERN, self.html)
- if m is None:
- self.parseError('data')
- url = 'http://datei.to/' + m.group(1)
- data = dict(x.split('=') for x in m.group(2).split('&'))
-
- if url.endswith('recaptcha.php'):
- m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
- recaptcha_key = m.group(1) if m else "6LdBbL8SAAAAAI0vKUo58XRwDd5Tu_Ze1DA7qTao"
-
- data['recaptcha_challenge_field'], data['recaptcha_response_field'] = recaptcha.challenge(recaptcha_key)
-
- else:
- self.fail('Too bad...')
-
- download_url = self.html
- self.logDebug('Download URL', download_url)
- self.download(download_url)
-
- def checkErrors(self):
- m = re.search(self.PARALELL_PATTERN, self.html)
- if m:
- m = re.search(self.WAIT_PATTERN, self.html)
- wait_time = int(m.group(1)) if m else 30
- self.wait(wait_time + 1, False)
- self.retry()
-
- def doWait(self):
- m = re.search(self.WAIT_PATTERN, self.html)
- wait_time = int(m.group(1)) if m else 30
-
- self.load('http://datei.to/ajax/download.php', post={'P': 'Ads'})
- self.wait(wait_time + 1, False)
-
-
-getInfo = create_getInfo(DateiTo)
diff --git a/module/plugins/hoster/DdlstorageCom.py b/module/plugins/hoster/DdlstorageCom.py
deleted file mode 100644
index 4d77289d7..000000000
--- a/module/plugins/hoster/DdlstorageCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class DdlstorageCom(DeadHoster):
- __name__ = "DdlstorageCom"
- __type__ = "hoster"
- __version__ = "1.02"
-
- __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/\w+'
-
- __description__ = """DDLStorage.com hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
-
-getInfo = create_getInfo(DdlstorageCom)
diff --git a/module/plugins/hoster/DebridItaliaCom.py b/module/plugins/hoster/DebridItaliaCom.py
deleted file mode 100644
index 5880b2738..000000000
--- a/module/plugins/hoster/DebridItaliaCom.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class DebridItaliaCom(Hoster):
- __name__ = "DebridItaliaCom"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'https?://(?:[^/]*\.)?debriditalia\.com'
-
- __description__ = """Debriditalia.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "DebridItalia")
- self.fail("No DebridItalia account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- url = "http://debriditalia.com/linkgen2.php?xjxfun=convertiLink&xjxargs[]=S<![CDATA[%s]]>" % pyfile.url
- page = self.load(url)
- self.logDebug("XML data: %s" % page)
-
- if 'File not available' in page:
- self.fail('File not available')
- else:
- new_url = re.search(r'<a href="(?:[^"]+)">(?P<direct>[^<]+)</a>', page).group('direct')
-
- if new_url != pyfile.url:
- self.logDebug("New URL: %s" % new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload({"empty": re.compile(r"^$")})
-
- if check == "empty":
- self.retry(5, 2 * 60, "Empty file downloaded")
diff --git a/module/plugins/hoster/DepositfilesCom.py b/module/plugins/hoster/DepositfilesCom.py
deleted file mode 100644
index e2b2d46fd..000000000
--- a/module/plugins/hoster/DepositfilesCom.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class DepositfilesCom(SimpleHoster):
- __name__ = "DepositfilesCom"
- __type__ = "hoster"
- __version__ = "0.48"
-
- __pattern__ = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
-
- __description__ = """Depositfiles.com hoster plugin"""
- __author_name__ = ("spoob", "zoidberg", "Walter Purcaro")
- __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz", "vuolter@gmail.com")
-
- FILE_NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
- FILE_SIZE_PATTERN = r': <b>(?P<S>[0-9.]+)&nbsp;(?P<U>[kKMG])i?B</b>'
- OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
-
- FILE_NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
- (r'.*<b title="(?P<N>[^"]+).*', "\g<N>")]
- FILE_URL_REPLACEMENTS = [(__pattern__, "https://dfiles.eu/files/\g<ID>")]
-
- SH_COOKIES = [(".dfiles.eu", "lang_current", "en")]
-
- RECAPTCHA_PATTERN = r"Recaptcha.create\('([^']+)'"
-
- FREE_LINK_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
- PREMIUM_LINK_PATTERN = r'class="repeat"><a href="(.+?)"'
- PREMIUM_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
-
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, post={"gateway_result": "1"}, cookies=True)
-
- if re.search(r'File is checked, please try again in a minute.', self.html) is not None:
- self.logInfo("DepositFiles.com: The file is being checked. Waiting 1 minute.")
- self.wait(61)
- self.retry()
-
- wait = re.search(r'html_download_api-limit_interval\">(\d+)</span>', self.html)
- if wait:
- wait_time = int(wait.group(1))
- self.logInfo("%s: Traffic used up. Waiting %d seconds." % (self.__name__, wait_time))
- self.wait(wait_time, True)
- self.retry()
-
- wait = re.search(r'>Try in (\d+) minutes or use GOLD account', self.html)
- if wait:
- wait_time = int(wait.group(1))
- self.logInfo("%s: All free slots occupied. Waiting %d minutes." % (self.__name__, wait_time))
- self.setWait(wait_time * 60, False)
-
- wait = re.search(r'Please wait (\d+) sec', self.html)
- if wait:
- self.setWait(int(wait.group(1)))
-
- m = re.search(r"var fid = '(\w+)';", self.html)
- if m is None:
- self.retry(wait_time=5)
- params = {'fid': m.group(1)}
- self.logDebug("FID: %s" % params['fid'])
-
- captcha_key = '6LdRTL8SAAAAAE9UOdWZ4d0Ky-aeA7XfSqyWDM2m'
- m = re.search(self.RECAPTCHA_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- self.logDebug("CAPTCHA_KEY: %s" % captcha_key)
-
- self.wait()
- recaptcha = ReCaptcha(self)
-
- for _ in xrange(5):
- self.html = self.load("https://dfiles.eu/get_file.php", get=params)
-
- if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
- if not captcha_key:
- self.parseError('Captcha key')
- if 'response' in params:
- self.invalidCaptcha()
- params['challenge'], params['response'] = recaptcha.challenge(captcha_key)
- self.logDebug(params)
- continue
-
- m = re.search(self.FREE_LINK_PATTERN, self.html)
- if m:
- if 'response' in params:
- self.correctCaptcha()
- link = unquote(m.group(1))
- self.logDebug("LINK: %s" % link)
- break
- else:
- self.parseError('Download link')
- else:
- self.fail('No valid captcha response received')
-
- try:
- self.download(link, disposition=True)
- except:
- self.retry(wait_time=60)
-
- def handlePremium(self):
- self.html = self.load(self.pyfile.url, cookies=self.SH_COOKIES)
-
- if '<span class="html_download_api-gold_traffic_limit">' in self.html:
- self.logWarning("Download limit reached")
- self.retry(25, 60 * 60, "Download limit reached")
- elif 'onClick="show_gold_offer' in self.html:
- self.account.relogin(self.user)
- self.retry()
- else:
- link = re.search(self.PREMIUM_LINK_PATTERN, self.html)
- mirror = re.search(self.PREMIUM_MIRROR_PATTERN, self.html)
- if link:
- dlink = link.group(1)
- elif mirror:
- dlink = mirror.group(1)
- else:
- self.parseError("No direct download link or mirror found")
- self.download(dlink, disposition=True)
-
-
-getInfo = create_getInfo(DepositfilesCom)
diff --git a/module/plugins/hoster/DlFreeFr.py b/module/plugins/hoster/DlFreeFr.py
deleted file mode 100644
index e25de18b4..000000000
--- a/module/plugins/hoster/DlFreeFr.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.common.json_layer import json_loads
-from module.network.Browser import Browser
-from module.network.CookieJar import CookieJar
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns
-
-
-class CustomBrowser(Browser):
-
- def __init__(self, bucket=None, options={}):
- Browser.__init__(self, bucket, options)
-
- def load(self, *args, **kwargs):
- post = kwargs.get("post")
-
- if post is None and len(args) > 2:
- post = args[2]
-
- if post:
- self.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.http.c.setopt(pycurl.POST, 1)
- self.http.c.setopt(pycurl.CUSTOMREQUEST, "POST")
- else:
- self.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
- self.http.c.setopt(pycurl.POST, 0)
- self.http.c.setopt(pycurl.CUSTOMREQUEST, "GET")
-
- return Browser.load(self, *args, **kwargs)
-
-
-class AdYouLike:
- """
- Class to support adyoulike captcha service
- """
- ADYOULIKE_INPUT_PATTERN = r'Adyoulike.create\((.*?)\);'
- ADYOULIKE_CALLBACK = r'Adyoulike.g._jsonp_5579316662423138'
- ADYOULIKE_CHALLENGE_PATTERN = ADYOULIKE_CALLBACK + r'\((.*?)\)'
-
- def __init__(self, plugin, engine="adyoulike"):
- self.plugin = plugin
- self.engine = engine
-
- def challenge(self, html):
- adyoulike_data_string = None
- m = re.search(self.ADYOULIKE_INPUT_PATTERN, html)
- if m:
- adyoulike_data_string = m.group(1)
- else:
- self.plugin.fail("Can't read AdYouLike input data")
-
- # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"},
- # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}}
- ayl_data = json_loads(adyoulike_data_string)
-
- res = self.plugin.load(
- r'http://api-ayl.appspot.com/challenge?key=%(ayl_key)s&env=%(ayl_env)s&callback=%(callback)s' % {
- "ayl_key": ayl_data[self.engine]['key'], "ayl_env": ayl_data['all']['env'],
- "callback": self.ADYOULIKE_CALLBACK})
-
- m = re.search(self.ADYOULIKE_CHALLENGE_PATTERN, res)
- challenge_string = None
- if m:
- challenge_string = m.group(1)
- else:
- self.plugin.fail("Invalid AdYouLike challenge")
- challenge_data = json_loads(challenge_string)
-
- return ayl_data, challenge_data
-
- def result(self, ayl, challenge):
- """
- Adyoulike.g._jsonp_5579316662423138
- ({"translations":{"fr":{"instructions_visual":"Recopiez « Soonnight » ci-dessous :"}},
- "site_under":true,"clickable":true,"pixels":{"VIDEO_050":[],"DISPLAY":[],"VIDEO_000":[],"VIDEO_100":[],
- "VIDEO_025":[],"VIDEO_075":[]},"medium_type":"image/adyoulike",
- "iframes":{"big":"<iframe src=\"http://www.soonnight.com/campagn.html\" scrolling=\"no\"
- height=\"250\" width=\"300\" frameborder=\"0\"></iframe>"},"shares":{},"id":256,
- "token":"e6QuI4aRSnbIZJg02IsV6cp4JQ9~MjA1","formats":{"small":{"y":300,"x":0,"w":300,"h":60},
- "big":{"y":0,"x":0,"w":300,"h":250},"hover":{"y":440,"x":0,"w":300,"h":60}},
- "tid":"SqwuAdxT1EZoi4B5q0T63LN2AkiCJBg5"})
- """
- response = None
- try:
- instructions_visual = challenge['translations'][ayl['all']['lang']]['instructions_visual']
- m = re.search(u".*«(.*)».*", instructions_visual)
- if m:
- response = m.group(1).strip()
- else:
- self.plugin.fail("Can't parse instructions visual")
- except KeyError:
- self.plugin.fail("No instructions visual")
-
- #TODO: Supports captcha
-
- if not response:
- self.plugin.fail("AdYouLike result failed")
-
- return {"_ayl_captcha_engine": self.engine,
- "_ayl_env": ayl['all']['env'],
- "_ayl_tid": challenge['tid'],
- "_ayl_token_challenge": challenge['token'],
- "_ayl_response": response}
-
-
-class DlFreeFr(SimpleHoster):
- __name__ = "DlFreeFr"
- __type__ = "hoster"
- __version__ = "0.25"
-
- __pattern__ = r'http://(?:www\.)?dl\.free\.fr/([a-zA-Z0-9]+|getfile\.pl\?file=/[a-zA-Z0-9]+)'
-
- __description__ = """Dl.free.fr hoster plugin"""
- __author_name__ = ("the-razer", "zoidberg", "Toilal")
- __author_mail__ = ("daniel_ AT gmx DOT net", "zoidberg@mujmail.cz", "toilal.dev@gmail.com")
-
- FILE_NAME_PATTERN = r'Fichier:</td>\s*<td[^>]*>(?P<N>[^>]*)</td>'
- FILE_SIZE_PATTERN = r'Taille:</td>\s*<td[^>]*>(?P<S>[\d.]+[KMG])o'
- OFFLINE_PATTERN = r"Erreur 404 - Document non trouv|Fichier inexistant|Le fichier demand&eacute; n'a pas &eacute;t&eacute; trouv&eacute;"
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
- self.limitDL = 5
- self.chunkLimit = 1
-
- def init(self):
- factory = self.core.requestFactory
- self.req = CustomBrowser(factory.bucket, factory.getOptions())
-
- def process(self, pyfile):
- self.req.setCookieJar(None)
-
- pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
- valid_url = pyfile.url
- headers = self.load(valid_url, just_header=True)
-
- self.html = None
- if headers.get('code') == 302:
- valid_url = headers.get('location')
- headers = self.load(valid_url, just_header=True)
-
- if headers.get('code') == 200:
- content_type = headers.get('content-type')
- if content_type and content_type.startswith("text/html"):
- # Undirect acces to requested file, with a web page providing it (captcha)
- self.html = self.load(valid_url)
- self.handleFree()
- else:
- # Direct access to requested file for users using free.fr as Internet Service Provider.
- self.download(valid_url, disposition=True)
- elif headers.get('code') == 404:
- self.offline()
- else:
- self.fail("Invalid return code: " + str(headers.get('code')))
-
- def handleFree(self):
- action, inputs = self.parseHtmlForm('action="getfile.pl"')
-
- adyoulike = AdYouLike(self)
- ayl, challenge = adyoulike.challenge(self.html)
- result = adyoulike.result(ayl, challenge)
- inputs.update(result)
-
- self.load("http://dl.free.fr/getfile.pl", post=inputs)
- headers = self.getLastHeaders()
- if headers.get("code") == 302 and "set-cookie" in headers and "location" in headers:
- m = re.search("(.*?)=(.*?); path=(.*?); domain=(.*?)", headers.get("set-cookie"))
- cj = CookieJar(__name__)
- if m:
- cj.setCookie(m.group(4), m.group(1), m.group(2), m.group(3))
- else:
- self.fail("Cookie error")
- location = headers.get("location")
- self.req.setCookieJar(cj)
- self.download(location, disposition=True)
- else:
- self.fail("Invalid response")
-
- def getLastHeaders(self):
- #parse header
- header = {"code": self.req.code}
- for line in self.req.http.header.splitlines():
- line = line.strip()
- if not line or ":" not in line:
- continue
-
- key, none, value = line.partition(":")
- key = key.lower().strip()
- value = value.strip()
-
- if key in header:
- if type(header[key]) == list:
- header[key].append(value)
- else:
- header[key] = [header[key], value]
- else:
- header[key] = value
- return header
-
-
-getInfo = create_getInfo(DlFreeFr)
diff --git a/module/plugins/hoster/DuploadOrg.py b/module/plugins/hoster/DuploadOrg.py
deleted file mode 100644
index c11d694c5..000000000
--- a/module/plugins/hoster/DuploadOrg.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class DuploadOrg(XFileSharingPro):
- __name__ = "DuploadOrg"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?dupload\.org/\w{12}'
-
- __description__ = """Dupload.grg hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- HOSTER_NAME = "dupload.org"
-
- FILE_INFO_PATTERN = r'<h3[^>]*>(?P<N>.+) \((?P<S>[\d.]+) (?P<U>\w+)\)</h3>'
-
-
-getInfo = create_getInfo(DuploadOrg)
diff --git a/module/plugins/hoster/EasybytezCom.py b/module/plugins/hoster/EasybytezCom.py
deleted file mode 100644
index f9dfe6a13..000000000
--- a/module/plugins/hoster/EasybytezCom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class EasybytezCom(XFileSharingPro):
- __name__ = "EasybytezCom"
- __type__ = "hoster"
- __version__ = "0.18"
-
- __pattern__ = r'http://(?:www\.)?easybytez.com/(\w+).*'
-
- __description__ = """Easybytez.com hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- HOSTER_NAME = "easybytez.com"
-
- FILE_INFO_PATTERN = r'<span class="name">(?P<N>.+)</span><br>\s*<span class="size">(?P<S>[^<]+)</span>'
- OFFLINE_PATTERN = r'<h1>File not available</h1>'
-
- LINK_PATTERN = r'(http://(\w+\.(easyload|easybytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/[^"<]+)'
- OVR_LINK_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
- ERROR_PATTERN = r'(?:class=["\']err["\'][^>]*>|<Center><b>)(.*?)</'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
-
-getInfo = create_getInfo(EasybytezCom)
diff --git a/module/plugins/hoster/EdiskCz.py b/module/plugins/hoster/EdiskCz.py
deleted file mode 100644
index 449dc0050..000000000
--- a/module/plugins/hoster/EdiskCz.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class EdiskCz(SimpleHoster):
- __name__ = "EdiskCz"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?edisk.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.*'
-
- __description__ = """Edisk.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<span class="fl" title="(?P<N>[^"]+)">\s*.*?\((?P<S>[0-9.]*) (?P<U>[kKMG])i?B\)</h1></span>'
- OFFLINE_PATTERN = r'<h3>This file does not exist due to one of the following:</h3><ul><li>'
-
- ACTION_PATTERN = r'/en/download/(\d+/.*\.html)'
- LINK_PATTERN = r'http://.*edisk.cz.*\.html'
-
-
- def setup(self):
- self.multiDL = False
-
- def process(self, pyfile):
- url = re.sub("/(stahni|sk/stahni)/", "/en/download/", pyfile.url)
-
- self.logDebug('URL:' + url)
-
- m = re.search(self.ACTION_PATTERN, url)
- if m is None:
- self.parseError("ACTION")
- action = m.group(1)
-
- self.html = self.load(url, decode=True)
- self.getFileInfo()
-
- self.html = self.load(re.sub("/en/download/", "/en/download-slow/", url))
-
- url = self.load(re.sub("/en/download/", "/x-download/", url), post={
- "action": action
- })
-
- if not re.match(self.LINK_PATTERN, url):
- self.fail("Unexpected server response")
-
- self.download(url)
-
-
-getInfo = create_getInfo(EdiskCz)
diff --git a/module/plugins/hoster/EgoFilesCom.py b/module/plugins/hoster/EgoFilesCom.py
deleted file mode 100644
index 547f042e4..000000000
--- a/module/plugins/hoster/EgoFilesCom.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://egofiles.com/mOZfMI1WLZ6HBkGG/random.bin
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class EgoFilesCom(SimpleHoster):
- __name__ = "EgoFilesCom"
- __type__ = "hoster"
- __version__ = "0.15"
-
- __pattern__ = r'https?://(?:www\.)?egofiles.com/(\w+)'
-
- __description__ = """Egofiles.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_INFO_PATTERN = r'<div class="down-file">\s+(?P<N>[^\t]+)\s+<div class="file-properties">\s+(File size|Rozmiar): (?P<S>[\w.]+) (?P<U>\w+) \|'
- OFFLINE_PATTERN = r'(File size|Rozmiar): 0 KB'
- WAIT_TIME_PATTERN = r'For next free download you have to wait <strong>((?P<m>\d*)m)? ?((?P<s>\d+)s)?</strong>'
- LINK_PATTERN = r'<a href="(?P<link>[^"]+)">Download ></a>'
- RECAPTCHA_KEY = "6LeXatQSAAAAAHezcjXyWAni-4t302TeYe7_gfvX"
-
-
- def setup(self):
- # Set English language
- self.load("https://egofiles.com/ajax/lang.php?lang=en", just_header=True)
-
- def process(self, pyfile):
- if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.handlePremium()
- else:
- self.handleFree()
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
- self.getFileInfo()
-
- # Wait time between free downloads
- if 'For next free download you have to wait' in self.html:
- m = re.search(self.WAIT_TIME_PATTERN, self.html).groupdict('0')
- waittime = int(m['m']) * 60 + int(m['s'])
- self.wait(waittime, True)
-
- downloadURL = r''
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- post_data = {'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field': response}
- self.html = self.load(self.pyfile.url, post=post_data, decode=True)
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.logInfo('Wrong captcha')
- self.invalidCaptcha()
- elif hasattr(m, 'group'):
- downloadURL = m.group('link')
- self.correctCaptcha()
- break
- else:
- self.fail('Unknown error - Plugin may be out of date')
-
- if not downloadURL:
- self.fail("No Download url retrieved/all captcha attempts failed")
-
- self.download(downloadURL, disposition=True)
-
- def handlePremium(self):
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header:
- self.logDebug('DIRECT LINK from header: ' + header['location'])
- self.download(header['location'])
- else:
- self.html = self.load(self.pyfile.url, decode=True)
- self.getFileInfo()
- m = re.search(r'<a href="(?P<link>[^"]+)">Download ></a>', self.html)
- if m is None:
- self.parseError('Unable to detect direct download url')
- else:
- self.logDebug('DIRECT URL from html: ' + m.group('link'))
- self.download(m.group('link'), disposition=True)
-
-
-getInfo = create_getInfo(EgoFilesCom)
diff --git a/module/plugins/hoster/EpicShareNet.py b/module/plugins/hoster/EpicShareNet.py
deleted file mode 100644
index 73b16c08e..000000000
--- a/module/plugins/hoster/EpicShareNet.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://epicshare.net/fch3m2bk6ihp/BigBuckBunny_320x180.mp4.html
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class EpicShareNet(XFileSharingPro):
- __name__ = "EpicShareNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?epicshare\.net/\w{12}'
-
- __description__ = """EpicShare.net hoster plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- HOSTER_NAME = "epicshare.net"
-
- OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
- FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h2>(?P<N>[^<]+)</h2>'
-
-
-getInfo = create_getInfo(EpicShareNet)
diff --git a/module/plugins/hoster/EuroshareEu.py b/module/plugins/hoster/EuroshareEu.py
deleted file mode 100644
index fdb76cd5c..000000000
--- a/module/plugins/hoster/EuroshareEu.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class EuroshareEu(SimpleHoster):
- __name__ = "EuroshareEu"
- __type__ = "hoster"
- __version__ = "0.25"
-
- __pattern__ = r'http://(?:www\.)?euroshare.(eu|sk|cz|hu|pl)/file/.*'
-
- __description__ = """Euroshare.eu hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<span style="float: left;"><strong>(?P<N>.+?)</strong> \((?P<S>.+?)\)</span>'
- OFFLINE_PATTERN = ur'<h2>S.bor sa nena.iel</h2>|Poşadovaná stránka neexistuje!'
-
- FREE_URL_PATTERN = r'<a href="(/file/\d+/[^/]*/download/)"><div class="downloadButton"'
- ERR_PARDL_PATTERN = r'<h2>Prebieha s.ahovanie</h2>|<p>Naraz je z jednej IP adresy mo.n. s.ahova. iba jeden s.bor'
- ERR_NOT_LOGGED_IN_PATTERN = r'href="/customer-zone/login/"'
-
- FILE_URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
- self.req.setOption("timeout", 120)
-
- def handlePremium(self):
- if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
- self.account.relogin(self.user)
- self.retry(reason="User not logged in")
-
- self.download(self.pyfile.url.rstrip('/') + "/download/")
-
- check = self.checkDownload({"login": re.compile(self.ERR_NOT_LOGGED_IN_PATTERN),
- "json": re.compile(r'\{"status":"error".*?"message":"(.*?)"')})
- if check == "login" or (check == "json" and self.lastCheck.group(1) == "Access token expired"):
- self.account.relogin(self.user)
- self.retry(reason="Access token expired")
- elif check == "json":
- self.fail(self.lastCheck.group(1))
-
- def handleFree(self):
- if re.search(self.ERR_PARDL_PATTERN, self.html) is not None:
- self.longWait(5 * 60, 12)
-
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m is None:
- self.parseError("Parse error (URL)")
- parsed_url = "http://euroshare.eu%s" % m.group(1)
- self.logDebug("URL", parsed_url)
- self.download(parsed_url, disposition=True)
-
- check = self.checkDownload({"multi_dl": re.compile(self.ERR_PARDL_PATTERN)})
- if check == "multi_dl":
- self.longWait(5 * 60, 12)
-
-
-getInfo = create_getInfo(EuroshareEu)
diff --git a/module/plugins/hoster/ExtabitCom.py b/module/plugins/hoster/ExtabitCom.py
deleted file mode 100644
index 2409d8517..000000000
--- a/module/plugins/hoster/ExtabitCom.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ExtabitCom(SimpleHoster):
- __name__ = "ExtabitCom"
- __type__ = "hoster"
- __version__ = "0.6"
-
- __pattern__ = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
-
- __description__ = """Extabit.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>[^"]+)">'
- FILE_SIZE_PATTERN = r'<th>Size:</th>\s*<td class="col-fileinfo">(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'>File not found<'
- TEMP_OFFLINE_PATTERN = r'>(File is temporary unavailable|No download mirror)<'
-
- LINK_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/[a-z0-9]+/.*?)[\'"]'
-
-
- def handleFree(self):
- if r">Only premium users can download this file" in self.html:
- self.fail("Only premium users can download this file")
-
- m = re.search(r"Next free download from your ip will be available in <b>(\d+)\s*minutes", self.html)
- if m:
- self.wait(int(m.group(1)) * 60, True)
- elif "The daily downloads limit from your IP is exceeded" in self.html:
- self.logWarning("You have reached your daily downloads limit for today")
- self.wait(secondsToMidnight(gmt=2), True)
-
- self.logDebug("URL: " + self.req.http.lastEffectiveURL)
- m = re.match(self.__pattern__, self.req.http.lastEffectiveURL)
- fileID = m.group('ID') if m else self.file_info('ID')
-
- m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
- if m:
- recaptcha = ReCaptcha(self)
- captcha_key = m.group(1)
-
- for _ in xrange(5):
- get_data = {"type": "recaptcha"}
- get_data['challenge'], get_data['capture'] = recaptcha.challenge(captcha_key)
- response = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
- if "ok" in response:
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
- else:
- self.fail("Invalid captcha")
- else:
- self.parseError('Captcha')
-
- if not "href" in response:
- self.parseError('JSON')
-
- self.html = self.load("http://extabit.com/file/%s%s" % (fileID, response['href']))
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError('Download URL')
- url = m.group(1)
- self.logDebug("Download URL: " + url)
- self.download(url)
-
-
-getInfo = create_getInfo(ExtabitCom)
diff --git a/module/plugins/hoster/FastixRu.py b/module/plugins/hoster/FastixRu.py
deleted file mode 100644
index 199544840..000000000
--- a/module/plugins/hoster/FastixRu.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import randrange
-from urllib import unquote
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-class FastixRu(Hoster):
- __name__ = "FastixRu"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?fastix\.(ru|it)/file/(?P<ID>[a-zA-Z0-9]{24})'
-
- __description__ = """Fastix hoster plugin"""
- __author_name__ = "Massimo Rosamilia"
- __author_mail__ = "max@spiritix.eu"
-
-
- def getFilename(self, url):
- try:
- name = unquote(url.rsplit("/", 1)[1])
- except IndexError:
- name = "Unknown_Filename..."
- if name.endswith("..."): # incomplete filename, append random stuff
- name += "%s.tmp" % randrange(100, 999)
- return name
-
- def setup(self):
- self.chunkLimit = 3
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Fastix")
- self.fail("No Fastix account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- api_key = self.account.getAccountData(self.user)
- api_key = api_key['api']
- url = "http://fastix.ru/api_v2/?apikey=%s&sub=getdirectlink&link=%s" % (api_key, pyfile.url)
- page = self.load(url)
- data = json_loads(page)
- self.logDebug("Json data: %s" % str(data))
- if "error\":true" in page:
- self.offline()
- else:
- new_url = data['downloadlink']
-
- if new_url != pyfile.url:
- self.logDebug("New URL: %s" % new_url)
-
- if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
- #only use when name wasnt already set
- pyfile.name = self.getFilename(new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload({"error": "<title>An error occurred while processing your request</title>",
- "empty": re.compile(r"^$")})
-
- if check == "error":
- self.retry(wait_time=60, reason="An error occurred while generating link.")
- elif check == "empty":
- self.retry(wait_time=60, reason="Downloaded File was empty.")
diff --git a/module/plugins/hoster/FastshareCz.py b/module/plugins/hoster/FastshareCz.py
deleted file mode 100644
index 7a847fdc4..000000000
--- a/module/plugins/hoster/FastshareCz.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.fastshare.cz/2141189/random.bin
-
-import re
-
-from urlparse import urljoin
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FastshareCz(SimpleHoster):
- __name__ = "FastshareCz"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
-
- __description__ = """FastShare.cz hoster plugin"""
- __author_name__ = ("zoidberg", "stickell", "Walter Purcaro")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it", "vuolter@gmail.com")
-
- FILE_INFO_PATTERN = r'<h1 class="dwp">(?P<N>[^<]+)</h1>\s*<div class="fileinfo">\s*Size\s*: (?P<S>\d+) (?P<U>\w+),'
- OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
-
- FILE_URL_REPLACEMENTS = [("#.*", "")]
-
- SH_COOKIES = [(".fastshare.cz", "lang", "en")]
-
- FREE_URL_PATTERN = r'action=(/free/.*?)>\s*<img src="([^"]*)"><br'
- PREMIUM_URL_PATTERN = r'(http://data\d+\.fastshare\.cz/download\.php\?id=\d+&)'
- CREDIT_PATTERN = r' credit for '
-
-
- def handleFree(self):
- if "> 100% of FREE slots are full" in self.html:
- self.retry(120, 60, "No free slots")
-
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m:
- action, captcha_src = m.groups()
- else:
- self.parseError("Free URL")
-
- baseurl = "http://www.fastshare.cz"
- captcha = self.decryptCaptcha(urljoin(baseurl, captcha_src))
- self.download(urljoin(baseurl, action), post={"code": captcha, "btn.x": 77, "btn.y": 18})
-
- check = self.checkDownload({
- "paralell_dl":
- "<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)",
- "wrong_captcha": "Download for FREE"
- })
-
- if check == "paralell_dl":
- self.retry(6, 10 * 60, "Paralell download")
- elif check == "wrong_captcha":
- self.retry(max_tries=5, reason="Wrong captcha")
-
- def handlePremium(self):
- header = self.load(self.pyfile.url, just_header=True)
- if "location" in header:
- url = header['location']
- else:
- self.html = self.load(self.pyfile.url)
-
- self.getFileInfo() #
-
- if self.CREDIT_PATTERN in self.html:
- self.logWarning("Not enough traffic left")
- self.resetAccount()
- else:
- m = re.search(self.PREMIUM_URL_PATTERN, self.html)
- if m:
- url = m.group(1)
- else:
- self.parseError("Premium URL")
-
- self.logDebug("PREMIUM URL: " + url)
- self.download(url, disposition=True)
-
- check = self.checkDownload({"credit": re.compile(self.CREDIT_PATTERN)})
- if check == "credit":
- self.resetAccount()
-
-
-getInfo = create_getInfo(FastshareCz)
diff --git a/module/plugins/hoster/File4safeCom.py b/module/plugins/hoster/File4safeCom.py
deleted file mode 100644
index 7ec775103..000000000
--- a/module/plugins/hoster/File4safeCom.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class File4safeCom(XFileSharingPro):
- __name__ = "File4safeCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?file4safe\.com/\w+'
-
- __description__ = """File4safe.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- HOSTER_NAME = "file4safe.com"
-
-
- def handlePremium(self):
- self.req.http.lastURL = self.pyfile.url
-
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
- self.load(self.pyfile.url, post=self.getPostParameters(), decode=True)
- self.header = self.req.http.header
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
-
- m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
- if m and re.match(self.LINK_PATTERN, m.group(1)):
- location = m.group(1).strip()
- self.startDownload(location)
- else:
- self.parseError("Unable to detect premium download link")
-
-
-getInfo = create_getInfo(File4safeCom)
diff --git a/module/plugins/hoster/FileApeCom.py b/module/plugins/hoster/FileApeCom.py
deleted file mode 100644
index 91744c7c2..000000000
--- a/module/plugins/hoster/FileApeCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FileApeCom(DeadHoster):
- __name__ = "FileApeCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+'
-
- __description__ = """FileApe.com hoster plugin"""
- __author_name__ = "espes"
- __author_mail__ = None
-
-
-getInfo = create_getInfo(FileApeCom)
diff --git a/module/plugins/hoster/FileParadoxIn.py b/module/plugins/hoster/FileParadoxIn.py
deleted file mode 100644
index d6395b130..000000000
--- a/module/plugins/hoster/FileParadoxIn.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class FileParadoxIn(XFileSharingPro):
- __name__ = "FileParadoxIn"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?fileparadox\.in/\w+'
-
- __description__ = """FileParadox.in hoster plugin"""
- __author_name__ = "RazorWing"
- __author_mail__ = "muppetuk1@hotmail.com"
-
- HOSTER_NAME = "fileparadox.in"
-
- FILE_SIZE_PATTERN = r'</font>\s*\(\s*(?P<S>[^)]+)\s*\)</font>'
- LINK_PATTERN = r'(http://([^/]*?fileparadox.in|\d+\.\d+\.\d+\.\d+)(:\d+/d/|/files/\w+/\w+/)[^"\'<]+)'
-
-
-getInfo = create_getInfo(FileParadoxIn)
diff --git a/module/plugins/hoster/FileStoreTo.py b/module/plugins/hoster/FileStoreTo.py
deleted file mode 100644
index 98d67609a..000000000
--- a/module/plugins/hoster/FileStoreTo.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FileStoreTo(SimpleHoster):
- __name__ = "FileStoreTo"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
-
- __description__ = """FileStore.to hoster plugin"""
- __author_name__ = ("Walter Purcaro", "stickell")
- __author_mail__ = ("vuolter@gmail.com", "l.stickell@yahoo.it")
-
- FILE_INFO_PATTERN = r'File: <span[^>]*>(?P<N>.+)</span><br />Size: (?P<S>[\d,.]+) (?P<U>\w+)'
- OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def handleFree(self):
- self.wait(10)
- ldc = re.search(r'wert="(\w+)"', self.html).group(1)
- link = self.load("http://filestore.to/ajax/download.php", get={"LDC": ldc})
- self.logDebug("Download link = " + link)
- self.download(link)
-
-
-getInfo = create_getInfo(FileStoreTo)
diff --git a/module/plugins/hoster/FilebeerInfo.py b/module/plugins/hoster/FilebeerInfo.py
deleted file mode 100644
index 3e8cfb6a3..000000000
--- a/module/plugins/hoster/FilebeerInfo.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FilebeerInfo(DeadHoster):
- __name__ = "FilebeerInfo"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+).*'
-
- __description__ = """Filebeer.info plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(FilebeerInfo)
diff --git a/module/plugins/hoster/FilecloudIo.py b/module/plugins/hoster/FilecloudIo.py
deleted file mode 100644
index 025bd483f..000000000
--- a/module/plugins/hoster/FilecloudIo.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilecloudIo(SimpleHoster):
- __name__ = "FilecloudIo"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+).*'
-
- __description__ = """Filecloud.io hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- FILE_SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
- FILE_NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
- OFFLINE_PATTERN = r'l10n.(FILES__DOESNT_EXIST|REMOVED)'
- TEMP_OFFLINE_PATTERN = r'l10n.FILES__WARNING'
-
- UKEY_PATTERN = r"'ukey'\s*:'(\w+)',"
- AB1_PATTERN = r"if\( __ab1 == '(\w+)' \)"
- ERROR_MSG_PATTERN = r'var __error_msg\s*=\s*l10n\.(.*?);'
- LINK_PATTERN = r'"(http://s\d+.filecloud.io/%s/\d+/.*?)"'
- RECAPTCHA_KEY_PATTERN = r"var __recaptcha_public\s*=\s*'([^']+)';"
- RECAPTCHA_KEY = "6Lf5OdISAAAAAEZObLcx5Wlv4daMaASRov1ysDB1"
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = 1
-
- def handleFree(self):
- data = {"ukey": self.file_info['ID']}
-
- m = re.search(self.AB1_PATTERN, self.html)
- if m is None:
- self.parseError("__AB1")
- data['__ab1'] = m.group(1)
-
- if not self.account:
- self.fail("User not logged in")
- elif not self.account.logged_in:
- recaptcha = ReCaptcha(self)
- captcha_challenge, captcha_response = recaptcha.challenge(self.RECAPTCHA_KEY)
- self.account.form_data = {"recaptcha_challenge_field": captcha_challenge,
- "recaptcha_response_field": captcha_response}
- self.account.relogin(self.user)
- self.retry(2)
-
- json_url = "http://filecloud.io/download-request.json"
- response = self.load(json_url, post=data)
- self.logDebug(response)
- response = json_loads(response)
-
- if "error" in response and response['error']:
- self.fail(response)
-
- self.logDebug(response)
- if response['captcha']:
- recaptcha = ReCaptcha(self)
- m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
- captcha_key = m.group(1) if m else self.RECAPTCHA_KEY
- data['ctype'] = "recaptcha"
-
- for _ in xrange(5):
- data['recaptcha_challenge'], data['recaptcha_response'] = recaptcha.challenge(captcha_key)
-
- json_url = "http://filecloud.io/download-request.json"
- response = self.load(json_url, post=data)
- self.logDebug(response)
- response = json_loads(response)
-
- if "retry" in response and response['retry']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("Incorrect captcha")
-
- if response['dl']:
- self.html = self.load('http://filecloud.io/download.html')
- m = re.search(self.LINK_PATTERN % self.file_info['ID'], self.html)
- if m is None:
- self.parseError("Download URL")
- download_url = m.group(1)
- self.logDebug("Download URL: %s" % download_url)
-
- if "size" in self.file_info and self.file_info['size']:
- self.check_data = {"size": int(self.file_info['size'])}
- self.download(download_url)
- else:
- self.fail("Unexpected server response")
-
- def handlePremium(self):
- akey = self.account.getAccountData(self.user)['akey']
- ukey = self.file_info['ID']
- self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey))
- rep = self.load("http://api.filecloud.io/api-fetch_download_url.api",
- post={"akey": akey, "ukey": ukey})
- self.logDebug("FetchDownloadUrl: " + rep)
- rep = json_loads(rep)
- if rep['status'] == 'ok':
- self.download(rep['download_url'], disposition=True)
- else:
- self.fail(rep['message'])
-
-
-getInfo = create_getInfo(FilecloudIo)
diff --git a/module/plugins/hoster/FilefactoryCom.py b/module/plugins/hoster/FilefactoryCom.py
deleted file mode 100644
index 1ac7550fc..000000000
--- a/module/plugins/hoster/FilefactoryCom.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- for url in urls:
- h = getURL(url, just_header=True)
- m = re.search(r'Location: (.+)\r\n', h)
- if m and not re.match(m.group(1), FilefactoryCom.__pattern__): # It's a direct link! Skipping
- yield (url, 0, 3, url)
- else: # It's a standard html page
- file_info = parseFileInfo(FilefactoryCom, url, getURL(url))
- yield file_info
-
-
-class FilefactoryCom(SimpleHoster):
- __name__ = "FilefactoryCom"
- __type__ = "hoster"
- __version__ = "0.50"
-
- __pattern__ = r'https?://(?:www\.)?filefactory\.com/file/(?P<id>[a-zA-Z0-9]+)'
-
- __description__ = """Filefactory.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_INFO_PATTERN = r'<div id="file_name"[^>]*>\s*<h2>(?P<N>[^<]+)</h2>\s*<div id="file_info">\s*(?P<S>[\d.]+) (?P<U>\w+) uploaded'
- LINK_PATTERN = r'<a href="(https?://[^"]+)"[^>]*><i[^>]*></i> Download with FileFactory Premium</a>'
- OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available'
- PREMIUM_ONLY_PATTERN = r'>Premium Account Required<'
-
- SH_COOKIES = [(".filefactory.com", "locale", "en_US.utf8")]
-
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
- if "Currently only Premium Members can download files larger than" in self.html:
- self.fail("File too large for free download")
- elif "All free download slots on this server are currently in use" in self.html:
- self.retry(50, 15 * 60, "All free slots are busy")
-
- m = re.search(r'data-href(?:-direct)?="(http://[^"]+)"', self.html)
- if m:
- t = re.search(r'<div id="countdown_clock" data-delay="(\d+)">', self.html)
- if t:
- t = t.group(1)
- else:
- self.logDebug("Unable to detect countdown duration. Guessing 60 seconds")
- t = 60
- self.wait(t)
- direct = m.group(1)
- else: # This section could be completely useless now
- # Load the page that contains the direct link
- url = re.search(r"document\.location\.host \+\s*'(.+)';", self.html)
- if url is None:
- self.parseError('Unable to detect free link')
- url = 'http://www.filefactory.com' + url.group(1)
- self.html = self.load(url, decode=True)
-
- # Free downloads wait time
- waittime = re.search(r'id="startWait" value="(\d+)"', self.html)
- if not waittime:
- self.parseError('Unable to detect wait time')
- self.wait(int(waittime.group(1)))
-
- # Parse the direct link and download it
- direct = re.search(r'data-href(?:-direct)?="(.*)" class="button', self.html)
- if not direct:
- self.parseError('Unable to detect free direct link')
- direct = direct.group(1)
-
- self.logDebug('DIRECT LINK: ' + direct)
- self.download(direct, disposition=True)
-
- check = self.checkDownload({"multiple": "You are currently downloading too many files at once.",
- "error": '<div id="errorMessage">'})
-
- if check == "multiple":
- self.logDebug("Parallel downloads detected; waiting 15 minutes")
- self.retry(wait_time=15 * 60, reason="Parallel downloads")
- elif check == "error":
- self.fail("Unknown error")
-
- def handlePremium(self):
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header:
- url = header['location'].strip()
- if not url.startswith("http://"):
- url = "http://www.filefactory.com" + url
- elif 'content-disposition' in header:
- url = self.pyfile.url
- else:
- self.logInfo('You could enable "Direct Downloads" on http://filefactory.com/account/')
- html = self.load(self.pyfile.url)
- m = re.search(self.LINK_PATTERN, html)
- if m:
- url = m.group(1)
- else:
- self.parseError('Unable to detect premium direct link')
-
- self.logDebug('DIRECT PREMIUM LINK: ' + url)
- self.download(url, disposition=True)
diff --git a/module/plugins/hoster/FilejungleCom.py b/module/plugins/hoster/FilejungleCom.py
deleted file mode 100644
index 9380be90e..000000000
--- a/module/plugins/hoster/FilejungleCom.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.FileserveCom import FileserveCom, checkFile
-from module.plugins.Plugin import chunks
-
-
-class FilejungleCom(FileserveCom):
- __name__ = "FilejungleCom"
- __type__ = "hoster"
- __version__ = "0.51"
-
- __pattern__ = r'http://(?:www\.)?filejungle\.com/f/(?P<id>[^/]+).*'
-
- __description__ = """Filejungle.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- URLS = ["http://www.filejungle.com/f/", "http://www.filejungle.com/check_links.php",
- "http://www.filejungle.com/checkReCaptcha.php"]
- LINKCHECK_TR = r'<li>\s*(<div class="col1">.*?)</li>'
- LINKCHECK_TD = r'<div class="(?:col )?col\d">(?:<[^>]*>|&nbsp;)*([^<]*)'
-
- LONG_WAIT_PATTERN = r'<h1>Please wait for (\d+) (\w+)\s*to download the next file\.</h1>'
-
-
-def getInfo(urls):
- for chunk in chunks(urls, 100):
- yield checkFile(FilejungleCom, chunk)
diff --git a/module/plugins/hoster/FileomCom.py b/module/plugins/hoster/FileomCom.py
deleted file mode 100644
index 2f876466b..000000000
--- a/module/plugins/hoster/FileomCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://fileom.com/gycaytyzdw3g/random.bin.html
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class FileomCom(XFileSharingPro):
- __name__ = "FileomCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?fileom\.com/\w+'
-
- __description__ = """Fileom.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- HOSTER_NAME = "fileom.com"
-
- FILE_URL_REPLACEMENTS = [(r'/$', "")]
- SH_COOKIES = [(".fileom.com", "lang", "english")]
-
- FILE_NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
- FILE_SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d\.]+) (?P<U>\w+)'
-
- ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.*?)(?:\'|</)'
-
- LINK_PATTERN = r"var url2 = '(.+?)';"
-
-
- def setup(self):
- self.resumeDownload = self.premium
- self.multiDL = True
- self.chunkLimit = 1
-
-
-getInfo = create_getInfo(FileomCom)
diff --git a/module/plugins/hoster/FilepostCom.py b/module/plugins/hoster/FilepostCom.py
deleted file mode 100644
index 0e1b6ea5a..000000000
--- a/module/plugins/hoster/FilepostCom.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilepostCom(SimpleHoster):
- __name__ = "FilepostCom"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'https?://(?:www\.)?(?:filepost\.com/files|fp.io)/([^/]+).*'
-
- __description__ = """Filepost.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<input type="text" id="url" value=\'<a href[^>]*>(?P<N>[^>]+?) - (?P<S>[0-9\.]+ [kKMG]i?B)</a>\' class="inp_text"/>'
- OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>|<div class="file_info file_info_deleted">'
-
- PREMIUM_ONLY_PATTERN = r'members only. Please upgrade to premium|a premium membership is required to download this file'
- RECAPTCHA_KEY_PATTERN = r"Captcha.init\({\s*key:\s*'([^']+)'"
- FLP_TOKEN_PATTERN = r"set_store_options\({token: '([^']+)'"
-
-
- def handleFree(self):
- # Find token and captcha key
- file_id = re.match(self.__pattern__, self.pyfile.url).group(1)
-
- m = re.search(self.FLP_TOKEN_PATTERN, self.html)
- if m is None:
- self.parseError("Token")
- flp_token = m.group(1)
-
- m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
- if m is None:
- self.parseError("Captcha key")
- captcha_key = m.group(1)
-
- # Get wait time
- get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time() * 10000)) + '-xml'}
- post_dict = {'action': 'set_download', 'token': flp_token, 'code': file_id}
- wait_time = int(self.getJsonResponse(get_dict, post_dict, 'wait_time'))
-
- if wait_time > 0:
- self.wait(wait_time)
-
- post_dict = {"token": flp_token, "code": file_id, "file_pass": ''}
-
- if 'var is_pass_exists = true;' in self.html:
- # Solve password
- for file_pass in self.getPassword().splitlines():
- get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
- post_dict['file_pass'] = file_pass
- self.logInfo("Password protected link, trying " + file_pass)
-
- download_url = self.getJsonResponse(get_dict, post_dict, 'link')
- if download_url:
- break
-
- else:
- self.fail("No or incorrect password")
-
- else:
- # Solve recaptcha
- recaptcha = ReCaptcha(self)
-
- for i in xrange(5):
- get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
- if i:
- post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field'] = recaptcha.challenge(
- captcha_key)
- self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
- captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
-
- download_url = self.getJsonResponse(get_dict, post_dict, 'link')
- if download_url:
- if i:
- self.correctCaptcha()
- break
- elif i:
- self.invalidCaptcha()
-
- else:
- self.fail("Invalid captcha")
-
- # Download
- self.download(download_url)
-
- def getJsonResponse(self, get_dict, post_dict, field):
- json_response = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
- self.logDebug(json_response)
-
- if not 'js' in json_response:
- self.parseError('JSON %s 1' % field)
-
- # i changed js_answer to json_response['js'] since js_answer is nowhere set.
- # i don't know the JSON-HTTP specs in detail, but the previous author
- # accessed json_response['js']['error'] as well as js_answer['error'].
- # see the two lines commented out with "# ~?".
- if 'error' in json_response['js']:
- if json_response['js']['error'] == 'download_delay':
- self.retry(wait_time=json_response['js']['params']['next_download'])
- # ~? self.retry(wait_time=js_answer['params']['next_download'])
- elif 'Wrong file password' in json_response['js']['error']:
- return None
- elif 'You entered a wrong CAPTCHA code' in json_response['js']['error']:
- return None
- elif 'CAPTCHA Code nicht korrekt' in json_response['js']['error']:
- return None
- elif 'CAPTCHA' in json_response['js']['error']:
- self.logDebug('error response is unknown, but mentions CAPTCHA -> return None')
- return None
- else:
- self.fail(json_response['js']['error'])
- # ~? self.fail(js_answer['error'])
-
- if not 'answer' in json_response['js'] or not field in json_response['js']['answer']:
- self.parseError('JSON %s 2' % field)
-
- return json_response['js']['answer'][field]
-
-
-getInfo = create_getInfo(FilepostCom)
diff --git a/module/plugins/hoster/FilerNet.py b/module/plugins/hoster/FilerNet.py
deleted file mode 100644
index a36607f8a..000000000
--- a/module/plugins/hoster/FilerNet.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://filer.net/get/ivgf5ztw53et3ogd
-# http://filer.net/get/hgo14gzcng3scbvv
-
-import pycurl
-import re
-
-from urlparse import urljoin
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FilerNet(SimpleHoster):
- __name__ = "FilerNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?filer\.net/get/(\w+)'
-
- __description__ = """Filer.net hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_INFO_PATTERN = r'<h1 class="page-header">Free Download (?P<N>\S+) <small>(?P<S>[\w.]+) (?P<U>\w+)</small></h1>'
- OFFLINE_PATTERN = r'Nicht gefunden'
- RECAPTCHA_KEY = "6LcFctISAAAAAAgaeHgyqhNecGJJRnxV1m_vAz3V"
- LINK_PATTERN = r'href="([^"]+)">Get download</a>'
-
-
- def process(self, pyfile):
- if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.handlePremium()
- else:
- self.handleFree()
-
- def handleFree(self):
- self.req.setOption("timeout", 120)
- self.html = self.load(self.pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
-
- # Wait between downloads
- m = re.search(r'musst du <span id="time">(\d+)</span> Sekunden warten', self.html)
- if m:
- waittime = int(m.group(1))
- self.retry(3, waittime, "Wait between free downloads")
-
- self.getFileInfo()
-
- self.html = self.load(self.pyfile.url, decode=True)
-
- inputs = self.parseHtmlForm(input_names='token')[1]
- if 'token' not in inputs:
- self.parseError('Unable to detect token')
- token = inputs['token']
- self.logDebug('Token: ' + token)
-
- self.html = self.load(self.pyfile.url, post={'token': token}, decode=True)
-
- inputs = self.parseHtmlForm(input_names='hash')[1]
- if 'hash' not in inputs:
- self.parseError('Unable to detect hash')
- hash_data = inputs['hash']
- self.logDebug('Hash: ' + hash_data)
-
- downloadURL = r''
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- post_data = {'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field': response,
- 'hash': hash_data}
-
- # Workaround for 0.4.9 just_header issue. In 0.5 clean the code using just_header
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
- self.load(self.pyfile.url, post=post_data)
- self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
-
- if 'location' in self.req.http.header.lower():
- location = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
- downloadURL = urljoin('http://filer.net', location)
- self.correctCaptcha()
- break
- else:
- self.logInfo('Wrong captcha')
- self.invalidCaptcha()
-
- if not downloadURL:
- self.fail("No Download url retrieved/all captcha attempts failed")
-
- self.download(downloadURL, disposition=True)
-
- def handlePremium(self):
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header: # Direct Download ON
- dl = self.pyfile.url
- else: # Direct Download OFF
- html = self.load(self.pyfile.url)
- m = re.search(self.LINK_PATTERN, html)
- if m is None:
- self.parseError("Unable to detect direct link, try to enable 'Direct download' in your user settings")
- dl = 'http://filer.net' + m.group(1)
-
- self.logDebug('Direct link: ' + dl)
- self.download(dl, disposition=True)
-
-
-getInfo = create_getInfo(FilerNet)
diff --git a/module/plugins/hoster/FilerioCom.py b/module/plugins/hoster/FilerioCom.py
deleted file mode 100644
index 5cac34b04..000000000
--- a/module/plugins/hoster/FilerioCom.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class FilerioCom(XFileSharingPro):
- __name__ = "FilerioCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
-
- __description__ = """FileRio.in hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "filerio.in"
-
- OFFLINE_PATTERN = r'<b>&quot;File Not Found&quot;</b>|File has been removed due to Copyright Claim'
- FILE_URL_REPLACEMENTS = [(r'http://.*?/', 'http://filerio.in/')]
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
-
-getInfo = create_getInfo(FilerioCom)
diff --git a/module/plugins/hoster/FilesMailRu.py b/module/plugins/hoster/FilesMailRu.py
deleted file mode 100644
index bbb6fa57b..000000000
--- a/module/plugins/hoster/FilesMailRu.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-
-
-def getInfo(urls):
- result = []
- for chunk in chunks(urls, 10):
- for url in chunk:
- src = getURL(url)
- if r'<div class="errorMessage mb10">' in src:
- result.append((url, 0, 1, url))
- elif r'Page cannot be displayed' in src:
- result.append((url, 0, 1, url))
- else:
- try:
- url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
- file_name = re.search(url_pattern, src).group(0).split(', event)">')[1].split('</a>')[0]
- result.append((file_name, 0, 2, url))
- except:
- pass
-
- # status 1=OFFLINE, 2=OK, 3=UNKNOWN
- # result.append((#name,#size,#status,#url))
- yield result
-
-
-class FilesMailRu(Hoster):
- __name__ = "FilesMailRu"
- __type__ = "hoster"
- __version__ = "0.31"
-
- __pattern__ = r'http://(?:www\.)?files\.mail\.ru/.*'
-
- __description__ = """Files.mail.ru hoster plugin"""
- __author_name__ = "oZiRiz"
- __author_mail__ = "ich@oziriz.de"
-
-
- def setup(self):
- if not self.account:
- self.multiDL = False
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url)
- self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
-
- #marks the file as "offline" when the pattern was found on the html-page'''
- if r'<div class="errorMessage mb10">' in self.html:
- self.offline()
-
- elif r'Page cannot be displayed' in self.html:
- self.offline()
-
- #the filename that will be showed in the list (e.g. test.part1.rar)'''
- pyfile.name = self.getFileName()
-
- #prepare and download'''
- if not self.account:
- self.prepare()
- self.download(self.getFileUrl())
- self.myPostProcess()
- else:
- self.download(self.getFileUrl())
- self.myPostProcess()
-
- def prepare(self):
- """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected"""
- self.setWait(10)
- self.wait()
- return True
-
- def getFileUrl(self):
- """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html"""
- return re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[0]
-
- def getFileName(self):
- """gives you the Name for each file. Also extracted from the HTML-Page"""
- return re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0]
-
- def myPostProcess(self):
- # searches the file for HTMl-Code. Sometimes the Redirect
- # doesn't work (maybe a curl Problem) and you get only a small
- # HTML file and the Download is marked as "finished"
- # then the download will be restarted. It's only bad for these
- # who want download a HTML-File (it's one in a million ;-) )
- #
- # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB
- # so i set it to check every download because sometimes there are downloads
- # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file
- # (Loading 100MB in to ram is not an option)
- check = self.checkDownload({"html": "<meta name="}, read_size=50000)
- if check == "html":
- self.logInfo(_(
- "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." %
- self.pyfile.name))
- self.retry()
diff --git a/module/plugins/hoster/FileserveCom.py b/module/plugins/hoster/FileserveCom.py
deleted file mode 100644
index b7f051d80..000000000
--- a/module/plugins/hoster/FileserveCom.py
+++ /dev/null
@@ -1,209 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.utils import parseFileSize
-
-
-def checkFile(plugin, urls):
- html = getURL(plugin.URLS[1], post={"urls": "\n".join(urls)}, decode=True)
-
- file_info = []
- for li in re.finditer(plugin.LINKCHECK_TR, html, re.DOTALL):
- try:
- cols = re.findall(plugin.LINKCHECK_TD, li.group(1))
- if cols:
- file_info.append((
- cols[1] if cols[1] != '--' else cols[0],
- parseFileSize(cols[2]) if cols[2] != '--' else 0,
- 2 if cols[3].startswith('Available') else 1,
- cols[0]))
- except Exception, e:
- continue
-
- return file_info
-
-
-class FileserveCom(Hoster):
- __name__ = "FileserveCom"
- __type__ = "hoster"
- __version__ = "0.52"
-
- __pattern__ = r'http://(?:www\.)?fileserve\.com/file/(?P<id>[^/]+).*'
-
- __description__ = """Fileserve.com hoster plugin"""
- __author_name__ = ("jeix", "mkaay", "Paul King", "zoidberg")
- __author_mail__ = ("jeix@hasnomail.de", "mkaay@mkaay.de", "", "zoidberg@mujmail.cz")
-
- URLS = ["http://www.fileserve.com/file/", "http://www.fileserve.com/link-checker.php",
- "http://www.fileserve.com/checkReCaptcha.php"]
- LINKCHECK_TR = r'<tr>\s*(<td>http://www.fileserve\.com/file/.*?)</tr>'
- LINKCHECK_TD = r'<td>(?:<[^>]*>|&nbsp;)*([^<]*)'
-
- CAPTCHA_KEY_PATTERN = r"var reCAPTCHA_publickey='(?P<key>[^']+)'"
- LONG_WAIT_PATTERN = r'<li class="title">You need to wait (\d+) (\w+) to start another download\.</li>'
- LINK_EXPIRED_PATTERN = r'Your download link has expired'
- DAILY_LIMIT_PATTERN = r'Your daily download limit has been reached'
- NOT_LOGGED_IN_PATTERN = r'<form (name="loginDialogBoxForm"|id="login_form")|<li><a href="/login.php">Login</a></li>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
- self.file_id = re.match(self.__pattern__, self.pyfile.url).group('id')
- self.url = "%s%s" % (self.URLS[0], self.file_id)
- self.logDebug("File ID: %s URL: %s" % (self.file_id, self.url))
-
- def process(self, pyfile):
- pyfile.name, pyfile.size, status, self.url = checkFile(self, [self.url])[0]
- if status != 2:
- self.offline()
- self.logDebug("File Name: %s Size: %d" % (pyfile.name, pyfile.size))
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- def handleFree(self):
- self.html = self.load(self.url)
- action = self.load(self.url, post={"checkDownload": "check"}, decode=True)
- action = json_loads(action)
- self.logDebug(action)
-
- if "fail" in action:
- if action['fail'] == "timeLimit":
- self.html = self.load(self.url, post={"checkDownload": "showError", "errorType": "timeLimit"},
- decode=True)
-
- self.doLongWait(re.search(self.LONG_WAIT_PATTERN, self.html))
-
- elif action['fail'] == "parallelDownload":
- self.logWarning(_("Parallel download error, now waiting 60s."))
- self.retry(wait_time=60, reason="parallelDownload")
-
- else:
- self.fail("Download check returned %s" % action['fail'])
-
- elif "success" in action:
- if action['success'] == "showCaptcha":
- self.doCaptcha()
- self.doTimmer()
- elif action['success'] == "showTimmer":
- self.doTimmer()
-
- else:
- self.fail("Unknown server response")
-
- # show download link
- response = self.load(self.url, post={"downloadLink": "show"}, decode=True)
- self.logDebug("show downloadLink response : %s" % response)
- if "fail" in response:
- self.fail("Couldn't retrieve download url")
-
- # this may either download our file or forward us to an error page
- self.download(self.url, post={"download": "normal"})
- self.logDebug(self.req.http.lastEffectiveURL)
-
- check = self.checkDownload({"expired": self.LINK_EXPIRED_PATTERN,
- "wait": re.compile(self.LONG_WAIT_PATTERN),
- "limit": self.DAILY_LIMIT_PATTERN})
-
- if check == "expired":
- self.logDebug("Download link was expired")
- self.retry()
- elif check == "wait":
- self.doLongWait(self.lastCheck)
- elif check == "limit":
- self.logWarning("Download limited reached for today")
- self.setWait(secondsToMidnight(gmt=2), True)
- self.wait()
- self.retry()
-
- self.thread.m.reconnecting.wait(3) # Ease issue with later downloads appearing to be in parallel
-
- def doTimmer(self):
- response = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
- self.logDebug("wait response : %s" % response[:80])
-
- if "fail" in response:
- self.fail("Failed getting wait time")
-
- if self.__name__ == "FilejungleCom":
- m = re.search(r'"waitTime":(\d+)', response)
- if m is None:
- self.fail("Cannot get wait time")
- wait_time = int(m.group(1))
- else:
- wait_time = int(response) + 3
-
- self.setWait(wait_time)
- self.wait()
-
- def doCaptcha(self):
- captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group("key")
- recaptcha = ReCaptcha(self)
-
- for _ in xrange(5):
- challenge, code = recaptcha.challenge(captcha_key)
-
- response = json_loads(self.load(self.URLS[2],
- post={'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field': code,
- 'recaptcha_shortencode_field': self.file_id}))
- self.logDebug("reCaptcha response : %s" % response)
- if not response['success']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("Invalid captcha")
-
- def doLongWait(self, m):
- wait_time = (int(m.group(1)) * {'seconds': 1, 'minutes': 60, 'hours': 3600}[m.group(2)]) if m else 12 * 60
- self.setWait(wait_time, True)
- self.wait()
- self.retry()
-
- def handlePremium(self):
- premium_url = None
- if self.__name__ == "FileserveCom":
- #try api download
- response = self.load("http://app.fileserve.com/api/download/premium/",
- post={"username": self.user,
- "password": self.account.getAccountData(self.user)['password'],
- "shorten": self.file_id},
- decode=True)
- if response:
- response = json_loads(response)
- if response['error_code'] == "302":
- premium_url = response['next']
- elif response['error_code'] in ["305", "500"]:
- self.tempOffline()
- elif response['error_code'] in ["403", "605"]:
- self.resetAccount()
- elif response['error_code'] in ["606", "607", "608"]:
- self.offline()
- else:
- self.logError(response['error_code'], response['error_message'])
-
- self.download(premium_url or self.pyfile.url)
-
- if not premium_url:
- check = self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)})
-
- if check == "login":
- self.account.relogin(self.user)
- self.retry(reason=_("Not logged in."))
-
-
-def getInfo(urls):
- for chunk in chunks(urls, 100):
- yield checkFile(FileserveCom, chunk)
diff --git a/module/plugins/hoster/FileshareInUa.py b/module/plugins/hoster/FileshareInUa.py
deleted file mode 100644
index f76942f6d..000000000
--- a/module/plugins/hoster/FileshareInUa.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class FileshareInUa(Hoster):
- __name__ = "FileshareInUa"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?fileshare.in.ua/[A-Za-z0-9]+'
-
- __description__ = """Fileshare.in.ua hoster plugin"""
- __author_name__ = "fwannmacher"
- __author_mail__ = "felipe@warhammerproject.com"
-
- PATTERN_FILENAME = r'<h3 class="b-filename">(.*?)</h3>'
- PATTERN_FILESIZE = r'<b class="b-filesize">(.*?)</b>'
- PATTERN_OFFLINE = r"This file doesn't exist, or has been removed."
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.html = self.load(pyfile.url, decode=True)
-
- if not self._checkOnline():
- self.offline()
-
- pyfile.name = self._getName()
-
- link = self._getLink()
-
- if not link.startswith('http://'):
- link = "http://fileshare.in.ua" + link
-
- self.download(link)
-
- def _checkOnline(self):
- if re.search(self.PATTERN_OFFLINE, self.html):
- return False
- else:
- return True
-
- def _getName(self):
- name = re.search(self.PATTERN_FILENAME, self.html)
- if name is None:
- self.fail("%s: Plugin broken." % self.__name__)
-
- return name.group(1)
-
- def _getLink(self):
- return re.search("<a href=\"(/get/.+)\" class=\"b-button m-blue m-big\" >", self.html).group(1)
-
-
-def getInfo(urls):
- result = []
-
- for url in urls:
- html = getURL(url)
-
- if re.search(FileshareInUa.PATTERN_OFFLINE, html):
- result.append((url, 0, 1, url))
- else:
- name = re.search(FileshareInUa.PATTERN_FILENAME, html)
-
- if name is None:
- result.append((url, 0, 1, url))
- continue
-
- name = name.group(1)
- size = re.search(FileshareInUa.PATTERN_FILESIZE, html)
- size = parseFileSize(size.group(1))
-
- result.append((name, size, 3, url))
-
- yield result
diff --git a/module/plugins/hoster/FilezyNet.py b/module/plugins/hoster/FilezyNet.py
deleted file mode 100644
index 969007a8a..000000000
--- a/module/plugins/hoster/FilezyNet.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class FilezyNet(XFileSharingPro):
- __name__ = "FilezyNet"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?filezy.net/.*/.*.html'
-
- __description__ = """Filezy.net hoster plugin"""
- __author_name__ = None
- __author_mail__ = None
-
- HOSTER_NAME = "filezy.net"
-
- FILE_SIZE_PATTERN = r'<span class="plansize">(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</span>'
- WAIT_PATTERN = r'<div id="countdown_str" class="seconds">\n<!--Wait--> <span id=".*?">(\d+)</span>'
- DOWNLOAD_JS_PATTERN = r"<script type='text/javascript'>eval(.*)"
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
- def getDownloadLink(self):
- self.logDebug("Getting download link")
-
- data = self.getPostParameters()
- self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
-
- obfuscated_js = re.search(self.DOWNLOAD_JS_PATTERN, self.html)
- dl_file_now = self.js.eval(obfuscated_js.group(1))
- link = re.search(self.LINK_PATTERN, dl_file_now)
- return link.group(1)
-
-
-getInfo = create_getInfo(FilezyNet)
diff --git a/module/plugins/hoster/FiredriveCom.py b/module/plugins/hoster/FiredriveCom.py
deleted file mode 100644
index 47c6a4214..000000000
--- a/module/plugins/hoster/FiredriveCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FiredriveCom(SimpleHoster):
- __name__ = "FiredriveCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
-
- __description__ = """Firedrive.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- FILE_NAME_PATTERN = r'<b>Name:</b> (?P<N>.+) <br>'
- FILE_SIZE_PATTERN = r'<b>Size:</b> (?P<S>[\d.]+) (?P<U>[a-zA-Z]+) <br>'
- OFFLINE_PATTERN = r'class="sad_face_image"|>No such page here.<'
- TEMP_OFFLINE_PATTERN = r'>(File Temporarily Unavailable|Server Error. Try again later)'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.firedrive.com/file/\g<ID>')]
-
- LINK_PATTERN = r'<a href="(https?://dl\.firedrive\.com/\?key=.+?)"'
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
- self.chunkLimit = -1
-
- def handleFree(self):
- link = self._getLink()
- self.logDebug("Direct link: " + link)
- self.download(link, disposition=True)
-
- def _getLink(self):
- f = re.search(self.LINK_PATTERN, self.html)
- if f:
- return f.group(1)
- else:
- self.html = self.load(self.pyfile.url, post={"confirm": re.search(r'name="confirm" value="(.+?)"', self.html).group(1)})
- f = re.search(self.LINK_PATTERN, self.html)
- if f:
- return f.group(1)
- else:
- self.parseError("Direct download link not found")
-
-
-getInfo = create_getInfo(FiredriveCom)
diff --git a/module/plugins/hoster/FlyFilesNet.py b/module/plugins/hoster/FlyFilesNet.py
deleted file mode 100644
index 2bf9e0caf..000000000
--- a/module/plugins/hoster/FlyFilesNet.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-class FlyFilesNet(SimpleHoster):
- __name__ = "FlyFilesNet"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?flyfiles\.net/.*'
-
- __description__ = """FlyFiles.net hoster plugin"""
- __author_name__ = None
- __author_mail__ = None
-
- SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
- FILE_NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
-
-
- def process(self, pyfile):
- name = re.search(self.FILE_NAME_PATTERN, pyfile.url).group(1)
- pyfile.name = unquote_plus(name)
-
- session = re.search(self.SESSION_PATTERN, pyfile.url).group(1)
-
- url = "http://flyfiles.net"
-
- # get download URL
- parsed_url = getURL(url, post={"getDownLink": session}, cookies=True)
- self.logDebug("Parsed URL: %s" % parsed_url)
-
- if parsed_url == '#downlink|' or parsed_url == "#downlink|#":
- self.logWarning("Could not get the download URL. Please wait 10 minutes.")
- self.wait(10 * 60, True)
- self.retry()
-
- download_url = parsed_url.replace('#downlink|', '')
-
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
diff --git a/module/plugins/hoster/FourSharedCom.py b/module/plugins/hoster/FourSharedCom.py
deleted file mode 100644
index 2668a22d3..000000000
--- a/module/plugins/hoster/FourSharedCom.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class FourSharedCom(SimpleHoster):
- __name__ = "FourSharedCom"
- __type__ = "hoster"
- __version__ = "0.29"
-
- __pattern__ = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+?/.*'
-
- __description__ = """4Shared.com hoster plugin"""
- __author_name__ = ("jeix", "zoidberg")
- __author_mail__ = ("jeix@hasnomail.de", "zoidberg@mujmail.cz")
-
- FILE_NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
- FILE_SIZE_PATTERN = r'<span title="Size: (?P<S>[0-9,.]+) (?P<U>[kKMG])i?B">'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
-
- FILE_NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
- FILE_SIZE_REPLACEMENTS = [(",", "")]
-
- DOWNLOAD_URL_PATTERN = r'name="d3link" value="(.*?)"'
- DOWNLOAD_BUTTON_PATTERN = r'id="btnLink" href="(.*?)"'
- FID_PATTERN = r'name="d3fid" value="(.*?)"'
-
-
- def handleFree(self):
- if not self.account:
- self.fail("User not logged in")
-
- m = re.search(self.DOWNLOAD_BUTTON_PATTERN, self.html)
- if m:
- link = m.group(1)
- else:
- link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', self.pyfile.url)
-
- self.html = self.load(link)
-
- m = re.search(self.DOWNLOAD_URL_PATTERN, self.html)
- if m is None:
- self.parseError('Download link')
- link = m.group(1)
-
- try:
- m = re.search(self.FID_PATTERN, self.html)
- response = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
- self.logDebug(response)
- except:
- pass
-
- self.wait(20)
- self.download(link)
-
-
-getInfo = create_getInfo(FourSharedCom)
diff --git a/module/plugins/hoster/FreakshareCom.py b/module/plugins/hoster/FreakshareCom.py
deleted file mode 100644
index 1b042bde3..000000000
--- a/module/plugins/hoster/FreakshareCom.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class FreakshareCom(Hoster):
- __name__ = "FreakshareCom"
- __type__ = "hoster"
- __version__ = "0.39"
-
- __pattern__ = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
-
- __description__ = """Freakshare.com hoster plugin"""
- __author_name__ = ("sitacuisses", "spoob", "mkaay", "Toilal")
- __author_mail__ = ("sitacuisses@yahoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "toilal.dev@gmail.com")
-
-
- def setup(self):
- self.multiDL = False
- self.req_opts = []
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- pyfile.url = pyfile.url.replace("freakshare.net/", "freakshare.com/")
-
- if self.account:
- self.html = self.load(pyfile.url, cookies=False)
- pyfile.name = self.get_file_name()
- self.download(pyfile.url)
-
- else:
- self.prepare()
- self.get_file_url()
-
- self.download(pyfile.url, post=self.req_opts)
-
- check = self.checkDownload({"bad": "bad try",
- "paralell": "> Sorry, you cant download more then 1 files at time. <",
- "empty": "Warning: Unknown: Filename cannot be empty",
- "wrong_captcha": "Wrong Captcha!",
- "downloadserver": "No Downloadserver. Please try again later!"})
-
- if check == "bad":
- self.fail("Bad Try.")
- elif check == "paralell":
- self.setWait(300, True)
- self.wait()
- self.retry()
- elif check == "empty":
- self.fail("File not downloadable")
- elif check == "wrong_captcha":
- self.invalidCaptcha()
- self.retry()
- elif check == "downloadserver":
- self.retry(5, 15 * 60, "No Download server")
-
- def prepare(self):
- pyfile = self.pyfile
-
- self.wantReconnect = False
-
- self.download_html()
-
- if not self.file_exists():
- self.offline()
-
- self.setWait(self.get_waiting_time())
-
- pyfile.name = self.get_file_name()
- pyfile.size = self.get_file_size()
-
- self.wait()
-
- return True
-
- def download_html(self):
- self.load("http://freakshare.com/index.php", {"language": "EN"}) # Set english language in server session
- self.html = self.load(self.pyfile.url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
- if not self.wantReconnect:
- self.req_opts = self.get_download_options() # get the Post options for the Request
- #file_url = self.pyfile.url
- #return file_url
- else:
- self.offline()
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
- if not self.wantReconnect:
- file_name = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
- if file_name is not None:
- file_name = file_name.group(1)
- else:
- file_name = self.pyfile.url
- return file_name
- else:
- return self.pyfile.url
-
- def get_file_size(self):
- size = 0
- if not self.html:
- self.download_html()
- if not self.wantReconnect:
- file_size_check = re.search(
- r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
- if file_size_check is not None:
- units = float(file_size_check.group(1).replace(",", ""))
- pow = {'KB': 1, 'MB': 2, 'GB': 3}[file_size_check.group(2)]
- size = int(units * 1024 ** pow)
-
- return size
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- if "Your Traffic is used up for today" in self.html:
- self.wantReconnect = True
- return secondsToMidnight(gmt=2)
-
- timestring = re.search('\s*var\s(?:downloadWait|time)\s=\s(\d*)[.\d]*;', self.html)
- if timestring:
- return int(timestring.group(1)) + 1 # add 1 sec as tenths of seconds are cut off
- else:
- return 60
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"This file does not exist!", self.html) is not None:
- return False
- else:
- return True
-
- def get_download_options(self):
- re_envelope = re.search(r".*?value=\"Free\sDownload\".*?\n*?(.*?<.*?>\n*)*?\n*\s*?</form>",
- self.html).group(0) # get the whole request
- to_sort = re.findall(r"<input\stype=\"hidden\"\svalue=\"(.*?)\"\sname=\"(.*?)\"\s\/>", re_envelope)
- request_options = dict((n, v) for (v, n) in to_sort)
-
- herewego = self.load(self.pyfile.url, None, request_options) # the actual download-Page
-
- # comment this in, when it doesnt work
- # with open("DUMP__FS_.HTML", "w") as fp:
- # fp.write(herewego)
-
- to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
- request_options = dict((n, v) for (v, n) in to_sort)
-
- # comment this in, when it doesnt work as well
- #print "\n\n%s\n\n" % ";".join(["%s=%s" % x for x in to_sort])
-
- challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=([0-9A-Za-z]+)", herewego)
-
- if challenge:
- re_captcha = ReCaptcha(self)
- (request_options['recaptcha_challenge_field'],
- request_options['recaptcha_response_field']) = re_captcha.challenge(challenge.group(1))
-
- return request_options
diff --git a/module/plugins/hoster/FreeWayMe.py b/module/plugins/hoster/FreeWayMe.py
deleted file mode 100644
index dc9188f05..000000000
--- a/module/plugins/hoster/FreeWayMe.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hoster import Hoster
-
-
-class FreeWayMe(Hoster):
- __name__ = "FreeWayMe"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'https://(?:www\.)?free-way.me/.*'
-
- __description__ = """FreeWayMe hoster plugin"""
- __author_name__ = "Nicolas Giese"
- __author_mail__ = "james@free-way.me"
-
-
- def setup(self):
- self.resumeDownload = False
- self.chunkLimit = 1
- self.multiDL = self.premium
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "FreeWayMe")
- self.fail("No FreeWay account provided")
-
- self.logDebug("Old URL: %s" % pyfile.url)
-
- (user, data) = self.account.selectAccount()
-
- self.download(
- "https://www.free-way.me/load.php",
- get={"multiget": 7, "url": pyfile.url, "user": user, "pw": self.account.getpw(user), "json": ""},
- disposition=True)
diff --git a/module/plugins/hoster/FreevideoCz.py b/module/plugins/hoster/FreevideoCz.py
deleted file mode 100644
index d6549a8df..000000000
--- a/module/plugins/hoster/FreevideoCz.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class FreevideoCz(DeadHoster):
- __name__ = "FreevideoCz"
- __type__ = "hoster"
- __version__ = "0.3"
-
- __pattern__ = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
-
- __description__ = """Freevideo.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(FreevideoCz) \ No newline at end of file
diff --git a/module/plugins/hoster/FshareVn.py b/module/plugins/hoster/FshareVn.py
deleted file mode 100644
index bc042cbcc..000000000
--- a/module/plugins/hoster/FshareVn.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import strptime, mktime, gmtime
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- for url in urls:
- html = getURL('http://www.fshare.vn/check_link.php', post={
- "action": "check_link",
- "arrlinks": url
- }, decode=True)
-
- file_info = parseFileInfo(FshareVn, url, html)
-
- yield file_info
-
-
-def doubleDecode(m):
- return m.group(1).decode('raw_unicode_escape')
-
-
-class FshareVn(SimpleHoster):
- __name__ = "FshareVn"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'http://(?:www\.)?fshare.vn/file/.*'
-
- __description__ = """FshareVn hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[0-9,.]+)\s*(?P<U>[kKMG])i?B<\\/p>'
- OFFLINE_PATTERN = r'<div class=\\"f_left file_w\\"|<\\/p>\\t\\t\\t\\t\\r\\n\\t\\t<p><\\/p>\\t\\t\\r\\n\\t\\t<p>0 KB<\\/p>'
-
- FILE_NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
-
- LINK_PATTERN = r'action="(http://download.*?)[#"]'
- WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
-
-
- def process(self, pyfile):
- self.html = self.load('http://www.fshare.vn/check_link.php', post={
- "action": "check_link",
- "arrlinks": pyfile.url
- }, decode=True)
- self.getFileInfo()
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
- self.checkDownloadedFile()
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- self.checkErrors()
-
- action, inputs = self.parseHtmlForm('frm_download')
- self.url = self.pyfile.url + action
-
- if not inputs:
- self.parseError('FORM')
- elif 'link_file_pwd_dl' in inputs:
- for password in self.getPassword().splitlines():
- self.logInfo('Password protected link, trying "%s"' % password)
- inputs['link_file_pwd_dl'] = password
- self.html = self.load(self.url, post=inputs, decode=True)
- if not 'name="link_file_pwd_dl"' in self.html:
- break
- else:
- self.fail("No or incorrect password")
- else:
- self.html = self.load(self.url, post=inputs, decode=True)
-
- self.checkErrors()
-
- m = re.search(r'var count = (\d+)', self.html)
- self.setWait(int(m.group(1)) if m else 30)
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError('FREE DL URL')
- self.url = m.group(1)
- self.logDebug("FREE DL URL: %s" % self.url)
-
- self.wait()
- self.download(self.url)
-
- def handlePremium(self):
- self.download(self.pyfile.url)
-
- def checkErrors(self):
- if '/error.php?' in self.req.lastEffectiveURL or u"Liên kết bạn chọn khÃŽng tồn" in self.html:
- self.offline()
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logInfo("Wait until %s ICT" % m.group(1))
- wait_until = mktime(strptime(m.group(1), "%d/%m/%Y %H:%M"))
- self.wait(wait_until - mktime(gmtime()) - 7 * 60 * 60, True)
- self.retry()
- elif '<ul class="message-error">' in self.html:
- self.logError("Unknown error occured or wait time not parsed")
- self.retry(30, 2 * 60, "Unknown error")
-
- def checkDownloadedFile(self):
- # check download
- check = self.checkDownload({
- "not_found": "<head><title>404 Not Found</title></head>"
- })
-
- if check == "not_found":
- self.fail("File not m on server")
diff --git a/module/plugins/hoster/Ftp.py b/module/plugins/hoster/Ftp.py
deleted file mode 100644
index 1b7eab3ab..000000000
--- a/module/plugins/hoster/Ftp.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from urllib import quote, unquote
-from urlparse import urlparse
-
-from module.plugins.Hoster import Hoster
-
-
-class Ftp(Hoster):
- __name__ = "Ftp"
- __type__ = "hoster"
- __version__ = "0.42"
- __pattern__ = r'(ftps?|sftp)://(.*?:.*?@)?.*?/.*' # ftp://user:password@ftp.server.org/path/to/file
- __description__ = """Download from ftp directory"""
- __author_name__ = ("jeix", "mkaay", "zoidberg")
- __author_mail__ = ("jeix@hasnomail.com", "mkaay@mkaay.de", "zoidberg@mujmail.cz")
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- parsed_url = urlparse(pyfile.url)
- netloc = parsed_url.netloc
-
- pyfile.name = parsed_url.path.rpartition('/')[2]
- try:
- pyfile.name = unquote(str(pyfile.name)).decode('utf8')
- except:
- pass
-
- if not "@" in netloc:
- servers = [x['login'] for x in self.account.getAllAccounts()] if self.account else []
-
- if netloc in servers:
- self.logDebug("Logging on to %s" % netloc)
- self.req.addAuth(self.account.accounts[netloc]['password'])
- else:
- for pwd in pyfile.package().password.splitlines():
- if ":" in pwd:
- self.req.addAuth(pwd.strip())
- break
-
- self.req.http.c.setopt(pycurl.NOBODY, 1)
-
- try:
- response = self.load(pyfile.url)
- except pycurl.error, e:
- self.fail("Error %d: %s" % e.args)
-
- self.req.http.c.setopt(pycurl.NOBODY, 0)
- self.logDebug(self.req.http.header)
-
- m = re.search(r"Content-Length:\s*(\d+)", response)
- if m:
- pyfile.size = int(m.group(1))
- self.download(pyfile.url)
- else:
- #Naive ftp directory listing
- if re.search(r'^25\d.*?"', self.req.http.header, re.M):
- pyfile.url = pyfile.url.rstrip('/')
- pkgname = "/".join(pyfile.package().name, urlparse(pyfile.url).path.rpartition('/')[2])
- pyfile.url += '/'
- self.req.http.c.setopt(48, 1) # CURLOPT_DIRLISTONLY
- response = self.load(pyfile.url, decode=False)
- links = [pyfile.url + quote(x) for x in response.splitlines()]
- self.logDebug("LINKS", links)
- self.core.api.addPackage(pkgname, links)
- else:
- self.fail("Unexpected server response")
diff --git a/module/plugins/hoster/GamefrontCom.py b/module/plugins/hoster/GamefrontCom.py
deleted file mode 100644
index 66cef3013..000000000
--- a/module/plugins/hoster/GamefrontCom.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class GamefrontCom(Hoster):
- __name__ = "GamefrontCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?gamefront.com/files/[A-Za-z0-9]+'
-
- __description__ = """Gamefront.com hoster plugin"""
- __author_name__ = "fwannmacher"
- __author_mail__ = "felipe@warhammerproject.com"
-
- PATTERN_FILENAME = r'<title>(.*?) | Game Front'
- PATTERN_FILESIZE = r'<dt>File Size:</dt>[\n\s]*<dd>(.*?)</dd>'
- PATTERN_OFFLINE = r"This file doesn't exist, or has been removed."
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = -1
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.html = self.load(pyfile.url, decode=True)
-
- if not self._checkOnline():
- self.offline()
-
- pyfile.name = self._getName()
-
- link = self._getLink()
-
- if not link.startswith('http://'):
- link = "http://www.gamefront.com/" + link
-
- self.download(link)
-
- def _checkOnline(self):
- if re.search(self.PATTERN_OFFLINE, self.html):
- return False
- else:
- return True
-
- def _getName(self):
- name = re.search(self.PATTERN_FILENAME, self.html)
- if name is None:
- self.fail("%s: Plugin broken." % self.__name__)
-
- return name.group(1)
-
- def _getLink(self):
- self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=[A-Za-z0-9]+)",
- self.html).group(1))
- return re.search("<a href=\"(http://media[0-9]+\.gamefront.com/.*)\">click here</a>", self.html2).group(1).replace("&amp;", "&")
-
-
-def getInfo(urls):
- result = []
-
- for url in urls:
- html = getURL(url)
-
- if re.search(GamefrontCom.PATTERN_OFFLINE, html):
- result.append((url, 0, 1, url))
- else:
- name = re.search(GamefrontCom.PATTERN_FILENAME, html)
- if name is None:
- result.append((url, 0, 1, url))
- else:
- name = name.group(1)
- size = re.search(GamefrontCom.PATTERN_FILESIZE, html)
- size = parseFileSize(size.group(1))
-
- result.append((name, size, 3, url))
-
- yield result
diff --git a/module/plugins/hoster/GigapetaCom.py b/module/plugins/hoster/GigapetaCom.py
deleted file mode 100644
index 966ac8094..000000000
--- a/module/plugins/hoster/GigapetaCom.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION
-from random import randint
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class GigapetaCom(SimpleHoster):
- __name__ = "GigapetaCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
-
- __description__ = """GigaPeta.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
- FILE_SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
- OFFLINE_PATTERN = r'<div id="page_error">'
-
- SH_COOKIES = [(".gigapeta.com", "lang", "us")]
-
-
- def handleFree(self):
- captcha_key = str(randint(1, 100000000))
- captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
-
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
-
- for _ in xrange(5):
- self.checkErrors()
-
- captcha = self.decryptCaptcha(captcha_url)
- self.html = self.load(self.pyfile.url, post={
- "captcha_key": captcha_key,
- "captcha": captcha,
- "download": "Download"})
-
- m = re.search(r"Location\s*:\s*(.*)", self.req.http.header, re.I)
- if m:
- download_url = m.group(1)
- break
- elif "Entered figures don&#96;t coincide with the picture" in self.html:
- self.invalidCaptcha()
- else:
- self.fail("No valid captcha code entered")
-
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
-
- def checkErrors(self):
- if "All threads for IP" in self.html:
- self.logDebug("Your IP is already downloading a file - wait and retry")
- self.wait(5 * 60, True)
- self.retry()
-
-
-getInfo = create_getInfo(GigapetaCom)
diff --git a/module/plugins/hoster/GooIm.py b/module/plugins/hoster/GooIm.py
deleted file mode 100644
index c43bd0fc9..000000000
--- a/module/plugins/hoster/GooIm.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class GooIm(SimpleHoster):
- __name__ = "GooIm"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?goo\.im/.+'
-
- __description__ = """Goo.im hoster plugin"""
- __author_name__ = "zapp-brannigan"
- __author_mail__ = "fuerst.reinje@web.de"
-
- FILE_NAME_PATTERN = r'You will be redirected to .*(?P<N>[^/ ]+) in'
- OFFLINE_PATTERN = r'The file you requested was not found'
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
-
- def handleFree(self):
- url = self.pyfile.url
- self.html = self.load(url, cookies=True)
- self.wait(10)
- self.download(url, cookies=True)
-
-
-getInfo = create_getInfo(GooIm)
diff --git a/module/plugins/hoster/HellshareCz.py b/module/plugins/hoster/HellshareCz.py
deleted file mode 100644
index 239d90f46..000000000
--- a/module/plugins/hoster/HellshareCz.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class HellshareCz(SimpleHoster):
- __name__ = "HellshareCz"
- __type__ = "hoster"
- __version__ = "0.82"
-
- __pattern__ = r'(http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+).*'
-
- __description__ = """Hellshare.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
- FILE_SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[0-9.]*)&nbsp;(?P<U>[kKMG])i?B</strong>'
- OFFLINE_PATTERN = r'<h1>File not found.</h1>'
- SHOW_WINDOW_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True if self.account else False
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if not self.account:
- self.fail("User not logged in")
- pyfile.url = re.match(self.__pattern__, pyfile.url).group(1)
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
- if not self.checkTrafficLeft():
- self.fail("Not enough traffic left for user %s." % self.user)
-
- m = re.search(self.SHOW_WINDOW_PATTERN, self.html)
- if m is None:
- self.parseError('SHOW WINDOW')
- self.url = "http://www.hellshare.com" + m.group(1)
- self.logDebug("DOWNLOAD URL: " + self.url)
-
- self.download(self.url)
-
-
-getInfo = create_getInfo(HellshareCz)
diff --git a/module/plugins/hoster/HellspyCz.py b/module/plugins/hoster/HellspyCz.py
deleted file mode 100644
index 5800f28a0..000000000
--- a/module/plugins/hoster/HellspyCz.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class HellspyCz(DeadHoster):
- __name__ = "HellspyCz"
- __type__ = "hoster"
- __version__ = "0.28"
-
- __pattern__ = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj.pl)(/\S+/\d+)/?.*'
-
- __description__ = """HellSpy.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(HellspyCz)
diff --git a/module/plugins/hoster/HotfileCom.py b/module/plugins/hoster/HotfileCom.py
deleted file mode 100644
index aac312a6b..000000000
--- a/module/plugins/hoster/HotfileCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class HotfileCom(DeadHoster):
- __name__ = "HotfileCom"
- __type__ = "hoster"
- __version__ = "0.37"
-
- __pattern__ = r'https?://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+/'
-
- __description__ = """Hotfile.com hoster plugin"""
- __author_name__ = ("sitacuisses", "spoob", "mkaay", "JoKoT3")
- __author_mail__ = ("sitacuisses@yhoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "jokot3@gmail.com")
-
-
-getInfo = create_getInfo(HotfileCom)
diff --git a/module/plugins/hoster/HugefilesNet.py b/module/plugins/hoster/HugefilesNet.py
deleted file mode 100644
index bf0a26c68..000000000
--- a/module/plugins/hoster/HugefilesNet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://hugefiles.net/prthf9ya4w6s
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class HugefilesNet(XFileSharingPro):
- __name__ = "HugefilesNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?hugefiles\.net/\w{12}'
-
- __description__ = """Hugefiles.net hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- HOSTER_NAME = "hugefiles.net"
-
- FILE_SIZE_PATTERN = r'File Size:</span>\s*<span[^>]*>(?P<S>[^<]+)</span></div>'
-
-
-getInfo = create_getInfo(HugefilesNet)
diff --git a/module/plugins/hoster/HundredEightyUploadCom.py b/module/plugins/hoster/HundredEightyUploadCom.py
deleted file mode 100644
index e2e34ad00..000000000
--- a/module/plugins/hoster/HundredEightyUploadCom.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://180upload.com/js9qdm6kjnrs
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class HundredEightyUploadCom(XFileSharingPro):
- __name__ = "HundredEightyUploadCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?180upload\.com/(\w+).*'
-
- __description__ = """180upload.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- HOSTER_NAME = "180upload.com"
-
- FILE_NAME_PATTERN = r'Filename:</b></td><td nowrap>(?P<N>.+)</td></tr>-->'
- FILE_SIZE_PATTERN = r'Size:</b></td><td>(?P<S>[\d.]+) (?P<U>[A-Z]+)\s*<small>'
-
-
-getInfo = create_getInfo(HundredEightyUploadCom)
diff --git a/module/plugins/hoster/IFileWs.py b/module/plugins/hoster/IFileWs.py
deleted file mode 100644
index 94d3e599e..000000000
--- a/module/plugins/hoster/IFileWs.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class IFileWs(XFileSharingPro):
- __name__ = "IFileWs"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?ifile\.ws/\w+(/.+)?'
-
- __description__ = """Ifile.ws hoster plugin"""
- __author_name__ = "z00nx"
- __author_mail__ = "z00nx0@gmail.com"
-
- HOSTER_NAME = "ifile.ws"
-
- FILE_INFO_PATTERN = r'<h1\s+style="display:inline;">(?P<N>[^<]+)</h1>\s+\[(?P<S>[^]]+)\]'
- OFFLINE_PATTERN = r'File Not Found|The file was removed by administrator'
-
-
-getInfo = create_getInfo(IFileWs)
diff --git a/module/plugins/hoster/IcyFilesCom.py b/module/plugins/hoster/IcyFilesCom.py
deleted file mode 100644
index a7c69009e..000000000
--- a/module/plugins/hoster/IcyFilesCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class IcyFilesCom(DeadHoster):
- __name__ = "IcyFilesCom"
- __type__ = "hoster"
- __version__ = "0.06"
-
- __pattern__ = r'http://(?:www\.)?icyfiles\.com/(.*)'
-
- __description__ = """IcyFiles.com hoster plugin"""
- __author_name__ = "godofdream"
- __author_mail__ = "soilfiction@gmail.com"
-
-
-getInfo = create_getInfo(IcyFilesCom)
diff --git a/module/plugins/hoster/IfileIt.py b/module/plugins/hoster/IfileIt.py
deleted file mode 100644
index 5dfd14d82..000000000
--- a/module/plugins/hoster/IfileIt.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class IfileIt(SimpleHoster):
- __name__ = "IfileIt"
- __type__ = "hoster"
- __version__ = "0.27"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """Ifile.it"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- LINK_PATTERN = r'</span> If it doesn\'t, <a target="_blank" href="([^"]+)">'
- RECAPTCHA_KEY_PATTERN = r"var __recaptcha_public\s*=\s*'([^']+)';"
- FILE_INFO_PATTERN = r'<span style="cursor: default;[^>]*>\s*(?P<N>.*?)\s*&nbsp;\s*<strong>\s*(?P<S>[0-9.]+)\s*(?P<U>[kKMG])i?B\s*</strong>\s*</span>'
- OFFLINE_PATTERN = r'<span style="cursor: default;[^>]*>\s*&nbsp;\s*<strong>\s*</strong>\s*</span>'
- TEMP_OFFLINE_PATTERN = r'<span class="msg_red">Downloading of this file is temporarily disabled</span>'
-
-
- def handleFree(self):
- ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
- json_url = 'http://ifile.it/new_download-request.json'
- post_data = {"ukey": ukey, "ab": "0"}
-
- json_response = json_loads(self.load(json_url, post=post_data))
- self.logDebug(json_response)
- if json_response['status'] == 3:
- self.offline()
-
- if json_response['captcha']:
- captcha_key = re.search(self.RECAPTCHA_KEY_PATTERN, self.html).group(1)
- recaptcha = ReCaptcha(self)
- post_data['ctype'] = "recaptcha"
-
- for _ in xrange(5):
- post_data['recaptcha_challenge'], post_data['recaptcha_response'] = recaptcha.challenge(captcha_key)
- json_response = json_loads(self.load(json_url, post=post_data))
- self.logDebug(json_response)
-
- if json_response['retry']:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("Incorrect captcha")
-
- if not "ticket_url" in json_response:
- self.parseError("Download URL")
-
- self.download(json_response['ticket_url'])
-
-
-getInfo = create_getInfo(IfileIt)
diff --git a/module/plugins/hoster/IfolderRu.py b/module/plugins/hoster/IfolderRu.py
deleted file mode 100644
index efa8d8ab9..000000000
--- a/module/plugins/hoster/IfolderRu.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class IfolderRu(SimpleHoster):
- __name__ = "IfolderRu"
- __type__ = "hoster"
- __version__ = "0.38"
-
- __pattern__ = r'http://(?:www\.)?(?:ifolder\.ru|rusfolder\.(?:com|net|ru))/(?:files/)?(?P<ID>\d+).*'
-
- __description__ = """Ifolder.ru hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
- FILE_NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
- FILE_SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
- OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>[^<]*</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
-
- SESSION_ID_PATTERN = r'<a href=(http://ints.(?:rusfolder.com|ifolder.ru)/ints/sponsor/\?bi=\d*&session=([^&]+)&u=[^>]+)>'
- INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag.value = "([^"]+)";\}'
- HIDDEN_INPUT_PATTERN = r"var v = .*?name='([^']+)' value='1'"
- LINK_PATTERN = r'<a id="download_file_href" href="([^"]+)"'
- WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True if self.account else False
- self.chunkLimit = 1
-
- def process(self, pyfile):
- file_id = re.match(self.__pattern__, pyfile.url).group('ID')
- self.html = self.load("http://rusfolder.com/%s" % file_id, cookies=True, decode=True)
- self.getFileInfo()
-
- url = re.search(r"location\.href = '(http://ints\..*?=)'", self.html).group(1)
- self.html = self.load(url, cookies=True, decode=True)
-
- url, session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
- self.html = self.load(url, cookies=True, decode=True)
-
- url = "http://ints.rusfolder.com/ints/frame/?session=%s" % session_id
- self.html = self.load(url, cookies=True)
-
- self.wait(31, False)
-
- captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
- for _ in xrange(5):
- self.html = self.load(url, cookies=True)
- action, inputs = self.parseHtmlForm('ID="Form1"')
- inputs['ints_session'] = re.search(self.INTS_SESSION_PATTERN, self.html).group(1)
- inputs[re.search(self.HIDDEN_INPUT_PATTERN, self.html).group(1)] = '1'
- inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
- inputs['action'] = '1'
- self.logDebug(inputs)
-
- self.html = self.load(url, decode=True, cookies=True, post=inputs)
- if self.WRONG_CAPTCHA_PATTERN in self.html:
- self.invalidCaptcha()
- else:
- break
- else:
- self.fail("Invalid captcha")
-
- download_url = re.search(self.LINK_PATTERN, self.html).group(1)
- self.correctCaptcha()
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
-
-
-getInfo = create_getInfo(IfolderRu)
diff --git a/module/plugins/hoster/JumbofilesCom.py b/module/plugins/hoster/JumbofilesCom.py
deleted file mode 100644
index 2c9e4418b..000000000
--- a/module/plugins/hoster/JumbofilesCom.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class JumbofilesCom(SimpleHoster):
- __name__ = "JumbofilesCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?jumbofiles.com/(\w{12}).*'
-
- __description__ = """JumboFiles.com hoster plugin"""
- __author_name__ = "godofdream"
- __author_mail__ = "soilfiction@gmail.com"
-
- FILE_INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.]+)\s*(?P<U>[KMG][bB])\)</small></TD></TR>'
- OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
- LINK_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def handleFree(self):
- ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
- post_data = {"id": ukey, "op": "download3", "rand": ""}
- html = self.load(self.pyfile.url, post=post_data, decode=True)
- url = re.search(self.LINK_PATTERN, html).group(1)
- self.logDebug("Download " + url)
- self.download(url)
-
-
-getInfo = create_getInfo(JumbofilesCom)
diff --git a/module/plugins/hoster/Keep2shareCC.py b/module/plugins/hoster/Keep2shareCC.py
deleted file mode 100644
index c1ec66435..000000000
--- a/module/plugins/hoster/Keep2shareCC.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://k2s.cc/file/55fb73e1c00c5/random.bin
-
-import re
-
-from urlparse import urlparse, urljoin
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class Keep2shareCC(SimpleHoster):
- __name__ = "Keep2shareCC"
- __type__ = "hoster"
- __version__ = "0.10"
-
- __pattern__ = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
-
- __description__ = """Keep2share.cc hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
- FILE_SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
- OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
-
- LINK_PATTERN = r'To download this file with slow speed, use <a href="([^"]+)">this link</a>'
- WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
- ALREADY_DOWNLOADING_PATTERN = r'Free account does not allow to download more than one file at the same time'
-
- RECAPTCHA_KEY = "6LcYcN0SAAAAABtMlxKj7X0hRxOY8_2U86kI1vbb"
-
-
- def handleFree(self):
- self.sanitize_url()
- self.html = self.load(self.pyfile.url)
-
- self.fid = re.search(r'<input type="hidden" name="slow_id" value="([^"]+)">', self.html).group(1)
- self.html = self.load(self.pyfile.url, post={'yt0': '', 'slow_id': self.fid})
-
- m = re.search(r"function download\(\){.*window\.location\.href = '([^']+)';", self.html, re.DOTALL)
- if m: # Direct mode
- self.startDownload(m.group(1))
- else:
- self.handleCaptcha()
-
- self.wait(30)
-
- self.html = self.load(self.pyfile.url, post={'uniqueId': self.fid, 'free': 1})
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.logDebug('Hoster told us to wait for %s' % m.group(1))
- # string to time convert courtesy of https://stackoverflow.com/questions/10663720
- ftr = [3600, 60, 1]
- wait_time = sum([a * b for a, b in zip(ftr, map(int, m.group(1).split(':')))])
- self.wait(wait_time, reconnect=True)
- self.retry()
-
- m = re.search(self.ALREADY_DOWNLOADING_PATTERN, self.html)
- if m:
- # if someone is already downloading on our line, wait 30min and retry
- self.logDebug('Already downloading, waiting for 30 minutes')
- self.wait(30 * 60, reconnect=True)
- self.retry()
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError("Unable to detect direct link")
- self.startDownload(m.group(1))
-
- def handleCaptcha(self):
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- post_data = {'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field': response,
- 'CaptchaForm%5Bcode%5D': '',
- 'free': 1,
- 'freeDownloadRequest': 1,
- 'uniqueId': self.fid,
- 'yt0': ''}
-
- self.html = self.load(self.pyfile.url, post=post_data)
-
- if 'recaptcha' not in self.html:
- self.correctCaptcha()
- break
- else:
- self.logInfo('Wrong captcha')
- self.invalidCaptcha()
- else:
- self.fail("All captcha attempts failed")
-
- def startDownload(self, url):
- d = urljoin(self.base_url, url)
- self.logDebug('Direct Link: ' + d)
- self.download(d, disposition=True)
-
- def sanitize_url(self):
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header:
- self.pyfile.url = header['location']
- p = urlparse(self.pyfile.url)
- self.base_url = "%s://%s" % (p.scheme, p.hostname)
-
-
-getInfo = create_getInfo(Keep2shareCC)
diff --git a/module/plugins/hoster/LemUploadsCom.py b/module/plugins/hoster/LemUploadsCom.py
deleted file mode 100644
index b8a6062cb..000000000
--- a/module/plugins/hoster/LemUploadsCom.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://lemuploads.com/uwol0aly9dld
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class LemUploadsCom(XFileSharingPro):
- __name__ = "LemUploadsCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?lemuploads.com/\w{12}'
-
- __description__ = """LemUploads.com hoster plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- HOSTER_NAME = "lemuploads.com"
-
- OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
- FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h2>(?P<N>[^<]+)</h2>'
-
-
-getInfo = create_getInfo(LemUploadsCom)
diff --git a/module/plugins/hoster/LetitbitNet.py b/module/plugins/hoster/LetitbitNet.py
deleted file mode 100644
index 3159be4f1..000000000
--- a/module/plugins/hoster/LetitbitNet.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# API Documentation:
-# http://api.letitbit.net/reg/static/api.pdf
-#
-# Test links:
-# http://letitbit.net/download/07874.0b5709a7d3beee2408bb1f2eefce/random.bin.html
-
-import re
-
-from urllib import urlencode, urlopen
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-def api_download_info(url):
- json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
- post_data = urlencode({'r': json_dumps(json_data)})
- api_rep = urlopen("http://api.letitbit.net/json", data=post_data).read()
- return json_loads(api_rep)
-
-
-def getInfo(urls):
- for url in urls:
- api_rep = api_download_info(url)
- if api_rep['status'] == 'OK':
- info = api_rep['data'][0]
- yield (info['name'], info['size'], 2, url)
- else:
- yield (url, 0, 1, url)
-
-
-class LetitbitNet(SimpleHoster):
- __name__ = "LetitbitNet"
- __type__ = "hoster"
- __version__ = "0.24"
-
- __pattern__ = r'http://(?:www\.)?(letitbit|shareflare).net/download/.*'
-
- __description__ = """Letitbit.net hoster plugin"""
- __author_name__ = ("zoidberg", "z00nx")
- __author_mail__ = ("zoidberg@mujmail.cz", "z00nx0@gmail.com")
-
- FILE_URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
-
- HOSTER_NAME = "letitbit.net"
-
- SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
- CAPTCHA_CONTROL_FIELD = r"recaptcha_control_field\s=\s'(?P<value>[^']+)'"
- RECAPTCHA_KEY = "6Lc9zdMSAAAAAF-7s2wuQ-036pLRbM0p8dDaQdAM"
-
-
- def setup(self):
- self.resumeDownload = True
- #TODO confirm that resume works
-
- def getFileInfo(self):
- api_rep = api_download_info(self.pyfile.url)
- if api_rep['status'] == 'OK':
- self.api_data = api_rep['data'][0]
- self.pyfile.name = self.api_data['name']
- self.pyfile.size = self.api_data['size']
- else:
- self.offline()
-
- def handleFree(self):
- action, inputs = self.parseHtmlForm('id="ifree_form"')
- if not action:
- self.parseError("page 1 / ifree_form")
-
- domain = "http://www." + self.HOSTER_NAME
- self.pyfile.size = float(inputs['sssize'])
- self.logDebug(action, inputs)
- inputs['desc'] = ""
-
- self.html = self.load(domain + action, post=inputs, cookies=True)
-
- # action, inputs = self.parseHtmlForm('id="d3_form"')
- # if not action:
- # self.parseError("page 2 / d3_form")
- # self.logDebug(action, inputs)
- #
- # self.html = self.load(action, post = inputs, cookies = True)
- #
- # try:
- # ajax_check_url, captcha_url = re.search(self.CHECK_URL_PATTERN, self.html).groups()
- # m = re.search(self.SECONDS_PATTERN, self.html)
- # seconds = int(m.group(1)) if m else 60
- # self.wait(seconds+1)
- # except Exception, e:
- # self.logError(e)
- # self.parseError("page 3 / js")
-
- m = re.search(self.SECONDS_PATTERN, self.html)
- seconds = int(m.group(1)) if m else 60
- self.logDebug("Seconds found", seconds)
- m = re.search(self.CAPTCHA_CONTROL_FIELD, self.html)
- recaptcha_control_field = m.group(1)
- self.logDebug("ReCaptcha control field found", recaptcha_control_field)
- self.wait(seconds + 1)
-
- response = self.load("%s/ajax/download3.php" % domain, post=" ", cookies=True)
- if response != '1':
- self.parseError('Unknown response - ajax_check_url')
- self.logDebug(response)
-
- recaptcha = ReCaptcha(self)
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- post_data = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": response,
- "recaptcha_control_field": recaptcha_control_field}
- self.logDebug("Post data to send", post_data)
- response = self.load('%s/ajax/check_recaptcha.php' % domain, post=post_data, cookies=True)
- self.logDebug(response)
- if not response:
- self.invalidCaptcha()
- if response == "error_free_download_blocked":
- self.logWarning("Daily limit reached")
- self.wait(secondsToMidnight(gmt=2), True)
- if response == "error_wrong_captcha":
- self.logError("Wrong Captcha")
- self.invalidCaptcha()
- self.retry()
- elif response.startswith('['):
- urls = json_loads(response)
- elif response.startswith('http://'):
- urls = [response]
- else:
- self.parseError("Unknown response - captcha check")
-
- self.correctCaptcha()
-
- for download_url in urls:
- try:
- self.logDebug("Download URL", download_url)
- self.download(download_url)
- break
- except Exception, e:
- self.logError(e)
- else:
- self.fail("Download did not finish correctly")
-
- def handlePremium(self):
- api_key = self.user
- premium_key = self.account.getAccountData(self.user)['password']
-
- json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": self.pyfile.url}]]
- api_rep = self.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
- self.logDebug('API Data: ' + api_rep)
- api_rep = json_loads(api_rep)
-
- if api_rep['status'] == 'FAIL':
- self.fail(api_rep['data'])
-
- direct_link = api_rep['data'][0][0]
- self.logDebug('Direct Link: ' + direct_link)
-
- self.download(direct_link, disposition=True)
diff --git a/module/plugins/hoster/LinksnappyCom.py b/module/plugins/hoster/LinksnappyCom.py
deleted file mode 100644
index e4200e9f2..000000000
--- a/module/plugins/hoster/LinksnappyCom.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urlparse import urlsplit
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.Hoster import Hoster
-
-
-class LinksnappyCom(Hoster):
- __name__ = "LinksnappyCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:[^/]*\.)?linksnappy\.com'
-
- __description__ = """Linksnappy.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- SINGLE_CHUNK_HOSTERS = ('easybytez.com')
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Linksnappy.com")
- self.fail("No Linksnappy.com account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- host = self._get_host(pyfile.url)
- json_params = json_dumps({'link': pyfile.url,
- 'type': host,
- 'username': self.user,
- 'password': self.account.getAccountData(self.user)['password']})
- r = self.load('http://gen.linksnappy.com/genAPI.php',
- post={'genLinks': json_params})
- self.logDebug("JSON data: " + r)
-
- j = json_loads(r)['links'][0]
-
- if j['error']:
- self.logError('Error converting the link: %s' % j['error'])
- self.fail('Error converting the link')
-
- pyfile.name = j['filename']
- new_url = j['generated']
-
- if host in self.SINGLE_CHUNK_HOSTERS:
- self.chunkLimit = 1
- else:
- self.setup()
-
- if new_url != pyfile.url:
- self.logDebug("New URL: " + new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload({"html302": "<title>302 Found</title>"})
- if check == "html302":
- self.retry(wait_time=5, reason="Linksnappy returns only HTML data.")
-
- @staticmethod
- def _get_host(url):
- host = urlsplit(url).netloc
- return re.search(r'[\w-]+\.\w+$', host).group(0)
diff --git a/module/plugins/hoster/LoadTo.py b/module/plugins/hoster/LoadTo.py
deleted file mode 100644
index 18398e905..000000000
--- a/module/plugins/hoster/LoadTo.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www.load.to/JWydcofUY6/random.bin
-# http://www.load.to/oeSmrfkXE/random100.bin
-
-import re
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LoadTo(SimpleHoster):
- __name__ = "LoadTo"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'http://(?:www\.)?load\.to/\w+'
-
- __description__ = """ Load.to hoster plugin """
- __author_name__ = ("halfman", "stickell")
- __author_mail__ = ("Pulpan3@gmail.com", "l.stickell@yahoo.it")
-
- FILE_NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
- FILE_SIZE_PATTERN = r'Size: (?P<S>[\d.]+) (?P<U>\w+)'
- OFFLINE_PATTERN = r'>Can\'t find file'
-
- LINK_PATTERN = r'<form method="post" action="(.+?)"'
- WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
- SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'
-
- FILE_URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
-
- def handleFree(self):
- # Search for Download URL
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError("Unable to detect download URL")
-
- download_url = m.group(1)
-
- # Set Timer - may be obsolete
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- self.wait(m.group(1))
-
- # Load.to is using solvemedia captchas since ~july 2014:
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m is None:
- self.download(download_url)
- else:
- captcha_key = m.group(1)
- solvemedia = SolveMedia(self)
- captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
- self.download(download_url, post={"adcopy_challenge": captcha_challenge, "adcopy_response": captcha_response})
- check = self.checkDownload({"404": re.compile("\A<h1>404 Not Found</h1>")})
- if check == "404":
- self.logWarning("The captcha you entered was incorrect. Please try again.")
- self.invalidCaptcha()
- self.retry()
-
-
-getInfo = create_getInfo(LoadTo)
diff --git a/module/plugins/hoster/LomafileCom.py b/module/plugins/hoster/LomafileCom.py
deleted file mode 100644
index 372d42fd3..000000000
--- a/module/plugins/hoster/LomafileCom.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LomafileCom(SimpleHoster):
- __name__ = "LomafileCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'https?://lomafile\.com/.+/[\w\.]+'
-
- __description__ = """ Lomafile.com hoster plugin """
- __author_name__ = "nath_schwarz"
- __author_mail__ = "nathan.notwhite@gmail.com"
-
- FILE_NAME_PATTERN = r'Filename:[^>]*>(?P<N>[\w\.]+)'
- FILE_SIZE_PATTERN = r'\((?P<S>\d+)\s(?P<U>\w+)\)'
- OFFLINE_PATTERN = r'Software error'
-
-
- def handleFree(self):
- for _ in range(3):
- captcha_id = re.search(r'src="http://lomafile\.com/captchas/(?P<id>\w+)\.jpg"', self.html)
- if not captcha_id:
- self.parseError("Unable to parse captcha id.")
- else:
- captcha_id = captcha_id.group("id")
-
- form_id = re.search(r'name="id" value="(?P<id>\w+)"', self.html)
- if not form_id:
- self.parseError("Unable to parse form id")
- else:
- form_id = form_id.group("id")
-
- captcha = self.decryptCaptcha("http://lomafile.com/captchas/" + captcha_id + ".jpg")
-
- self.wait(60)
-
- self.html = self.load(self.pyfile.url, post={
- "op": "download2",
- "id": form_id,
- "rand": captcha_id,
- "code": captcha,
- "down_direct": "1"})
-
- download_url = re.search(r'http://[\d\.]+:\d+/d/\w+/[\w\.]+', self.html)
- if download_url is None:
- self.invalidCaptcha()
- self.logDebug("Invalid captcha.")
- else:
- download_url = download_url.group(0)
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
- else:
- self.fail("Invalid captcha-code entered.")
-
-
-getInfo = create_getInfo(LomafileCom)
diff --git a/module/plugins/hoster/LuckyShareNet.py b/module/plugins/hoster/LuckyShareNet.py
deleted file mode 100644
index 60f1204e5..000000000
--- a/module/plugins/hoster/LuckyShareNet.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.lib.bottle import json_loads
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class LuckyShareNet(SimpleHoster):
- __name__ = "LuckyShareNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?luckyshare.net/(?P<ID>\d{10,})'
-
- __description__ = """LuckyShare.net hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- FILE_INFO_PATTERN = r"<h1 class='file_name'>(?P<N>\S+)</h1>\s*<span class='file_size'>Filesize: (?P<S>[\d.]+)(?P<U>\w+)</span>"
- OFFLINE_PATTERN = r'There is no such file available'
- RECAPTCHA_KEY = "6LdivsgSAAAAANWh-d7rPE1mus4yVWuSQIJKIYNw"
-
-
- def parseJson(self, rep):
- if 'AJAX Error' in rep:
- html = self.load(self.pyfile.url, decode=True)
- m = re.search(r"waitingtime = (\d+);", html)
- if m:
- waittime = int(m.group(1))
- self.logDebug('You have to wait %d seconds between free downloads' % waittime)
- self.retry(wait_time=waittime)
- else:
- self.parseError('Unable to detect wait time between free downloads')
- elif 'Hash expired' in rep:
- self.retry(reason="Hash expired")
- return json_loads(rep)
-
- # TODO: There should be a filesize limit for free downloads
- # TODO: Some files could not be downloaded in free mode
- def handleFree(self):
- file_id = re.match(self.__pattern__, self.pyfile.url).group('ID')
- self.logDebug('File ID: ' + file_id)
- rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + file_id, decode=True)
- self.logDebug('JSON: ' + rep)
- json = self.parseJson(rep)
-
- self.wait(int(json['time']))
-
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- rep = self.load(r"http://luckyshare.net/download/verify/challenge/%s/response/%s/hash/%s" %
- (challenge, response, json['hash']), decode=True)
- self.logDebug('JSON: ' + rep)
- if 'link' in rep:
- json.update(self.parseJson(rep))
- self.correctCaptcha()
- break
- elif 'Verification failed' in rep:
- self.logInfo('Wrong captcha')
- self.invalidCaptcha()
- else:
- self.parseError('Unable to get downlaod link')
-
- if not json['link']:
- self.fail("No Download url retrieved/all captcha attempts failed")
-
- self.logDebug('Direct URL: ' + json['link'])
- self.download(json['link'])
-
-
-getInfo = create_getInfo(LuckyShareNet)
diff --git a/module/plugins/hoster/MediafireCom.py b/module/plugins/hoster/MediafireCom.py
deleted file mode 100644
index d0878a34d..000000000
--- a/module/plugins/hoster/MediafireCom.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-from module.network.RequestFactory import getURL
-
-
-def replace_eval(js_expr):
- return js_expr.replace(r'eval("', '').replace(r"\'", r"'").replace(r'\"', r'"')
-
-
-def checkHTMLHeader(url):
- try:
- for _ in xrange(3):
- header = getURL(url, just_header=True)
- for line in header.splitlines():
- line = line.lower()
- if 'location' in line:
- url = line.split(':', 1)[1].strip()
- if 'error.php?errno=320' in url:
- return url, 1
- if not url.startswith('http://'):
- url = 'http://www.mediafire.com' + url
- break
- elif 'content-disposition' in line:
- return url, 2
- else:
- break
- except:
- return url, 3
-
- return url, 0
-
-
-def getInfo(urls):
- for url in urls:
- location, status = checkHTMLHeader(url)
- if status:
- file_info = (url, 0, status, url)
- else:
- file_info = parseFileInfo(MediafireCom, url, getURL(url, decode=True))
- yield file_info
-
-
-class MediafireCom(SimpleHoster):
- __name__ = "MediafireCom"
- __type__ = "hoster"
- __version__ = "0.79"
-
- __pattern__ = r'http://(?:www\.)?mediafire\.com/(file/|(view/?|download.php)?\?)(\w{11}|\w{15})($|/)'
-
- __description__ = """Mediafire.com hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- LINK_PATTERN = r'<div class="download_link"[^>]*(?:z-index:(?P<zindex>\d+))?[^>]*>\s*<a href="(?P<href>http://[^"]+)"'
- JS_KEY_PATTERN = r"DoShow\('mfpromo1'\);[^{]*{((\w+)='';.*?)eval\(\2\);"
- JS_ZMODULO_PATTERN = r"\('z-index'\)\) \% (\d+)\)\);"
- SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'
- PAGE1_ACTION_PATTERN = r'<link rel="canonical" href="([^"]+)"/>'
- PASSWORD_PATTERN = r'<form name="form_password"'
-
- FILE_NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>[^"]+)"/>'
- FILE_INFO_PATTERN = r"oFileSharePopup\.ald\('(?P<ID>[^']*)','(?P<N>[^']*)','(?P<S>[^']*)','','(?P<sha256>[^']*)'\)"
- OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>'
-
-
- def setup(self):
- self.multiDL = False
-
- def process(self, pyfile):
- pyfile.url = re.sub(r'/view/?\?', '/?', pyfile.url)
-
- self.url, result = checkHTMLHeader(pyfile.url)
- self.logDebug('Location (%d): %s' % (result, self.url))
-
- if result == 0:
- self.html = self.load(self.url, decode=True)
- self.checkCaptcha()
- self.multiDL = True
- self.check_data = self.getFileInfo()
-
- if self.account:
- self.handlePremium()
- else:
- self.handleFree()
- elif result == 1:
- self.offline()
- else:
- self.multiDL = True
- self.download(self.url, disposition=True)
-
- def handleFree(self):
- passwords = self.getPassword().splitlines()
- while self.PASSWORD_PATTERN in self.html:
- if len(passwords):
- password = passwords.pop(0)
- self.logInfo("Password protected link, trying " + password)
- self.html = self.load(self.url, post={"downloadp": password})
- else:
- self.fail("No or incorrect password")
-
- m = re.search(r'kNO = r"(http://.*?)";', self.html)
- if m is None:
- self.parseError("Download URL")
- download_url = m.group(1)
- self.logDebug("DOWNLOAD LINK:", download_url)
-
- self.download(download_url)
-
- def checkCaptcha(self):
- for _ in xrange(5):
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- solvemedia = SolveMedia(self)
- captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
- self.html = self.load(self.url, post={"adcopy_challenge": captcha_challenge,
- "adcopy_response": captcha_response}, decode=True)
- else:
- break
- else:
- self.fail("No valid recaptcha solution received")
diff --git a/module/plugins/hoster/MegaDebridEu.py b/module/plugins/hoster/MegaDebridEu.py
deleted file mode 100644
index 13415d063..000000000
--- a/module/plugins/hoster/MegaDebridEu.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote_plus
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-class MegaDebridEu(Hoster):
- __name__ = "MegaDebridEu"
- __type__ = "hoster"
- __version__ = "0.4"
-
- __pattern__ = r'^https?://(?:w{3}\d+\.mega-debrid.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[^/]+/.+$'
-
- __description__ = """mega-debrid.eu hoster plugin"""
- __author_name__ = "D.Ducatel"
- __author_mail__ = "dducatel@je-geek.fr"
-
- API_URL = "https://www.mega-debrid.eu/api.php"
-
-
- def getFilename(self, url):
- try:
- return unquote_plus(url.rsplit("/", 1)[1])
- except IndexError:
- return ""
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.exitOnFail(_("Please enter your %s account or deactivate this plugin") % "Mega-debrid.eu")
- else:
- if not self.connectToApi():
- self.exitOnFail(_("Unable to connect to %s") % "Mega-debrid.eu")
-
- self.logDebug("Old URL: %s" % pyfile.url)
- new_url = self.debridLink(pyfile.url)
- self.logDebug("New URL: " + new_url)
-
- filename = self.getFilename(new_url)
- if filename != "":
- pyfile.name = filename
- self.download(new_url, disposition=True)
-
- def connectToApi(self):
- """
- Connexion to the mega-debrid API
- Return True if succeed
- """
- user, data = self.account.selectAccount()
- jsonResponse = self.load(self.API_URL,
- get={'action': 'connectUser', 'login': user, 'password': data['password']})
- response = json_loads(jsonResponse)
-
- if response['response_code'] == "ok":
- self.token = response['token']
- return True
- else:
- return False
-
- def debridLink(self, linkToDebrid):
- """
- Debrid a link
- Return The debrided link if succeed or original link if fail
- """
- jsonResponse = self.load(self.API_URL, get={'action': 'getLink', 'token': self.token},
- post={"link": linkToDebrid})
- response = json_loads(jsonResponse)
-
- if response['response_code'] == "ok":
- debridedLink = response['debridLink'][1:-1]
- return debridedLink
- else:
- self.exitOnFail("Unable to debrid %s" % linkToDebrid)
-
- def exitOnFail(self, msg):
- """
- exit the plugin on fail case
- And display the reason of this failure
- """
- if self.getConfig("unloadFailing"):
- self.logError(msg)
- self.resetAccount()
- else:
- self.fail(msg)
diff --git a/module/plugins/hoster/MegaFilesSe.py b/module/plugins/hoster/MegaFilesSe.py
deleted file mode 100644
index 975708597..000000000
--- a/module/plugins/hoster/MegaFilesSe.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class MegaFilesSe(XFileSharingPro):
- __name__ = "MegaFilesSe"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?megafiles\.se/\w{12}'
-
- __description__ = """MegaFiles.se hoster plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- HOSTER_NAME = "megafiles.se"
-
- OFFLINE_PATTERN = r'<b><font[^>]*>File Not Found</font></b><br><br>'
- FILE_NAME_PATTERN = r'<div[^>]+>\s*<b>(?P<N>[^<]+)</b>\s*</div>'
-
-
-getInfo = create_getInfo(MegaFilesSe)
diff --git a/module/plugins/hoster/MegaNz.py b/module/plugins/hoster/MegaNz.py
deleted file mode 100644
index 5562aad06..000000000
--- a/module/plugins/hoster/MegaNz.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-
-from Crypto.Cipher import AES
-from Crypto.Util import Counter
-from array import array
-from base64 import standard_b64decode
-from os import remove
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.Hoster import Hoster
-
-
-class MegaNz(Hoster):
- __name__ = "MegaNz"
- __type__ = "hoster"
- __version__ = "0.14"
-
- __pattern__ = r'https?://([a-z0-9]+\.)?mega\.co\.nz/#!([a-zA-Z0-9!_\-]+)'
-
- __description__ = """Mega.co.nz hoster plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "ranan@pyload.org"
-
- API_URL = "https://g.api.mega.co.nz/cs?id=%d"
- FILE_SUFFIX = ".crypted"
-
-
- def b64_decode(self, data):
- data = data.replace("-", "+").replace("_", "/")
- return standard_b64decode(data + '=' * (-len(data) % 4))
-
- def getCipherKey(self, key):
- """ Construct the cipher key from the given data """
- a = array("I", key)
- key_array = array("I", [a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]])
- return key_array
-
- def callApi(self, **kwargs):
- """ Dispatch a call to the api, see https://mega.co.nz/#developers """
- # generate a session id, no idea where to obtain elsewhere
- uid = random.randint(10 << 9, 10 ** 10)
-
- resp = self.load(self.API_URL % uid, post=json_dumps([kwargs]))
- self.logDebug("Api Response: " + resp)
- return json_loads(resp)
-
- def decryptAttr(self, data, key):
-
- cbc = AES.new(self.getCipherKey(key), AES.MODE_CBC, "\0" * 16)
- attr = cbc.decrypt(self.b64_decode(data))
- self.logDebug("Decrypted Attr: " + attr)
- if not attr.startswith("MEGA"):
- self.fail(_("Decryption failed"))
-
- # Data is padded, 0-bytes must be stripped
- return json_loads(attr.replace("MEGA", "").rstrip("\0").strip())
-
- def decryptFile(self, key):
- """ Decrypts the file at lastDownload` """
-
- # upper 64 bit of counter start
- n = key[16:24]
-
- # convert counter to long and shift bytes
- ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
- cipher = AES.new(self.getCipherKey(key), AES.MODE_CTR, counter=ctr)
-
- self.pyfile.setStatus("decrypting")
-
- file_crypted = self.lastDownload
- file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
- f = open(file_crypted, "rb")
- df = open(file_decrypted, "wb")
-
- # TODO: calculate CBC-MAC for checksum
-
- size = 2 ** 15 # buffer size, 32k
- while True:
- buf = f.read(size)
- if not buf:
- break
-
- df.write(cipher.decrypt(buf))
-
- f.close()
- df.close()
- remove(file_crypted)
-
- self.lastDownload = file_decrypted
-
- def process(self, pyfile):
-
- key = None
-
- # match is guaranteed because plugin was chosen to handle url
- node = re.match(self.__pattern__, pyfile.url).group(2)
- if "!" in node:
- node, key = node.split("!")
-
- self.logDebug("File id: %s | Key: %s" % (node, key))
-
- if not key:
- self.fail(_("No file key provided in the URL"))
-
- # g is for requesting a download url
- # this is similar to the calls in the mega js app, documentation is very bad
- dl = self.callApi(a="g", g=1, p=node, ssl=1)[0]
-
- if "e" in dl:
- e = dl['e']
- # ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
- if e == -18:
- self.retry()
- else:
- self.fail(_("Error code:") + e)
-
- # TODO: map other error codes, e.g
- # EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
-
- key = self.b64_decode(key)
- attr = self.decryptAttr(dl['at'], key)
-
- pyfile.name = attr['n'] + self.FILE_SUFFIX
-
- self.download(dl['g'])
- self.decryptFile(key)
-
- # Everything is finished and final name can be set
- pyfile.name = attr['n']
diff --git a/module/plugins/hoster/MegacrypterCom.py b/module/plugins/hoster/MegacrypterCom.py
deleted file mode 100644
index 7a86dbf70..000000000
--- a/module/plugins/hoster/MegacrypterCom.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads, json_dumps
-from module.plugins.hoster.MegaNz import MegaNz
-
-
-class MegacrypterCom(MegaNz):
- __name__ = "MegacrypterCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'(https?://[a-z0-9]{0,10}\.?megacrypter\.com/[a-zA-Z0-9!_\-]+)'
-
- __description__ = """Megacrypter.com decrypter plugin"""
- __author_name__ = "GonzaloSR"
- __author_mail__ = "gonzalo@gonzalosr.com"
-
- API_URL = "http://megacrypter.com/api"
- FILE_SUFFIX = ".crypted"
-
-
- def callApi(self, **kwargs):
- """ Dispatch a call to the api, see megacrypter.com/api_doc """
- self.logDebug("JSON request: " + json_dumps(kwargs))
- resp = self.load(self.API_URL, post=json_dumps(kwargs))
- self.logDebug("API Response: " + resp)
- return json_loads(resp)
-
- def process(self, pyfile):
- # match is guaranteed because plugin was chosen to handle url
- node = re.match(self.__pattern__, pyfile.url).group(1)
-
- # get Mega.co.nz link info
- info = self.callApi(link=node, m="info")
-
- # get crypted file URL
- dl = self.callApi(link=node, m="dl")
-
- # TODO: map error codes, implement password protection
- # if info['pass'] is True:
- # crypted_file_key, md5_file_key = info['key'].split("#")
-
- key = self.b64_decode(info['key'])
-
- pyfile.name = info['name'] + self.FILE_SUFFIX
-
- self.download(dl['url'])
- self.decryptFile(key)
-
- # Everything is finished and final name can be set
- pyfile.name = info['name']
diff --git a/module/plugins/hoster/MegareleaseOrg.py b/module/plugins/hoster/MegareleaseOrg.py
deleted file mode 100644
index 05b05c3b5..000000000
--- a/module/plugins/hoster/MegareleaseOrg.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class MegareleaseOrg(XFileSharingPro):
- __name__ = "MegareleaseOrg"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?megarelease.org/\w{12}'
-
- __description__ = """Megarelease.org hoster plugin"""
- __author_name__ = ("derek3x", "stickell")
- __author_mail__ = ("derek3x@vmail.me", "l.stickell@yahoo.it")
-
- HOSTER_NAME = "megarelease.org"
-
- FILE_INFO_PATTERN = r'<font color="red">%s/(?P<N>.+)</font> \((?P<S>[^)]+)\)</font>' % __pattern__
-
-
-getInfo = create_getInfo(MegareleaseOrg)
diff --git a/module/plugins/hoster/MegasharesCom.py b/module/plugins/hoster/MegasharesCom.py
deleted file mode 100644
index c12897ed0..000000000
--- a/module/plugins/hoster/MegasharesCom.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import time
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MegasharesCom(SimpleHoster):
- __name__ = "MegasharesCom"
- __type__ = "hoster"
- __version__ = "0.24"
-
- __pattern__ = r'http://(?:www\.)?megashares.com/.*'
-
- __description__ = """Megashares.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>[^"]+)">'
- FILE_SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[0-9.]+) (?P<U>[kKMG])i?B<br />'
- OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted)'
-
- LINK_PATTERN = r'<div id="show_download_button_%d"[^>]*>\s*<a href="([^"]+)">'
- PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <[^>]*>(\w+).*\s*You have\s*<[^>]*>\s*([0-9.]+) ([kKMG]i?B)'
- PASSPORT_RENEW_PATTERN = r'Your download passport will renew in\s*<strong>(\d+)</strong>:<strong>(\d+)</strong>:<strong>(\d+)</strong>'
- REACTIVATE_NUM_PATTERN = r'<input[^>]*id="random_num" value="(\d+)" />'
- REACTIVATE_PASSPORT_PATTERN = r'<input[^>]*id="passport_num" value="(\w+)" />'
- REQUEST_URI_PATTERN = r'var request_uri = "([^"]+)";'
- NO_SLOTS_PATTERN = r'<dd class="red">All download slots for this link are currently filled'
-
-
- def setup(self):
- self.resumeDownload = True
- self.multiDL = self.premium
-
- def handlePremium(self):
- self.handleDownload(True)
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- if self.NO_SLOTS_PATTERN in self.html:
- self.retry(wait_time=5 * 60)
-
- self.getFileInfo()
- # if self.pyfile.size > 576716800:
- # self.fail("This file is too large for free download")
-
- # Reactivate passport if needed
- m = re.search(self.REACTIVATE_PASSPORT_PATTERN, self.html)
- if m:
- passport_num = m.group(1)
- request_uri = re.search(self.REQUEST_URI_PATTERN, self.html).group(1)
-
- for _ in xrange(5):
- random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
-
- verifyinput = self.decryptCaptcha(
- "http://d01.megashares.com/index.php?secgfx=gfx&random_num=%s" % random_num)
- self.logInfo("Reactivating passport %s: %s %s" % (passport_num, random_num, verifyinput))
-
- url = ("http://d01.megashares.com%s&rs=check_passport_renewal" % request_uri +
- "&rsargs[]=%s&rsargs[]=%s&rsargs[]=%s" % (verifyinput, random_num, passport_num) +
- "&rsargs[]=replace_sec_pprenewal&rsrnd=%s" % str(int(time() * 1000)))
- self.logDebug(url)
- response = self.load(url)
-
- if 'Thank you for reactivating your passport.' in response:
- self.correctCaptcha()
- self.retry()
- else:
- self.invalidCaptcha()
- else:
- self.fail("Failed to reactivate passport")
-
- # Check traffic left on passport
- m = re.search(self.PASSPORT_LEFT_PATTERN, self.html)
- if m is None:
- self.fail('Passport not found')
- self.logInfo("Download passport: %s" % m.group(1))
- data_left = float(m.group(2)) * 1024 ** {'KB': 1, 'MB': 2, 'GB': 3}[m.group(3)]
- self.logInfo("Data left: %s %s (%d MB needed)" % (m.group(2), m.group(3), self.pyfile.size / 1048576))
-
- if not data_left:
- m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
- renew = m.group(1) + m.group(2) + m.group(3) * 60 * 60 if m else 10 * 60
- self.retry(max_tries=15, wait_time=renew, reason="Unable to get passport")
-
- self.handleDownload(False)
-
- def handleDownload(self, premium=False):
- # Find download link;
- m = re.search(self.LINK_PATTERN % (1 if premium else 2), self.html)
- msg = '%s download URL' % ('Premium' if premium else 'Free')
- if m is None:
- self.parseError(msg)
-
- download_url = m.group(1)
- self.logDebug("%s: %s" % (msg, download_url))
- self.download(download_url)
-
-
-getInfo = create_getInfo(MegasharesCom)
diff --git a/module/plugins/hoster/MovReelCom.py b/module/plugins/hoster/MovReelCom.py
deleted file mode 100644
index 6b13422b0..000000000
--- a/module/plugins/hoster/MovReelCom.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class MovReelCom(XFileSharingPro):
- __name__ = "MovReelCom"
- __type__ = "hoster"
- __version__ = "1.20"
-
- __pattern__ = r'http://(?:www\.)?movreel.com/.*'
-
- __description__ = """MovReel.com hoster plugin"""
- __author_name__ = "JorisV83"
- __author_mail__ = "jorisv83-pyload@yahoo.com"
-
- HOSTER_NAME = "movreel.com"
-
- FILE_INFO_PATTERN = r'<h3>(?P<N>.+?) <small><sup>(?P<S>[\d.]+) (?P<U>..)</sup> </small></h3>'
- OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
- LINK_PATTERN = r'<a href="(http://[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/.*)">Download Link</a>'
-
-
-getInfo = create_getInfo(MovReelCom)
diff --git a/module/plugins/hoster/MultishareCz.py b/module/plugins/hoster/MultishareCz.py
deleted file mode 100644
index fdf5fbd70..000000000
--- a/module/plugins/hoster/MultishareCz.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import random
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class MultishareCz(SimpleHoster):
- __name__ = "MultishareCz"
- __type__ = "hoster"
- __version__ = "0.34"
-
- __pattern__ = r'http://(?:www\.)?multishare.cz/stahnout/(?P<ID>\d+).*'
-
- __description__ = """MultiShare.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = ur'(?:<li>Název|Soubor): <strong>(?P<N>[^<]+)</strong><(?:/li><li|br)>Velikost: <strong>(?P<S>[^<]+)</strong>'
- OFFLINE_PATTERN = ur'<h1>Stáhnout soubor</h1><p><strong>PoşadovanÜ soubor neexistuje.</strong></p>'
- FILE_SIZE_REPLACEMENTS = [('&nbsp;', '')]
-
-
- def process(self, pyfile):
- msurl = re.match(self.__pattern__, pyfile.url)
- if msurl:
- self.fileID = msurl.group('ID')
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
- else:
- self.handleOverriden()
-
- def handleFree(self):
- self.download("http://www.multishare.cz/html/download_free.php?ID=%s" % self.fileID)
-
- def handlePremium(self):
- if not self.checkCredit():
- self.logWarning("Not enough credit left to download file")
- self.resetAccount()
-
- self.download("http://www.multishare.cz/html/download_premium.php?ID=%s" % self.fileID)
-
- def handleOverriden(self):
- if not self.premium:
- self.fail("Only premium users can download from other hosters")
-
- self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": self.pyfile.url}, decode=True)
- self.getFileInfo()
-
- if not self.checkCredit():
- self.fail("Not enough credit left to download file")
-
- url = "http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random() * 10000 * random())
- params = {"u_ID": self.acc_info['u_ID'], "u_hash": self.acc_info['u_hash'], "link": self.pyfile.url}
- self.logDebug(url, params)
- self.download(url, get=params)
-
- def checkCredit(self):
- self.acc_info = self.account.getAccountInfo(self.user, True)
- self.logInfo("User %s has %i MB left" % (self.user, self.acc_info['trafficleft'] / 1024))
-
- return self.pyfile.size / 1024 <= self.acc_info['trafficleft']
-
-
-getInfo = create_getInfo(MultishareCz)
diff --git a/module/plugins/hoster/MyfastfileCom.py b/module/plugins/hoster/MyfastfileCom.py
deleted file mode 100644
index 7a5c2f515..000000000
--- a/module/plugins/hoster/MyfastfileCom.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-class MyfastfileCom(Hoster):
- __name__ = "MyfastfileCom"
- __type__ = "hoster"
- __version__ = "0.04"
- __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
- __description__ = """Myfastfile.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Myfastfile.com")
- self.fail("No Myfastfile.com account provided")
- else:
- self.logDebug("Original URL: %s" % pyfile.url)
- page = self.req.load('http://myfastfile.com/api.php',
- get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
- 'link': pyfile.url})
- self.logDebug("JSON data: " + page)
- page = json_loads(page)
- if page['status'] != 'ok':
- self.fail('Unable to unrestrict link')
- new_url = page['link']
-
- if new_url != pyfile.url:
- self.logDebug("Unrestricted URL: " + new_url)
-
- self.download(new_url, disposition=True)
diff --git a/module/plugins/hoster/MyvideoDe.py b/module/plugins/hoster/MyvideoDe.py
deleted file mode 100644
index 630c97ec0..000000000
--- a/module/plugins/hoster/MyvideoDe.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.unescape import unescape
-
-
-class MyvideoDe(Hoster):
- __name__ = "MyvideoDe"
- __type__ = "hoster"
- __version__ = "0.9"
-
- __pattern__ = r'http://(?:www\.)?myvideo.de/watch/'
-
- __description__ = """Myvideo.de hoster plugin"""
- __author_name__ = "spoob"
- __author_mail__ = "spoob@pyload.org"
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.download_html()
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- def download_html(self):
- self.html = self.load(self.pyfile.url)
-
- def get_file_url(self):
- videoId = re.search(r"addVariable\('_videoid','(.*)'\);p.addParam\('quality'", self.html).group(1)
- videoServer = re.search("rel='image_src' href='(.*)thumbs/.*' />", self.html).group(1)
- file_url = videoServer + videoId + ".flv"
- return file_url
-
- def get_file_name(self):
- file_name_pattern = r"<h1 class='globalHd'>(.*)</h1>"
- return unescape(re.search(file_name_pattern, self.html).group(1).replace("/", "") + '.flv')
-
- def file_exists(self):
- self.download_html()
- self.load(str(self.pyfile.url), cookies=False, just_header=True)
- if self.req.lastEffectiveURL == "http://www.myvideo.de/":
- return False
- return True
diff --git a/module/plugins/hoster/NarodRu.py b/module/plugins/hoster/NarodRu.py
deleted file mode 100644
index 18bed0231..000000000
--- a/module/plugins/hoster/NarodRu.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import random
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class NarodRu(SimpleHoster):
- __name__ = "NarodRu"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/[0-9]+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
-
- __description__ = """Narod.ru hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
- FILE_SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
- OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
-
- FILE_SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
- FILE_URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
- (r"/start/[0-9]+\.\w+-narod\.yandex\.ru/([0-9]{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
-
- CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
- LINK_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
-
-
- def handleFree(self):
- for _ in xrange(5):
- self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random() * 777))
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.parseError('Captcha')
- post_data = {"action": "sendcapcha"}
- captcha_url, post_data['key'] = m.groups()
- post_data['rep'] = self.decryptCaptcha(captcha_url)
-
- self.html = self.load(self.pyfile.url, post=post_data, decode=True)
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- url = 'http://narod.ru' + m.group(1)
- self.correctCaptcha()
- break
- elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
- self.invalidCaptcha()
- else:
- self.parseError('Download link')
- else:
- self.fail("No valid captcha code entered")
-
- self.logDebug('Download link: ' + url)
- self.download(url)
-
-
-getInfo = create_getInfo(NarodRu)
diff --git a/module/plugins/hoster/NetloadIn.py b/module/plugins/hoster/NetloadIn.py
deleted file mode 100644
index a45aafa63..000000000
--- a/module/plugins/hoster/NetloadIn.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import sleep, time
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-
-
-def getInfo(urls):
- ## returns list of tupels (name, size (in bytes), status (see FileDatabase), url)
-
- apiurl = "http://api.netload.in/info.php?auth=Zf9SnQh9WiReEsb18akjvQGqT0I830e8&bz=1&md5=1&file_id="
- id_regex = re.compile(NetloadIn.__pattern__)
- urls_per_query = 80
-
- for chunk in chunks(urls, urls_per_query):
- ids = ""
- for url in chunk:
- match = id_regex.search(url)
- if match:
- ids = ids + match.group(1) + ";"
-
- api = getURL(apiurl + ids, decode=True)
-
- if api is None or len(api) < 10:
- print "Netload prefetch: failed "
- return
- if api.find("unknown_auth") >= 0:
- print "Netload prefetch: Outdated auth code "
- return
-
- result = []
-
- for i, r in enumerate(api.splitlines()):
- try:
- tmp = r.split(";")
- try:
- size = int(tmp[2])
- except:
- size = 0
- result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i]))
- except:
- print "Netload prefetch: Error while processing response: "
- print r
-
- yield result
-
-
-class NetloadIn(Hoster):
- __name__ = "NetloadIn"
- __type__ = "hoster"
- __version__ = "0.45"
-
- __pattern__ = r'https?://(?:[^/]*\.)?netload\.in/(?:datei(.*?)(?:\.htm|/)|index.php?id=10&file_id=)'
-
- __description__ = """Netload.in hoster plugin"""
- __author_name__ = ("spoob", "RaNaN", "Gregy")
- __author_mail__ = ("spoob@pyload.org", "ranan@pyload.org", "gregy@gregy.cz")
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
-
- def process(self, pyfile):
- self.url = pyfile.url
- self.prepare()
- pyfile.setStatus("downloading")
- self.proceed(self.url)
-
- def prepare(self):
- self.download_api_data()
-
- if self.api_data and self.api_data['filename']:
- self.pyfile.name = self.api_data['filename']
-
- if self.premium:
- self.logDebug("Netload: Use Premium Account")
- settings = self.load("http://www.netload.in/index.php?id=2&lang=en")
- if '<option value="2" selected="selected">Direkter Download' in settings:
- self.logDebug("Using direct download")
- return True
- else:
- self.logDebug("Direct downloads not enabled. Parsing html for a download URL")
-
- if self.download_html():
- return True
- else:
- self.fail("Failed")
- return False
-
- def download_api_data(self, n=0):
- url = self.url
- id_regex = re.compile(self.__pattern__)
- match = id_regex.search(url)
-
- if match:
- #normalize url
- self.url = 'http://www.netload.in/datei%s.htm' % match.group(1)
- self.logDebug("URL: %s" % self.url)
- else:
- self.api_data = False
- return
-
- apiurl = "http://api.netload.in/info.php"
- src = self.load(apiurl, cookies=False,
- get={"file_id": match.group(1), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
- "md5": "1"}, decode=True).strip()
- if not src and n <= 3:
- sleep(0.2)
- self.download_api_data(n + 1)
- return
-
- self.logDebug("Netload: APIDATA: " + src)
- self.api_data = {}
- if src and ";" in src and src not in ("unknown file_data", "unknown_server_data", "No input file specified."):
- lines = src.split(";")
- self.api_data['exists'] = True
- self.api_data['fileid'] = lines[0]
- self.api_data['filename'] = lines[1]
- self.api_data['size'] = lines[2]
- self.api_data['status'] = lines[3]
- if self.api_data['status'] == "online":
- self.api_data['checksum'] = lines[4].strip()
- else:
- self.api_data = False # check manually since api data is useless sometimes
-
- if lines[0] == lines[1] and lines[2] == "0": # useless api data
- self.api_data = False
- else:
- self.api_data = False
-
- def final_wait(self, page):
- wait_time = self.get_wait_time(page)
- self.setWait(wait_time)
- self.logDebug("Netload: final wait %d seconds" % wait_time)
- self.wait()
- self.url = self.get_file_url(page)
-
- def download_html(self):
- self.logDebug("Netload: Entering download_html")
- page = self.load(self.url, decode=True)
- t = time() + 30
-
- if "/share/templates/download_hddcrash.tpl" in page:
- self.logError("Netload HDD Crash")
- self.fail(_("File temporarily not available"))
-
- if not self.api_data:
- self.logDebug("API Data may be useless, get details from html page")
-
- if "* The file was deleted" in page:
- self.offline()
-
- name = re.search(r'class="dl_first_filename">([^<]+)', page, re.MULTILINE)
- # the found filename is not truncated
- if name:
- name = name.group(1).strip()
- if not name.endswith(".."):
- self.pyfile.name = name
-
- captchawaited = False
- for i in xrange(10):
-
- if not page:
- page = self.load(self.url)
- t = time() + 30
-
- if "/share/templates/download_hddcrash.tpl" in page:
- self.logError("Netload HDD Crash")
- self.fail(_("File temporarily not available"))
-
- self.logDebug("Netload: try number %d " % i)
-
- if ">Your download is being prepared.<" in page:
- self.logDebug("Netload: We will prepare your download")
- self.final_wait(page)
- return True
- if ">An access request has been made from IP address <" in page:
- wait = self.get_wait_time(page)
- if not wait:
- self.logDebug("Netload: Wait was 0 setting 30")
- wait = 30 * 60
- self.logInfo(_("Netload: waiting between downloads %d s." % wait))
- self.wantReconnect = True
- self.setWait(wait)
- self.wait()
-
- return self.download_html()
-
- self.logDebug("Netload: Trying to find captcha")
-
- try:
- url_captcha_html = "http://netload.in/" + re.search('(index.php\?id=10&amp;.*&amp;captcha=1)',
- page).group(1).replace("amp;", "")
- except:
- page = None
- continue
-
- try:
- page = self.load(url_captcha_html, cookies=True)
- captcha_url = "http://netload.in/" + re.search('(share/includes/captcha.php\?t=\d*)', page).group(1)
- except:
- self.logDebug("Netload: Could not find captcha, try again from beginning")
- captchawaited = False
- continue
-
- file_id = re.search('<input name="file_id" type="hidden" value="(.*)" />', page).group(1)
- if not captchawaited:
- wait = self.get_wait_time(page)
- if i == 0:
- self.pyfile.waitUntil = time() # dont wait contrary to time on website
- else:
- self.pyfile.waitUntil = t
- self.logInfo(_("Netload: waiting for captcha %d s.") % (self.pyfile.waitUntil - time()))
- #self.setWait(wait)
- self.wait()
- captchawaited = True
-
- captcha = self.decryptCaptcha(captcha_url)
- page = self.load("http://netload.in/index.php?id=10", post={"file_id": file_id, "captcha_check": captcha},
- cookies=True)
-
- return False
-
- def get_file_url(self, page):
- try:
- file_url_pattern = r"<a class=\"Orange_Link\" href=\"(http://.+)\".?>Or click here"
- attempt = re.search(file_url_pattern, page)
- if attempt is not None:
- return attempt.group(1)
- else:
- self.logDebug("Netload: Backup try for final link")
- file_url_pattern = r"<a href=\"(.+)\" class=\"Orange_Link\">Click here"
- attempt = re.search(file_url_pattern, page)
- return "http://netload.in/" + attempt.group(1)
- except:
- self.logDebug("Netload: Getting final link failed")
- return None
-
- def get_wait_time(self, page):
- wait_seconds = int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
- return wait_seconds
-
- def proceed(self, url):
- self.logDebug("Netload: Downloading..")
-
- self.download(url, disposition=True)
-
- check = self.checkDownload({"empty": re.compile(r"^$"), "offline": re.compile("The file was deleted")})
-
- if check == "empty":
- self.logInfo(_("Downloaded File was empty"))
- self.retry()
- elif check == "offline":
- self.offline()
diff --git a/module/plugins/hoster/NosuploadCom.py b/module/plugins/hoster/NosuploadCom.py
deleted file mode 100644
index 3187dd89f..000000000
--- a/module/plugins/hoster/NosuploadCom.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class NosuploadCom(XFileSharingPro):
- __name__ = "NosuploadCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
-
- __description__ = """Nosupload.com hoster plugin"""
- __author_name__ = "igel"
- __author_mail__ = "igelkun@myopera.com"
-
- HOSTER_NAME = "nosupload.com"
-
- FILE_SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[0-9\.]+) (?P<U>[kKMG]?B)</p>'
- LINK_PATTERN = r'<a class="select" href="(http://.+?)">Download</a>'
- WAIT_PATTERN = r'Please wait.*?>(\d+)</span>'
-
-
- def getDownloadLink(self):
- # stage1: press the "Free Download" button
- data = self.getPostParameters()
- self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
-
- # stage2: wait some time and press the "Download File" button
- data = self.getPostParameters()
- wait_time = re.search(self.WAIT_PATTERN, self.html, re.MULTILINE | re.DOTALL).group(1)
- self.logDebug("hoster told us to wait %s seconds" % wait_time)
- self.wait(wait_time)
- self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
-
- # stage3: get the download link
- return re.search(self.LINK_PATTERN, self.html, re.S).group(1)
-
-
-getInfo = create_getInfo(NosuploadCom)
diff --git a/module/plugins/hoster/NovafileCom.py b/module/plugins/hoster/NovafileCom.py
deleted file mode 100644
index 4a89064ea..000000000
--- a/module/plugins/hoster/NovafileCom.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://novafile.com/vfun4z6o2cit
-# http://novafile.com/s6zrr5wemuz4
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class NovafileCom(XFileSharingPro):
- __name__ = "NovafileCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?novafile\.com/\w{12}'
-
- __description__ = """Novafile.com hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- HOSTER_NAME = "novafile.com"
-
- FILE_SIZE_PATTERN = r'<div class="size">(?P<S>.+?)</div>'
- ERROR_PATTERN = r'class="alert[^"]*alert-separate"[^>]*>\s*(?:<p>)?(.*?)\s*</'
- LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
- WAIT_PATTERN = r'<p>Please wait <span id="count"[^>]*>(\d+)</span> seconds</p>'
-
-
- def setup(self):
- self.multiDL = False
-
-
-getInfo = create_getInfo(NovafileCom)
diff --git a/module/plugins/hoster/NowDownloadEu.py b/module/plugins/hoster/NowDownloadEu.py
deleted file mode 100644
index 193698f17..000000000
--- a/module/plugins/hoster/NowDownloadEu.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-from module.utils import fixup
-
-
-class NowDownloadEu(SimpleHoster):
- __name__ = "NowDownloadEu"
- __type__ = "hoster"
- __version__ = "0.05"
-
- __pattern__ = r'http://(?:www\.)?nowdownload\.(ch|co|eu|sx)/(dl/|download\.php\?id=)(?P<ID>\w+)'
-
- __description__ = """NowDownload.ch hoster plugin"""
- __author_name__ = ("godofdream", "Walter Purcaro")
- __author_mail__ = ("soilfiction@gmail.com", "vuolter@gmail.com")
-
- FILE_INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[0-9,.]+) (?P<U>[kKMG])i?B </h4>'
- OFFLINE_PATTERN = r'(This file does not exist!)'
-
- TOKEN_PATTERN = r'"(/api/token\.php\?token=[a-z0-9]+)"'
- CONTINUE_PATTERN = r'"(/dl2/[a-z0-9]+/[a-z0-9]+)"'
- WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
- LINK_PATTERN = r'"(http://f\d+\.nowdownload\.ch/dl/[a-z0-9]+/[a-z0-9]+/[^<>"]*?)"'
-
- FILE_NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<[^>]*>', '')]
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
- self.chunkLimit = -1
-
- def handleFree(self):
- tokenlink = re.search(self.TOKEN_PATTERN, self.html)
- continuelink = re.search(self.CONTINUE_PATTERN, self.html)
- if tokenlink is None or continuelink is None:
- self.fail('Plugin out of Date')
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait = int(m.group(1))
- else:
- wait = 60
-
- baseurl = "http://www.nowdownload.ch"
- self.html = self.load(baseurl + str(tokenlink.group(1)))
- self.wait(wait)
-
- self.html = self.load(baseurl + str(continuelink.group(1)))
-
- url = re.search(self.LINK_PATTERN, self.html)
- if url is None:
- self.fail('Download Link not Found (Plugin out of Date?)')
- self.logDebug('Download link: ' + str(url.group(1)))
- self.download(str(url.group(1)))
-
-
-getInfo = create_getInfo(NowDownloadEu)
diff --git a/module/plugins/hoster/OboomCom.py b/module/plugins/hoster/OboomCom.py
deleted file mode 100644
index f30c64184..000000000
--- a/module/plugins/hoster/OboomCom.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# https://www.oboom.com/B7CYZIEB/10Mio.dat
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-class OboomCom(Hoster):
- __name__ = "OboomCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>[A-Z0-9]{8})'
-
- __description__ = """oboom.com hoster plugin"""
- __author_name__ = "stanley"
- __author_mail__ = "stanley.foerster@gmail.com"
-
- RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
-
-
- def loadUrl(self, url, get=None):
- if get is None:
- get = dict()
- return json_loads(self.load(url, get, decode=True))
-
- def getFileId(self, url):
- self.fileId = re.match(OboomCom.__pattern__, url).group('ID')
-
- def getSessionToken(self):
- if self.premium:
- accountInfo = self.account.getAccountInfo(self.user, True)
- if "session" in accountInfo:
- self.sessionToken = accountInfo['session']
- else:
- self.fail("Could not retrieve premium session")
- else:
- apiUrl = "https://www.oboom.com/1.0/guestsession"
- result = self.loadUrl(apiUrl)
- if result[0] == 200:
- self.sessionToken = result[1]
- else:
- self.fail("Could not retrieve token for guest session. Error code %s" % result[0])
-
- def solveCaptcha(self):
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
- apiUrl = "https://www.oboom.com/1.0/download/ticket"
- params = {"recaptcha_challenge_field": challenge,
- "recaptcha_response_field": response,
- "download_id": self.fileId,
- "token": self.sessionToken}
- result = self.loadUrl(apiUrl, params)
-
- if result[0] == 200:
- self.downloadToken = result[1]
- self.downloadAuth = result[2]
- self.correctCaptcha()
- self.setWait(30)
- self.wait()
- break
- elif result[0] == 400:
- if result[1] == "incorrect-captcha-sol":
- self.invalidCaptcha()
- elif result[1] == "captcha-timeout":
- self.invalidCaptcha()
- elif result[1] == "forbidden":
- self.retry(5, 15 * 60, "Service unavailable")
- elif result[0] == 403:
- if result[1] == -1: # another download is running
- self.setWait(15 * 60)
- else:
- self.setWait(result[1], reconnect=True)
- self.wait()
- self.retry(5)
- else:
- self.invalidCaptcha()
- self.fail("Received invalid captcha 5 times")
-
- def getFileInfo(self, token, fileId):
- apiUrl = "https://api.oboom.com/1.0/info"
- params = {"token": token, "items": fileId, "http_errors": 0}
-
- result = self.loadUrl(apiUrl, params)
- if result[0] == 200:
- item = result[1][0]
- if item['state'] == "online":
- self.fileSize = item['size']
- self.fileName = item['name']
- else:
- self.offline()
- else:
- self.fail("Could not retrieve file info. Error code %s: %s" % (result[0], result[1]))
-
- def getDownloadTicket(self):
- apiUrl = "https://api.oboom.com/1.0/dl"
- params = {"item": self.fileId, "http_errors": 0}
- if self.premium:
- params['token'] = self.sessionToken
- else:
- params['token'] = self.downloadToken
- params['auth'] = self.downloadAuth
-
- result = self.loadUrl(apiUrl, params)
- if result[0] == 200:
- self.downloadDomain = result[1]
- self.downloadTicket = result[2]
- else:
- self.fail("Could not retrieve download ticket. Error code %s" % result[0])
-
- def setup(self):
- self.chunkLimit = 1
- self.multiDL = self.premium
-
- def process(self, pyfile):
- self.pyfile.url.replace(".com/#id=", ".com/#")
- self.pyfile.url.replace(".com/#/", ".com/#")
- self.getFileId(self.pyfile.url)
- self.getSessionToken()
- self.getFileInfo(self.sessionToken, self.fileId)
- self.pyfile.name = self.fileName
- self.pyfile.size = self.fileSize
- if not self.premium:
- self.solveCaptcha()
- self.getDownloadTicket()
- self.download("https://%s/1.0/dlh" % self.downloadDomain, get={"ticket": self.downloadTicket, "http_errors": 0})
diff --git a/module/plugins/hoster/OneFichierCom.py b/module/plugins/hoster/OneFichierCom.py
deleted file mode 100644
index 0536f7185..000000000
--- a/module/plugins/hoster/OneFichierCom.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://5pnm24ltcw.1fichier.com/
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class OneFichierCom(SimpleHoster):
- __name__ = "OneFichierCom"
- __type__ = "hoster"
- __version__ = "0.61"
-
- __pattern__ = r'(http://(?P<id>\w+)\.(?P<host>(1fichier|d(es)?fichiers|pjointe)\.(com|fr|net|org)|(cjoint|mesfichiers|piecejointe|oi)\.(org|net)|tenvoi\.(com|org|net)|dl4free\.com|alterupload\.com|megadl.fr))/?'
-
- __description__ = """1fichier.com hoster plugin"""
- __author_name__ = ("fragonib", "the-razer", "zoidberg", "imclem", "stickell", "Elrick69")
- __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "daniel_ AT gmx DOT net", "zoidberg@mujmail.cz",
- "imclem on github", "l.stickell@yahoo.it", "elrick69[AT]rocketmail[DOT]com")
-
- FILE_NAME_PATTERN = r'">Filename :</th>\s*<td>(?P<N>[^<]+)</td>'
- FILE_SIZE_PATTERN = r'<th>Size :</th>\s*<td>(?P<S>[^<]+)</td>'
- OFFLINE_PATTERN = r'The (requested)? file (could not be found|has been deleted)'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'http://\g<id>.\g<host>/en/')]
-
- WAITING_PATTERN = r'Warning ! Without premium status, you must wait between each downloads'
- NOT_PARALLEL = r'Warning ! Without premium status, you can download only one file at a time'
- WAIT_TIME = 10 * 60 # Retry time between each free download
- RETRY_TIME = 15 * 60 # Default retry time in seconds (if detected parallel download)
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- if self.WAITING_PATTERN in self.html:
- self.logInfo('You have to wait been each free download! Retrying in %d seconds.' % self.WAIT_TIME)
- self.waitAndRetry(self.WAIT_TIME)
- else: # detect parallel download
- m = re.search(self.NOT_PARALLEL, self.html)
- if m:
- self.waitAndRetry(self.RETRY_TIME)
-
- url, inputs = self.parseHtmlForm('action="http://%s' % self.file_info['id'])
- if not url:
- self.parseError("Download link not found")
-
- # Check for protection
- if "pass" in inputs:
- inputs['pass'] = self.getPassword()
- inputs['submit'] = "Download"
-
- self.download(url, post=inputs)
-
- # Check download
- self.checkDownloadedFile()
-
- def handlePremium(self):
- url, inputs = self.parseHtmlForm('action="http://%s' % self.file_info['id'])
- if not url:
- self.parseError("Download link not found")
-
- # Check for protection
- if "pass" in inputs:
- inputs['pass'] = self.getPassword()
- inputs['submit'] = "Download"
-
- self.download(url, post=inputs)
-
- # Check download
- self.checkDownloadedFile()
-
- def checkDownloadedFile(self):
- check = self.checkDownload({"wait": self.WAITING_PATTERN})
- if check == "wait":
- self.waitAndRetry(int(self.lastcheck.group(1)) * 60)
-
- def waitAndRetry(self, wait_time):
- self.wait(wait_time, True)
- self.retry()
-
-
-
-getInfo = create_getInfo(OneFichierCom)
diff --git a/module/plugins/hoster/OverLoadMe.py b/module/plugins/hoster/OverLoadMe.py
deleted file mode 100644
index 7b7c83893..000000000
--- a/module/plugins/hoster/OverLoadMe.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import randrange
-from urllib import unquote
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class OverLoadMe(Hoster):
- __name__ = "OverLoadMe"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://.*overload\.me.*'
-
- __description__ = """Over-Load.me hoster plugin"""
- __author_name__ = "marley"
- __author_mail__ = "marley@over-load.me"
-
-
- def getFilename(self, url):
- try:
- name = unquote(url.rsplit("/", 1)[1])
- except IndexError:
- name = "Unknown_Filename..."
- if name.endswith("..."): # incomplete filename, append random stuff
- name += "%s.tmp" % randrange(100, 999)
- return name
-
- def setup(self):
- self.chunkLimit = 5
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Over-Load")
- self.fail("No Over-Load account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- data = self.account.getAccountData(self.user)
-
- page = self.load("https://api.over-load.me/getdownload.php",
- get={"auth": data['password'], "link": pyfile.url})
- data = json_loads(page)
-
- self.logDebug("Returned Data: %s" % data)
-
- if data['err'] == 1:
- self.logWarning(data['msg'])
- self.tempOffline()
- else:
- if pyfile.name is not None and pyfile.name.endswith('.tmp') and data['filename']:
- pyfile.name = data['filename']
- pyfile.size = parseFileSize(data['filesize'])
- new_url = data['downloadlink']
-
- if self.getConfig("https"):
- new_url = new_url.replace("http://", "https://")
- else:
- new_url = new_url.replace("https://", "http://")
-
- if new_url != pyfile.url:
- self.logDebug("New URL: %s" % new_url)
-
- if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
- # only use when name wasn't already set
- pyfile.name = self.getFilename(new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload(
- {"error": "<title>An error occured while processing your request</title>"})
-
- if check == "error":
- # usual this download can safely be retried
- self.retry(reason="An error occured while generating link.", wait_time=60)
diff --git a/module/plugins/hoster/PandaPlanet.py b/module/plugins/hoster/PandaPlanet.py
deleted file mode 100644
index 6f852ac9a..000000000
--- a/module/plugins/hoster/PandaPlanet.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# test.bin - 214 B - http://pandapla.net/pew1cz3ot586
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://pandapla.net/tz0rgjfyyoh7
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class PandaPlanet(XFileSharingPro):
- __name__ = "PandaPlanet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?pandapla\.net/\w{12}'
-
- __description__ = """Pandapla.net hoster plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- HOSTER_NAME = "pandapla.net"
-
- FILE_SIZE_PATTERN = r'File Size:</b>\s*</td>\s*<td[^>]*>(?P<S>[^<]+)</td>\s*</tr>'
- FILE_NAME_PATTERN = r'File Name:</b>\s*</td>\s*<td[^>]*>(?P<N>[^<]+)</td>\s*</tr>'
- LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<]+\/(?!video\.mp4)[^"\'<]+)' % HOSTER_NAME
-
-
-getInfo = create_getInfo(PandaPlanet)
diff --git a/module/plugins/hoster/PornhostCom.py b/module/plugins/hoster/PornhostCom.py
deleted file mode 100644
index f0621c420..000000000
--- a/module/plugins/hoster/PornhostCom.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhostCom(Hoster):
- __name__ = "PornhostCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?pornhost\.com/([0-9]+/[0-9]+\.html|[0-9]+)'
-
- __description__ = """Pornhost.com hoster plugin"""
- __author_name__ = "jeix"
- __author_mail__ = "jeix@hasnomail.de"
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- # Old interface
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- url = re.search(r'download this file</label>.*?<a href="(.*?)"', self.html)
- if url is None:
- url = re.search(r'"(http://dl[0-9]+\.pornhost\.com/files/.*?/.*?/.*?/.*?/.*?/.*?\..*?)"', self.html)
- if url is None:
- url = re.search(r'width: 894px; height: 675px">.*?<img src="(.*?)"', self.html)
- if url is None:
- url = re.search(r'"http://file[0-9]+\.pornhost\.com/[0-9]+/.*?"',
- self.html) # TODO: fix this one since it doesn't match
-
- return url.group(1).strip()
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- name = re.search(r'<title>pornhost\.com - free file hosting with a twist - gallery(.*?)</title>', self.html)
- if name is None:
- name = re.search(r'id="url" value="http://www\.pornhost\.com/(.*?)/"', self.html)
- if name is None:
- name = re.search(r'<title>pornhost\.com - free file hosting with a twist -(.*?)</title>', self.html)
- if name is None:
- name = re.search(r'"http://file[0-9]+\.pornhost\.com/.*?/(.*?)"', self.html)
-
- name = name.group(1).strip() + ".flv"
-
- return name
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if (re.search(r'gallery not found', self.html) is not None or
- re.search(r'You will be redirected to', self.html) is not None):
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PornhubCom.py b/module/plugins/hoster/PornhubCom.py
deleted file mode 100644
index cecdc4339..000000000
--- a/module/plugins/hoster/PornhubCom.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class PornhubCom(Hoster):
- __name__ = "PornhubCom"
- __type__ = "hoster"
- __version__ = "0.5"
-
- __pattern__ = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=[\w\d]+'
-
- __description__ = """Pornhub.com hoster plugin"""
- __author_name__ = "jeix"
- __author_mail__ = "jeix@hasnomail.de"
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- url = "http://www.pornhub.com//gateway.php"
- video_id = self.pyfile.url.split('=')[-1]
- # thanks to jD team for this one v
- post_data = "\x00\x03\x00\x00\x00\x01\x00\x0c\x70\x6c\x61\x79\x65\x72\x43\x6f\x6e\x66\x69\x67\x00\x02\x2f\x31\x00\x00\x00\x44\x0a\x00\x00\x00\x03\x02\x00"
- post_data += chr(len(video_id))
- post_data += video_id
- post_data += "\x02\x00\x02\x2d\x31\x02\x00\x20"
- post_data += "add299463d4410c6d1b1c418868225f7"
-
- content = self.req.load(url, post=str(post_data))
-
- new_content = ""
- for x in content:
- if ord(x) < 32 or ord(x) > 176:
- new_content += '#'
- else:
- new_content += x
-
- content = new_content
-
- return re.search(r'flv_url.*(http.*?)##post_roll', content).group(1)
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- m = re.search(r'<title[^>]+>([^<]+) - ', self.html)
- if m:
- name = m.group(1)
- else:
- matches = re.findall('<h1>(.*?)</h1>', self.html)
- if len(matches) > 1:
- name = matches[1]
- else:
- name = matches[0]
-
- return name + '.flv'
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r'This video is no longer in our database or is in conversion', self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/PotloadCom.py b/module/plugins/hoster/PotloadCom.py
deleted file mode 100644
index 087b7a265..000000000
--- a/module/plugins/hoster/PotloadCom.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class PotloadCom(XFileSharingPro):
- __name__ = "PotloadCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?potload\.com/\w{12}'
-
- __description__ = """Potload.com hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
- HOSTER_NAME = "potload.com"
-
- FILE_INFO_PATTERN = r'<h[1-6]>(?P<N>.+) \((?P<S>\d+) (?P<U>\w+)\)</h'
-
-
-getInfo = create_getInfo(PotloadCom)
diff --git a/module/plugins/hoster/PremiumTo.py b/module/plugins/hoster/PremiumTo.py
deleted file mode 100644
index 3ab7e34ac..000000000
--- a/module/plugins/hoster/PremiumTo.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from os import remove
-from os.path import exists
-from urllib import quote
-
-from module.plugins.Hoster import Hoster
-from module.utils import fs_encode
-
-
-class PremiumTo(Hoster):
- __name__ = "PremiumTo"
- __type__ = "hoster"
- __version__ = "0.09"
- __pattern__ = r'https?://(?:www\.)?premium.to/.*'
- __description__ = """Premium.to hoster plugin"""
- __author_name__ = ("RaNaN", "zoidberg", "stickell")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- def setup(self):
- self.resumeDownload = True
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "premium.to")
- self.fail("No premium.to account provided")
-
- self.logDebug("Old URL: %s" % pyfile.url)
-
- tra = self.getTraffic()
-
- #raise timeout to 2min
- self.req.setOption("timeout", 120)
-
- self.download(
- "http://premium.to/api/getfile.php",
- get={"username": self.account.username, "password": self.account.password, "link": quote(pyfile.url, "")},
- disposition=True)
-
- check = self.checkDownload({"nopremium": "No premium account available"})
-
- if check == "nopremium":
- self.retry(60, 5 * 60, "No premium account available")
-
- err = ''
- if self.req.http.code == '420':
- # Custom error code send - fail
- lastDownload = fs_encode(self.lastDownload)
-
- if exists(lastDownload):
- f = open(lastDownload, "rb")
- err = f.read(256).strip()
- f.close()
- remove(lastDownload)
- else:
- err = 'File does not exist'
-
- trb = self.getTraffic()
- self.logInfo("Filesize: %d, Traffic used %d, traffic left %d" % (pyfile.size, tra - trb, trb))
-
- if err:
- self.fail(err)
-
- def getTraffic(self):
- try:
- api_r = self.load("http://premium.to/api/straffic.php",
- get={'username': self.account.username, 'password': self.account.password})
- traffic = sum(map(int, api_r.split(';')))
- except:
- traffic = 0
- return traffic
diff --git a/module/plugins/hoster/PremiumizeMe.py b/module/plugins/hoster/PremiumizeMe.py
deleted file mode 100644
index 7d0ca9ed2..000000000
--- a/module/plugins/hoster/PremiumizeMe.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-class PremiumizeMe(Hoster):
- __name__ = "PremiumizeMe"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = None #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
-
- __description__ = """Premiumize.me hoster plugin"""
- __author_name__ = "Florian Franzen"
- __author_mail__ = "FlorianFranzen@gmail.com"
-
-
- def process(self, pyfile):
- # Check account
- if not self.account or not self.account.canUse():
- self.logError(_("Please enter your %s account or deactivate this plugin") % "premiumize.me")
- self.fail("No valid premiumize.me account provided")
-
- # In some cases hostsers do not supply us with a filename at download, so we
- # are going to set a fall back filename (e.g. for freakshare or xfileshare)
- pyfile.name = pyfile.name.split('/').pop() # Remove everthing before last slash
-
- # Correction for automatic assigned filename: Removing html at end if needed
- suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
- temp = pyfile.name.split('.')
- if temp.pop() in suffix_to_remove:
- pyfile.name = ".".join(temp)
-
- # Get account data
- (user, data) = self.account.selectAccount()
-
- # Get rewritten link using the premiumize.me api v1 (see https://secure.premiumize.me/?show=api)
- answer = self.load(
- "https://api.premiumize.me/pm-api/v1.php?method=directdownloadlink&params[login]=%s&params[pass]=%s&params[link]=%s" % (
- user, data['password'], pyfile.url))
- data = json_loads(answer)
-
- # Check status and decide what to do
- status = data['status']
- if status == 200:
- self.download(data['result']['location'], disposition=True)
- elif status == 400:
- self.fail("Invalid link")
- elif status == 404:
- self.offline()
- elif status >= 500:
- self.tempOffline()
- else:
- self.fail(data['statusmessage'])
diff --git a/module/plugins/hoster/PromptfileCom.py b/module/plugins/hoster/PromptfileCom.py
deleted file mode 100644
index d16af7e5a..000000000
--- a/module/plugins/hoster/PromptfileCom.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class PromptfileCom(SimpleHoster):
- __name__ = "PromptfileCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'https?://(?:www\.)?promptfile\.com/'
-
- __description__ = """Promptfile.com hoster plugin"""
- __author_name__ = "igel"
- __author_mail__ = "igelkun@myopera.com"
-
- FILE_INFO_PATTERN = r'<span style="[^"]*" title="[^"]*">(?P<N>.*?) \((?P<S>[\d.]+) (?P<U>\w+)\)</span>'
- OFFLINE_PATTERN = r'<span style="[^"]*" title="File Not Found">File Not Found</span>'
-
- CHASH_PATTERN = r'<input type="hidden" name="chash" value="([^"]*)" />'
- LINK_PATTERN = r"clip: {\s*url: '(https?://(?:www\.)promptfile[^']*)',"
-
-
- def handleFree(self):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.parseError("Unable to detect chash")
- chash = m.group(1)
- self.logDebug("read chash %s" % chash)
- # continue to stage2
- self.html = self.load(self.pyfile.url, decode=True, post={'chash': chash})
-
- # STAGE 2: get the direct link
- m = re.search(self.LINK_PATTERN, self.html, re.MULTILINE | re.DOTALL)
- if m is None:
- self.parseError("Unable to detect direct link")
- direct = m.group(1)
- self.logDebug("found direct link: " + direct)
- self.download(direct, disposition=True)
-
-
-getInfo = create_getInfo(PromptfileCom)
diff --git a/module/plugins/hoster/QuickshareCz.py b/module/plugins/hoster/QuickshareCz.py
deleted file mode 100644
index 972effffb..000000000
--- a/module/plugins/hoster/QuickshareCz.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class QuickshareCz(SimpleHoster):
- __name__ = "QuickshareCz"
- __type__ = "hoster"
- __version__ = "0.54"
-
- __pattern__ = r'http://(?:[^/]*\.)?quickshare.cz/stahnout-soubor/.*'
-
- __description__ = """Quickshare.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
- FILE_SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</td>'
- OFFLINE_PATTERN = r'<script type="text/javascript">location.href=\'/chyba\';</script>'
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- # parse js variables
- self.jsvars = dict((x, y.strip("'")) for x, y in re.findall(r"var (\w+) = ([0-9.]+|'[^']*')", self.html))
- self.logDebug(self.jsvars)
- pyfile.name = self.jsvars['ID3']
-
- # determine download type - free or premium
- if self.premium:
- if 'UU_prihlasen' in self.jsvars:
- if self.jsvars['UU_prihlasen'] == '0':
- self.logWarning('User not logged in')
- self.relogin(self.user)
- self.retry()
- elif float(self.jsvars['UU_kredit']) < float(self.jsvars['kredit_odecet']):
- self.logWarning('Not enough credit left')
- self.premium = False
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- check = self.checkDownload({"err": re.compile(r"\AChyba!")}, max_size=100)
- if check == "err":
- self.fail("File not m or plugin defect")
-
- def handleFree(self):
- # get download url
- download_url = '%s/download.php' % self.jsvars['server']
- data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID3", "ID4"))
- self.logDebug("FREE URL1:" + download_url, data)
-
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
- self.load(download_url, post=data)
- self.header = self.req.http.header
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
-
- m = re.search("Location\s*:\s*(.*)", self.header, re.I)
- if m is None:
- self.fail('File not found')
- download_url = m.group(1)
- self.logDebug("FREE URL2:" + download_url)
-
- # check errors
- m = re.search(r'/chyba/(\d+)', download_url)
- if m:
- if m.group(1) == '1':
- self.retry(60, 2 * 60, "This IP is already downloading")
- elif m.group(1) == '2':
- self.retry(60, 60, "No free slots available")
- else:
- self.fail('Error %d' % m.group(1))
-
- # download file
- self.download(download_url)
-
- def handlePremium(self):
- download_url = '%s/download_premium.php' % self.jsvars['server']
- data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID4", "ID5"))
- self.logDebug("PREMIUM URL:" + download_url, data)
- self.download(download_url, get=data)
-
-
-getInfo = create_getInfo(QuickshareCz)
diff --git a/module/plugins/hoster/RPNetBiz.py b/module/plugins/hoster/RPNetBiz.py
deleted file mode 100644
index 47f255074..000000000
--- a/module/plugins/hoster/RPNetBiz.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.common.json_layer import json_loads
-
-
-class RPNetBiz(Hoster):
- __name__ = "RPNetBiz"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __description__ = """RPNet.biz hoster plugin"""
-
- __pattern__ = r'https?://.*rpnet\.biz'
- __author_name__ = "Dman"
- __author_mail__ = "dmanugm@gmail.com"
-
-
- def setup(self):
- self.chunkLimit = -1
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- link_status = {'generated': pyfile.url}
- elif not self.account:
- # Check account
- self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
- self.fail("No rpnet account provided")
- else:
- (user, data) = self.account.selectAccount()
-
- self.logDebug("Original URL: %s" % pyfile.url)
- # Get the download link
- response = self.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": data['password'],
- "action": "generate", "links": pyfile.url})
-
- self.logDebug("JSON data: %s" % response)
- link_status = json_loads(response)['links'][0] # get the first link... since we only queried one
-
- # Check if we only have an id as a HDD link
- if 'id' in link_status:
- self.logDebug("Need to wait at least 30 seconds before requery")
- self.setWait(30) # wait for 30 seconds
- self.wait()
- # Lets query the server again asking for the status on the link,
- # we need to keep doing this until we reach 100
- max_tries = 30
- my_try = 0
- while (my_try <= max_tries):
- self.logDebug("Try: %d ; Max Tries: %d" % (my_try, max_tries))
- response = self.load("https://premium.rpnet.biz/client_api.php",
- get={"username": user, "password": data['password'],
- "action": "downloadInformation", "id": link_status['id']})
- self.logDebug("JSON data hdd query: %s" % response)
- download_status = json_loads(response)['download']
-
- if download_status['status'] == '100':
- link_status['generated'] = download_status['rpnet_link']
- self.logDebug("Successfully downloaded to rpnet HDD: %s" % link_status['generated'])
- break
- else:
- self.logDebug("At %s%% for the file download" % download_status['status'])
-
- self.setWait(30)
- self.wait()
- my_try += 1
-
- if my_try > max_tries: # We went over the limit!
- self.fail("Waited for about 15 minutes for download to finish but failed")
-
- if 'generated' in link_status:
- self.download(link_status['generated'], disposition=True)
- elif 'error' in link_status:
- self.fail(link_status['error'])
- else:
- self.fail("Something went wrong, not supposed to enter here")
diff --git a/module/plugins/hoster/RapidgatorNet.py b/module/plugins/hoster/RapidgatorNet.py
deleted file mode 100644
index ce4d9ab36..000000000
--- a/module/plugins/hoster/RapidgatorNet.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import HTTPHEADER
-
-from module.common.json_layer import json_loads
-from module.network.HTTPRequest import BadHeader
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.CaptchaService import AdsCaptcha, ReCaptcha, SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RapidgatorNet(SimpleHoster):
- __name__ = "RapidgatorNet"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
-
- __description__ = """Rapidgator.net hoster plugin"""
- __author_name__ = ("zoidberg", "chrox", "stickell", "Walter Purcaro")
- __author_mail__ = ("zoidberg@mujmail.cz", "", "l.stickell@yahoo.it", "vuolter@gmail.com")
-
- API_URL = "http://rapidgator.net/api/file"
-
- FILE_NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
- FILE_SIZE_PATTERN = r'File size:\s*<strong>(?P<S>[\d\.]+) (?P<U>\w+)</strong>'
- OFFLINE_PATTERN = r'>(File not found|Error 404)'
-
- JSVARS_PATTERN = r"\s+var\s*(startTimerUrl|getDownloadUrl|captchaUrl|fid|secs)\s*=\s*'?(.*?)'?;"
- PREMIUM_ONLY_ERROR_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
- DOWNLOAD_LIMIT_ERROR_PATTERN = r'You have reached your (daily|hourly) downloads limit'
- WAIT_PATTERN = r'(?:Delay between downloads must be not less than|Try again in)\s*(\d+)\s*(hour|min)'
- LINK_PATTERN = r"return '(http://\w+.rapidgator.net/.*)';"
-
- RECAPTCHA_KEY_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
- ADSCAPTCHA_SRC_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']*)'
- SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
- self.sid = None
- self.chunkLimit = 1
- self.req.setOption("timeout", 120)
-
- def process(self, pyfile):
- if self.account:
- self.sid = self.account.getAccountData(self.user).get('SID', None)
-
- if self.sid:
- self.handlePremium()
- else:
- self.handleFree()
-
- def api_response(self, cmd):
- try:
- json = self.load('%s/%s' % (self.API_URL, cmd),
- get={'sid': self.sid,
- 'url': self.pyfile.url}, decode=True)
- self.logDebug('API:%s' % cmd, json, "SID: %s" % self.sid)
- json = json_loads(json)
- status = json['response_status']
- msg = json['response_details']
- except BadHeader, e:
- self.logError('API:%s' % cmd, e, "SID: %s" % self.sid)
- status = e.code
- msg = e
-
- if status == 200:
- return json['response']
- elif status == 423:
- self.account.empty(self.user)
- self.retry()
- else:
- self.account.relogin(self.user)
- self.retry(wait_time=60)
-
- def handlePremium(self):
- #self.logDebug("ACCOUNT_DATA", self.account.getAccountData(self.user))
- self.api_data = self.api_response('info')
- self.api_data['md5'] = self.api_data['hash']
- self.pyfile.name = self.api_data['filename']
- self.pyfile.size = self.api_data['size']
- url = self.api_response('download')['url']
- self.download(url)
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- self.checkFree()
-
- jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
- self.logDebug(jsvars)
-
- self.req.http.lastURL = self.pyfile.url
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
-
- url = "http://rapidgator.net%s?fid=%s" % (
- jsvars.get('startTimerUrl', '/download/AjaxStartTimer'), jsvars['fid'])
- jsvars.update(self.getJsonResponse(url))
-
- self.wait(int(jsvars.get('secs', 45)) + 1, False)
-
- url = "http://rapidgator.net%s?sid=%s" % (
- jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
- jsvars.update(self.getJsonResponse(url))
-
- self.req.http.lastURL = self.pyfile.url
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
-
- url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
- self.html = self.load(url)
-
- for _ in xrange(5):
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- link = m.group(1)
- self.logDebug(link)
- self.download(link, disposition=True)
- break
- else:
- captcha, captcha_key = self.getCaptcha()
- captcha_challenge, captcha_response = captcha.challenge(captcha_key)
-
- self.html = self.load(url, post={
- "DownloadCaptchaForm[captcha]": "",
- "adcopy_challenge": captcha_challenge,
- "adcopy_response": captcha_response
- })
-
- if "The verification code is incorrect" in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- else:
- self.parseError("Download link")
-
- def getCaptcha(self):
- m = re.search(self.ADSCAPTCHA_SRC_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- captcha = AdsCaptcha(self)
- else:
- m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- captcha = ReCaptcha(self)
- else:
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- captcha = SolveMedia(self)
- else:
- self.parseError("Captcha")
-
- return captcha, captcha_key
-
- def checkFree(self):
- m = re.search(self.PREMIUM_ONLY_ERROR_PATTERN, self.html)
- if m:
- self.fail("Premium account needed for download")
- else:
- m = re.search(self.WAIT_PATTERN, self.html)
-
- if m:
- wait_time = int(m.group(1)) * {"hour": 60, "min": 1}[m.group(2)]
- else:
- m = re.search(self.DOWNLOAD_LIMIT_ERROR_PATTERN, self.html)
- if m is None:
- return
- elif m.group(1) == "daily":
- self.logWarning("You have reached your daily downloads limit for today")
- wait_time = secondsToMidnight(gmt=2)
- else:
- wait_time = 1 * 60 * 60
-
- self.logDebug("Waiting %d minutes" % wait_time / 60)
- self.wait(wait_time, True)
- self.retry()
-
- def getJsonResponse(self, url):
- response = self.load(url, decode=True)
- if not response.startswith('{'):
- self.retry()
- self.logDebug(url, response)
- return json_loads(response)
-
-
-getInfo = create_getInfo(RapidgatorNet)
diff --git a/module/plugins/hoster/RapidshareCom.py b/module/plugins/hoster/RapidshareCom.py
deleted file mode 100644
index 19d6cf772..000000000
--- a/module/plugins/hoster/RapidshareCom.py
+++ /dev/null
@@ -1,223 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-
-
-def getInfo(urls):
- ids = ""
- names = ""
-
- p = re.compile(RapidshareCom.__pattern__)
-
- for url in urls:
- r = p.search(url)
- if r.group("name"):
- ids += "," + r.group("id")
- names += "," + r.group("name")
- elif r.group("name_new"):
- ids += "," + r.group("id_new")
- names += "," + r.group("name_new")
-
- url = "http://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=checkfiles&files=%s&filenames=%s" % (ids[1:], names[1:])
-
- api = getURL(url)
- result = []
- i = 0
- for res in api.split():
- tmp = res.split(",")
- if tmp[4] in ("0", "4", "5"):
- status = 1
- elif tmp[4] == "1":
- status = 2
- else:
- status = 3
-
- result.append((tmp[1], tmp[2], status, urls[i]))
- i += 1
-
- yield result
-
-
-class RapidshareCom(Hoster):
- __name__ = "RapidshareCom"
- __type__ = "hoster"
- __version__ = "1.39"
-
- __pattern__ = r'https?://(?:www\.)?rapidshare.com/(?:files/(?P<id>\d*?)/(?P<name>[^?]+)|#!download\|(?:\w+)\|(?P<id_new>\d+)\|(?P<name_new>[^|]+))'
- __config__ = [("server",
- "Cogent;Deutsche Telekom;Level(3);Level(3) #2;GlobalCrossing;Level(3) #3;Teleglobe;GlobalCrossing #2;TeliaSonera #2;Teleglobe #2;TeliaSonera #3;TeliaSonera",
- "Preferred Server", "None")]
-
- __description__ = """Rapidshare.com hoster plugin"""
- __author_name__ = ("spoob", "RaNaN", "mkaay")
- __author_mail__ = ("spoob@pyload.org", "ranan@pyload.org", "mkaay@mkaay.de")
-
-
- def setup(self):
- self.no_download = True
- self.api_data = None
- self.offset = 0
- self.dl_dict = {}
-
- self.id = None
- self.name = None
-
- self.chunkLimit = -1 if self.premium else 1
- self.multiDL = self.resumeDownload = self.premium
-
- def process(self, pyfile):
- self.url = pyfile.url
- self.prepare()
-
- def prepare(self):
- m = re.match(self.__pattern__, self.url)
-
- if m.group("name"):
- self.id = m.group("id")
- self.name = m.group("name")
- else:
- self.id = m.group("id_new")
- self.name = m.group("name_new")
-
- self.download_api_data()
- if self.api_data['status'] == "1":
- self.pyfile.name = self.get_file_name()
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- elif self.api_data['status'] == "2":
- self.logInfo(_("Rapidshare: Traffic Share (direct download)"))
- self.pyfile.name = self.get_file_name()
-
- self.download(self.pyfile.url, get={"directstart": 1})
-
- elif self.api_data['status'] in ("0", "4", "5"):
- self.offline()
- elif self.api_data['status'] == "3":
- self.tempOffline()
- else:
- self.fail("Unknown response code.")
-
- def handleFree(self):
- while self.no_download:
- self.dl_dict = self.freeWait()
-
- #tmp = "#!download|%(server)s|%(id)s|%(name)s|%(size)s"
- download = "http://%(host)s/cgi-bin/rsapi.cgi?sub=download&editparentlocation=0&bin=1&fileid=%(id)s&filename=%(name)s&dlauth=%(auth)s" % self.dl_dict
-
- self.logDebug("RS API Request: %s" % download)
- self.download(download, ref=False)
-
- check = self.checkDownload({"ip": "You need RapidPro to download more files from your IP address",
- "auth": "Download auth invalid"})
- if check == "ip":
- self.setWait(60)
- self.logInfo(_("Already downloading from this ip address, waiting 60 seconds"))
- self.wait()
- self.handleFree()
- elif check == "auth":
- self.logInfo(_("Invalid Auth Code, download will be restarted"))
- self.offset += 5
- self.handleFree()
-
- def handlePremium(self):
- info = self.account.getAccountInfo(self.user, True)
- self.logDebug("%s: Use Premium Account" % self.__name__)
- url = self.api_data['mirror']
- self.download(url, get={"directstart": 1})
-
- def download_api_data(self, force=False):
- """
- http://images.rapidshare.com/apidoc.txt
- """
- if self.api_data and not force:
- return
- api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
- api_param_file = {"sub": "checkfiles", "incmd5": "1", "files": self.id, "filenames": self.name}
- src = self.load(api_url_base, cookies=False, get=api_param_file).strip()
- self.logDebug("RS INFO API: %s" % src)
- if src.startswith("ERROR"):
- return
- fields = src.split(",")
-
- # status codes:
- # 0=File not found
- # 1=File OK (Anonymous downloading)
- # 3=Server down
- # 4=File marked as illegal
- # 5=Anonymous file locked, because it has more than 10 downloads already
- # 50+n=File OK (TrafficShare direct download type "n" without any logging.)
- # 100+n=File OK (TrafficShare direct download type "n" with logging.
- # Read our privacy policy to see what is logged.)
-
- self.api_data = {"fileid": fields[0], "filename": fields[1], "size": int(fields[2]), "serverid": fields[3],
- "status": fields[4], "shorthost": fields[5], "checksum": fields[6].strip().lower()}
-
- if int(self.api_data['status']) > 100:
- self.api_data['status'] = str(int(self.api_data['status']) - 100)
- elif int(self.api_data['status']) > 50:
- self.api_data['status'] = str(int(self.api_data['status']) - 50)
-
- self.api_data['mirror'] = "http://rs%(serverid)s%(shorthost)s.rapidshare.com/files/%(fileid)s/%(filename)s" % self.api_data
-
- def freeWait(self):
- """downloads html with the important information
- """
- self.no_download = True
-
- id = self.id
- name = self.name
-
- prepare = "https://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=download&fileid=%(id)s&filename=%(name)s&try=1&cbf=RSAPIDispatcher&cbid=1" % {
- "name": name, "id": id}
-
- self.logDebug("RS API Request: %s" % prepare)
- result = self.load(prepare, ref=False)
- self.logDebug("RS API Result: %s" % result)
-
- between_wait = re.search("You need to wait (\d+) seconds", result)
-
- if "You need RapidPro to download more files from your IP address" in result:
- self.setWait(60)
- self.logInfo(_("Already downloading from this ip address, waiting 60 seconds"))
- self.wait()
- elif ("Too many users downloading from this server right now" in result or
- "All free download slots are full" in result):
- self.setWait(120)
- self.logInfo(_("RapidShareCom: No free slots"))
- self.wait()
- elif "This file is too big to download it for free" in result:
- self.fail(_("You need a premium account for this file"))
- elif "Filename invalid." in result:
- self.fail(_("Filename reported invalid"))
- elif between_wait:
- self.setWait(int(between_wait.group(1)))
- self.wantReconnect = True
- self.wait()
- else:
- self.no_download = False
-
- tmp, info = result.split(":")
- data = info.split(",")
-
- dl_dict = {"id": id,
- "name": name,
- "host": data[0],
- "auth": data[1],
- "server": self.api_data['serverid'],
- "size": self.api_data['size']}
- self.setWait(int(data[2]) + 2 + self.offset)
- self.wait()
-
- return dl_dict
-
- def get_file_name(self):
- if self.api_data['filename']:
- return self.api_data['filename']
- return self.url.split("/")[-1]
diff --git a/module/plugins/hoster/RarefileNet.py b/module/plugins/hoster/RarefileNet.py
deleted file mode 100644
index 51df5c882..000000000
--- a/module/plugins/hoster/RarefileNet.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-from module.utils import html_unescape
-
-
-class RarefileNet(XFileSharingPro):
- __name__ = "RarefileNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?rarefile.net/\w{12}'
-
- __description__ = """Rarefile.net hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "rarefile.net"
-
- FILE_NAME_PATTERN = r'<td><font color="red">(?P<N>.*?)</font></td>'
- FILE_SIZE_PATTERN = r'<td>Size : (?P<S>.+?)&nbsp;'
- LINK_PATTERN = r'<a href="(?P<link>[^"]+)">(?P=link)</a>'
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
-
- def handleCaptcha(self, inputs):
- captcha_div = re.search(r'<b>Enter code.*?<div.*?>(.*?)</div>', self.html, re.S).group(1)
- self.logDebug(captcha_div)
- numerals = re.findall('<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
- inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
- self.logDebug("CAPTCHA", inputs['code'], numerals)
- return 3
-
-
-getInfo = create_getInfo(RarefileNet)
diff --git a/module/plugins/hoster/RealdebridCom.py b/module/plugins/hoster/RealdebridCom.py
deleted file mode 100644
index de7540628..000000000
--- a/module/plugins/hoster/RealdebridCom.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from random import randrange
-from urllib import quote, unquote
-from time import time
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-from module.utils import parseFileSize
-
-
-class RealdebridCom(Hoster):
- __name__ = "RealdebridCom"
- __type__ = "hoster"
- __version__ = "0.53"
-
- __pattern__ = r'https?://(?:[^/]*\.)?real-debrid\..*'
-
- __description__ = """Real-Debrid.com hoster plugin"""
- __author_name__ = "Devirex Hazzard"
- __author_mail__ = "naibaf_11@yahoo.de"
-
-
- def getFilename(self, url):
- try:
- name = unquote(url.rsplit("/", 1)[1])
- except IndexError:
- name = "Unknown_Filename..."
- if not name or name.endswith(".."): # incomplete filename, append random stuff
- name += "%s.tmp" % randrange(100, 999)
- return name
-
- def setup(self):
- self.chunkLimit = 3
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Real-debrid")
- self.fail("No Real-debrid account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- password = self.getPassword().splitlines()
- if not password:
- password = ""
- else:
- password = password[0]
-
- url = "https://real-debrid.com/ajax/unrestrict.php?lang=en&link=%s&password=%s&time=%s" % (
- quote(pyfile.url, ""), password, int(time() * 1000))
- page = self.load(url)
- data = json_loads(page)
-
- self.logDebug("Returned Data: %s" % data)
-
- if data['error'] != 0:
- if data['message'] == "Your file is unavailable on the hoster.":
- self.offline()
- else:
- self.logWarning(data['message'])
- self.tempOffline()
- else:
- if pyfile.name is not None and pyfile.name.endswith('.tmp') and data['file_name']:
- pyfile.name = data['file_name']
- pyfile.size = parseFileSize(data['file_size'])
- new_url = data['generated_links'][0][-1]
-
- if self.getConfig("https"):
- new_url = new_url.replace("http://", "https://")
- else:
- new_url = new_url.replace("https://", "http://")
-
- if new_url != pyfile.url:
- self.logDebug("New URL: %s" % new_url)
-
- if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
- #only use when name wasnt already set
- pyfile.name = self.getFilename(new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload(
- {"error": "<title>An error occured while processing your request</title>"})
-
- if check == "error":
- #usual this download can safely be retried
- self.retry(wait_time=60, reason="An error occured while generating link.")
diff --git a/module/plugins/hoster/RedtubeCom.py b/module/plugins/hoster/RedtubeCom.py
deleted file mode 100644
index bdb948d6d..000000000
--- a/module/plugins/hoster/RedtubeCom.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-from module.unescape import unescape
-
-
-class RedtubeCom(Hoster):
- __name__ = "RedtubeCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?redtube\.com/\d+'
-
- __description__ = """Redtube.com hoster plugin"""
- __author_name__ = "jeix"
- __author_mail__ = "jeix@hasnomail.de"
-
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- file_url = unescape(re.search(r'hashlink=(http.*?)"', self.html).group(1))
-
- return file_url
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search('<title>(.*?)- RedTube - Free Porn Videos</title>', self.html).group(1).strip() + ".flv"
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r'This video has been removed.', self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/RehostTo.py b/module/plugins/hoster/RehostTo.py
deleted file mode 100644
index 98fb10d94..000000000
--- a/module/plugins/hoster/RehostTo.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from urllib import quote, unquote
-
-from module.plugins.Hoster import Hoster
-
-
-class RehostTo(Hoster):
- __name__ = "RehostTo"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'https?://.*rehost.to\..*'
-
- __description__ = """Rehost.com hoster plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- def getFilename(self, url):
- return unquote(url.rsplit("/", 1)[1])
-
- def setup(self):
- self.chunkLimit = 1
- self.resumeDownload = True
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "rehost.to")
- self.fail("No rehost.to account provided")
-
- data = self.account.getAccountInfo(self.user)
- long_ses = data['long_ses']
-
- self.logDebug("Rehost.to: Old URL: %s" % pyfile.url)
- new_url = "http://rehost.to/process_download.php?user=cookie&pass=%s&dl=%s" % (long_ses, quote(pyfile.url, ""))
-
- #raise timeout to 2min
- self.req.setOption("timeout", 120)
-
- self.download(new_url, disposition=True)
diff --git a/module/plugins/hoster/RemixshareCom.py b/module/plugins/hoster/RemixshareCom.py
deleted file mode 100644
index ea396495e..000000000
--- a/module/plugins/hoster/RemixshareCom.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://remixshare.com/download/p946u
-#
-# Note:
-# The remixshare.com website is very very slow, so
-# if your download not starts because of pycurl timeouts:
-# Adjust timeouts in /usr/share/pyload/module/network/HTTPRequest.py
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RemixshareCom(SimpleHoster):
- __name__ = "RemixshareCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://remixshare\.com/(download|dl)/\w+'
-
- __description__ = """Remixshare.com hoster plugin"""
- __author_name__ = ("zapp-brannigan", "Walter Purcaro")
- __author_mail__ = ("fuerst.reinje@web.de", "vuolter@gmail.com")
-
- FILE_INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>\w+)\)<'
- OFFLINE_PATTERN = r'<h1>Ooops!<'
-
- LINK_PATTERN = r'(http://remixshare\.com/downloadfinal/.+?)"'
- TOKEN_PATTERN = r'var acc = (\d+)'
- WAIT_PATTERN = r'var XYZ = r"(\d+)"'
-
-
- def setup(self):
- self.multiDL = True
- self.chunkLimit = 1
-
- def handleFree(self):
- b = re.search(self.LINK_PATTERN, self.html)
- if not b:
- self.parseError("Cannot parse download url")
- c = re.search(self.TOKEN_PATTERN, self.html)
- if not c:
- self.parseError("Cannot parse file token")
- dl_url = b.group(1) + c.group(1)
-
- #Check if we have to wait
- seconds = re.search(self.WAIT_PATTERN, self.html)
- if seconds:
- self.logDebug("Wait " + seconds.group(1))
- self.wait(seconds.group(1))
-
- # Finally start downloading...
- self.logDebug("Download URL = r" + dl_url)
- self.download(dl_url, disposition=True)
-
-
-getInfo = create_getInfo(RemixshareCom)
diff --git a/module/plugins/hoster/RgHostNet.py b/module/plugins/hoster/RgHostNet.py
deleted file mode 100644
index dccc6e557..000000000
--- a/module/plugins/hoster/RgHostNet.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class RgHostNet(SimpleHoster):
- __name__ = "RgHostNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?rghost\.net/\d+(?:r=\d+)?'
-
- __description__ = """RgHost.net hoster plugin"""
- __author_name__ = "z00nx"
- __author_mail__ = "z00nx0@gmail.com"
-
- FILE_INFO_PATTERN = r'<h1>\s+(<a[^>]+>)?(?P<N>[^<]+)(</a>)?\s+<small[^>]+>\s+\((?P<S>[^)]+)\)\s+</small>\s+</h1>'
- OFFLINE_PATTERN = r'File is deleted|this page is not found'
- LINK_PATTERN = r'''<a\s+href="([^"]+)"\s+class="btn\s+large\s+download"[^>]+>Download</a>'''
-
-
- def handleFree(self):
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError("Unable to detect the direct link")
- download_link = m.group(1)
- self.download(download_link, disposition=True)
-
-
-getInfo = create_getInfo(RgHostNet)
diff --git a/module/plugins/hoster/RyushareCom.py b/module/plugins/hoster/RyushareCom.py
deleted file mode 100644
index a24090cde..000000000
--- a/module/plugins/hoster/RyushareCom.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://ryushare.com/cl0jy8ric2js/random.bin
-
-import re
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-from module.plugins.internal.CaptchaService import SolveMedia
-
-
-class RyushareCom(XFileSharingPro):
- __name__ = "RyushareCom"
- __type__ = "hoster"
- __version__ = "0.16"
-
- __pattern__ = r'http://(?:www\.)?ryushare\.com/\w+'
-
- __description__ = """Ryushare.com hoster plugin"""
- __author_name__ = ("zoidberg", "stickell", "quareevo")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it", "quareevo@arcor.de")
-
- HOSTER_NAME = "ryushare.com"
-
- FILE_SIZE_PATTERN = r'You have requested <font color="red">[^<]+</font> \((?P<S>[\d\.]+) (?P<U>\w+)'
-
- WAIT_PATTERN = r'You have to wait ((?P<hour>\d+) hour[s]?, )?((?P<min>\d+) minute[s], )?(?P<sec>\d+) second[s]'
- LINK_PATTERN = r'<a href="([^"]+)">Click here to download<'
- SOLVEMEDIA_PATTERN = r'http:\/\/api\.solvemedia\.com\/papi\/challenge\.script\?k=(.*?)"'
-
-
- def getDownloadLink(self):
- retry = False
- self.html = self.load(self.pyfile.url)
- action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
- if "method_premium" in inputs:
- del inputs['method_premium']
-
- self.html = self.load(self.pyfile.url, post=inputs)
- action, inputs = self.parseHtmlForm('F1')
-
- self.setWait(65)
- # Wait 1 hour
- if "You have reached the download-limit" in self.html:
- self.setWait(1 * 60 * 60, True)
- retry = True
-
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait = m.groupdict(0)
- waittime = int(wait['hour']) * 60 * 60 + int(wait['min']) * 60 + int(wait['sec'])
- self.setWait(waittime, True)
- retry = True
-
- self.wait()
- if retry:
- self.retry()
-
- for _ in xrange(5):
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m is None:
- self.parseError("Error parsing captcha")
-
- captchaKey = m.group(1)
- captcha = SolveMedia(self)
- challenge, response = captcha.challenge(captchaKey)
-
- inputs['adcopy_challenge'] = challenge
- inputs['adcopy_response'] = response
-
- self.html = self.load(self.pyfile.url, post=inputs)
- if "WRONG CAPTCHA" in self.html:
- self.invalidCaptcha()
- self.logInfo("Invalid Captcha")
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("You have entered 5 invalid captcha codes")
-
- if "Click here to download" in self.html:
- return re.search(r'<a href="([^"]+)">Click here to download</a>', self.html).group(1)
-
-
-getInfo = create_getInfo(RyushareCom)
diff --git a/module/plugins/hoster/SecureUploadEu.py b/module/plugins/hoster/SecureUploadEu.py
deleted file mode 100644
index 3691be7da..000000000
--- a/module/plugins/hoster/SecureUploadEu.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class SecureUploadEu(XFileSharingPro):
- __name__ = "SecureUploadEu"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?secureupload\.eu/(\w){12}(/\w+)'
-
- __description__ = """SecureUpload.eu hoster plugin"""
- __author_name__ = "z00nx"
- __author_mail__ = "z00nx0@gmail.com"
-
- HOSTER_NAME = "secureupload.eu"
-
- FILE_INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
- OFFLINE_PATTERN = r'The file was removed|File Not Found'
-
-
-getInfo = create_getInfo(SecureUploadEu)
diff --git a/module/plugins/hoster/SendmywayCom.py b/module/plugins/hoster/SendmywayCom.py
deleted file mode 100644
index f5e9e9ca6..000000000
--- a/module/plugins/hoster/SendmywayCom.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class SendmywayCom(XFileSharingPro):
- __name__ = "SendmywayCom"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?sendmyway.com/\w{12}'
-
- __description__ = """SendMyWay hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "sendmyway.com"
-
- FILE_NAME_PATTERN = r'<p class="file-name" ><.*?>\s*(?P<N>.+)'
- FILE_SIZE_PATTERN = r'<small>\((?P<S>\d+) bytes\)</small>'
-
-
-getInfo = create_getInfo(SendmywayCom)
diff --git a/module/plugins/hoster/SendspaceCom.py b/module/plugins/hoster/SendspaceCom.py
deleted file mode 100644
index 1dac231eb..000000000
--- a/module/plugins/hoster/SendspaceCom.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SendspaceCom(SimpleHoster):
- __name__ = "SendspaceCom"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?sendspace.com/file/.*'
-
- __description__ = """Sendspace.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
- FILE_SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[0-9.]+)(?P<U>[kKMG])i?B\s*</div>'
- OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
-
- LINK_PATTERN = r'<a id="download_button" href="([^"]+)"'
- CAPTCHA_PATTERN = r'<td><img src="(/captchas/captcha.php?captcha=([^"]+))"></td>'
- USER_CAPTCHA_PATTERN = r'<td><img src="/captchas/captcha.php?user=([^"]+))"></td>'
-
-
- def handleFree(self):
- params = {}
- for _ in xrange(3):
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- if 'captcha_hash' in params:
- self.correctCaptcha()
- download_url = m.group(1)
- break
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m:
- if 'captcha_hash' in params:
- self.invalidCaptcha()
- captcha_url1 = "http://www.sendspace.com/" + m.group(1)
- m = re.search(self.USER_CAPTCHA_PATTERN, self.html)
- captcha_url2 = "http://www.sendspace.com/" + m.group(1)
- params = {'captcha_hash': m.group(2),
- 'captcha_submit': 'Verify',
- 'captcha_answer': self.decryptCaptcha(captcha_url1) + " " + self.decryptCaptcha(captcha_url2)}
- else:
- params = {'download': "Regular Download"}
-
- self.logDebug(params)
- self.html = self.load(self.pyfile.url, post=params)
- else:
- self.fail("Download link not found")
-
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
-
-
-create_getInfo(SendspaceCom)
diff --git a/module/plugins/hoster/Share4webCom.py b/module/plugins/hoster/Share4webCom.py
deleted file mode 100644
index e5221baa9..000000000
--- a/module/plugins/hoster/Share4webCom.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.UnibytesCom import UnibytesCom
-from module.plugins.internal.SimpleHoster import create_getInfo
-
-
-class Share4webCom(UnibytesCom):
- __name__ = "Share4webCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?share4web\.com/get/\w+'
-
- __description__ = """Share4web.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- HOSTER_NAME = "share4web.com"
-
-
-getInfo = create_getInfo(UnibytesCom)
diff --git a/module/plugins/hoster/Share76Com.py b/module/plugins/hoster/Share76Com.py
deleted file mode 100644
index 2c5dd877d..000000000
--- a/module/plugins/hoster/Share76Com.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class Share76Com(DeadHoster):
- __name__ = "Share76Com"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?share76.com/\w{12}'
-
- __description__ = """Share76.com hoster plugin"""
- __author_name__ = "me"
- __author_mail__ = None
-
-
-getInfo = create_getInfo(Share76Com)
diff --git a/module/plugins/hoster/ShareFilesCo.py b/module/plugins/hoster/ShareFilesCo.py
deleted file mode 100644
index 54ae7777a..000000000
--- a/module/plugins/hoster/ShareFilesCo.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ShareFilesCo(DeadHoster):
- __name__ = "ShareFilesCo"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?sharefiles\.co/\w{12}'
-
- __description__ = """Sharefiles.co hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
-getInfo = create_getInfo(ShareFilesCo)
diff --git a/module/plugins/hoster/ShareRapidCom.py b/module/plugins/hoster/ShareRapidCom.py
deleted file mode 100644
index 414e92feb..000000000
--- a/module/plugins/hoster/ShareRapidCom.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import HTTPHEADER
-
-from module.network.RequestFactory import getRequest
-from module.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
-
-
-def getInfo(urls):
- h = getRequest()
- h.c.setopt(HTTPHEADER,
- ["Accept: text/html",
- "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"])
- for url in urls:
- html = h.load(url, decode=True)
- file_info = parseFileInfo(ShareRapidCom, url, html)
- yield file_info
-
-
-class ShareRapidCom(SimpleHoster):
- __name__ = "ShareRapidCom"
- __type__ = "hoster"
- __version__ = "0.54"
-
- __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
-
- __description__ = """MegaRapid.cz hoster plugin"""
- __author_name__ = ("MikyWoW", "zoidberg", "stickell", "Walter Purcaro")
- __author_mail__ = ("mikywow@seznam.cz", "zoidberg@mujmail.cz", "l.stickell@yahoo.it", "vuolter@gmail.com")
-
- FILE_NAME_PATTERN = r'<h1[^>]*><span[^>]*>(?:<a[^>]*>)?(?P<N>[^<]+)'
- FILE_SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</strong></td>'
- OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
-
- SH_CHECK_TRAFFIC = True
-
- LINK_PATTERN = r'<a href="([^"]+)" title="Stahnout">([^<]+)</a>'
- ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahování je přístupné pouze přihlášenÜm uÅŸivatelům'
- ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahování zdarma je moÅŸné jen přes náš'
-
-
- def setup(self):
- self.chunkLimit = 1
-
- def handlePremium(self):
- try:
- self.html = self.load(self.pyfile.url, decode=True)
- except BadHeader, e:
- self.account.relogin(self.user)
- self.retry(max_tries=3, reason=str(e))
-
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- link = m.group(1)
- self.logDebug("Premium link: %s" % link)
- self.download(link, disposition=True)
- else:
- if re.search(self.ERR_LOGIN_PATTERN, self.html):
- self.relogin(self.user)
- self.retry(max_tries=3, reason="User login failed")
- elif re.search(self.ERR_CREDIT_PATTERN, self.html):
- self.fail("Not enough credit left")
- else:
- self.fail("Download link not found")
diff --git a/module/plugins/hoster/SharebeesCom.py b/module/plugins/hoster/SharebeesCom.py
deleted file mode 100644
index d5b4a3bbc..000000000
--- a/module/plugins/hoster/SharebeesCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SharebeesCom(DeadHoster):
- __name__ = "SharebeesCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'http://(?:www\.)?sharebees.com/\w{12}'
-
- __description__ = """ShareBees hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(SharebeesCom)
diff --git a/module/plugins/hoster/ShareonlineBiz.py b/module/plugins/hoster/ShareonlineBiz.py
deleted file mode 100644
index feaa95603..000000000
--- a/module/plugins/hoster/ShareonlineBiz.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import time
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-from module.plugins.internal.CaptchaService import ReCaptcha
-
-
-def getInfo(urls):
- api_url_base = "http://api.share-online.biz/linkcheck.php"
-
- urls = [url.replace("https://", "http://") for url in urls]
-
- for chunk in chunks(urls, 90):
- api_param_file = {"links": "\n".join(x.replace("http://www.share-online.biz/dl/", "").rstrip("/") for x in
- chunk)} # api only supports old style links
- src = getURL(api_url_base, post=api_param_file, decode=True)
- result = []
- for i, res in enumerate(src.split("\n")):
- if not res:
- continue
- fields = res.split(";")
-
- if fields[1] == "OK":
- status = 2
- elif fields[1] in ("DELETED", "NOT FOUND"):
- status = 1
- else:
- status = 3
-
- result.append((fields[2], int(fields[3]), status, chunk[i]))
- yield result
-
-
-class ShareonlineBiz(Hoster):
- __name__ = "ShareonlineBiz"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download.php\?id=|dl/)(?P<ID>\w+)'
-
- __description__ = """Shareonline.biz hoster plugin"""
- __author_name__ = ("spoob", "mkaay", "zoidberg", "Walter Purcaro")
- __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de", "zoidberg@mujmail.cz", "vuolter@gmail.com")
-
- ERROR_INFO_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
-
-
- def setup(self):
- # range request not working?
- # api supports resume, only one chunk
- # website isn't supporting resuming in first place
- self.file_id = re.match(self.__pattern__, self.pyfile.url).group("ID")
- self.pyfile.url = "http://www.share-online.biz/dl/" + self.file_id
-
- self.resumeDownload = self.premium
- self.multiDL = False
- #self.chunkLimit = 1
-
- self.check_data = None
-
- def process(self, pyfile):
- if self.premium:
- self.handlePremium()
- #web-download fallback removed - didn't work anyway
- else:
- self.handleFree()
-
- # check = self.checkDownload({"failure": re.compile(self.ERROR_INFO_PATTERN)})
- # if check == "failure":
- # try:
- # self.retry(reason=self.lastCheck.group(1).decode("utf8"))
- # except:
- # self.retry(reason="Unknown error")
-
- if self.api_data:
- self.check_data = {"size": int(self.api_data['size']), "md5": self.api_data['md5']}
-
- def loadAPIData(self):
- api_url_base = "http://api.share-online.biz/linkcheck.php?md5=1"
- api_param_file = {"links": self.file_id} # api only supports old style links
- src = self.load(api_url_base, cookies=False, post=api_param_file, decode=True)
-
- fields = src.split(";")
- self.api_data = {"fileid": fields[0],
- "status": fields[1]}
- if not self.api_data['status'] == "OK":
- self.offline()
- else:
- self.api_data['filename'] = fields[2]
- self.api_data['size'] = fields[3] # in bytes
- self.api_data['md5'] = fields[4].strip().lower().replace("\n\n", "") # md5
-
- def handleFree(self):
- self.loadAPIData()
- self.pyfile.name = self.api_data['filename']
- self.pyfile.size = int(self.api_data['size'])
-
- self.html = self.load(self.pyfile.url, cookies=True) # refer, stuff
- self.setWait(3)
- self.wait()
-
- self.html = self.load("%s/free/" % self.pyfile.url, post={"dl_free": "1", "choice": "free"}, decode=True)
- self.checkErrors()
-
- m = re.search(r'var wait=(\d+);', self.html)
-
- recaptcha = ReCaptcha(self)
- for _ in xrange(5):
- challenge, response = recaptcha.challenge("6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX")
- self.setWait(int(m.group(1)) if m else 30)
- response = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time() * 1000)), post={
- 'dl_free': '1',
- 'recaptcha_challenge_field': challenge,
- 'recaptcha_response_field': response})
-
- if not response == '0':
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
- else:
- self.invalidCaptcha()
- self.fail("No valid captcha solution received")
-
- download_url = response.decode("base64")
- self.logDebug(download_url)
- if not download_url.startswith("http://"):
- self.parseError("download url")
-
- self.wait()
- self.download(download_url)
- # check download
- check = self.checkDownload({
- "cookie": re.compile(r'<div id="dl_failure"'),
- "fail": re.compile(r"<title>Share-Online")
- })
- if check == "cookie":
- self.invalidCaptcha()
- self.retry(5, 60, "Cookie failure")
- elif check == "fail":
- self.invalidCaptcha()
- self.retry(5, 5 * 60, "Download failed")
- else:
- self.correctCaptcha()
-
- def handlePremium(self): #: should be working better loading (account) api internally
- self.account.getAccountInfo(self.user, True)
- src = self.load("http://api.share-online.biz/account.php",
- {"username": self.user, "password": self.account.accounts[self.user]['password'],
- "act": "download", "lid": self.file_id})
-
- self.api_data = dlinfo = {}
- for line in src.splitlines():
- key, value = line.split(": ")
- dlinfo[key.lower()] = value
-
- self.logDebug(dlinfo)
- if not dlinfo['status'] == "online":
- self.offline()
- else:
- self.pyfile.name = dlinfo['name']
- self.pyfile.size = int(dlinfo['size'])
-
- dlLink = dlinfo['url']
- if dlLink == "server_under_maintenance":
- self.tempOffline()
- else:
- self.multiDL = True
- self.download(dlLink)
-
- def checkErrors(self):
- m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
- if m is None:
- return
-
- err = m.group(1)
- m = re.search(self.ERROR_INFO_PATTERN, self.html)
- msg = m.group(1) if m else ""
- self.logError(err, msg or "Unknown error occurred")
-
- if err == "invalid":
- self.fail(msg or "File not available")
- elif err in ("freelimit", "size", "proxy"):
- self.fail(msg or "Premium account needed")
- else:
- if err in 'server':
- self.setWait(600, False)
- elif err in 'expired':
- self.setWait(30, False)
- else:
- self.setWait(300, True)
-
- self.wait()
- self.retry(max_tries=25, reason=msg)
diff --git a/module/plugins/hoster/ShareplaceCom.py b/module/plugins/hoster/ShareplaceCom.py
deleted file mode 100644
index 0d236fe30..000000000
--- a/module/plugins/hoster/ShareplaceCom.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.Hoster import Hoster
-
-
-class ShareplaceCom(Hoster):
- __name__ = "ShareplaceCom"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'(http://)?(?:www\.)?shareplace\.(com|org)/\?[a-zA-Z0-9]+'
-
- __description__ = """Shareplace.com hoster plugin"""
- __author_name__ = "ACCakut"
- __author_mail__ = None
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.prepare()
- self.download(self.get_file_url())
-
- def prepare(self):
- if not self.file_exists():
- self.offline()
-
- self.pyfile.name = self.get_file_name()
-
- wait_time = self.get_waiting_time()
- self.setWait(wait_time)
- self.logDebug("%s: Waiting %d seconds." % (self.__name__, wait_time))
- self.wait()
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- #var zzipitime = 15;
- m = re.search(r'var zzipitime = (\d+);', self.html)
- if m:
- sec = int(m.group(1))
- else:
- sec = 0
-
- return sec
-
- def download_html(self):
- url = re.sub("shareplace.com\/\?", "shareplace.com//index1.php/?a=", self.pyfile.url)
- self.html = self.load(url, decode=True)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- url = re.search(r"var beer = '(.*?)';", self.html)
- if url:
- url = url.group(1)
- url = unquote(
- url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
- "teletubbies", ""))
- self.logDebug("URL: %s" % url)
- return url
- else:
- self.fail("absolute filepath could not be found. offline? ")
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search("<title>\s*(.*?)\s*</title>", self.html).group(1)
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r"HTTP Status 404", self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/ShragleCom.py b/module/plugins/hoster/ShragleCom.py
deleted file mode 100644
index ba3356d66..000000000
--- a/module/plugins/hoster/ShragleCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class ShragleCom(DeadHoster):
- __name__ = "ShragleCom"
- __type__ = "hoster"
- __version__ = "0.22"
-
- __pattern__ = r'http://(?:www\.)?(cloudnator|shragle).com/files/(?P<ID>.*?)/'
-
- __description__ = """Cloudnator.com (Shragle.com) hoster plugin"""
- __author_name__ = ("RaNaN", "zoidberg")
- __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
-
-
-getInfo = create_getInfo(ShragleCom)
diff --git a/module/plugins/hoster/SimplyPremiumCom.py b/module/plugins/hoster/SimplyPremiumCom.py
deleted file mode 100644
index 5db9f5daa..000000000
--- a/module/plugins/hoster/SimplyPremiumCom.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from datetime import datetime, timedelta
-
-from module.plugins.Hoster import Hoster
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-
-
-class SimplyPremiumCom(Hoster):
- __name__ = "SimplyPremiumCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://.*(simply-premium)\.com'
-
- __description__ = """Simply-Premium.com hoster plugin"""
- __author_name__ = "EvolutionClip"
- __author_mail__ = "evolutionclip@live.de"
-
-
- def setup(self):
- self.chunkLimit = 16
- self.resumeDownload = False
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Simply-Premium.com")
- self.fail("No Simply-Premium.com account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- for i in xrange(5):
- page = self.load('http://www.simply-premium.com/premium.php?info&link=' + pyfile.url)
- self.logDebug("JSON data: " + page)
- if page != '':
- break
- else:
- self.logInfo("Unable to get API data, waiting 1 minute and retry")
- self.retry(5, 60, "Unable to get API data")
-
- if '<valid>0</valid>' in page or (
- "You are not allowed to download from this host" in page and self.premium):
- self.account.relogin(self.user)
- self.retry()
- elif "NOTFOUND" in page:
- self.offline()
- elif "downloadlimit" in page:
- self.logWarning("Reached maximum connctions")
- self.retry(5, 60, "Reached maximum connctions")
- elif "trafficlimit" in page:
- self.logWarning("Reached daily limit for this host")
- self.retry(1, secondsToMidnight(gmt=2), "Daily limit for this host reached")
- elif "hostererror" in page:
- self.logWarning("Hoster temporarily unavailable, waiting 1 minute and retry")
- self.retry(5, 60, "Hoster is temporarily unavailable")
- #page = json_loads(page)
- #new_url = page.keys()[0]
- #self.api_data = page[new_url]
-
- try:
- self.pyfile.name = re.search(r'<name>([^<]+)</name>', page).group(1)
- except AttributeError:
- self.pyfile.name = ""
-
- try:
- self.pyfile.size = re.search(r'<size>(\d+)</size>', page).group(1)
- except AttributeError:
- self.pyfile.size = 0
-
- try:
- new_url = re.search(r'<download>([^<]+)</download>', page).group(1)
- except AttributeError:
- new_url = 'http://www.simply-premium.com/premium.php?link=' + pyfile.url
-
- if new_url != pyfile.url:
- self.logDebug("New URL: " + new_url)
-
- self.download(new_url, disposition=True)
diff --git a/module/plugins/hoster/SimplydebridCom.py b/module/plugins/hoster/SimplydebridCom.py
deleted file mode 100644
index 78f6c0a34..000000000
--- a/module/plugins/hoster/SimplydebridCom.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class SimplydebridCom(Hoster):
- __name__ = "SimplydebridCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd.php/*'
-
- __description__ = """Simply-debrid.com hoster plugin"""
- __author_name__ = "Kagenoshin"
- __author_mail__ = "kagenoshin@gmx.ch"
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "simply-debrid.com")
- self.fail("No simply-debrid.com account provided")
-
- self.logDebug("Old URL: %s" % pyfile.url)
-
- #fix the links for simply-debrid.com!
- new_url = pyfile.url
- new_url = new_url.replace("clz.to", "cloudzer.net/file")
- new_url = new_url.replace("http://share-online", "http://www.share-online")
- new_url = new_url.replace("ul.to", "uploaded.net/file")
- new_url = new_url.replace("uploaded.com", "uploaded.net")
- new_url = new_url.replace("filerio.com", "filerio.in")
- new_url = new_url.replace("lumfile.com", "lumfile.se")
- if('fileparadox' in new_url):
- new_url = new_url.replace("http://", "https://")
-
- if re.match(self.__pattern__, new_url):
- new_url = new_url
-
- self.logDebug("New URL: %s" % new_url)
-
- if not re.match(self.__pattern__, new_url):
- page = self.load('http://simply-debrid.com/api.php', get={'dl': new_url}) # +'&u='+self.user+'&p='+self.account.getAccountData(self.user)['password'])
- if 'tiger Link' in page or 'Invalid Link' in page or ('API' in page and 'ERROR' in page):
- self.fail('Unable to unrestrict link')
- new_url = page
-
- self.setWait(5)
- self.wait()
- self.logDebug("Unrestricted URL: " + new_url)
-
- self.download(new_url, disposition=True)
-
- check = self.checkDownload({"bad1": "No address associated with hostname", "bad2": "<html"})
-
- if check == "bad1" or check == "bad2":
- self.retry(24, 3 * 60, "Bad file downloaded")
diff --git a/module/plugins/hoster/SockshareCom.py b/module/plugins/hoster/SockshareCom.py
deleted file mode 100644
index 90f092473..000000000
--- a/module/plugins/hoster/SockshareCom.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from os import rename
-
-from module.plugins.hoster.UnrestrictLi import secondsToMidnight
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SockshareCom(SimpleHoster):
- __name__ = "SockshareCom"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
-
- __description__ = """Sockshare.com hoster plugin"""
- __author_name__ = ("jeix", "stickell", "Walter Purcaro")
- __author_mail__ = ("jeix@hasnomail.de", "l.stickell@yahoo.it", "vuolter@gmail.com")
-
- FILE_INFO_PATTERN = r'site-content">\s*<h1>(?P<N>.+)<strong>\( (?P<S>[^)]+) \)</strong></h1>'
- OFFLINE_PATTERN = r'>This file doesn\'t exist, or has been removed.<'
- TEMP_OFFLINE_PATTERN = r'(>This content server has been temporarily disabled for upgrades|Try again soon\\. You can still download it below\\.<)'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.sockshare.com/file/\g<ID>')]
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = True
- self.chunkLimit = -1
-
- def handleFree(self):
- name = self.pyfile.name
- link = self._getLink()
- self.logDebug("Direct link: " + link)
- self.download(link, disposition=True)
- self.processName(name)
-
- def _getLink(self):
- hash_data = re.search(r'<input type="hidden" value="([a-z0-9]+)" name="hash">', self.html)
- if not hash_data:
- self.parseError("Unable to detect hash")
-
- post_data = {"hash": hash_data.group(1), "confirm": "Continue+as+Free+User"}
- self.html = self.load(self.pyfile.url, post=post_data)
- if ">You have exceeded the daily stream limit for your country\\. You can wait until tomorrow" in self.html:
- self.logWarning("You have exceeded your daily stream limit for today")
- self.wait(secondsToMidnight(gmt=2), True)
- elif re.search(self.TEMP_OFFLINE_PATTERN, self.html):
- self.retry(wait_time=2 * 60 * 60, reason="Server temporarily offline") # 2 hours wait
-
- patterns = (r'(/get_file\.php\?id=[A-Z0-9]+&key=[a-zA-Z0-9=]+&original=1)',
- r'(/get_file\.php\?download=[A-Z0-9]+&key=[a-z0-9]+)',
- r'(/get_file\.php\?download=[A-Z0-9]+&key=[a-z0-9]+&original=1)',
- r'<a href="/gopro\.php">Tired of ads and waiting\? Go Pro!</a>[\t\n\rn ]+</div>[\t\n\rn ]+<a href="(/.*?)"')
- for pattern in patterns:
- link = re.search(pattern, self.html)
- if link:
- break
- else:
- link = re.search(r"playlist: '(/get_file\.php\?stream=[a-zA-Z0-9=]+)'", self.html)
- if link:
- self.html = self.load("http://www.sockshare.com" + link.group(1))
- link = re.search(r'media:content url="(http://.*?)"', self.html)
- if link is None:
- link = re.search(r'\"(http://media\\-b\\d+\\.sockshare\\.com/download/\\d+/.*?)\"', self.html)
- else:
- self.parseError('Unable to detect a download link')
-
- link = link.group(1).replace("&amp;", "&")
- if link.startswith("http://"):
- return link
- else:
- return "http://www.sockshare.com" + link
-
- def processName(self, name_old):
- name = self.pyfile.name
- if name <= name_old:
- return
- name_new = re.sub(r'\.[^.]+$', "", name_old) + name[len(name_old):]
- filename = self.lastDownload
- self.pyfile.name = name_new
- rename(filename, filename.rsplit(name)[0] + name_new)
- self.logInfo("%(name)s renamed to %(newname)s" % {"name": name, "newname": name_new})
-
-
-getInfo = create_getInfo(SockshareCom)
diff --git a/module/plugins/hoster/SoundcloudCom.py b/module/plugins/hoster/SoundcloudCom.py
deleted file mode 100644
index 05fe897d2..000000000
--- a/module/plugins/hoster/SoundcloudCom.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import pycurl
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class SoundcloudCom(Hoster):
- __name__ = "SoundcloudCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'https?://(?:www\.)?soundcloud\.com/(?P<UID>.*?)/(?P<SID>.*)'
-
- __description__ = """SoundCloud.com hoster plugin"""
- __author_name__ = "Peekayy"
- __author_mail__ = "peekayy.dev@gmail.com"
-
-
- def process(self, pyfile):
- # default UserAgent of HTTPRequest fails for this hoster so we use this one
- self.req.http.c.setopt(pycurl.USERAGENT, 'Mozilla/5.0')
- page = self.load(pyfile.url)
- m = re.search(r'<div class="haudio.*?large.*?" data-sc-track="(?P<ID>[0-9]*)"', page)
- songId = clientId = ""
- if m:
- songId = m.group("ID")
- if len(songId) <= 0:
- self.logError("Could not find song id")
- self.offline()
- else:
- m = re.search(r'"clientID":"(?P<CID>.*?)"', page)
- if m:
- clientId = m.group("CID")
-
- if len(clientId) <= 0:
- clientId = "b45b1aa10f1ac2941910a7f0d10f8e28"
-
- m = re.search(r'<em itemprop="name">\s(?P<TITLE>.*?)\s</em>', page)
- if m:
- pyfile.name = m.group("TITLE") + ".mp3"
- else:
- pyfile.name = re.match(self.__pattern__, pyfile.url).group("SID") + ".mp3"
-
- # url to retrieve the actual song url
- page = self.load("https://api.sndcdn.com/i1/tracks/%s/streams" % songId, get={"client_id": clientId})
- # getting streams
- # for now we choose the first stream found in all cases
- # it could be improved if relevant for this hoster
- streams = [
- (result.group("QUALITY"), result.group("URL"))
- for result in re.finditer(r'"(?P<QUALITY>.*?)":"(?P<URL>.*?)"', page)
- ]
- self.logDebug("Found Streams", streams)
- self.logDebug("Downloading", streams[0][0], streams[0][1])
- self.download(streams[0][1])
diff --git a/module/plugins/hoster/SpeedLoadOrg.py b/module/plugins/hoster/SpeedLoadOrg.py
deleted file mode 100644
index 8cf1163b2..000000000
--- a/module/plugins/hoster/SpeedLoadOrg.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SpeedLoadOrg(DeadHoster):
- __name__ = "SpeedLoadOrg"
- __type__ = "hoster"
- __version__ = "1.02"
-
- __pattern__ = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)'
-
- __description__ = """Speedload.org hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
-getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/module/plugins/hoster/SpeedfileCz.py b/module/plugins/hoster/SpeedfileCz.py
deleted file mode 100644
index 6a42d1dbe..000000000
--- a/module/plugins/hoster/SpeedfileCz.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class SpeedfileCz(DeadHoster):
- __name__ = "SpeedFileCz"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __pattern__ = r'http://(?:www\.)?speedfile.cz/.*'
-
- __description__ = """Speedfile.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(SpeedfileCz)
diff --git a/module/plugins/hoster/SpeedyshareCom.py b/module/plugins/hoster/SpeedyshareCom.py
deleted file mode 100644
index 22f4ee511..000000000
--- a/module/plugins/hoster/SpeedyshareCom.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Testlink:
-# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class SpeedyshareCom(SimpleHoster):
- __name__ = "SpeedyshareCom"
- __type__ = "hoster"
- __pattern__ = r"https?://(www\.)?(speedyshare.com|speedy.sh)/.*"
- __version__ = "0.01"
- __description__ = """speedyshare.com hoster plugin"""
- __author_name__ = ("zapp-brannigan")
- __author_mail__ = ("fuerst.reinje@web.de")
-
- FILE_NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
- FILE_SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
- LINK_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload.png alt=\'Slow Download\' border=0'
- FILE_OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
- BASE_URL = 'www.speedyshare.com'
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = 1
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- try:
- dl_link = re.search(self.LINK_PATTERN, self.html).group(1)
- self.logDebug("Link: " + dl_link)
- except:
- self.parseError("Unable to find download link")
- self.download(self.BASE_URL + dl_link, disposition=True)
- check = self.checkDownload({"is_html": re.compile("html")})
- if check == "is_html":
- self.fail("The downloaded file is html, maybe the plugin is out of date")
-
-
-getInfo = create_getInfo(SpeedyshareCom)
diff --git a/module/plugins/hoster/StreamCz.py b/module/plugins/hoster/StreamCz.py
deleted file mode 100644
index 526e61eea..000000000
--- a/module/plugins/hoster/StreamCz.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-
-
-def getInfo(urls):
- result = []
-
- for url in urls:
-
- html = getURL(url)
- if re.search(StreamCz.OFFLINE_PATTERN, html):
- # File offline
- result.append((url, 0, 1, url))
- else:
- result.append((url, 0, 2, url))
- yield result
-
-
-class StreamCz(Hoster):
- __name__ = "StreamCz"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+.*'
-
- __description__ = """Stream.cz hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<link rel="video_src" href="http://www.stream.cz/\w+/(\d+)-([^"]+)" />'
- OFFLINE_PATTERN = r'<h1 class="commonTitle">Str.nku nebylo mo.n. nal.zt \(404\)</h1>'
-
- CDN_PATTERN = r'<param name="flashvars" value="[^"]*&id=(?P<ID>\d+)(?:&cdnLQ=(?P<cdnLQ>\d*))?(?:&cdnHQ=(?P<cdnHQ>\d*))?(?:&cdnHD=(?P<cdnHD>\d*))?&'
-
-
- def setup(self):
- self.multiDL = True
- self.resumeDownload = True
-
- def process(self, pyfile):
-
- self.html = self.load(pyfile.url, decode=True)
-
- if re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
-
- m = re.search(self.CDN_PATTERN, self.html)
- if m is None:
- self.fail("Parse error (CDN)")
- cdn = m.groupdict()
- self.logDebug(cdn)
- for cdnkey in ("cdnHD", "cdnHQ", "cdnLQ"):
- if cdnkey in cdn and cdn[cdnkey] > '':
- cdnid = cdn[cdnkey]
- break
- else:
- self.fail("Stream URL not found")
-
- m = re.search(self.FILE_NAME_PATTERN, self.html)
- if m is None:
- self.fail("Parse error (NAME)")
- pyfile.name = "%s-%s.%s.mp4" % (m.group(2), m.group(1), cdnkey[-2:])
-
- download_url = "http://cdn-dispatcher.stream.cz/?id=" + cdnid
- self.logInfo("STREAM (%s): %s" % (cdnkey[-2:], download_url))
- self.download(download_url)
diff --git a/module/plugins/hoster/StreamcloudEu.py b/module/plugins/hoster/StreamcloudEu.py
deleted file mode 100644
index 11333640e..000000000
--- a/module/plugins/hoster/StreamcloudEu.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import sleep
-
-from module.network.HTTPRequest import HTTPRequest
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class StreamcloudEu(XFileSharingPro):
- __name__ = "StreamcloudEu"
- __type__ = "hoster"
- __version__ = "0.04"
-
- __pattern__ = r'http://(?:www\.)?streamcloud\.eu/\S+'
-
- __description__ = """Streamcloud.eu hoster plugin"""
- __author_name__ = "seoester"
- __author_mail__ = "seoester@googlemail.com"
-
- HOSTER_NAME = "streamcloud.eu"
-
- LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud.eu:?\d*/.*/video\.(mp4|flv))",'
-
-
- def setup(self):
- super(StreamcloudEu, self).setup()
- self.multiDL = True
-
- def getDownloadLink(self):
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- return m.group(1)
-
- for i in xrange(5):
- self.logDebug("Getting download link: #%d" % i)
- data = self.getPostParameters()
- httpRequest = HTTPRequest(options=self.req.options)
- httpRequest.cj = self.req.cj
- sleep(10)
- self.html = httpRequest.load(self.pyfile.url, post=data, referer=False, cookies=True, decode=True)
- self.header = httpRequest.header
-
- m = re.search("Location\s*:\s*(.*)", self.header, re.I)
- if m:
- break
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
-
- else:
- if self.errmsg and 'captcha' in self.errmsg:
- self.fail("No valid captcha code entered")
- else:
- self.fail("Download link not found")
-
- return m.group(1)
-
- def getPostParameters(self):
- for i in xrange(3):
- if not self.errmsg:
- self.checkErrors()
-
- if hasattr(self, "FORM_PATTERN"):
- action, inputs = self.parseHtmlForm(self.FORM_PATTERN)
- else:
- action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
-
- if not inputs:
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- if self.errmsg:
- self.retry()
- else:
- self.parseError("Form not found")
-
- self.logDebug(self.HOSTER_NAME, inputs)
-
- if 'op' in inputs and inputs['op'] in ("download1", "download2", "download3"):
- if "password" in inputs:
- if self.passwords:
- inputs['password'] = self.passwords.pop(0)
- else:
- self.fail("No or invalid passport")
-
- if not self.premium:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1)) + 1
- self.setWait(wait_time, False)
- else:
- wait_time = 0
-
- self.captcha = self.handleCaptcha(inputs)
-
- if wait_time:
- self.wait()
-
- self.errmsg = None
- self.logDebug("getPostParameters {0}".format(i))
- return inputs
-
- else:
- inputs['referer'] = self.pyfile.url
-
- if self.premium:
- inputs['method_premium'] = "Premium Download"
- if 'method_free' in inputs:
- del inputs['method_free']
- else:
- inputs['method_free'] = "Free Download"
- if 'method_premium' in inputs:
- del inputs['method_premium']
-
- self.html = self.load(self.pyfile.url, post=inputs, ref=False)
- self.errmsg = None
-
- else:
- self.parseError('FORM: %s' % (inputs['op'] if 'op' in inputs else 'UNKNOWN'))
-
-
-getInfo = create_getInfo(StreamcloudEu)
diff --git a/module/plugins/hoster/TurbobitNet.py b/module/plugins/hoster/TurbobitNet.py
deleted file mode 100644
index a9cc46614..000000000
--- a/module/plugins/hoster/TurbobitNet.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import random
-import re
-import time
-
-from Crypto.Cipher import ARC4
-from binascii import hexlify, unhexlify
-from pycurl import HTTPHEADER
-from urllib import quote
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
-
-
-class TurbobitNet(SimpleHoster):
- __name__ = "TurbobitNet"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?(turbobit.net|unextfiles.com)/(?!download/folder/)(?:download/free/)?(?P<ID>\w+).*'
-
- __description__ = """Turbobit.net plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r"<span class='file-icon1[^>]*>(?P<N>[^<]+)</span>\s*\((?P<S>[^\)]+)\)\s*</h1>" #: long filenames are shortened
- FILE_NAME_PATTERN = r'<meta name="keywords" content="\s+(?P<N>[^,]+)' #: full name but missing on page2
- OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
-
- FILE_URL_REPLACEMENTS = [(r"http://(?:www\.)?(turbobit.net|unextfiles.com)/(?:download/free/)?(?P<ID>\w+).*",
- "http://turbobit.net/\g<ID>.html")]
- SH_COOKIES = [(".turbobit.net", "user_lang", "en")]
-
- LINK_PATTERN = r'(?P<url>/download/redirect/[^"\']+)'
- LIMIT_WAIT_PATTERN = r'<div id="time-limit-text">\s*.*?<span id=\'timeout\'>(\d+)</span>'
- CAPTCHA_KEY_PATTERN = r'src="http://api\.recaptcha\.net/challenge\?k=([^"]+)"'
- CAPTCHA_SRC_PATTERN = r'<img alt="Captcha" src="(.*?)"'
-
-
- def handleFree(self):
- self.url = "http://turbobit.net/download/free/%s" % self.file_info['ID']
- self.html = self.load(self.url)
-
- rtUpdate = self.getRtUpdate()
-
- self.solveCaptcha()
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.url = self.getDownloadUrl(rtUpdate)
-
- self.wait()
- self.html = self.load(self.url)
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
- self.downloadFile()
-
- def solveCaptcha(self):
- for _ in xrange(5):
- m = re.search(self.LIMIT_WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1))
- self.wait(wait_time, wait_time > 60)
- self.retry()
-
- action, inputs = self.parseHtmlForm("action='#'")
- if not inputs:
- self.parseError("captcha form")
- self.logDebug(inputs)
-
- if inputs['captcha_type'] == 'recaptcha':
- recaptcha = ReCaptcha(self)
- m = re.search(self.CAPTCHA_KEY_PATTERN, self.html)
- captcha_key = m.group(1) if m else '6LcTGLoSAAAAAHCWY9TTIrQfjUlxu6kZlTYP50_c'
- inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(
- captcha_key)
- else:
- m = re.search(self.CAPTCHA_SRC_PATTERN, self.html)
- if m is None:
- self.parseError('captcha')
- captcha_url = m.group(1)
- inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
-
- self.logDebug(inputs)
- self.html = self.load(self.url, post=inputs)
-
- if not "<div class='download-timer-header'>" in self.html:
- self.invalidCaptcha()
- else:
- self.correctCaptcha()
- break
- else:
- self.fail("Invalid captcha")
-
- def getRtUpdate(self):
- rtUpdate = self.getStorage("rtUpdate")
- if not rtUpdate:
- if self.getStorage("version") != self.__version__ or int(
- self.getStorage("timestamp", 0)) + 86400000 < timestamp():
- # that's right, we are even using jdownloader updates
- rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js")
- rtUpdate = self.decrypt(rtUpdate.splitlines()[1])
- # but we still need to fix the syntax to work with other engines than rhino
- rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{',
- r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];', rtUpdate)
- rtUpdate = re.sub(r"for\((\w+)=", r"for(var \1=", rtUpdate)
-
- self.logDebug("rtUpdate")
- self.setStorage("rtUpdate", rtUpdate)
- self.setStorage("timestamp", timestamp())
- self.setStorage("version", self.__version__)
- else:
- self.logError("Unable to download, wait for update...")
- self.tempOffline()
-
- return rtUpdate
-
- def getDownloadUrl(self, rtUpdate):
- self.req.http.lastURL = self.url
-
- m = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html)
- url = "http://turbobit.net%s%s" % (m.groups() if m else (
- '/files/timeout.js?ver=', ''.join(random.choice('0123456789ABCDEF') for _ in xrange(32))))
- fun = self.load(url)
-
- self.setWait(65, False)
-
- for b in [1, 3]:
- self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % (
- self.file_info['ID'], b, quote(fun), rtUpdate)
-
- try:
- out = self.js.eval(self.jscode)
- self.logDebug("URL", self.js.engine, out)
- if out.startswith('/download/'):
- return "http://turbobit.net%s" % out.strip()
- except Exception, e:
- self.logError(e)
- else:
- if self.retries >= 2:
- # retry with updated js
- self.delStorage("rtUpdate")
- self.retry()
-
- def decrypt(self, data):
- cipher = ARC4.new(hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
- return unhexlify(cipher.encrypt(unhexlify(data)))
-
- def getLocalTimeString(self):
- lt = time.localtime()
- tz = time.altzone if lt.tm_isdst else time.timezone
- return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600)
-
- def handlePremium(self):
- self.logDebug("Premium download as user %s" % self.user)
- self.html = self.load(self.pyfile.url) # Useless in 0.5
- self.downloadFile()
-
- def downloadFile(self):
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError("download link")
- self.url = "http://turbobit.net" + m.group('url')
- self.logDebug(self.url)
- self.download(self.url)
-
-
-getInfo = create_getInfo(TurbobitNet)
diff --git a/module/plugins/hoster/TurbouploadCom.py b/module/plugins/hoster/TurbouploadCom.py
deleted file mode 100644
index bcfa2d7d4..000000000
--- a/module/plugins/hoster/TurbouploadCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class TurbouploadCom(DeadHoster):
- __name__ = "TurbouploadCom"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'http://(?:www\.)?turboupload.com/(\w+).*'
-
- __description__ = """Turboupload.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
-getInfo = create_getInfo(TurbouploadCom)
diff --git a/module/plugins/hoster/TusfilesNet.py b/module/plugins/hoster/TusfilesNet.py
deleted file mode 100644
index 7fb2a375e..000000000
--- a/module/plugins/hoster/TusfilesNet.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class TusfilesNet(XFileSharingPro):
- __name__ = "TusfilesNet"
- __type__ = "hoster"
- __version__ = "0.03"
-
- __pattern__ = r'https?://(?:www\.)?tusfiles\.net/(?P<ID>\w+)'
-
- __description__ = """Tusfiles.net hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- HOSTER_NAME = "tusfiles.net"
-
- FILE_INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.]+) (?P<U>\w+)\['
- OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!'
-
- SH_COOKIES = [(".tusfiles.net", "lang", "english")]
-
-
- def setup(self):
- self.multiDL = False
- self.chunkLimit = -1
- self.resumeDownload = True
-
-
-getInfo = create_getInfo(TusfilesNet)
diff --git a/module/plugins/hoster/TwoSharedCom.py b/module/plugins/hoster/TwoSharedCom.py
deleted file mode 100644
index dbd33dd83..000000000
--- a/module/plugins/hoster/TwoSharedCom.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class TwoSharedCom(SimpleHoster):
- __name__ = "TwoSharedCom"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = r'http://(?:www\.)?2shared.com/(account/)?(download|get|file|document|photo|video|audio)/.*'
-
- __description__ = """2Shared.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
- FILE_SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[0-9,.]+) (?P<U>[kKMG])i?B'
- OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
-
- LINK_PATTERN = r"window.location ='([^']+)';"
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def handleFree(self):
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError('Download link')
- link = m.group(1)
- self.logDebug("Download URL %s" % link)
-
- self.download(link)
-
-
-getInfo = create_getInfo(TwoSharedCom)
diff --git a/module/plugins/hoster/UlozTo.py b/module/plugins/hoster/UlozTo.py
deleted file mode 100644
index dbdaf3f8e..000000000
--- a/module/plugins/hoster/UlozTo.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import time
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-def convertDecimalPrefix(m):
- # decimal prefixes used in filesize and traffic
- return ("%%.%df" % {'k': 3, 'M': 6, 'G': 9}[m.group(2)] % float(m.group(1))).replace('.', '')
-
-
-class UlozTo(SimpleHoster):
- __name__ = "UlozTo"
- __type__ = "hoster"
- __version__ = "0.98"
-
- __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj.cz|zachowajto.pl)/(?:live/)?(?P<id>\w+/[^/?]*)'
-
- __description__ = """Uloz.to hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
- FILE_NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz.to</title>'
- FILE_SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[0-9.]+\s[kMG]?B)</span>'
- OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
-
- FILE_SIZE_REPLACEMENTS = [('([0-9.]+)\s([kMG])B', convertDecimalPrefix)]
- FILE_URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "www.ulozto.net")]
-
- ADULT_PATTERN = r'<form action="(?P<link>[^\"]*)" method="post" id="frm-askAgeForm">'
- PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
- VIPLINK_PATTERN = r'<a href="[^"]*\?disclaimer=1" class="linkVip">'
- FREE_URL_PATTERN = r'<div class="freeDownloadForm"><form action="([^"]+)"'
- PREMIUM_URL_PATTERN = r'<div class="downloadForm"><form action="([^"]+)"'
- TOKEN_PATTERN = r'<input type="hidden" name="_token_" id="[^\"]*" value="(?P<token>[^\"]*)" />'
-
-
- def setup(self):
- self.multiDL = self.premium
- self.resumeDownload = True
-
- def process(self, pyfile):
- pyfile.url = re.sub(r"(?<=http://)([^/]+)", "www.ulozto.net", pyfile.url)
- self.html = self.load(pyfile.url, decode=True, cookies=True)
-
- if re.search(self.ADULT_PATTERN, self.html):
- self.logInfo("Adult content confirmation needed. Proceeding..")
-
- m = re.search(self.TOKEN_PATTERN, self.html)
- if m is None:
- self.parseError('TOKEN')
- token = m.group(1)
-
- self.html = self.load(pyfile.url, get={"do": "askAgeForm-submit"},
- post={"agree": "Confirm", "_token_": token}, cookies=True)
-
- passwords = self.getPassword().splitlines()
- while self.PASSWD_PATTERN in self.html:
- if passwords:
- password = passwords.pop(0)
- self.logInfo("Password protected link, trying " + password)
- self.html = self.load(pyfile.url, get={"do": "passwordProtectedForm-submit"},
- post={"password": password, "password_send": 'Send'}, cookies=True)
- else:
- self.fail("No or incorrect password")
-
- if re.search(self.VIPLINK_PATTERN, self.html):
- self.html = self.load(pyfile.url, get={"disclaimer": "1"})
-
- self.file_info = self.getFileInfo()
-
- if self.premium and self.checkTrafficLeft():
- self.handlePremium()
- else:
- self.handleFree()
-
- self.doCheckDownload()
-
- def handleFree(self):
- action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
- if not action or not inputs:
- self.parseError("free download form")
-
- self.logDebug('inputs.keys() = ' + str(inputs.keys()))
- # get and decrypt captcha
- if all(key in inputs for key in ("captcha_value", "captcha_id", "captcha_key")):
- # Old version - last seen 9.12.2013
- self.logDebug('Using "old" version')
-
- captcha_value = self.decryptCaptcha("http://img.uloz.to/captcha/%s.png" % inputs['captcha_id'])
- self.logDebug('CAPTCHA ID: ' + inputs['captcha_id'] + ", CAPTCHA VALUE: " + captcha_value)
-
- inputs.update({'captcha_id': inputs['captcha_id'], 'captcha_key': inputs['captcha_key'], 'captcha_value': captcha_value})
-
- elif all(key in inputs for key in ("captcha_value", "timestamp", "salt", "hash")):
- # New version - better to get new parameters (like captcha reload) because of image url - since 6.12.2013
- self.logDebug('Using "new" version')
-
- xapca = self.load("http://www.ulozto.net/reloadXapca.php", get={"rnd": str(int(time.time()))})
- self.logDebug('xapca = ' + str(xapca))
-
- data = json_loads(xapca)
- captcha_value = self.decryptCaptcha(str(data['image']))
- self.logDebug("CAPTCHA HASH: " + data['hash'] + ", CAPTCHA SALT: " + str(data['salt']) + ", CAPTCHA VALUE: " + captcha_value)
-
- inputs.update({'timestamp': data['timestamp'], 'salt': data['salt'], 'hash': data['hash'], 'captcha_value': captcha_value})
- else:
- self.parseError("CAPTCHA form changed")
-
- self.multiDL = True
- self.download("http://www.ulozto.net" + action, post=inputs, cookies=True, disposition=True)
-
- def handlePremium(self):
- self.download(self.pyfile.url + "?do=directDownload", disposition=True)
- #parsed_url = self.findDownloadURL(premium=True)
- #self.download(parsed_url, post={"download": "Download"})
-
- def findDownloadURL(self, premium=False):
- msg = "%s link" % ("Premium" if premium else "Free")
- m = re.search(self.PREMIUM_URL_PATTERN if premium else self.FREE_URL_PATTERN, self.html)
- if m is None:
- self.parseError(msg)
- parsed_url = "http://www.ulozto.net" + m.group(1)
- self.logDebug("%s: %s" % (msg, parsed_url))
- return parsed_url
-
- def doCheckDownload(self):
- check = self.checkDownload({
- "wrong_captcha": re.compile(r'<ul class="error">\s*<li>Error rewriting the text.</li>'),
- "offline": re.compile(self.OFFLINE_PATTERN),
- "passwd": self.PASSWD_PATTERN,
- "server_error": 'src="http://img.ulozto.cz/error403/vykricnik.jpg"', # paralell dl, server overload etc.
- "not_found": "<title>UloÅŸ.to</title>"
- })
-
- if check == "wrong_captcha":
- #self.delStorage("captcha_id")
- #self.delStorage("captcha_text")
- self.invalidCaptcha()
- self.retry(reason="Wrong captcha code")
- elif check == "offline":
- self.offline()
- elif check == "passwd":
- self.fail("Wrong password")
- elif check == "server_error":
- self.logError("Server error, try downloading later")
- self.multiDL = False
- self.wait(1 * 60 * 60, True)
- self.retry()
- elif check == "not_found":
- self.fail("Server error - file not downloadable")
-
-
-getInfo = create_getInfo(UlozTo)
diff --git a/module/plugins/hoster/UloziskoSk.py b/module/plugins/hoster/UloziskoSk.py
deleted file mode 100644
index ac70f42d4..000000000
--- a/module/plugins/hoster/UloziskoSk.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UloziskoSk(SimpleHoster):
- __name__ = "UloziskoSk"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?ulozisko.sk/.*'
-
- __description__ = """Ulozisko.sk hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
- FILE_SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</strong><br />'
- OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
-
- LINK_PATTERN = r'<form name = "formular" action = "([^"]+)" method = "post">'
- ID_PATTERN = r'<input type = "hidden" name = "id" value = "([^"]+)" />'
- CAPTCHA_PATTERN = r'<img src="(/obrazky/obrazky.php\?fid=[^"]+)" alt="" />'
- IMG_PATTERN = ur'<strong>PRE ZVÄČŠENIE KLIKNITE NA OBRÁZOK</strong><br /><a href = "([^"]+)">'
-
-
- def process(self, pyfile):
- self.html = self.load(pyfile.url, decode=True)
- self.getFileInfo()
-
- m = re.search(self.IMG_PATTERN, self.html)
- if m:
- url = "http://ulozisko.sk" + m.group(1)
- self.download(url)
- else:
- self.handleFree()
-
- def handleFree(self):
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError('URL')
- parsed_url = 'http://www.ulozisko.sk' + m.group(1)
-
- m = re.search(self.ID_PATTERN, self.html)
- if m is None:
- self.parseError('ID')
- id = m.group(1)
-
- self.logDebug('URL:' + parsed_url + ' ID:' + id)
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.parseError('CAPTCHA')
- captcha_url = 'http://www.ulozisko.sk' + m.group(1)
-
- captcha = self.decryptCaptcha(captcha_url, cookies=True)
-
- self.logDebug('CAPTCHA_URL:' + captcha_url + ' CAPTCHA:' + captcha)
-
- self.download(parsed_url, post={
- "antispam": captcha,
- "id": id,
- "name": self.pyfile.name,
- "but": "++++STIAHNI+S%DABOR++++"
- })
-
-
-getInfo = create_getInfo(UloziskoSk)
diff --git a/module/plugins/hoster/UnibytesCom.py b/module/plugins/hoster/UnibytesCom.py
deleted file mode 100644
index e936b84b1..000000000
--- a/module/plugins/hoster/UnibytesCom.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UnibytesCom(SimpleHoster):
- __name__ = "UnibytesCom"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?unibytes\.com/[a-zA-Z0-9-._ ]{11}B'
-
- __description__ = """UniBytes.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_INFO_PATTERN = r'<span[^>]*?id="fileName"[^>]*>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
-
- HOSTER_NAME = "unibytes.com"
- WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
- LINK_PATTERN = r'<a href="([^"]+)">Download</a>'
-
-
- def handleFree(self):
- domain = "http://www." + self.HOSTER_NAME
- action, post_data = self.parseHtmlForm('id="startForm"')
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
-
- for _ in xrange(8):
- self.logDebug(action, post_data)
- self.html = self.load(domain + action, post=post_data)
-
- m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
- if m:
- url = m.group(1)
- break
-
- if '>Somebody else is already downloading using your IP-address<' in self.html:
- self.wait(10 * 60, True)
- self.retry()
-
- if post_data['step'] == 'last':
- m = re.search(self.LINK_PATTERN, self.html)
- if m:
- url = m.group(1)
- self.correctCaptcha()
- break
- else:
- self.invalidCaptcha()
-
- last_step = post_data['step']
- action, post_data = self.parseHtmlForm('id="stepForm"')
-
- if last_step == 'timer':
- m = re.search(self.WAIT_PATTERN, self.html)
- self.wait(int(m.group(1)) if m else 60, False)
- elif last_step in ("captcha", "last"):
- post_data['captcha'] = self.decryptCaptcha(domain + '/captcha.jpg')
- else:
- self.fail("No valid captcha code entered")
-
- self.logDebug('Download link: ' + url)
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
- self.download(url)
-
-
-getInfo = create_getInfo(UnibytesCom)
diff --git a/module/plugins/hoster/UnrestrictLi.py b/module/plugins/hoster/UnrestrictLi.py
deleted file mode 100644
index ed70e023d..000000000
--- a/module/plugins/hoster/UnrestrictLi.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from datetime import datetime, timedelta
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-def secondsToMidnight(gmt=0):
- now = datetime.utcnow() + timedelta(hours=gmt)
- if now.hour is 0 and now.minute < 10:
- midnight = now
- else:
- midnight = now + timedelta(days=1)
- midnight = midnight.replace(hour=0, minute=10, second=0, microsecond=0)
- return int((midnight - now).total_seconds())
-
-
-class UnrestrictLi(Hoster):
- __name__ = "UnrestrictLi"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'https?://(?:[^/]*\.)?(unrestrict|unr)\.li'
-
- __description__ = """Unrestrict.li hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def setup(self):
- self.chunkLimit = 16
- self.resumeDownload = True
-
- def process(self, pyfile):
- if re.match(self.__pattern__, pyfile.url):
- new_url = pyfile.url
- elif not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "Unrestrict.li")
- self.fail("No Unrestrict.li account provided")
- else:
- self.logDebug("Old URL: %s" % pyfile.url)
- for _ in xrange(5):
- page = self.req.load('https://unrestrict.li/unrestrict.php',
- post={'link': pyfile.url, 'domain': 'long'})
- self.logDebug("JSON data: " + page)
- if page != '':
- break
- else:
- self.logInfo("Unable to get API data, waiting 1 minute and retry")
- self.retry(5, 60, "Unable to get API data")
-
- if 'Expired session' in page or ("You are not allowed to "
- "download from this host" in page and self.premium):
- self.account.relogin(self.user)
- self.retry()
- elif "File offline" in page:
- self.offline()
- elif "You are not allowed to download from this host" in page:
- self.fail("You are not allowed to download from this host")
- elif "You have reached your daily limit for this host" in page:
- self.logWarning("Reached daily limit for this host")
- self.retry(5, secondsToMidnight(gmt=2), "Daily limit for this host reached")
- elif "ERROR_HOSTER_TEMPORARILY_UNAVAILABLE" in page:
- self.logInfo("Hoster temporarily unavailable, waiting 1 minute and retry")
- self.retry(5, 60, "Hoster is temporarily unavailable")
- page = json_loads(page)
- new_url = page.keys()[0]
- self.api_data = page[new_url]
-
- if new_url != pyfile.url:
- self.logDebug("New URL: " + new_url)
-
- if hasattr(self, 'api_data'):
- self.setNameSize()
-
- self.download(new_url, disposition=True)
-
- if self.getConfig("history"):
- self.load("https://unrestrict.li/history/&delete=all")
- self.logInfo("Download history deleted")
-
- def setNameSize(self):
- if 'name' in self.api_data:
- self.pyfile.name = self.api_data['name']
- if 'size' in self.api_data:
- self.pyfile.size = self.api_data['size']
diff --git a/module/plugins/hoster/UploadStationCom.py b/module/plugins/hoster/UploadStationCom.py
deleted file mode 100644
index ac5a47f3f..000000000
--- a/module/plugins/hoster/UploadStationCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class UploadStationCom(DeadHoster):
- __name__ = "UploadStationCom"
- __type__ = "hoster"
- __version__ = "0.52"
-
- __pattern__ = r'http://(?:www\.)?uploadstation\.com/file/(?P<id>[A-Za-z0-9]+)'
-
- __description__ = """UploadStation.com hoster plugin"""
- __author_name__ = ("fragonib", "zoidberg")
- __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "zoidberg@mujmail.cz")
-
-
-getInfo = create_getInfo(UploadStationCom)
diff --git a/module/plugins/hoster/UploadedTo.py b/module/plugins/hoster/UploadedTo.py
deleted file mode 100644
index 286f1caf0..000000000
--- a/module/plugins/hoster/UploadedTo.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://ul.to/044yug9o
-# http://ul.to/gzfhd0xs
-
-import re
-
-from time import sleep
-
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.plugins.Plugin import chunks
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.utils import html_unescape, parseFileSize
-
-
-key = "bGhGMkllZXByd2VEZnU5Y2NXbHhYVlZ5cEE1bkEzRUw=".decode('base64')
-
-
-def getID(url):
- """ returns id from file url"""
- m = re.match(UploadedTo.__pattern__, url)
- return m.group('ID')
-
-
-def getAPIData(urls):
- post = {"apikey": key}
-
- idMap = {}
-
- for i, url in enumerate(urls):
- id = getID(url)
- post['id_%s' % i] = id
- idMap[id] = url
-
- for _ in xrange(5):
- api = unicode(getURL("http://uploaded.net/api/filemultiple", post=post, decode=False), 'iso-8859-1')
- if api != "can't find request":
- break
- else:
- sleep(3)
-
- result = {}
-
- if api:
- for line in api.splitlines():
- data = line.split(",", 4)
- if data[1] in idMap:
- result[data[1]] = (data[0], data[2], data[4], data[3], idMap[data[1]])
-
- return result
-
-
-def parseFileInfo(self, url='', html=''):
- if not html and hasattr(self, "html"):
- html = self.html
-
- name = url
- size = 0
- fileid = None
-
- if re.search(self.OFFLINE_PATTERN, html):
- # File offline
- status = 1
- else:
- m = re.search(self.FILE_INFO_PATTERN, html)
- if m:
- name, fileid = html_unescape(m.group('N')), m.group('ID')
- size = parseFileSize(m.group('S'))
- status = 2
- else:
- status = 3
-
- return name, size, status, fileid
-
-
-def getInfo(urls):
- for chunk in chunks(urls, 80):
- result = []
-
- api = getAPIData(chunk)
-
- for data in api.itervalues():
- if data[0] == "online":
- result.append((html_unescape(data[2]), data[1], 2, data[4]))
-
- elif data[0] == "offline":
- result.append((data[4], 0, 1, data[4]))
-
- yield result
-
-
-class UploadedTo(Hoster):
- __name__ = "UploadedTo"
- __type__ = "hoster"
- __version__ = "0.73"
-
- __pattern__ = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
-
- __description__ = """Uploaded.net hoster plugin"""
- __author_name__ = ("spoob", "mkaay", "zoidberg", "netpok", "stickell")
- __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de", "zoidberg@mujmail.cz",
- "netpok@gmail.com", "l.stickell@yahoo.it")
-
- FILE_INFO_PATTERN = r'<a href="file/(?P<ID>\w+)" id="filename">(?P<N>[^<]+)</a> &nbsp;\s*<small[^>]*>(?P<S>[^<]+)</small>'
- OFFLINE_PATTERN = r'<small class="cL">Error: 404</small>'
- DL_LIMIT_PATTERN = r'You have reached the max. number of possible free downloads for this hour'
-
-
- def setup(self):
- self.multiDL = self.resumeDownload = self.premium
- self.chunkLimit = 1 # critical problems with more chunks
-
- self.fileID = getID(self.pyfile.url)
- self.pyfile.url = "http://uploaded.net/file/%s" % self.fileID
-
- def process(self, pyfile):
- self.load("http://uploaded.net/language/en", just_header=True)
-
- api = getAPIData([pyfile.url])
-
- # TODO: fallback to parse from site, because api sometimes delivers wrong status codes
-
- if not api:
- self.logWarning("No response for API call")
-
- self.html = unicode(self.load(pyfile.url, decode=False), 'iso-8859-1')
- name, size, status, self.fileID = parseFileInfo(self)
- self.logDebug(name, size, status, self.fileID)
- if status == 1:
- self.offline()
- elif status == 2:
- pyfile.name, pyfile.size = name, size
- else:
- self.fail('Parse error - file info')
- elif api == 'Access denied':
- self.fail(_("API key invalid"))
-
- else:
- if self.fileID not in api:
- self.offline()
-
- self.data = api[self.fileID]
- if self.data[0] != "online":
- self.offline()
-
- pyfile.name = html_unescape(self.data[2])
-
- # pyfile.name = self.get_file_name()
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- def handlePremium(self):
- info = self.account.getAccountInfo(self.user, True)
- self.logDebug("%(name)s: Use Premium Account (%(left)sGB left)" % {"name": self.__name__,
- "left": info['trafficleft'] / 1024 / 1024})
- if int(self.data[1]) / 1024 > info['trafficleft']:
- self.logInfo(_("%s: Not enough traffic left" % self.__name__))
- self.account.empty(self.user)
- self.resetAccount()
- self.fail(_("Traffic exceeded"))
-
- header = self.load("http://uploaded.net/file/%s" % self.fileID, just_header=True)
- if "location" in header:
- #Direct download
- print "Direct Download: " + header['location']
- self.download(header['location'])
- else:
- #Indirect download
- self.html = self.load("http://uploaded.net/file/%s" % self.fileID)
- m = re.search(r'<div class="tfree".*\s*<form method="post" action="(.*?)"', self.html)
- if m is None:
- self.fail("Download URL not m. Try to enable direct downloads.")
- url = m.group(1)
- print "Premium URL: " + url
- self.download(url, post={})
-
- def handleFree(self):
- self.html = self.load(self.pyfile.url, decode=True)
-
- if 'var free_enabled = false;' in self.html:
- self.logError("Free-download capacities exhausted.")
- self.retry(max_tries=24, wait_time=5 * 60)
-
- m = re.search(r"Current waiting period: <span>(\d+)</span> seconds", self.html)
- if m is None:
- self.fail("File not downloadable for free users")
- self.setWait(int(m.group(1)))
-
- js = self.load("http://uploaded.net/js/download.js", decode=True)
-
- challengeId = re.search(r'Recaptcha\.create\("([^"]+)', js)
-
- url = "http://uploaded.net/io/ticket/captcha/%s" % self.fileID
- downloadURL = ""
-
- for _ in xrange(5):
- re_captcha = ReCaptcha(self)
- challenge, result = re_captcha.challenge(challengeId.group(1))
- options = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": result}
- self.wait()
-
- result = self.load(url, post=options)
- self.logDebug("result: %s" % result)
-
- if "limit-size" in result:
- self.fail("File too big for free download")
- elif "limit-slot" in result: # Temporary restriction so just wait a bit
- self.setWait(30 * 60, True)
- self.wait()
- self.retry()
- elif "limit-parallel" in result:
- self.fail("Cannot download in parallel")
- elif self.DL_LIMIT_PATTERN in result: # limit-dl
- self.setWait(3 * 60 * 60, True)
- self.wait()
- self.retry()
- elif '"err":"captcha"' in result:
- self.logError("ul.net captcha is disabled")
- self.invalidCaptcha()
- elif "type:'download'" in result:
- self.correctCaptcha()
- downloadURL = re.search("url:'([^']+)", result).group(1)
- break
- else:
- self.fail("Unknown error '%s'" % result)
-
- if not downloadURL:
- self.fail("No Download url retrieved/all captcha attempts failed")
-
- self.download(downloadURL, disposition=True)
- check = self.checkDownload({"limit-dl": self.DL_LIMIT_PATTERN})
- if check == "limit-dl":
- self.setWait(3 * 60 * 60, True)
- self.wait()
- self.retry()
diff --git a/module/plugins/hoster/UploadheroCom.py b/module/plugins/hoster/UploadheroCom.py
deleted file mode 100644
index e34701ed7..000000000
--- a/module/plugins/hoster/UploadheroCom.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://uploadhero.co/dl/wQBRAVSM
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UploadheroCom(SimpleHoster):
- __name__ = "UploadheroCom"
- __type__ = "hoster"
- __version__ = "0.15"
-
- __pattern__ = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
-
- __description__ = """UploadHero.co plugin"""
- __author_name__ = ("mcmyst", "zoidberg")
- __author_mail__ = ("mcmyst@hotmail.fr", "zoidberg@mujmail.cz")
-
- FILE_NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.*?)</div>'
- FILE_SIZE_PATTERN = r'Taille du fichier : </span><strong>(?P<S>.*?)</strong>'
- OFFLINE_PATTERN = r'<p class="titre_dl_2">|<div class="raison"><strong>Le lien du fichier ci-dessus n\'existe plus.'
-
- SH_COOKIES = [(".uploadhero.co", "lang", "en")]
-
- IP_BLOCKED_PATTERN = r'href="(/lightbox_block_download.php\?min=.*?)"'
- IP_WAIT_PATTERN = r'<span id="minutes">(\d+)</span>.*\s*<span id="seconds">(\d+)</span>'
-
- CAPTCHA_PATTERN = r'"(/captchadl\.php\?[a-z0-9]+)"'
- FREE_URL_PATTERN = r'var magicomfg = \'<a href="(http://[^<>"]*?)"|"(http://storage\d+\.uploadhero\.co/\?d=[A-Za-z0-9]+/[^<>"/]+)"'
- PREMIUM_URL_PATTERN = r'<a href="([^"]+)" id="downloadnow"'
-
-
- def handleFree(self):
- self.checkErrors()
-
- m = re.search(self.CAPTCHA_PATTERN, self.html)
- if m is None:
- self.parseError("Captcha URL")
- captcha_url = "http://uploadhero.co" + m.group(1)
-
- for _ in xrange(5):
- captcha = self.decryptCaptcha(captcha_url)
- self.html = self.load(self.pyfile.url, get={"code": captcha})
- m = re.search(self.FREE_URL_PATTERN, self.html)
- if m:
- self.correctCaptcha()
- download_url = m.group(1) or m.group(2)
- break
- else:
- self.invalidCaptcha()
- else:
- self.fail("No valid captcha code entered")
-
- self.download(download_url)
-
- def handlePremium(self):
- self.logDebug("%s: Use Premium Account" % self.__name__)
- self.html = self.load(self.pyfile.url)
- link = re.search(self.PREMIUM_URL_PATTERN, self.html).group(1)
- self.logDebug("Downloading link : '%s'" % link)
- self.download(link)
-
- def checkErrors(self):
- m = re.search(self.IP_BLOCKED_PATTERN, self.html)
- if m:
- self.html = self.load("http://uploadhero.co%s" % m.group(1))
-
- m = re.search(self.IP_WAIT_PATTERN, self.html)
- wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 5 * 60
- self.wait(wait_time, True)
- self.retry()
-
-
-getInfo = create_getInfo(UploadheroCom)
diff --git a/module/plugins/hoster/UploadingCom.py b/module/plugins/hoster/UploadingCom.py
deleted file mode 100644
index 45ab1d4cd..000000000
--- a/module/plugins/hoster/UploadingCom.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import HTTPHEADER
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
-
-
-class UploadingCom(SimpleHoster):
- __name__ = "UploadingCom"
- __type__ = "hoster"
- __version__ = "0.36"
-
- __pattern__ = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>[\w\d]+)'
-
- __description__ = """Uploading.com hoster plugin"""
- __author_name__ = ("jeix", "mkaay", "zoidberg")
- __author_mail__ = ("jeix@hasnomail.de", "mkaay@mkaay.de", "zoidberg@mujmail.cz")
-
- FILE_NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
- FILE_SIZE_PATTERN = r'size tip_container">(?P<S>[\d.]+) (?P<U>\w+)<'
- OFFLINE_PATTERN = r'(Page|file) not found'
-
-
- def process(self, pyfile):
- # set lang to english
- self.req.cj.setCookie(".uploading.com", "lang", "1")
- self.req.cj.setCookie(".uploading.com", "language", "1")
- self.req.cj.setCookie(".uploading.com", "setlang", "en")
- self.req.cj.setCookie(".uploading.com", "_lang", "en")
-
- if not "/get/" in pyfile.url:
- pyfile.url = pyfile.url.replace("/files", "/files/get")
-
- self.html = self.load(pyfile.url, decode=True)
- self.file_info = self.getFileInfo()
-
- if self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- def handlePremium(self):
- postData = {'action': 'get_link',
- 'code': self.file_info['ID'],
- 'pass': 'undefined'}
-
- self.html = self.load('http://uploading.com/files/get/?JsHttpRequest=%d-xml' % timestamp(), post=postData)
- url = re.search(r'"link"\s*:\s*"(.*?)"', self.html)
- if url:
- url = url.group(1).replace("\\/", "/")
- self.download(url)
-
- raise Exception("Plugin defect.")
-
- def handleFree(self):
- m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
- if m:
- self.pyfile.error = m.group(1)
- self.logWarning(self.pyfile.error)
- self.retry(max_tries=6, wait_time=6 * 60 * 60 if m.group(2) else 15 * 60, reason=self.pyfile.error)
-
- ajax_url = "http://uploading.com/files/get/?ajax"
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = self.pyfile.url
-
- response = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.file_info['ID']}))
- if 'answer' in response and 'wait_time' in response['answer']:
- wait_time = int(response['answer']['wait_time'])
- self.logInfo("%s: Waiting %d seconds." % (self.__name__, wait_time))
- self.wait(wait_time)
- else:
- self.parseError("AJAX/WAIT")
-
- response = json_loads(
- self.load(ajax_url, post={'action': 'get_link', 'code': self.file_info['ID'], 'pass': 'false'}))
- if 'answer' in response and 'link' in response['answer']:
- url = response['answer']['link']
- else:
- self.parseError("AJAX/URL")
-
- self.html = self.load(url)
- m = re.search(r'<form id="file_form" action="(.*?)"', self.html)
- if m:
- url = m.group(1)
- else:
- self.parseError("URL")
-
- self.download(url)
-
- check = self.checkDownload({"html": re.compile("\A<!DOCTYPE html PUBLIC")})
- if check == "html":
- self.logWarning("Redirected to a HTML page, wait 10 minutes and retry")
- self.wait(10 * 60, True)
-
-
-getInfo = create_getInfo(UploadingCom)
diff --git a/module/plugins/hoster/UpstoreNet.py b/module/plugins/hoster/UpstoreNet.py
deleted file mode 100644
index 140024731..000000000
--- a/module/plugins/hoster/UpstoreNet.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.CaptchaService import ReCaptcha
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class UpstoreNet(SimpleHoster):
- __name__ = "UpstoreNet"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?upstore\.net/'
-
- __description__ = """Upstore.Net File Download Hoster"""
- __author_name__ = "igel"
- __author_mail__ = "igelkun@myopera.com"
-
- FILE_INFO_PATTERN = r'<div class="comment">.*?</div>\s*\n<h2 style="margin:0">(?P<N>.*?)</h2>\s*\n<div class="comment">\s*\n\s*(?P<S>[\d.]+) (?P<U>\w+)'
- OFFLINE_PATTERN = r'<span class="error">File not found</span>'
-
- WAIT_PATTERN = r'var sec = (\d+)'
- CHASH_PATTERN = r'<input type="hidden" name="hash" value="([^"]*)">'
- LINK_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
-
-
- def handleFree(self):
- # STAGE 1: get link to continue
- m = re.search(self.CHASH_PATTERN, self.html)
- if m is None:
- self.parseError("could not detect hash")
- chash = m.group(1)
- self.logDebug("read hash " + chash)
- # continue to stage2
- post_data = {'hash': chash, 'free': 'Slow download'}
- self.html = self.load(self.pyfile.url, post=post_data, decode=True)
-
- # STAGE 2: solv captcha and wait
- # first get the infos we need: recaptcha key and wait time
- recaptcha = ReCaptcha(self)
- if not recaptcha.detect_key(self.html):
- self.parseError("could not find recaptcha pattern")
- self.logDebug("using captcha key " + recaptcha.recaptcha_key)
- # try the captcha 5 times
- for i in xrange(5):
- m = re.search(self.WAIT_PATTERN, self.html)
- if m is None:
- self.parseError("could not find wait pattern")
- wait_time = m.group(1)
-
- # then, do the waiting
- self.wait(wait_time)
-
- # then, handle the captcha
- challenge, code = recaptcha.challenge()
- post_data['recaptcha_challenge_field'] = challenge
- post_data['recaptcha_response_field'] = code
-
- self.html = self.load(self.pyfile.url, post=post_data, decode=True)
-
- # STAGE 3: get direct link
- m = re.search(self.LINK_PATTERN, self.html, re.DOTALL)
- if m:
- break
-
- if m is None:
- self.parseError("could not detect direct link")
-
- direct = m.group(1)
- self.logDebug('found direct link: ' + direct)
- self.download(direct, disposition=True)
-
-
-getInfo = create_getInfo(UpstoreNet)
diff --git a/module/plugins/hoster/UptoboxCom.py b/module/plugins/hoster/UptoboxCom.py
deleted file mode 100644
index 6d86c5559..000000000
--- a/module/plugins/hoster/UptoboxCom.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-from module.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
-from module.utils import html_unescape
-
-
-class UptoboxCom(XFileSharingPro):
- __name__ = "UptoboxCom"
- __type__ = "hoster"
- __version__ = "0.09"
-
- __pattern__ = r'https?://(?:www\.)?uptobox\.com/\w+'
-
- __description__ = """Uptobox.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- HOSTER_NAME = "uptobox.com"
-
- FILE_INFO_PATTERN = r'"para_title">(?P<N>.+) \((?P<S>[\d\.]+) (?P<U>\w+)\)'
- OFFLINE_PATTERN = r'>(File not found|Access Denied|404 Not Found)'
- TEMP_OFFLINE_PATTERN = r'>This server is in maintenance mode'
-
- WAIT_PATTERN = r'>(\d+)</span> seconds<'
-
- LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
-
-
- def handleCaptcha(self, inputs):
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- captcha = SolveMedia(self)
- inputs['adcopy_challenge'], inputs['adcopy_response'] = captcha.challenge(captcha_key)
- return 4
- else:
- m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
- if m:
- captcha_url = m.group(1)
- inputs['code'] = self.decryptCaptcha(captcha_url)
- return 2
- else:
- m = re.search(self.CAPTCHA_DIV_PATTERN, self.html, re.DOTALL)
- if m:
- captcha_div = m.group(1)
- self.logDebug(captcha_div)
- numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>',
- html_unescape(captcha_div))
- inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
- self.logDebug("CAPTCHA", inputs['code'], numerals)
- return 3
- else:
- m = re.search(self.RECAPTCHA_URL_PATTERN, self.html)
- if m:
- recaptcha_key = unquote(m.group(1))
- self.logDebug("RECAPTCHA KEY: %s" % recaptcha_key)
- recaptcha = ReCaptcha(self)
- inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(
- recaptcha_key)
- return 1
- return 0
-
-
-getInfo = create_getInfo(UptoboxCom)
diff --git a/module/plugins/hoster/VeehdCom.py b/module/plugins/hoster/VeehdCom.py
deleted file mode 100644
index 66c258439..000000000
--- a/module/plugins/hoster/VeehdCom.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class VeehdCom(Hoster):
- __name__ = "VeehdCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://veehd\.com/video/\d+_\S+'
- __config__ = [("filename_spaces", "bool", "Allow spaces in filename", False),
- ("replacement_char", "str", "Filename replacement character", "_")]
-
- __description__ = """Veehd.com hoster plugin"""
- __author_name__ = "cat"
- __author_mail__ = "cat@pyload"
-
-
- def _debug(self, msg):
- self.logDebug('[%s] %s' % (self.__name__, msg))
-
- def setup(self):
- self.multiDL = True
- self.req.canContinue = True
-
- def process(self, pyfile):
- self.download_html()
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- def download_html(self):
- url = self.pyfile.url
- self._debug("Requesting page: %s" % (repr(url),))
- self.html = self.load(url)
-
- def file_exists(self):
- if not self.html:
- self.download_html()
-
- if '<title>Veehd</title>' in self.html:
- return False
- return True
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- m = re.search(r'<title[^>]*>([^<]+) on Veehd</title>', self.html)
- if m is None:
- self.fail("video title not found")
-
- name = m.group(1)
-
- # replace unwanted characters in filename
- if self.getConfig('filename_spaces'):
- pattern = '[^0-9A-Za-z\.\ ]+'
- else:
- pattern = '[^0-9A-Za-z\.]+'
-
- return re.sub(pattern, self.getConfig('replacement_char'), name) + '.avi'
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- m = re.search(r'<embed type="video/divx" src="(http://([^/]*\.)?veehd\.com/dl/[^"]+)"',
- self.html)
- if m is None:
- self.fail("embedded video url not found")
-
- return m.group(1)
diff --git a/module/plugins/hoster/VeohCom.py b/module/plugins/hoster/VeohCom.py
deleted file mode 100644
index fcd5e90a4..000000000
--- a/module/plugins/hoster/VeohCom.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class VeohCom(SimpleHoster):
- __name__ = "VeohCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
- __config__ = [("quality", "Low;High;Auto", "Quality", "Auto")]
-
- __description__ = """Veoh.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- FILE_NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
- OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.veoh.com/watch/\g<ID>')]
-
- SH_COOKIES = [(".veoh.com", "lassieLocale", "en")]
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = -1
-
- def handleFree(self):
- quality = self.getConfig("quality")
- if quality == "Auto":
- quality = ("High", "Low")
- for q in quality:
- pattern = r'"fullPreviewHash%sPath":"(.+?)"' % q
- m = re.search(pattern, self.html)
- if m:
- self.pyfile.name += ".mp4"
- link = m.group(1).replace("\\", "")
- self.logDebug("Download link: " + link)
- self.download(link)
- return
- else:
- self.logInfo("No %s quality video found" % q.upper())
- else:
- self.fail("No video found!")
-
-
-getInfo = create_getInfo(VeohCom)
diff --git a/module/plugins/hoster/VidPlayNet.py b/module/plugins/hoster/VidPlayNet.py
deleted file mode 100644
index eb7a13e4c..000000000
--- a/module/plugins/hoster/VidPlayNet.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://vidplay.net/38lkev0h3jv0
-
-from module.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
-
-
-class VidPlayNet(XFileSharingPro):
- __name__ = "VidPlayNet"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'https?://(?:www\.)?vidplay\.net/\w{12}'
-
- __description__ = """VidPlay.net hoster plugin"""
- __author_name__ = "t4skforce"
- __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
-
- HOSTER_NAME = "vidplay.net"
-
- OFFLINE_PATTERN = r'<b>File Not Found</b><br>\s*<br>'
- FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
- LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<&]+)' % HOSTER_NAME
-
-
-getInfo = create_getInfo(VidPlayNet)
diff --git a/module/plugins/hoster/VimeoCom.py b/module/plugins/hoster/VimeoCom.py
deleted file mode 100644
index 145d9053f..000000000
--- a/module/plugins/hoster/VimeoCom.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class VimeoCom(SimpleHoster):
- __name__ = "VimeoCom"
- __type__ = "hoster"
- __version__ = "0.02"
-
- __pattern__ = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
- __config__ = [("quality", "Lowest;Mobile;SD;HD;Highest", "Quality", "Highest"),
- ("original", "bool", "Try to download the original file first", True)]
-
- __description__ = """Vimeo.com hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
- FILE_NAME_PATTERN = r'<title>(?P<N>.+) on Vimeo<'
- OFFLINE_PATTERN = r'class="exception_header"'
- TEMP_OFFLINE_PATTERN = r'Please try again in a few minutes.<'
-
- FILE_URL_REPLACEMENTS = [(__pattern__, r'https://www.vimeo.com/\g<ID>')]
-
- SH_COOKIES = [(".vimeo.com", "language", "en")]
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = -1
-
- def handleFree(self):
- password = self.getPassword()
-
- if self.js and 'class="btn iconify_down_b"' in self.html:
- html = self.js.eval(self.load(self.pyfile.url, get={'action': "download", 'password': password}, decode=True))
- pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
- else:
- id = re.match(self.__pattern__, self.pyfile.url).group("ID")
- html = self.load("https://player.vimeo.com/video/" + id, get={'password': password})
- pattern = r'"(?P<QL>\w+)":{"profile".*?"(?P<URL>http://pdl\.vimeocdn\.com.+?)"'
-
- link = dict([(l.group('QL').lower(), l.group('URL')) for l in re.finditer(pattern, html)])
-
- if self.getConfig("original"):
- if "original" in link:
- self.download(link[q])
- return
- else:
- self.logInfo("Original file not downloadable")
-
- quality = self.getConfig("quality")
- if quality == "Highest":
- qlevel = ("hd", "sd", "mobile")
- elif quality == "Lowest":
- qlevel = ("mobile", "sd", "hd")
- else:
- qlevel = quality.lower()
-
- for q in qlevel:
- if q in link:
- self.download(link[q])
- return
- else:
- self.logInfo("No %s quality video found" % q.upper())
- else:
- self.fail("No video found!")
-
-
-getInfo = create_getInfo(VimeoCom)
diff --git a/module/plugins/hoster/Vipleech4uCom.py b/module/plugins/hoster/Vipleech4uCom.py
deleted file mode 100644
index da7131d39..000000000
--- a/module/plugins/hoster/Vipleech4uCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class Vipleech4uCom(DeadHoster):
- __name__ = "Vipleech4uCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
-
- __description__ = """Vipleech4u.com hoster plugin"""
- __author_name__ = "Kagenoshin"
- __author_mail__ = "kagenoshin@gmx.ch"
-
-
-getInfo = create_getInfo(Vipleech4uCom)
diff --git a/module/plugins/hoster/WarserverCz.py b/module/plugins/hoster/WarserverCz.py
deleted file mode 100644
index 8a565d777..000000000
--- a/module/plugins/hoster/WarserverCz.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class WarserverCz(DeadHoster):
- __name__ = "WarserverCz"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'http://(?:www\.)?warserver\.cz/stahnout/\d+'
-
- __description__ = """Warserver.cz hoster plugin"""
- __author_name__ = "Walter Purcaro"
- __author_mail__ = "vuolter@gmail.com"
-
-
-getInfo = create_getInfo(WarserverCz)
diff --git a/module/plugins/hoster/WebshareCz.py b/module/plugins/hoster/WebshareCz.py
deleted file mode 100644
index a3918d5c4..000000000
--- a/module/plugins/hoster/WebshareCz.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.network.RequestFactory import getRequest
-from module.plugins.internal.SimpleHoster import SimpleHoster
-
-
-def getInfo(urls):
- h = getRequest()
- for url in urls:
- h.load(url)
- fid = re.search(WebshareCz.__pattern__, url).group('ID')
- api_data = h.load('https://webshare.cz/api/file_info/', post={'ident': fid})
- if 'File not found' in api_data:
- file_info = (url, 0, 1, url)
- else:
- name = re.search('<name>(.+)</name>', api_data).group(1)
- size = re.search('<size>(.+)</size>', api_data).group(1)
- file_info = (name, size, 2, url)
- yield file_info
-
-
-class WebshareCz(SimpleHoster):
- __name__ = "WebshareCz"
- __type__ = "hoster"
- __version__ = "0.13"
-
- __pattern__ = r'https?://(?:www\.)?webshare.cz/(?:#/)?file/(?P<ID>\w+)'
-
- __description__ = """WebShare.cz hoster plugin"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def handleFree(self):
- api_data = self.load('https://webshare.cz/api/file_link/', post={'ident': self.fid})
- self.logDebug("API data: " + api_data)
- m = re.search('<link>(.+)</link>', api_data)
- if m is None:
- self.parseError('Unable to detect direct link')
- direct = m.group(1)
- self.logDebug("Direct link: " + direct)
- self.download(direct, disposition=True)
-
- def getFileInfo(self):
- self.logDebug("URL: %s" % self.pyfile.url)
-
- self.fid = re.match(self.__pattern__, self.pyfile.url).group('ID')
-
- self.load(self.pyfile.url)
- api_data = self.load('https://webshare.cz/api/file_info/', post={'ident': self.fid})
-
- if 'File not found' in api_data:
- self.offline()
- else:
- self.pyfile.name = re.search('<name>(.+)</name>', api_data).group(1)
- self.pyfile.size = re.search('<size>(.+)</size>', api_data).group(1)
-
- self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size))
diff --git a/module/plugins/hoster/WrzucTo.py b/module/plugins/hoster/WrzucTo.py
deleted file mode 100644
index 3b26b1a02..000000000
--- a/module/plugins/hoster/WrzucTo.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import HTTPHEADER
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class WrzucTo(SimpleHoster):
- __name__ = "WrzucTo"
- __type__ = "hoster"
- __version__ = "0.01"
-
- __pattern__ = r'http://(?:www\.)?wrzuc\.to/([a-zA-Z0-9]+(\.wt|\.html)|(\w+/?linki/[a-zA-Z0-9]+))'
-
- __description__ = """Wrzuc.to hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
- FILE_SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
-
- SH_COOKIES = [(".wrzuc.to", "language", "en")]
-
-
- def setup(self):
- self.multiDL = True
-
- def handleFree(self):
- data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
- if len(data) != 2:
- self.parseError('File ID')
-
- self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
- self.req.http.lastURL = self.pyfile.url
- self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
-
- self.req.http.lastURL = self.pyfile.url
- self.html = self.load("http://www.wrzuc.to/ajax/server/download_link", post={"file": data['file']})
-
- data.update(re.findall(r'"(download_link|server_id)":"(.*?)"', self.html))
- if len(data) != 4:
- self.parseError('Download URL')
-
- download_url = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
- self.logDebug("Download URL: %s" % download_url)
- self.download(download_url)
-
-
-getInfo = create_getInfo(WrzucTo)
diff --git a/module/plugins/hoster/WuploadCom.py b/module/plugins/hoster/WuploadCom.py
deleted file mode 100644
index b06318d42..000000000
--- a/module/plugins/hoster/WuploadCom.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class WuploadCom(DeadHoster):
- __name__ = "WuploadCom"
- __type__ = "hoster"
- __version__ = "0.23"
-
- __pattern__ = r'http://(?:www\.)?wupload\..*?/file/(([a-z][0-9]+/)?[0-9]+)(/.*)?'
-
- __description__ = """Wupload.com hoster plugin"""
- __author_name__ = ("jeix", "Paul King")
- __author_mail__ = ("jeix@hasnomail.de", "")
-
-
-getInfo = create_getInfo(WuploadCom)
diff --git a/module/plugins/hoster/X7To.py b/module/plugins/hoster/X7To.py
deleted file mode 100644
index cbd4b8b8e..000000000
--- a/module/plugins/hoster/X7To.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.internal.DeadHoster import DeadHoster, create_getInfo
-
-
-class X7To(DeadHoster):
- __name__ = "X7To"
- __type__ = "hoster"
- __version__ = "0.41"
-
- __pattern__ = r'http://(?:www\.)?x7.to/'
-
- __description__ = """X7.to hoster plugin"""
- __author_name__ = "ernieb"
- __author_mail__ = "ernieb"
-
-
-getInfo = create_getInfo(X7To)
diff --git a/module/plugins/hoster/XFileSharingPro.py b/module/plugins/hoster/XFileSharingPro.py
deleted file mode 100644
index 25492fb49..000000000
--- a/module/plugins/hoster/XFileSharingPro.py
+++ /dev/null
@@ -1,324 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from pycurl import FOLLOWLOCATION, LOW_SPEED_TIME
-from random import random
-from urllib import unquote
-from urlparse import urlparse
-
-from module.network.RequestFactory import getURL
-from module.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, PluginParseError, replace_patterns
-from module.utils import html_unescape
-
-
-class XFileSharingPro(SimpleHoster):
- """
- Common base for XFileSharingPro hosters like EasybytezCom, CramitIn, FiledinoCom...
- Some hosters may work straight away when added to __pattern__
- However, most of them will NOT work because they are either down or running a customized version
- """
- __name__ = "XFileSharingPro"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __pattern__ = r'^unmatchable$'
-
- __description__ = """XFileSharingPro base hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- FILE_INFO_PATTERN = r'<tr><td align=right><b>Filename:</b></td><td nowrap>(?P<N>[^<]+)</td></tr>\s*.*?<small>\((?P<S>[^<]+)\)</small>'
- FILE_NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>[^"]+)"'
- FILE_SIZE_PATTERN = r'You have requested .*\((?P<S>[\d\.\,]+) ?(?P<U>\w+)?\)</font>'
- OFFLINE_PATTERN = r'>\w+ (Not Found|file (was|has been) removed)'
-
- WAIT_PATTERN = r'<span id="countdown_str">.*?>(\d+)</span>'
-
- OVR_LINK_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
-
- CAPTCHA_URL_PATTERN = r'(http://[^"\']+?/captchas?/[^"\']+)'
- RECAPTCHA_URL_PATTERN = r'http://[^"\']+?recaptcha[^"\']+?\?k=([^"\']+)"'
- CAPTCHA_DIV_PATTERN = r'>Enter code.*?<div.*?>(.*?)</div>'
- SOLVEMEDIA_PATTERN = r'http:\/\/api\.solvemedia\.com\/papi\/challenge\.script\?k=(.*?)"'
-
- ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.*?)</'
-
-
- def setup(self):
- if self.__name__ == "XFileSharingPro":
- self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
- self.multiDL = True
- else:
- self.resumeDownload = self.multiDL = self.premium
-
- self.chunkLimit = 1
-
- def process(self, pyfile):
- self.prepare()
-
- pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
-
- if not re.match(self.__pattern__, pyfile.url):
- if self.premium:
- self.handleOverriden()
- else:
- self.fail("Only premium users can download from other hosters with %s" % self.HOSTER_NAME)
- else:
- try:
- # Due to a 0.4.9 core bug self.load would use cookies even if
- # cookies=False. Workaround using getURL to avoid cookies.
- # Can be reverted in 0.5 as the cookies bug has been fixed.
- self.html = getURL(pyfile.url, decode=True)
- self.file_info = self.getFileInfo()
- except PluginParseError:
- self.file_info = None
-
- self.location = self.getDirectDownloadLink()
-
- if not self.file_info:
- pyfile.name = html_unescape(unquote(urlparse(
- self.location if self.location else pyfile.url).path.split("/")[-1]))
-
- if self.location:
- self.startDownload(self.location)
- elif self.premium:
- self.handlePremium()
- else:
- self.handleFree()
-
- def prepare(self):
- """ Initialize important variables """
- if not hasattr(self, "HOSTER_NAME"):
- self.HOSTER_NAME = re.match(self.__pattern__, self.pyfile.url).group(1)
- if not hasattr(self, "LINK_PATTERN"):
- self.LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<]+)' % self.HOSTER_NAME
-
- self.captcha = self.errmsg = None
- self.passwords = self.getPassword().splitlines()
-
- def getDirectDownloadLink(self):
- """ Get download link for premium users with direct download enabled """
- self.req.http.lastURL = self.pyfile.url
-
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
- self.html = self.load(self.pyfile.url, cookies=True, decode=True)
- self.header = self.req.http.header
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
-
- location = None
- m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
- if m and re.match(self.LINK_PATTERN, m.group(1)):
- location = m.group(1).strip()
-
- return location
-
- def handleFree(self):
- url = self.getDownloadLink()
- self.logDebug("Download URL: %s" % url)
- self.startDownload(url)
-
- def getDownloadLink(self):
- for i in xrange(5):
- self.logDebug("Getting download link: #%d" % i)
- data = self.getPostParameters()
-
- self.req.http.c.setopt(FOLLOWLOCATION, 0)
- self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
- self.header = self.req.http.header
- self.req.http.c.setopt(FOLLOWLOCATION, 1)
-
- m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
- if m:
- break
-
- m = re.search(self.LINK_PATTERN, self.html, re.S)
- if m:
- break
-
- else:
- if self.errmsg and 'captcha' in self.errmsg:
- self.fail("No valid captcha code entered")
- else:
- self.fail("Download link not found")
-
- return m.group(1)
-
- def handlePremium(self):
- self.html = self.load(self.pyfile.url, post=self.getPostParameters())
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError('DIRECT LINK')
- self.startDownload(m.group(1))
-
- def handleOverriden(self):
- #only tested with easybytez.com
- self.html = self.load("http://www.%s/" % self.HOSTER_NAME)
- action, inputs = self.parseHtmlForm('')
- upload_id = "%012d" % int(random() * 10 ** 12)
- action += upload_id + "&js_on=1&utype=prem&upload_type=url"
- inputs['tos'] = '1'
- inputs['url_mass'] = self.pyfile.url
- inputs['up1oad_type'] = 'url'
-
- self.logDebug(self.HOSTER_NAME, action, inputs)
- #wait for file to upload to easybytez.com
- self.req.http.c.setopt(LOW_SPEED_TIME, 600)
- self.html = self.load(action, post=inputs)
-
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- self.parseError('TEXTAREA')
- self.logDebug(self.HOSTER_NAME, inputs)
- if inputs['st'] == 'OK':
- self.html = self.load(action, post=inputs)
- elif inputs['st'] == 'Can not leech file':
- self.retry(max_tries=20, wait_time=3 * 60, reason=inputs['st'])
- else:
- self.fail(inputs['st'])
-
- #get easybytez.com link for uploaded file
- m = re.search(self.OVR_LINK_PATTERN, self.html)
- if m is None:
- self.parseError('DIRECT LINK (OVR)')
- self.pyfile.url = m.group(1)
- header = self.load(self.pyfile.url, just_header=True)
- if 'location' in header: # Direct link
- self.startDownload(self.pyfile.url)
- else:
- self.retry()
-
- def startDownload(self, link):
- link = link.strip()
- if self.captcha:
- self.correctCaptcha()
- self.logDebug('DIRECT LINK: %s' % link)
- self.download(link, disposition=True)
-
- def checkErrors(self):
- m = re.search(self.ERROR_PATTERN, self.html)
- if m:
- self.errmsg = m.group(1)
- self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
-
- if 'wait' in self.errmsg:
- wait_time = sum([int(v) * {"hour": 3600, "minute": 60, "second": 1}[u] for v, u in
- re.findall(r'(\d+)\s*(hour|minute|second)', self.errmsg)])
- self.wait(wait_time, True)
- elif 'captcha' in self.errmsg:
- self.invalidCaptcha()
- elif 'premium' in self.errmsg and 'require' in self.errmsg:
- self.fail("File can be downloaded by premium users only")
- elif 'limit' in self.errmsg:
- self.wait(1 * 60 * 60, True)
- self.retry(25)
- elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
- self.retry()
- elif 'maintenance' in self.errmsg:
- self.tempOffline()
- elif 'download files up to' in self.errmsg:
- self.fail("File too large for free download")
- else:
- self.fail(self.errmsg)
-
- else:
- self.errmsg = None
-
- return self.errmsg
-
- def getPostParameters(self):
- for _ in xrange(3):
- if not self.errmsg:
- self.checkErrors()
-
- if hasattr(self, "FORM_PATTERN"):
- action, inputs = self.parseHtmlForm(self.FORM_PATTERN)
- else:
- action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
-
- if not inputs:
- action, inputs = self.parseHtmlForm('F1')
- if not inputs:
- if self.errmsg:
- self.retry()
- else:
- self.parseError("Form not found")
-
- self.logDebug(self.HOSTER_NAME, inputs)
-
- if 'op' in inputs and inputs['op'] in ("download2", "download3"):
- if "password" in inputs:
- if self.passwords:
- inputs['password'] = self.passwords.pop(0)
- else:
- self.fail("No or invalid passport")
-
- if not self.premium:
- m = re.search(self.WAIT_PATTERN, self.html)
- if m:
- wait_time = int(m.group(1)) + 1
- self.setWait(wait_time, False)
- else:
- wait_time = 0
-
- self.captcha = self.handleCaptcha(inputs)
-
- if wait_time:
- self.wait()
-
- self.errmsg = None
- return inputs
-
- else:
- inputs['referer'] = self.pyfile.url
-
- if self.premium:
- inputs['method_premium'] = "Premium Download"
- if 'method_free' in inputs:
- del inputs['method_free']
- else:
- inputs['method_free'] = "Free Download"
- if 'method_premium' in inputs:
- del inputs['method_premium']
-
- self.html = self.load(self.pyfile.url, post=inputs, ref=True)
- self.errmsg = None
-
- else:
- self.parseError('FORM: %s' % (inputs['op'] if 'op' in inputs else 'UNKNOWN'))
-
- def handleCaptcha(self, inputs):
- m = re.search(self.RECAPTCHA_URL_PATTERN, self.html)
- if m:
- recaptcha_key = unquote(m.group(1))
- self.logDebug("RECAPTCHA KEY: %s" % recaptcha_key)
- recaptcha = ReCaptcha(self)
- inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(recaptcha_key)
- return 1
- else:
- m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
- if m:
- captcha_url = m.group(1)
- inputs['code'] = self.decryptCaptcha(captcha_url)
- return 2
- else:
- m = re.search(self.CAPTCHA_DIV_PATTERN, self.html, re.DOTALL)
- if m:
- captcha_div = m.group(1)
- self.logDebug(captcha_div)
- numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
- inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
- self.logDebug("CAPTCHA", inputs['code'], numerals)
- return 3
- else:
- m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
- if m:
- captcha_key = m.group(1)
- captcha = SolveMedia(self)
- inputs['adcopy_challenge'], inputs['adcopy_response'] = captcha.challenge(captcha_key)
- return 4
- return 0
-
-
-getInfo = create_getInfo(XFileSharingPro)
diff --git a/module/plugins/hoster/XHamsterCom.py b/module/plugins/hoster/XHamsterCom.py
deleted file mode 100644
index 3e002a0bb..000000000
--- a/module/plugins/hoster/XHamsterCom.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.common.json_layer import json_loads
-from module.plugins.Hoster import Hoster
-
-
-def clean_json(json_expr):
- json_expr = re.sub('[\n\r]', '', json_expr)
- json_expr = re.sub(' +', '', json_expr)
- json_expr = re.sub('\'', '"', json_expr)
-
- return json_expr
-
-
-class XHamsterCom(Hoster):
- __name__ = "XHamsterCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?xhamster\.com/movies/.+'
- __config__ = [("type", ".mp4;.flv", "Preferred type", ".mp4")]
-
- __description__ = """XHamster.com hoster plugin"""
- __author_name__ = None
- __author_mail__ = None
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- if not self.file_exists():
- self.offline()
-
- if self.getConfig("type"):
- self.desired_fmt = self.getConfig("type")
-
- pyfile.name = self.get_file_name() + self.desired_fmt
- self.download(self.get_file_url())
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- flashvar_pattern = re.compile('flashvars = ({.*?});', re.DOTALL)
- json_flashvar = flashvar_pattern.search(self.html)
-
- if not json_flashvar:
- self.fail("Parse error (flashvars)")
-
- j = clean_json(json_flashvar.group(1))
- flashvars = json_loads(j)
-
- if flashvars['srv']:
- srv_url = flashvars['srv'] + '/'
- else:
- self.fail("Parse error (srv_url)")
-
- if flashvars['url_mode']:
- url_mode = flashvars['url_mode']
- else:
- self.fail("Parse error (url_mode)")
-
- if self.desired_fmt == ".mp4":
- file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
- if file_url is None:
- self.fail("Parse error (file_url)")
- file_url = file_url.group(1)
- long_url = srv_url + file_url
- self.logDebug("long_url: %s" % long_url)
- else:
- if flashvars['file']:
- file_url = unquote(flashvars['file'])
- else:
- self.fail("Parse error (file_url)")
-
- if url_mode == '3':
- long_url = file_url
- self.logDebug("long_url: %s" % long_url)
- else:
- long_url = srv_url + "key=" + file_url
- self.logDebug("long_url: %s" % long_url)
-
- return long_url
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- pattern = r"<title>(.*?) - xHamster\.com</title>"
- name = re.search(pattern, self.html)
- if name is None:
- pattern = r"<h1 >(.*)</h1>"
- name = re.search(pattern, self.html)
- if name is None:
- pattern = r"http://[www.]+xhamster\.com/movies/.*/(.*?)\.html?"
- name = re.match(file_name_pattern, self.pyfile.url)
- if name is None:
- pattern = r"<div id=\"element_str_id\" style=\"display:none;\">(.*)</div>"
- name = re.search(pattern, self.html)
- if name is None:
- return "Unknown"
-
- return name.group(1)
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"(.*Video not found.*)", self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/XVideosCom.py b/module/plugins/hoster/XVideosCom.py
deleted file mode 100644
index 4f2611740..000000000
--- a/module/plugins/hoster/XVideosCom.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.Hoster import Hoster
-
-
-class XVideosCom(Hoster):
- __name__ = "XVideos.com"
- __type__ = "hoster"
- __version__ = "0.1"
-
- __pattern__ = r'http://(?:www\.)?xvideos\.com/video([0-9]+)/.*'
-
- __description__ = """XVideos.com hoster plugin"""
- __author_name__ = None
- __author_mail__ = None
-
-
- def process(self, pyfile):
- site = self.load(pyfile.url)
- pyfile.name = "%s (%s).flv" % (
- re.search(r"<h2>([^<]+)<span", site).group(1),
- re.match(self.__pattern__, pyfile.url).group(1),
- )
- self.download(unquote(re.search(r"flv_url=([^&]+)&", site).group(1)))
diff --git a/module/plugins/hoster/Xdcc.py b/module/plugins/hoster/Xdcc.py
deleted file mode 100644
index a26e239ad..000000000
--- a/module/plugins/hoster/Xdcc.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-import socket
-import struct
-import sys
-import time
-
-from os import makedirs
-from os.path import exists, join
-from select import select
-
-from module.plugins.Hoster import Hoster
-from module.utils import save_join
-
-
-class Xdcc(Hoster):
- __name__ = "Xdcc"
- __type__ = "hoster"
- __version__ = "0.32"
-
- __config__ = [("nick", "str", "Nickname", "pyload"),
- ("ident", "str", "Ident", "pyloadident"),
- ("realname", "str", "Realname", "pyloadreal")]
-
- __description__ = """Download from IRC XDCC bot"""
- __author_name__ = "jeix"
- __author_mail__ = "jeix@hasnomail.com"
-
-
- def setup(self):
- self.debug = 0 # 0,1,2
- self.timeout = 30
- self.multiDL = False
-
- def process(self, pyfile):
- # change request type
- self.req = pyfile.m.core.requestFactory.getRequest(self.__name__, type="XDCC")
-
- self.pyfile = pyfile
- for _ in xrange(0, 3):
- try:
- nmn = self.doDownload(pyfile.url)
- self.logDebug("%s: Download of %s finished." % (self.__name__, nmn))
- return
- except socket.error, e:
- if hasattr(e, "errno"):
- errno = e.errno
- else:
- errno = e.args[0]
-
- if errno == 10054:
- self.logDebug("XDCC: Server blocked our ip, retry in 5 min")
- self.setWait(300)
- self.wait()
- continue
-
- self.fail("Failed due to socket errors. Code: %d" % errno)
-
- self.fail("Server blocked our ip, retry again later manually")
-
- def doDownload(self, url):
- self.pyfile.setStatus("waiting") # real link
-
- m = re.match(r'xdcc://(.*?)/#?(.*?)/(.*?)/#?(\d+)/?', url)
- server = m.group(1)
- chan = m.group(2)
- bot = m.group(3)
- pack = m.group(4)
- nick = self.getConfig('nick')
- ident = self.getConfig('ident')
- real = self.getConfig('realname')
-
- temp = server.split(':')
- ln = len(temp)
- if ln == 2:
- host, port = temp
- elif ln == 1:
- host, port = temp[0], 6667
- else:
- self.fail("Invalid hostname for IRC Server (%s)" % server)
-
- #######################
- # CONNECT TO IRC AND IDLE FOR REAL LINK
- dl_time = time.time()
-
- sock = socket.socket()
- sock.connect((host, int(port)))
- if nick == "pyload":
- nick = "pyload-%d" % (time.time() % 1000) # last 3 digits
- sock.send("NICK %s\r\n" % nick)
- sock.send("USER %s %s bla :%s\r\n" % (ident, host, real))
- time.sleep(3)
- sock.send("JOIN #%s\r\n" % chan)
- sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
-
- # IRC recv loop
- readbuffer = ""
- done = False
- retry = None
- m = None
- while True:
-
- # done is set if we got our real link
- if done:
- break
-
- if retry:
- if time.time() > retry:
- retry = None
- dl_time = time.time()
- sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
-
- else:
- if (dl_time + self.timeout) < time.time(): # todo: add in config
- sock.send("QUIT :byebye\r\n")
- sock.close()
- self.fail("XDCC Bot did not answer")
-
- fdset = select([sock], [], [], 0)
- if sock not in fdset[0]:
- continue
-
- readbuffer += sock.recv(1024)
- temp = readbuffer.split("\n")
- readbuffer = temp.pop()
-
- for line in temp:
- if self.debug is 2:
- print "*> " + unicode(line, errors='ignore')
- line = line.rstrip()
- first = line.split()
-
- if first[0] == "PING":
- sock.send("PONG %s\r\n" % first[1])
-
- if first[0] == "ERROR":
- self.fail("IRC-Error: %s" % line)
-
- msg = line.split(None, 3)
- if len(msg) != 4:
- continue
-
- msg = {
- "origin": msg[0][1:],
- "action": msg[1],
- "target": msg[2],
- "text": msg[3][1:]
- }
-
- if nick == msg['target'][0:len(nick)] and "PRIVMSG" == msg['action']:
- if msg['text'] == "\x01VERSION\x01":
- self.logDebug("XDCC: Sending CTCP VERSION.")
- sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
- elif msg['text'] == "\x01TIME\x01":
- self.logDebug("Sending CTCP TIME.")
- sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
- elif msg['text'] == "\x01LAG\x01":
- pass # don't know how to answer
-
- if not (bot == msg['origin'][0:len(bot)]
- and nick == msg['target'][0:len(nick)]
- and msg['action'] in ("PRIVMSG", "NOTICE")):
- continue
-
- if self.debug is 1:
- print "%s: %s" % (msg['origin'], msg['text'])
-
- if "You already requested that pack" in msg['text']:
- retry = time.time() + 300
-
- if "you must be on a known channel to request a pack" in msg['text']:
- self.fail("Wrong channel")
-
- m = re.match('\x01DCC SEND (.*?) (\d+) (\d+)(?: (\d+))?\x01', msg['text'])
- if m:
- done = True
-
- # get connection data
- ip = socket.inet_ntoa(struct.pack('L', socket.ntohl(int(m.group(2)))))
- port = int(m.group(3))
- packname = m.group(1)
-
- if len(m.groups()) > 3:
- self.req.filesize = int(m.group(4))
-
- self.pyfile.name = packname
-
- download_folder = self.config['general']['download_folder']
- filename = save_join(download_folder, packname)
-
- self.logInfo("XDCC: Downloading %s from %s:%d" % (packname, ip, port))
-
- self.pyfile.setStatus("downloading")
- newname = self.req.download(ip, port, filename, sock, self.pyfile.setProgress)
- if newname and newname != filename:
- self.logInfo("%(name)s saved as %(newname)s" % {"name": self.pyfile.name, "newname": newname})
- filename = newname
-
- # kill IRC socket
- # sock.send("QUIT :byebye\r\n")
- sock.close()
-
- self.lastDownload = filename
- return self.lastDownload
diff --git a/module/plugins/hoster/YibaishiwuCom.py b/module/plugins/hoster/YibaishiwuCom.py
deleted file mode 100644
index c6c285367..000000000
--- a/module/plugins/hoster/YibaishiwuCom.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.common.json_layer import json_loads
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class YibaishiwuCom(SimpleHoster):
- __name__ = "YibaishiwuCom"
- __type__ = "hoster"
- __version__ = "0.12"
-
- __pattern__ = r'http://(?:www\.)?(?:u\.)?115.com/file/(?P<ID>\w+)'
-
- __description__ = """115.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- FILE_NAME_PATTERN = r"file_name: '(?P<N>[^']+)'"
- FILE_SIZE_PATTERN = r"file_size: '(?P<S>[^']+)'"
- OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
-
- LINK_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
-
-
- def handleFree(self):
- m = re.search(self.LINK_PATTERN, self.html)
- if m is None:
- self.parseError("AJAX URL")
- url = m.group(1)
- self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
-
- response = json_loads(self.load("http://115.com" + url, decode=False))
- if "urls" in response:
- mirrors = response['urls']
- elif "data" in response:
- mirrors = response['data']
- else:
- mirrors = None
-
- for mr in mirrors:
- try:
- url = mr['url'].replace("\\", "")
- self.logDebug("Trying URL: " + url)
- self.download(url)
- break
- except:
- continue
- else:
- self.fail('No working link found')
-
-
-getInfo = create_getInfo(YibaishiwuCom)
diff --git a/module/plugins/hoster/YoupornCom.py b/module/plugins/hoster/YoupornCom.py
deleted file mode 100644
index 23b9b0a20..000000000
--- a/module/plugins/hoster/YoupornCom.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hoster import Hoster
-
-
-class YoupornCom(Hoster):
- __name__ = "YoupornCom"
- __type__ = "hoster"
- __version__ = "0.2"
-
- __pattern__ = r'http://(?:www\.)?youporn\.com/watch/.+'
-
- __description__ = """Youporn.com hoster plugin"""
- __author_name__ = "willnix"
- __author_mail__ = "willnix@pyload.org"
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
-
- if not self.file_exists():
- self.offline()
-
- pyfile.name = self.get_file_name()
- self.download(self.get_file_url())
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url, post={"user_choice": "Enter"}, cookies=False)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- if not self.html:
- self.download_html()
-
- return re.search(r'(http://download\.youporn\.com/download/\d+\?save=1)">', self.html).group(1)
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- file_name_pattern = r"<title>(.*) - Free Porn Videos - YouPorn</title>"
- return re.search(file_name_pattern, self.html).group(1).replace("&amp;", "&").replace("/", "") + '.flv'
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
- if re.search(r"(.*invalid video_id.*)", self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YourfilesTo.py b/module/plugins/hoster/YourfilesTo.py
deleted file mode 100644
index bdb91819f..000000000
--- a/module/plugins/hoster/YourfilesTo.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from urllib import unquote
-
-from module.plugins.Hoster import Hoster
-
-
-class YourfilesTo(Hoster):
- __name__ = "YourfilesTo"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'(http://)?(?:www\.)?yourfiles\.(to|biz)/\?d=[a-zA-Z0-9]+'
-
- __description__ = """Youfiles.to hoster plugin"""
- __author_name__ = ("jeix", "skydancer")
- __author_mail__ = ("jeix@hasnomail.de", "skydancer@hasnomail.de")
-
-
- def process(self, pyfile):
- self.pyfile = pyfile
- self.prepare()
- self.download(self.get_file_url())
-
- def prepare(self):
- if not self.file_exists():
- self.offline()
-
- self.pyfile.name = self.get_file_name()
-
- wait_time = self.get_waiting_time()
- self.setWait(wait_time)
- self.logDebug("%s: Waiting %d seconds." % (self.__name__, wait_time))
- self.wait()
-
- def get_waiting_time(self):
- if not self.html:
- self.download_html()
-
- #var zzipitime = 15;
- m = re.search(r'var zzipitime = (\d+);', self.html)
- if m:
- sec = int(m.group(1))
- else:
- sec = 0
-
- return sec
-
- def download_html(self):
- url = self.pyfile.url
- self.html = self.load(url)
-
- def get_file_url(self):
- """ returns the absolute downloadable filepath
- """
- url = re.search(r"var bla = '(.*?)';", self.html)
- if url:
- url = url.group(1)
- url = unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
- return url
- else:
- self.fail("absolute filepath could not be found. offline? ")
-
- def get_file_name(self):
- if not self.html:
- self.download_html()
-
- return re.search("<title>(.*)</title>", self.html).group(1)
-
- def file_exists(self):
- """ returns True or False
- """
- if not self.html:
- self.download_html()
-
- if re.search(r"HTTP Status 404", self.html) is not None:
- return False
- else:
- return True
diff --git a/module/plugins/hoster/YoutubeCom.py b/module/plugins/hoster/YoutubeCom.py
deleted file mode 100644
index 5ffef5531..000000000
--- a/module/plugins/hoster/YoutubeCom.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-import subprocess
-
-from urllib import unquote
-
-from module.plugins.Hoster import Hoster
-from module.plugins.internal.SimpleHoster import replace_patterns
-from module.utils import html_unescape
-
-
-def which(program):
- """Works exactly like the unix command which
-
- Courtesy of http://stackoverflow.com/a/377028/675646"""
-
- def is_exe(fpath):
- return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
-
- fpath, fname = os.path.split(program)
- if fpath:
- if is_exe(program):
- return program
- else:
- for path in os.environ['PATH'].split(os.pathsep):
- path = path.strip('"')
- exe_file = os.path.join(path, program)
- if is_exe(exe_file):
- return exe_file
-
- return None
-
-
-class YoutubeCom(Hoster):
- __name__ = "YoutubeCom"
- __type__ = "hoster"
- __version__ = "0.40"
-
- __pattern__ = r'https?://(?:[^/]*\.)?(?:youtube\.com|youtu\.be)/watch.*?[?&]v=.*'
- __config__ = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting", "hd"),
- ("fmt", "int", "FMT/ITAG Number (5-102, 0 for auto)", 0),
- (".mp4", "bool", "Allow .mp4", True),
- (".flv", "bool", "Allow .flv", True),
- (".webm", "bool", "Allow .webm", False),
- (".3gp", "bool", "Allow .3gp", False),
- ("3d", "bool", "Prefer 3D", False)]
-
- __description__ = """Youtube.com hoster plugin"""
- __author_name__ = ("spoob", "zoidberg")
- __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz")
-
- FILE_URL_REPLACEMENTS = [(r'youtu\.be/', 'youtube.com/')]
-
- # Invalid characters that must be removed from the file name
- invalidChars = u'\u2605:?><"|\\'
-
- # name, width, height, quality ranking, 3D
- formats = {5: (".flv", 400, 240, 1, False),
- 6: (".flv", 640, 400, 4, False),
- 17: (".3gp", 176, 144, 0, False),
- 18: (".mp4", 480, 360, 2, False),
- 22: (".mp4", 1280, 720, 8, False),
- 43: (".webm", 640, 360, 3, False),
- 34: (".flv", 640, 360, 4, False),
- 35: (".flv", 854, 480, 6, False),
- 36: (".3gp", 400, 240, 1, False),
- 37: (".mp4", 1920, 1080, 9, False),
- 38: (".mp4", 4096, 3072, 10, False),
- 44: (".webm", 854, 480, 5, False),
- 45: (".webm", 1280, 720, 7, False),
- 46: (".webm", 1920, 1080, 9, False),
- 82: (".mp4", 640, 360, 3, True),
- 83: (".mp4", 400, 240, 1, True),
- 84: (".mp4", 1280, 720, 8, True),
- 85: (".mp4", 1920, 1080, 9, True),
- 100: (".webm", 640, 360, 3, True),
- 101: (".webm", 640, 360, 4, True),
- 102: (".webm", 1280, 720, 8, True)}
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
-
- def process(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
- html = self.load(pyfile.url, decode=True)
-
- if re.search(r'<div id="player-unavailable" class="\s*player-width player-height\s*">', html):
- self.offline()
-
- if "We have been receiving a large volume of requests from your network." in html:
- self.tempOffline()
-
- #get config
- use3d = self.getConfig("3d")
- if use3d:
- quality = {"sd": 82, "hd": 84, "fullhd": 85, "240p": 83, "360p": 82,
- "480p": 82, "720p": 84, "1080p": 85, "3072p": 85}
- else:
- quality = {"sd": 18, "hd": 22, "fullhd": 37, "240p": 5, "360p": 18,
- "480p": 35, "720p": 22, "1080p": 37, "3072p": 38}
- desired_fmt = self.getConfig("fmt")
- if desired_fmt and desired_fmt not in self.formats:
- self.logWarning("FMT %d unknown - using default." % desired_fmt)
- desired_fmt = 0
- if not desired_fmt:
- desired_fmt = quality.get(self.getConfig("quality"), 18)
-
- #parse available streams
- streams = re.search(r'"url_encoded_fmt_stream_map": "(.*?)",', html).group(1)
- streams = [x.split('\u0026') for x in streams.split(',')]
- streams = [dict((y.split('=', 1)) for y in x) for x in streams]
- streams = [(int(x['itag']), unquote(x['url'])) for x in streams]
- #self.logDebug("Found links: %s" % streams)
- self.logDebug("AVAILABLE STREAMS: %s" % [x[0] for x in streams])
-
- #build dictionary of supported itags (3D/2D)
- allowed = lambda x: self.getConfig(self.formats[x][0])
- streams = [x for x in streams if x[0] in self.formats and allowed(x[0])]
- if not streams:
- self.fail("No available stream meets your preferences")
- fmt_dict = dict([x for x in streams if self.formats[x[0]][4] == use3d] or streams)
-
- self.logDebug("DESIRED STREAM: ITAG:%d (%s) %sfound, %sallowed" %
- (desired_fmt, "%s %dx%d Q:%d 3D:%s" % self.formats[desired_fmt],
- "" if desired_fmt in fmt_dict else "NOT ", "" if allowed(desired_fmt) else "NOT "))
-
- #return fmt nearest to quality index
- if desired_fmt in fmt_dict and allowed(desired_fmt):
- fmt = desired_fmt
- else:
- sel = lambda x: self.formats[x][3] # select quality index
- comp = lambda x, y: abs(sel(x) - sel(y))
-
- self.logDebug("Choosing nearest fmt: %s" % [(x, allowed(x), comp(x, desired_fmt)) for x in fmt_dict.keys()])
- fmt = reduce(lambda x, y: x if comp(x, desired_fmt) <= comp(y, desired_fmt) and
- sel(x) > sel(y) else y, fmt_dict.keys())
-
- self.logDebug("Chosen fmt: %s" % fmt)
- url = fmt_dict[fmt]
- self.logDebug("URL: %s" % url)
-
- #set file name
- file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv"
- file_name_pattern = '<meta name="title" content="(.+?)">'
- name = re.search(file_name_pattern, html).group(1).replace("/", "")
-
- # Cleaning invalid characters from the file name
- name = name.encode('ascii', 'replace')
- for c in self.invalidChars:
- name = name.replace(c, '_')
-
- pyfile.name = html_unescape(name)
-
- time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url)
- ffmpeg = which("ffmpeg")
- if ffmpeg and time:
- m, s = time.groups()[1:]
- if m is None:
- m = "0"
-
- pyfile.name += " (starting at %s:%s)" % (m, s)
- pyfile.name += file_suffix
-
- filename = self.download(url)
-
- if ffmpeg and time:
- inputfile = filename + "_"
- os.rename(filename, inputfile)
-
- subprocess.call([
- ffmpeg,
- "-ss", "00:%s:%s" % (m, s),
- "-i", inputfile,
- "-vcodec", "copy",
- "-acodec", "copy",
- filename])
- os.remove(inputfile)
diff --git a/module/plugins/hoster/ZDF.py b/module/plugins/hoster/ZDF.py
deleted file mode 100644
index d51a16a80..000000000
--- a/module/plugins/hoster/ZDF.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from xml.etree.ElementTree import fromstring
-
-from module.plugins.Hoster import Hoster
-
-
-# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/)
-class ZDF(Hoster):
- __name__ = "ZDF Mediathek"
- __type__ = "hoster"
- __version__ = "0.8"
-
- __pattern__ = r'http://(?:www\.)?zdf\.de/ZDFmediathek/[^0-9]*([0-9]+)[^0-9]*'
-
- __description__ = """ZDF.de hoster plugin"""
- __author_name__ = None
- __author_mail__ = None
-
- XML_API = "http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?id=%i"
-
-
- @staticmethod
- def video_key(video):
- return (
- int(video.findtext("videoBitrate", "0")),
- any(f.text == "progressive" for f in video.iter("facet")),
- )
-
- @staticmethod
- def video_valid(video):
- return video.findtext("url").startswith("http") and video.findtext("url").endswith(".mp4") and \
- video.findtext("facets/facet").startswith("progressive")
-
- @staticmethod
- def get_id(url):
- return int(re.search(r"[^0-9]*([0-9]{4,})[^0-9]*", url).group(1))
-
- def process(self, pyfile):
- xml = fromstring(self.load(self.XML_API % self.get_id(pyfile.url)))
-
- status = xml.findtext("./status/statuscode")
- if status != "ok":
- self.fail("Error retrieving manifest.")
-
- video = xml.find("video")
- title = video.findtext("information/title")
-
- pyfile.name = title
-
- target_url = sorted((v for v in video.iter("formitaet") if self.video_valid(v)),
- key=self.video_key)[-1].findtext("url")
-
- self.download(target_url)
diff --git a/module/plugins/hoster/ZeveraCom.py b/module/plugins/hoster/ZeveraCom.py
deleted file mode 100644
index b6b59b242..000000000
--- a/module/plugins/hoster/ZeveraCom.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hoster import Hoster
-
-
-class ZeveraCom(Hoster):
- __name__ = "ZeveraCom"
- __type__ = "hoster"
- __version__ = "0.21"
-
- __pattern__ = r'http://(?:www\.)?zevera.com/.*'
-
- __description__ = """Zevera.com hoster plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def setup(self):
- self.resumeDownload = self.multiDL = True
- self.chunkLimit = 1
-
- def process(self, pyfile):
- if not self.account:
- self.logError(_("Please enter your %s account or deactivate this plugin") % "zevera.com")
- self.fail("No zevera.com account provided")
-
- self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
-
- if self.account.getAPIData(self.req, cmd="checklink", olink=pyfile.url) != "Alive":
- self.fail("Offline or not downloadable - contact Zevera support")
-
- header = self.account.getAPIData(self.req, just_header=True, cmd="generatedownloaddirect", olink=pyfile.url)
- if not "location" in header:
- self.fail("Unable to initialize download - contact Zevera support")
-
- self.download(header['location'], disposition=True)
-
- check = self.checkDownload({"error": 'action="ErrorDownload.aspx'})
- if check == "error":
- self.fail("Error response received - contact Zevera support")
-
- # BitAPI not used - defunct, probably abandoned by Zevera
- #
- # api_url = "http://zevera.com/API.ashx"
- #
- # def process(self, pyfile):
- # if not self.account:
- # self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
- # self.fail("No zevera.com account provided")
- #
- # self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
- #
- # last_size = retries = 0
- # olink = pyfile.url #quote(pyfile.url.encode('utf_8'))
- #
- # for _ in xrange(100):
- # self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_request', olink = olink)
- # self.checkAPIErrors(self.retData)
- #
- # if self.retData['FileInfo']['StatusID'] == 100:
- # break
- # elif self.retData['FileInfo']['StatusID'] == 99:
- # self.fail('Failed to initialize download (99)')
- # else:
- # if self.retData['FileInfo']['Progress']['BytesReceived'] <= last_size:
- # if retries >= 6:
- # self.fail('Failed to initialize download (%d)' % self.retData['FileInfo']['StatusID'] )
- # retries += 1
- # else:
- # retries = 0
- #
- # last_size = self.retData['FileInfo']['Progress']['BytesReceived']
- #
- # self.setWait(self.retData['Update_Wait'])
- # self.wait()
- #
- # pyfile.name = self.retData['FileInfo']['RealFileName']
- # pyfile.size = self.retData['FileInfo']['FileSizeInBytes']
- #
- # self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_start',
- # FileID = self.retData['FileInfo']['FileID'])
- # self.checkAPIErrors(self.retData)
- #
- # self.download(self.api_url, get = {
- # 'cmd': "open_stream",
- # 'login': self.account.loginname,
- # 'pass': self.account.password,
- # 'FileID': self.retData['FileInfo']['FileID'],
- # 'startBytes': 0
- # }
- # )
- #
- # def checkAPIErrors(self, retData):
- # if not retData:
- # self.fail('Unknown API response')
- #
- # if retData['ErrorCode']:
- # self.logError(retData['ErrorCode'], retData['ErrorMessage'])
- # #self.fail('ERROR: ' + retData['ErrorMessage'])
- #
- # if pyfile.size / 1024000 > retData['AccountInfo']['AvailableTODAYTrafficForUseInMBytes']:
- # self.logWarning("Not enough data left to download the file")
- #
- # def crazyDecode(self, ustring):
- # # accepts decoded ie. unicode string - API response is double-quoted, double-utf8-encoded
- # # no idea what the proper order of calling these functions would be :-/
- # return html_unescape(unquote(unquote(ustring.replace(
- # '@DELIMITER@','#'))).encode('raw_unicode_escape').decode('utf-8'))
diff --git a/module/plugins/hoster/ZippyshareCom.py b/module/plugins/hoster/ZippyshareCom.py
deleted file mode 100644
index 33a672198..000000000
--- a/module/plugins/hoster/ZippyshareCom.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Test links:
-# http://www13.zippyshare.com/v/18665333/file.html
-
-import re
-
-from module.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
-
-
-class ZippyshareCom(SimpleHoster):
- __name__ = "ZippyshareCom"
- __type__ = "hoster"
- __version__ = "0.49"
-
- __pattern__ = r'(?P<HOST>http://www\d{0,2}\.zippyshare.com)/v(?:/|iew.jsp.*key=)(?P<KEY>\d+)'
-
- __description__ = """Zippyshare.com hoster plugin"""
- __author_name__ = ("spoob", "zoidberg", "stickell", "skylab")
- __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it", "development@sky-lab.de")
-
- FILE_NAME_PATTERN = r'<title>Zippyshare\.com - (?P<N>[^<]+)</title>'
- FILE_SIZE_PATTERN = r'>Size:</font>\s*<font [^>]*>(?P<S>[0-9.,]+) (?P<U>[kKMG]+)i?B</font><br />'
- FILE_INFO_PATTERN = r'document\.getElementById\(\'dlbutton\'\)\.href = "[^;]*/(?P<N>[^"]+)";'
- OFFLINE_PATTERN = r'>File does not exist on this server</div>'
-
- SH_COOKIES = [(".zippyshare.com", "ziplocale", "en")]
-
-
- def setup(self):
- self.multiDL = True
-
- def handleFree(self):
- url = self.get_file_url()
- if not url:
- self.fail("Download URL not found.")
- self.logDebug("Download URL: %s" % url)
- self.download(url)
-
- def get_file_url(self):
- """returns the absolute downloadable filepath"""
- url_parts = re.search(r'(addthis:url="(http://www(\d+).zippyshare.com/v/(\d*)/file.html))', self.html)
- number = url_parts.group(4)
- check = re.search(r'<script type="text/javascript">([^<]*?)(var a = (\d*);)', self.html)
- if check:
- a = int(re.search(r'<script type="text/javascript">([^<]*?)(var a = (\d*);)', self.html).group(3))
- k = int(re.search(r'<script type="text/javascript">([^<]*?)(\d*%(\d*))', self.html).group(3))
- checksum = ((a + 3) % k) * ((a + 3) % 3) + 18
- else:
- # This might work but is insecure
- # checksum = eval(re.search("((\d*)\s\%\s(\d*)\s\+\s(\d*)\s\%\s(\d*))", self.html).group(0))
-
- m = re.search(r"((?P<a>\d*)\s%\s(?P<b>\d*)\s\+\s(?P<c>\d*)\s%\s(?P<k>\d*))", self.html)
- if m is None:
- self.parseError("Unable to detect values to calculate direct link")
- a = int(m.group("a"))
- b = int(m.group("b"))
- c = int(m.group("c"))
- k = int(m.group("k"))
- if a == c:
- checksum = ((a % b) + (a % k))
- else:
- checksum = ((a % b) + (c % k))
-
- self.logInfo('Checksum: %s' % checksum)
-
- filename = re.search(r'>Name:</font>\s*<font [^>]*>(?P<N>[^<]+)</font><br />', self.html).group('N')
-
- url = "/d/%s/%s/%s" % (number, checksum, filename)
- self.logInfo(self.file_info['HOST'] + url)
- return self.file_info['HOST'] + url
-
-
-getInfo = create_getInfo(ZippyshareCom)
diff --git a/module/plugins/internal/DeadCrypter.py b/module/plugins/internal/DeadCrypter.py
deleted file mode 100644
index 296de739d..000000000
--- a/module/plugins/internal/DeadCrypter.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Crypter import Crypter as _Crypter
-
-
-class DeadCrypter(_Crypter):
- __name__ = "DeadCrypter"
- __type__ = "crypter"
- __version__ = "0.01"
-
- __pattern__ = None
-
- __description__ = """Crypter is no longer available"""
- __author_name__ = "stickell"
- __author_mail__ = "l.stickell@yahoo.it"
-
-
- def setup(self):
- self.fail("Crypter is no longer available")
diff --git a/module/plugins/internal/DeadHoster.py b/module/plugins/internal/DeadHoster.py
deleted file mode 100644
index 35a948824..000000000
--- a/module/plugins/internal/DeadHoster.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from module.plugins.Hoster import Hoster as _Hoster
-
-
-def create_getInfo(plugin):
-
- def getInfo(urls):
- yield [('#N/A: ' + url, 0, 1, url) for url in urls]
-
- return getInfo
-
-
-class DeadHoster(_Hoster):
- __name__ = "DeadHoster"
- __type__ = "hoster"
- __version__ = "0.11"
-
- __pattern__ = None
-
- __description__ = """Hoster is no longer available"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
-
- def setup(self):
- self.fail("Hoster is no longer available")
diff --git a/module/plugins/internal/MultiHoster.py b/module/plugins/internal/MultiHoster.py
deleted file mode 100644
index f6d202ee9..000000000
--- a/module/plugins/internal/MultiHoster.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Hook import Hook
-from module.utils import remove_chars
-
-
-class MultiHoster(Hook):
- __name__ = "AbtractExtractor"
- __version__ = "0.19"
-
- __description__ = """Generic MultiHoster plugin"""
- __author_name__ = "pyLoad Team"
- __author_mail__ = "admin@pyload.org"
-
- replacements = [("2shared.com", "twoshared.com"), ("4shared.com", "fourshared.com"), ("cloudnator.com", "shragle.com"),
- ("ifile.it", "filecloud.io"), ("easy-share.com", "crocko.com"), ("freakshare.net", "freakshare.com"),
- ("hellshare.com", "hellshare.cz"), ("share-rapid.cz", "sharerapid.com"), ("sharerapid.cz", "sharerapid.com"),
- ("ul.to", "uploaded.to"), ("uploaded.net", "uploaded.to"), ("1fichier.com", "onefichier.com")]
- ignored = []
- interval = 24 * 60 * 60 #: reload hosters daily
-
-
- def setup(self):
- self.hosters = []
- self.supported = []
- self.new_supported = []
-
- def getConfig(self, option, default=''):
- """getConfig with default value - sublass may not implements all config options"""
- try:
- return self.getConf(option)
- except KeyError:
- return default
-
- def getHosterCached(self):
- if not self.hosters:
-
- try:
- hosterSet = self.toHosterSet(self.getHoster()) - set(self.ignored)
- except Exception, e:
- self.logError("%s" % str(e))
- return []
-
- try:
- configMode = self.getConfig('hosterListMode', 'all')
- if configMode in ("listed", "unlisted"):
- configSet = self.toHosterSet(self.getConfig('hosterList', '').replace('|', ',').replace(';', ',').split(','))
-
- if configMode == "listed":
- hosterSet &= configSet
- else:
- hosterSet -= configSet
-
- except Exception, e:
- self.logError("%s" % str(e))
-
- self.hosters = list(hosterSet)
-
- return self.hosters
-
- def toHosterSet(self, hosters):
- hosters = set((str(x).strip().lower() for x in hosters))
-
- for rep in self.replacements:
- if rep[0] in hosters:
- hosters.remove(rep[0])
- hosters.add(rep[1])
-
- hosters.discard('')
- return hosters
-
- def getHoster(self):
- """Load list of supported hoster
-
- :return: List of domain names
- """
- raise NotImplementedError
-
- def coreReady(self):
- if self.cb:
- self.core.scheduler.removeJob(self.cb)
-
- self.setConfig("activated", True) #: config not in sync after plugin reload
-
- cfg_interval = self.getConfig("interval", None) #: reload interval in hours
- if cfg_interval is not None:
- self.interval = cfg_interval * 60 * 60
-
- if self.interval:
- self._periodical()
- else:
- self.periodical()
-
- def initPeriodical(self):
- pass
-
- def periodical(self):
- """reload hoster list periodically"""
- self.logInfo("Reloading supported hoster list")
-
- old_supported = self.supported
- self.supported, self.new_supported, self.hosters = [], [], []
-
- self.overridePlugins()
-
- old_supported = [hoster for hoster in old_supported if hoster not in self.supported]
- if old_supported:
- self.logDebug("UNLOAD: %s" % ", ".join(old_supported))
- for hoster in old_supported:
- self.unloadHoster(hoster)
-
- def overridePlugins(self):
- pluginMap = {}
- for name in self.core.pluginManager.hosterPlugins.keys():
- pluginMap[name.lower()] = name
-
- accountList = [name.lower() for name, data in self.core.accountManager.accounts.items() if data]
- excludedList = []
-
- for hoster in self.getHosterCached():
- name = remove_chars(hoster.lower(), "-.")
-
- if name in accountList:
- excludedList.append(hoster)
- else:
- if name in pluginMap:
- self.supported.append(pluginMap[name])
- else:
- self.new_supported.append(hoster)
-
- if not self.supported and not self.new_supported:
- self.logError(_("No Hoster loaded"))
- return
-
- module = self.core.pluginManager.getPlugin(self.__name__)
- klass = getattr(module, self.__name__)
-
- # inject plugin plugin
- self.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(self.supported)))
- for hoster in self.supported:
- dict = self.core.pluginManager.hosterPlugins[hoster]
- dict['new_module'] = module
- dict['new_name'] = self.__name__
-
- if excludedList:
- self.logInfo("The following hosters were not overwritten - account exists: %s" % ", ".join(sorted(excludedList)))
-
- if self.new_supported:
- self.logDebug("New Hosters: %s" % ", ".join(sorted(self.new_supported)))
-
- # create new regexp
- regexp = r".*(%s).*" % "|".join([x.replace(".", "\\.") for x in self.new_supported])
- if hasattr(klass, "__pattern__") and isinstance(klass.__pattern__, basestring) and '://' in klass.__pattern__:
- regexp = r"%s|%s" % (klass.__pattern__, regexp)
-
- self.logDebug("Regexp: %s" % regexp)
-
- dict = self.core.pluginManager.hosterPlugins[self.__name__]
- dict['pattern'] = regexp
- dict['re'] = re.compile(regexp)
-
- def unloadHoster(self, hoster):
- dict = self.core.pluginManager.hosterPlugins[hoster]
- if "module" in dict:
- del dict['module']
-
- if "new_module" in dict:
- del dict['new_module']
- del dict['new_name']
-
- def unload(self):
- """Remove override for all hosters. Scheduler job is removed by hookmanager"""
- for hoster in self.supported:
- self.unloadHoster(hoster)
-
- # reset pattern
- klass = getattr(self.core.pluginManager.getPlugin(self.__name__), self.__name__)
- dict = self.core.pluginManager.hosterPlugins[self.__name__]
- dict['pattern'] = getattr(klass, "__pattern__", r'^unmatchable$')
- dict['re'] = re.compile(dict['pattern'])
-
- def downloadFailed(self, pyfile):
- """remove plugin override if download fails but not if file is offline/temp.offline"""
- if pyfile.hasStatus("failed") and self.getConfig("unloadFailing", True):
- hdict = self.core.pluginManager.hosterPlugins[pyfile.pluginname]
- if "new_name" in hdict and hdict['new_name'] == self.__name__:
- self.logDebug("Unload MultiHoster", pyfile.pluginname, hdict)
- self.unloadHoster(pyfile.pluginname)
- pyfile.setStatus("queued")
diff --git a/module/plugins/internal/SimpleCrypter.py b/module/plugins/internal/SimpleCrypter.py
deleted file mode 100644
index 0b99feb42..000000000
--- a/module/plugins/internal/SimpleCrypter.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from module.plugins.Crypter import Crypter
-from module.plugins.internal.SimpleHoster import PluginParseError, replace_patterns, set_cookies
-from module.utils import html_unescape
-
-
-class SimpleCrypter(Crypter):
- __name__ = "SimpleCrypter"
- __type__ = "crypter"
- __version__ = "0.10"
-
- __pattern__ = None
-
- __description__ = """Simple decrypter plugin"""
- __author_name__ = ("stickell", "zoidberg", "Walter Purcaro")
- __author_mail__ = ("l.stickell@yahoo.it", "zoidberg@mujmail.cz", "vuolter@gmail.com")
-
- """
- Following patterns should be defined by each crypter:
-
- LINK_PATTERN: group(1) must be a download link or a regex to catch more links
- example: LINK_PATTERN = r'<div class="link"><a href="(http://speedload.org/\w+)'
-
- TITLE_PATTERN: (optional) The group defined by 'title' should be the title
- example: TITLE_PATTERN = r'<title>Files of: (?P<title>[^<]+) folder</title>'
-
- OFFLINE_PATTERN: (optional) Checks if the file is yet available online
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: (optional) Checks if the file is temporarily offline
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
-
- If it's impossible to extract the links using the LINK_PATTERN only you can override the getLinks method.
-
- If the links are disposed on multiple pages you need to define a pattern:
-
- PAGES_PATTERN: The group defined by 'pages' must be the total number of pages
- example: PAGES_PATTERN = r'Pages: (?P<pages>\d+)'
-
- and a function:
-
- loadPage(self, page_n):
- return the html of the page number 'page_n'
- """
-
- URL_REPLACEMENTS = []
-
- SH_COOKIES = True # or False or list of tuples [(domain, name, value)]
-
-
- def setup(self):
- if isinstance(self.SH_COOKIES, list):
- set_cookies(self.req.cj, self.SH_COOKIES)
-
- def decrypt(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
-
- self.html = self.load(pyfile.url, decode=True)
-
- self.checkOnline()
-
- package_name, folder_name = self.getPackageNameAndFolder()
-
- self.package_links = self.getLinks()
-
- if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
- self.handleMultiPages()
-
- self.logDebug('Package has %d links' % len(self.package_links))
-
- if self.package_links:
- self.packages = [(package_name, self.package_links, folder_name)]
- else:
- self.fail('Could not extract any links')
-
- def getLinks(self):
- """
- Returns the links extracted from self.html
- You should override this only if it's impossible to extract links using only the LINK_PATTERN.
- """
- return re.findall(self.LINK_PATTERN, self.html)
-
- def checkOnline(self):
- if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html):
- self.offline()
- elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html):
- self.tempOffline()
-
- def getPackageNameAndFolder(self):
- if hasattr(self, 'TITLE_PATTERN'):
- m = re.search(self.TITLE_PATTERN, self.html)
- if m:
- name = folder = html_unescape(m.group('title').strip())
- self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
- return name, folder
-
- name = self.pyfile.package().name
- folder = self.pyfile.package().folder
- self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
- return name, folder
-
- def handleMultiPages(self):
- pages = re.search(self.PAGES_PATTERN, self.html)
- if pages:
- pages = int(pages.group('pages'))
- else:
- pages = 1
-
- for p in xrange(2, pages + 1):
- self.html = self.loadPage(p)
- self.package_links += self.getLinks()
-
- def parseError(self, msg):
- raise PluginParseError(msg)
diff --git a/module/plugins/internal/SimpleHoster.py b/module/plugins/internal/SimpleHoster.py
deleted file mode 100644
index ec9cf1b70..000000000
--- a/module/plugins/internal/SimpleHoster.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import time
-from urlparse import urlparse
-
-from module.network.CookieJar import CookieJar
-from module.network.RequestFactory import getURL
-from module.plugins.Hoster import Hoster
-from module.utils import fixup, html_unescape, parseFileSize
-
-
-def replace_patterns(string, ruleslist):
- for r in ruleslist:
- rf, rt = r
- string = re.sub(rf, rt, string)
- #self.logDebug(rf, rt, string)
- return string
-
-
-def set_cookies(cj, cookies):
- for cookie in cookies:
- if isinstance(cookie, tuple) and len(cookie) == 3:
- domain, name, value = cookie
- cj.setCookie(domain, name, value)
-
-
-def parseHtmlTagAttrValue(attr_name, tag):
- m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I)
- return m.group(2) if m else None
-
-
-def parseHtmlForm(attr_str, html, input_names=None):
- for form in re.finditer(r"(?P<tag><form[^>]*%s[^>]*>)(?P<content>.*?)</?(form|body|html)[^>]*>" % attr_str,
- html, re.S | re.I):
- inputs = {}
- action = parseHtmlTagAttrValue("action", form.group('tag'))
- for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('content'), re.S | re.I):
- name = parseHtmlTagAttrValue("name", inputtag.group(1))
- if name:
- value = parseHtmlTagAttrValue("value", inputtag.group(1))
- if not value:
- inputs[name] = inputtag.group(3) or ''
- else:
- inputs[name] = value
-
- if isinstance(input_names, dict):
- # check input attributes
- for key, val in input_names.items():
- if key in inputs:
- if isinstance(val, basestring) and inputs[key] == val:
- continue
- elif isinstance(val, tuple) and inputs[key] in val:
- continue
- elif hasattr(val, "search") and re.match(val, inputs[key]):
- continue
- break # attibute value does not match
- else:
- break # attibute name does not match
- else:
- return action, inputs # passed attribute check
- else:
- # no attribute check
- return action, inputs
-
- return {}, None # no matching form found
-
-
-def parseFileInfo(self, url='', html=''):
- info = {"name": url, "size": 0, "status": 3}
-
- if hasattr(self, "pyfile"):
- url = self.pyfile.url
-
- if hasattr(self, "req") and self.req.http.code == '404':
- info['status'] = 1
- else:
- if not html and hasattr(self, "html"):
- html = self.html
- if isinstance(self.SH_BROKEN_ENCODING, (str, unicode)):
- html = unicode(html, self.SH_BROKEN_ENCODING)
- if hasattr(self, "html"):
- self.html = html
-
- if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, html):
- info['status'] = 1
- elif hasattr(self, "FILE_OFFLINE_PATTERN") and re.search(self.FILE_OFFLINE_PATTERN, html): #@TODO: Remove in 0.4.10
- info['status'] = 1
- elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, html):
- info['status'] = 6
- else:
- online = False
- try:
- info.update(re.match(self.__pattern__, url).groupdict())
- except:
- pass
-
- for pattern in ("FILE_INFO_PATTERN", "FILE_NAME_PATTERN", "FILE_SIZE_PATTERN"):
- try:
- info.update(re.search(getattr(self, pattern), html).groupdict())
- online = True
- except AttributeError:
- continue
-
- if online:
- # File online, return name and size
- info['status'] = 2
- if 'N' in info:
- info['name'] = replace_patterns(info['N'], self.FILE_NAME_REPLACEMENTS)
- if 'S' in info:
- size = replace_patterns(info['S'] + info['U'] if 'U' in info else info['S'],
- self.FILE_SIZE_REPLACEMENTS)
- info['size'] = parseFileSize(size)
- elif isinstance(info['size'], (str, unicode)):
- if 'units' in info:
- info['size'] += info['units']
- info['size'] = parseFileSize(info['size'])
-
- if hasattr(self, "file_info"):
- self.file_info = info
-
- return info['name'], info['size'], info['status'], url
-
-
-def create_getInfo(plugin):
-
- def getInfo(urls):
- for url in urls:
- cj = CookieJar(plugin.__name__)
- if isinstance(plugin.SH_COOKIES, list):
- set_cookies(cj, plugin.SH_COOKIES)
- file_info = parseFileInfo(plugin, url, getURL(replace_patterns(url, plugin.FILE_URL_REPLACEMENTS),
- decode=not plugin.SH_BROKEN_ENCODING, cookies=cj))
- yield file_info
-
- return getInfo
-
-
-def timestamp():
- return int(time() * 1000)
-
-
-class PluginParseError(Exception):
-
- def __init__(self, msg):
- Exception.__init__(self)
- self.value = 'Parse error (%s) - plugin may be out of date' % msg
-
- def __str__(self):
- return repr(self.value)
-
-
-class SimpleHoster(Hoster):
- __name__ = "SimpleHoster"
- __type__ = "hoster"
- __version__ = "0.35"
-
- __pattern__ = None
-
- __description__ = """Simple hoster plugin"""
- __author_name__ = ("zoidberg", "stickell")
- __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
-
- """
- Following patterns should be defined by each hoster:
-
- FILE_INFO_PATTERN: Name and Size of the file
- example: FILE_INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
- or
- FILE_NAME_PATTERN: Name that will be set for the file
- example: FILE_NAME_PATTERN = r'(?P<N>file_name)'
- FILE_SIZE_PATTERN: Size that will be checked for the file
- example: FILE_SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
-
- OFFLINE_PATTERN: Checks if the file is yet available online
- example: OFFLINE_PATTERN = r'File (deleted|not found)'
-
- TEMP_OFFLINE_PATTERN: Checks if the file is temporarily offline
- example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
-
- PREMIUM_ONLY_PATTERN: (optional) Checks if the file can be downloaded only with a premium account
- example: PREMIUM_ONLY_PATTERN = r'Premium account required'
- """
-
- FILE_NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
- FILE_SIZE_REPLACEMENTS = []
- FILE_URL_REPLACEMENTS = []
-
- SH_BROKEN_ENCODING = False # Set to True or encoding name if encoding in http header is not correct
- SH_COOKIES = True # or False or list of tuples [(domain, name, value)]
- SH_CHECK_TRAFFIC = False # True = force check traffic left for a premium account
-
-
- def init(self):
- self.file_info = {}
-
- def setup(self):
- self.resumeDownload = self.multiDL = self.premium
- if isinstance(self.SH_COOKIES, list):
- set_cookies(self.req.cj, self.SH_COOKIES)
-
- def process(self, pyfile):
- pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
- self.req.setOption("timeout", 120)
- # Due to a 0.4.9 core bug self.load would keep previous cookies even if overridden by cookies parameter.
- # Workaround using getURL. Can be reverted in 0.5 as the cookies bug has been fixed.
- self.html = getURL(pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
- premium_only = hasattr(self, 'PREMIUM_ONLY_PATTERN') and re.search(self.PREMIUM_ONLY_PATTERN, self.html)
- if not premium_only: # Usually premium only pages doesn't show the file information
- self.getFileInfo()
-
- if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
- self.handlePremium()
- elif premium_only:
- self.fail("This link require a premium account")
- else:
- # This line is required due to the getURL workaround. Can be removed in 0.5
- self.html = self.load(pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
- self.handleFree()
-
- def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
- if type(url) == unicode:
- url = url.encode('utf8')
- return Hoster.load(self, url=url, get=get, post=post, ref=ref, cookies=cookies,
- just_header=just_header, decode=decode)
-
- def getFileInfo(self):
- self.logDebug("URL: %s" % self.pyfile.url)
-
- name, size, status = parseFileInfo(self)[:3]
-
- if status == 1:
- self.offline()
- elif status == 6:
- self.tempOffline()
- elif status != 2:
- self.logDebug(self.file_info)
- self.parseError('File info')
-
- if name:
- self.pyfile.name = name
- else:
- self.pyfile.name = html_unescape(urlparse(self.pyfile.url).path.split("/")[-1])
-
- if size:
- self.pyfile.size = size
- else:
- self.logError("File size not parsed")
-
- self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size))
- return self.file_info
-
- def handleFree(self):
- self.fail("Free download not implemented")
-
- def handlePremium(self):
- self.fail("Premium download not implemented")
-
- def parseError(self, msg):
- raise PluginParseError(msg)
-
- def longWait(self, wait_time=None, max_tries=3):
- if wait_time and isinstance(wait_time, (int, long, float)):
- time_str = "%dh %dm" % divmod(wait_time / 60, 60)
- else:
- wait_time = 900
- time_str = "(unknown time)"
- max_tries = 100
-
- self.logInfo("Download limit reached, reconnect or wait %s" % time_str)
-
- self.setWait(wait_time, True)
- self.wait()
- self.retry(max_tries=max_tries, reason="Download limit reached")
-
- def parseHtmlForm(self, attr_str='', input_names=None):
- return parseHtmlForm(attr_str, self.html, input_names)
-
- def checkTrafficLeft(self):
- traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
- if traffic == -1:
- return True
- size = self.pyfile.size / 1024
- self.logInfo("Filesize: %i KiB, Traffic left for user %s: %i KiB" % (size, self.user, traffic))
- return size <= traffic
-
- # TODO: Remove in 0.5
- def wait(self, seconds=False, reconnect=False):
- if seconds:
- self.setWait(seconds, reconnect)
- super(SimpleHoster, self).wait()
diff --git a/module/plugins/internal/UnRar.py b/module/plugins/internal/UnRar.py
deleted file mode 100644
index 19c278735..000000000
--- a/module/plugins/internal/UnRar.py
+++ /dev/null
@@ -1,212 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import os
-import re
-
-from glob import glob
-from os.path import join
-from string import digits
-from subprocess import Popen, PIPE
-
-from module.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError
-from module.utils import save_join, decode
-
-
-class UnRar(AbtractExtractor):
- __name__ = "UnRar"
- __version__ = "0.16"
-
- __description__ = """Rar extractor plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
- CMD = "unrar"
-
- # there are some more uncovered rar formats
- re_version = re.compile(r"(UNRAR 5[\.\d]+(.*?)freeware)")
- re_splitfile = re.compile(r"(.*)\.part(\d+)\.rar$", re.I)
- re_partfiles = re.compile(r".*\.(rar|r[0-9]+)", re.I)
- re_filelist = re.compile(r"(.+)\s+(\d+)\s+(\d+)\s+")
- re_filelist5 = re.compile(r"(.+)\s+(\d+)\s+\d\d-\d\d-\d\d\s+\d\d:\d\d\s+(.+)")
- re_wrongpwd = re.compile("(Corrupt file or wrong password|password incorrect)", re.I)
-
-
- @staticmethod
- def checkDeps():
- if os.name == "nt":
- UnRar.CMD = join(pypath, "UnRAR.exe")
- p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
- p.communicate()
- else:
- try:
- p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
- p.communicate()
- except OSError:
-
- # fallback to rar
- UnRar.CMD = "rar"
- p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
- p.communicate()
-
- return True
-
- @staticmethod
- def getTargets(files_ids):
- result = []
-
- for file, id in files_ids:
- if not file.endswith(".rar"):
- continue
-
- match = UnRar.re_splitfile.findall(file)
- if match:
- # only add first parts
- if int(match[0][1]) == 1:
- result.append((file, id))
- else:
- result.append((file, id))
-
- return result
-
- def init(self):
- self.passwordProtected = False
- self.headerProtected = False #: list files will not work without password
- self.smallestFile = None #: small file to test passwords
- self.password = "" #: save the correct password
-
- def checkArchive(self):
- p = self.call_unrar("l", "-v", self.file)
- out, err = p.communicate()
- if self.re_wrongpwd.search(err):
- self.passwordProtected = True
- self.headerProtected = True
- return True
-
- # output only used to check if passworded files are present
- if self.re_version.search(out):
- for attr, size, name in self.re_filelist5.findall(out):
- if attr.startswith("*"):
- self.passwordProtected = True
- return True
- else:
- for name, size, packed in self.re_filelist.findall(out):
- if name.startswith("*"):
- self.passwordProtected = True
- return True
-
- self.listContent()
- if not self.files:
- raise ArchiveError("Empty Archive")
-
- return False
-
- def checkPassword(self, password):
- # at this point we can only verify header protected files
- if self.headerProtected:
- p = self.call_unrar("l", "-v", self.file, password=password)
- out, err = p.communicate()
- if self.re_wrongpwd.search(err):
- return False
-
- return True
-
- def extract(self, progress, password=None):
- command = "x" if self.fullpath else "e"
-
- p = self.call_unrar(command, self.file, self.out, password=password)
- renice(p.pid, self.renice)
-
- progress(0)
- progressstring = ""
- while True:
- c = p.stdout.read(1)
- # quit loop on eof
- if not c:
- break
- # reading a percentage sign -> set progress and restart
- if c == '%':
- progress(int(progressstring))
- progressstring = ""
- # not reading a digit -> therefore restart
- elif c not in digits:
- progressstring = ""
- # add digit to progressstring
- else:
- progressstring = progressstring + c
- progress(100)
-
- # retrieve stderr
- err = p.stderr.read()
-
- if "CRC failed" in err and not password and not self.passwordProtected:
- raise CRCError
- elif "CRC failed" in err:
- raise WrongPassword
- if err.strip(): #: raise error if anything is on stderr
- raise ArchiveError(err.strip())
- if p.returncode:
- raise ArchiveError("Process terminated")
-
- if not self.files:
- self.password = password
- self.listContent()
-
- def getDeleteFiles(self):
- if ".part" in self.file:
- return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.IGNORECASE))
- # get files which matches .r* and filter unsuited files out
- parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.IGNORECASE))
- return filter(lambda x: self.re_partfiles.match(x), parts)
-
- def listContent(self):
- command = "vb" if self.fullpath else "lb"
- p = self.call_unrar(command, "-v", self.file, password=self.password)
- out, err = p.communicate()
-
- if "Cannot open" in err:
- raise ArchiveError("Cannot open file")
-
- if err.strip(): #: only log error at this point
- self.m.logError(err.strip())
-
- result = set()
-
- for f in decode(out).splitlines():
- f = f.strip()
- result.add(save_join(self.out, f))
-
- self.files = result
-
- def call_unrar(self, command, *xargs, **kwargs):
- args = []
- # overwrite flag
- args.append("-o+") if self.overwrite else args.append("-o-")
-
- if self.excludefiles:
- for word in self.excludefiles.split(';'):
- args.append("-x%s" % word)
-
- # assume yes on all queries
- args.append("-y")
-
- # set a password
- if "password" in kwargs and kwargs['password']:
- args.append("-p%s" % kwargs['password'])
- else:
- args.append("-p-")
-
- # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
- call = [self.CMD, command] + args + list(xargs)
- self.m.logDebug(" ".join(call))
-
- p = Popen(call, stdout=PIPE, stderr=PIPE)
-
- return p
-
-
-def renice(pid, value):
- if os.name != "nt" and value:
- try:
- Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1)
- except:
- print "Renice failed"
diff --git a/module/plugins/internal/UnZip.py b/module/plugins/internal/UnZip.py
deleted file mode 100644
index e339434f9..000000000
--- a/module/plugins/internal/UnZip.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import zipfile
-
-from module.plugins.internal.AbstractExtractor import AbtractExtractor
-
-
-class UnZip(AbtractExtractor):
- __name__ = "UnZip"
- __version__ = "0.1"
-
- __description__ = """Zip extractor plugin"""
- __author_name__ = "RaNaN"
- __author_mail__ = "RaNaN@pyload.org"
-
-
- @staticmethod
- def checkDeps():
- return sys.version_info[:2] >= (2, 6)
-
- @staticmethod
- def getTargets(files_ids):
- result = []
-
- for file, id in files_ids:
- if file.endswith(".zip"):
- result.append((file, id))
-
- return result
-
- def extract(self, progress, password=None):
- z = zipfile.ZipFile(self.file)
- self.files = z.namelist()
- z.extractall(self.out)
-
- def getDeleteFiles(self):
- return [self.file]
diff --git a/module/plugins/internal/XFSPAccount.py b/module/plugins/internal/XFSPAccount.py
deleted file mode 100644
index 5c0bfc893..000000000
--- a/module/plugins/internal/XFSPAccount.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import re
-
-from time import mktime, strptime
-
-from module.plugins.Account import Account
-from module.plugins.internal.SimpleHoster import parseHtmlForm
-from module.utils import parseFileSize
-
-
-class XFSPAccount(Account):
- __name__ = "XFSPAccount"
- __type__ = "account"
- __version__ = "0.06"
-
- __description__ = """XFileSharingPro base account plugin"""
- __author_name__ = "zoidberg"
- __author_mail__ = "zoidberg@mujmail.cz"
-
- MAIN_PAGE = None
-
- VALID_UNTIL_PATTERN = r'>Premium.[Aa]ccount expire:</TD><TD><b>([^<]+)</b>'
- TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><b>([^<]+)</b>'
- LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|>Error<'
- PREMIUM_PATTERN = r'>Renew premium<'
-
-
- def loadAccountInfo(self, user, req):
- html = req.load(self.MAIN_PAGE + "?op=my_account", decode=True)
-
- validuntil = trafficleft = None
- premium = True if re.search(self.PREMIUM_PATTERN, html) else False
-
- m = re.search(self.VALID_UNTIL_PATTERN, html)
- if m:
- premium = True
- trafficleft = -1
- try:
- self.logDebug(m.group(1))
- validuntil = mktime(strptime(m.group(1), "%d %B %Y"))
- except Exception, e:
- self.logError(e)
- else:
- m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
- if m:
- trafficleft = m.group(1)
- if "Unlimited" in trafficleft:
- premium = True
- else:
- trafficleft = parseFileSize(trafficleft) / 1024
-
- return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
-
- def login(self, user, data, req):
- html = req.load('%slogin.html' % self.MAIN_PAGE, decode=True)
-
- action, inputs = parseHtmlForm('name="FL"', html)
- if not inputs:
- inputs = {"op": "login",
- "redirect": self.MAIN_PAGE}
-
- inputs.update({"login": user,
- "password": data['password']})
-
- html = req.load(self.MAIN_PAGE, post=inputs, decode=True)
-
- if re.search(self.LOGIN_FAIL_PATTERN, html):
- self.wrongPassword()
diff --git a/module/remote/ClickAndLoadBackend.py b/module/remote/ClickAndLoadBackend.py
deleted file mode 100644
index ad8031587..000000000
--- a/module/remote/ClickAndLoadBackend.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-import re
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-from cgi import FieldStorage
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-from RemoteManager import BackendBase
-
-core = None
-js = None
-
-class ClickAndLoadBackend(BackendBase):
- def setup(self, host, port):
- self.httpd = HTTPServer((host, port), CNLHandler)
- global core, js
- core = self.m.core
- js = core.js
-
- def serve(self):
- while self.enabled:
- self.httpd.handle_request()
-
-class CNLHandler(BaseHTTPRequestHandler):
-
- def add_package(self, name, urls, queue=0):
- print "name", name
- print "urls", urls
- print "queue", queue
-
- def get_post(self, name, default=""):
- if name in self.post:
- return self.post[name]
- else:
- return default
-
- def start_response(self, string):
-
- self.send_response(200)
-
- self.send_header("Content-Length", len(string))
- self.send_header("Content-Language", "de")
- self.send_header("Vary", "Accept-Language, Cookie")
- self.send_header("Cache-Control", "no-cache, must-revalidate")
- self.send_header("Content-type", "text/html")
- self.end_headers()
-
- def do_GET(self):
- path = self.path.strip("/").lower()
- #self.wfile.write(path+"\n")
-
- self.map = [ (r"add$", self.add),
- (r"addcrypted$", self.addcrypted),
- (r"addcrypted2$", self.addcrypted2),
- (r"flashgot", self.flashgot),
- (r"crossdomain\.xml", self.crossdomain),
- (r"checkSupportForUrl", self.checksupport),
- (r"jdcheck.js", self.jdcheck),
- (r"", self.flash) ]
-
- func = None
- for r, f in self.map:
- if re.match(r"(flash(got)?/?)?"+r, path):
- func = f
- break
-
- if func:
- try:
- resp = func()
- if not resp: resp = "success"
- resp += "\r\n"
- self.start_response(resp)
- self.wfile.write(resp)
- except Exception,e :
- self.send_error(500, str(e))
- else:
- self.send_error(404, "Not Found")
-
- def do_POST(self):
- form = FieldStorage(
- fp=self.rfile,
- headers=self.headers,
- environ={'REQUEST_METHOD':'POST',
- 'CONTENT_TYPE':self.headers['Content-Type'],
- })
-
- self.post = {}
- for name in form.keys():
- self.post[name] = form[name].value
-
- return self.do_GET()
-
- def flash(self):
- return "JDownloader"
-
- def add(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, 0)
-
- def addcrypted(self):
- package = self.get_post('referer', 'ClickAndLoad Package')
- dlc = self.get_post('crypted').replace(" ", "+")
-
- core.upload_container(package, dlc)
-
- def addcrypted2(self):
- package = self.get_post("source", "ClickAndLoad Package")
- crypted = self.get_post("crypted")
- jk = self.get_post("jk")
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- jk = "%s f()" % jk
- jk = js.eval(jk)
- Key = unhexlify(jk)
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- self.add_package(package, result, 0)
-
-
- def flashgot(self):
- autostart = int(self.get_post('autostart', 0))
- package = self.get_post('package', "FlashGot")
- urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
-
- self.add_package(package, urls, autostart)
-
- def crossdomain(self):
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
- def checksupport(self):
- pass
-
- def jdcheck(self):
- rep = "jdownloader=true;\n"
- rep += "var version='10629';\n"
- return rep
diff --git a/module/remote/RemoteManager.py b/module/remote/RemoteManager.py
deleted file mode 100644
index 36eb52a4a..000000000
--- a/module/remote/RemoteManager.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-from threading import Thread
-from traceback import print_exc
-
-class BackendBase(Thread):
- def __init__(self, manager):
- Thread.__init__(self)
- self.m = manager
- self.core = manager.core
- self.enabled = True
- self.running = False
-
- def run(self):
- self.running = True
- try:
- self.serve()
- except Exception, e:
- self.core.log.error(_("Remote backend error: %s") % e)
- if self.core.debug:
- print_exc()
- finally:
- self.running = False
-
- def setup(self, host, port):
- pass
-
- def checkDeps(self):
- return True
-
- def serve(self):
- pass
-
- def shutdown(self):
- pass
-
- def stop(self):
- self.enabled = False# set flag and call shutdowm message, so thread can react
- self.shutdown()
-
-
-class RemoteManager():
- available = []
-
- def __init__(self, core):
- self.core = core
- self.backends = []
-
- if self.core.remote:
- self.available.append("ThriftBackend")
-# else:
-# self.available.append("SocketBackend")
-
-
- def startBackends(self):
- host = self.core.config["remote"]["listenaddr"]
- port = self.core.config["remote"]["port"]
-
- for b in self.available:
- klass = getattr(__import__("module.remote.%s" % b, globals(), locals(), [b], -1), b)
- backend = klass(self)
- if not backend.checkDeps():
- continue
- try:
- backend.setup(host, port)
- self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port})
- except Exception, e:
- self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)})
- if self.core.debug:
- print_exc()
- else:
- backend.start()
- self.backends.append(backend)
-
- port += 1
diff --git a/module/remote/SocketBackend.py b/module/remote/SocketBackend.py
deleted file mode 100644
index 1a157cf1d..000000000
--- a/module/remote/SocketBackend.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import SocketServer
-
-from RemoteManager import BackendBase
-
-class RequestHandler(SocketServer.BaseRequestHandler):
-
- def setup(self):
- pass
-
- def handle(self):
-
- print self.request.recv(1024)
-
-
-
-class SocketBackend(BackendBase):
-
- def setup(self, host, port):
- #local only
- self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler)
-
- def serve(self):
- self.server.serve_forever()
diff --git a/module/remote/ThriftBackend.py b/module/remote/ThriftBackend.py
deleted file mode 100644
index b4a2bb25e..000000000
--- a/module/remote/ThriftBackend.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay, RaNaN
-"""
-from os.path import exists
-
-from module.remote.RemoteManager import BackendBase
-
-from thriftbackend.Processor import Processor
-from thriftbackend.Protocol import ProtocolFactory
-from thriftbackend.Socket import ServerSocket
-from thriftbackend.Transport import TransportFactory
-#from thriftbackend.Transport import TransportFactoryCompressed
-
-from thrift.server import TServer
-
-class ThriftBackend(BackendBase):
- def setup(self, host, port):
- processor = Processor(self.core.api)
-
- key = None
- cert = None
-
- if self.core.config['ssl']['activated']:
- if exists(self.core.config['ssl']['cert']) and exists(self.core.config['ssl']['key']):
- self.core.log.info(_("Using SSL ThriftBackend"))
- key = self.core.config['ssl']['key']
- cert = self.core.config['ssl']['cert']
-
- transport = ServerSocket(port, host, key, cert)
-
-
-# tfactory = TransportFactoryCompressed()
- tfactory = TransportFactory()
- pfactory = ProtocolFactory()
-
- self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
- #self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory)
-
- #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
-
- def serve(self):
- self.server.serve()
diff --git a/module/remote/__init__.py b/module/remote/__init__.py
deleted file mode 100644
index 9298f5337..000000000
--- a/module/remote/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# -*- coding: utf-8 -*-
-activated = True
diff --git a/module/remote/socketbackend/__init__.py b/module/remote/socketbackend/__init__.py
deleted file mode 100644
index de6d13128..000000000
--- a/module/remote/socketbackend/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-__author__ = 'christian'
- \ No newline at end of file
diff --git a/module/remote/socketbackend/create_ttypes.py b/module/remote/socketbackend/create_ttypes.py
deleted file mode 100644
index 05662cb50..000000000
--- a/module/remote/socketbackend/create_ttypes.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import inspect
-import sys
-from os.path import abspath, dirname, join
-
-path = dirname(abspath(__file__))
-module = join(path, "..", "..")
-
-sys.path.append(join(module, "lib"))
-sys.path.append(join(module, "remote"))
-
-from thriftbackend.thriftgen.pyload import ttypes
-from thriftbackend.thriftgen.pyload.Pyload import Iface
-
-
-def main():
-
- enums = []
- classes = []
-
- print "generating lightweight ttypes.py"
-
- for name in dir(ttypes):
- klass = getattr(ttypes, name)
-
- if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)):
- continue
-
- if hasattr(klass, "thrift_spec"):
- classes.append(klass)
- else:
- enums.append(klass)
-
-
- f = open(join(path, "ttypes.py"), "wb")
-
- f.write(
- """#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Autogenerated by pyload
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-
-class BaseObject(object):
-\t__slots__ = []
-
-""")
-
- ## generate enums
- for enum in enums:
- name = enum.__name__
- f.write("class %s:\n" % name)
-
- for attr in dir(enum):
- if attr.startswith("_") or attr in ("read", "write"): continue
-
- f.write("\t%s = %s\n" % (attr, getattr(enum, attr)))
-
- f.write("\n")
-
- for klass in classes:
- name = klass.__name__
- base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject"
- f.write("class %s(%s):\n" % (name, base))
- f.write("\t__slots__ = %s\n\n" % klass.__slots__)
-
- #create init
- args = ["self"] + ["%s=None" % x for x in klass.__slots__]
-
- f.write("\tdef __init__(%s):\n" % ", ".join(args))
- for attr in klass.__slots__:
- f.write("\t\tself.%s = %s\n" % (attr, attr))
-
- f.write("\n")
-
- f.write("class Iface:\n")
-
- for name in dir(Iface):
- if name.startswith("_"): continue
-
- func = inspect.getargspec(getattr(Iface, name))
-
- f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args)))
-
- f.write("\n")
-
- f.close()
-
-if __name__ == "__main__":
- main() \ No newline at end of file
diff --git a/module/remote/socketbackend/ttypes.py b/module/remote/socketbackend/ttypes.py
deleted file mode 100644
index f8ea121fa..000000000
--- a/module/remote/socketbackend/ttypes.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Autogenerated by pyload
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-
-class BaseObject(object):
- __slots__ = []
-
-class Destination:
- Collector = 0
- Queue = 1
-
-class DownloadStatus:
- Aborted = 9
- Custom = 11
- Decrypting = 10
- Downloading = 12
- Failed = 8
- Finished = 0
- Offline = 1
- Online = 2
- Processing = 13
- Queued = 3
- Skipped = 4
- Starting = 7
- TempOffline = 6
- Unknown = 14
- Waiting = 5
-
-class ElementType:
- File = 1
- Package = 0
-
-class Input:
- BOOL = 4
- CHOICE = 6
- CLICK = 5
- LIST = 8
- MULTIPLE = 7
- NONE = 0
- PASSWORD = 3
- TABLE = 9
- TEXT = 1
- TEXTBOX = 2
-
-class Output:
- CAPTCHA = 1
- NOTIFICATION = 4
- QUESTION = 2
-
-class AccountInfo(BaseObject):
- __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type']
-
- def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None):
- self.validuntil = validuntil
- self.login = login
- self.options = options
- self.valid = valid
- self.trafficleft = trafficleft
- self.maxtraffic = maxtraffic
- self.premium = premium
- self.type = type
-
-class CaptchaTask(BaseObject):
- __slots__ = ['tid', 'data', 'type', 'resultType']
-
- def __init__(self, tid=None, data=None, type=None, resultType=None):
- self.tid = tid
- self.data = data
- self.type = type
- self.resultType = resultType
-
-class ConfigItem(BaseObject):
- __slots__ = ['name', 'description', 'value', 'type']
-
- def __init__(self, name=None, description=None, value=None, type=None):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
-
-class ConfigSection(BaseObject):
- __slots__ = ['name', 'description', 'items', 'outline']
-
- def __init__(self, name=None, description=None, items=None, outline=None):
- self.name = name
- self.description = description
- self.items = items
- self.outline = outline
-
-class DownloadInfo(BaseObject):
- __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
-
- def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None):
- self.fid = fid
- self.name = name
- self.speed = speed
- self.eta = eta
- self.format_eta = format_eta
- self.bleft = bleft
- self.size = size
- self.format_size = format_size
- self.percent = percent
- self.status = status
- self.statusmsg = statusmsg
- self.format_wait = format_wait
- self.wait_until = wait_until
- self.packageID = packageID
- self.packageName = packageName
- self.plugin = plugin
-
-class EventInfo(BaseObject):
- __slots__ = ['eventname', 'id', 'type', 'destination']
-
- def __init__(self, eventname=None, id=None, type=None, destination=None):
- self.eventname = eventname
- self.id = id
- self.type = type
- self.destination = destination
-
-class FileData(BaseObject):
- __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order']
-
- def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None):
- self.fid = fid
- self.url = url
- self.name = name
- self.plugin = plugin
- self.size = size
- self.format_size = format_size
- self.status = status
- self.statusmsg = statusmsg
- self.packageID = packageID
- self.error = error
- self.order = order
-
-class FileDoesNotExists(Exception):
- __slots__ = ['fid']
-
- def __init__(self, fid=None):
- self.fid = fid
-
-class InteractionTask(BaseObject):
- __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin']
-
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None):
- self.iid = iid
- self.input = input
- self.structure = structure
- self.preset = preset
- self.output = output
- self.data = data
- self.title = title
- self.description = description
- self.plugin = plugin
-
-class OnlineCheck(BaseObject):
- __slots__ = ['rid', 'data']
-
- def __init__(self, rid=None, data=None):
- self.rid = rid
- self.data = data
-
-class OnlineStatus(BaseObject):
- __slots__ = ['name', 'plugin', 'packagename', 'status', 'size']
-
- def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None):
- self.name = name
- self.plugin = plugin
- self.packagename = packagename
- self.status = status
- self.size = size
-
-class PackageData(BaseObject):
- __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids']
-
- def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None):
- self.pid = pid
- self.name = name
- self.folder = folder
- self.site = site
- self.password = password
- self.dest = dest
- self.order = order
- self.linksdone = linksdone
- self.sizedone = sizedone
- self.sizetotal = sizetotal
- self.linkstotal = linkstotal
- self.links = links
- self.fids = fids
-
-class PackageDoesNotExists(Exception):
- __slots__ = ['pid']
-
- def __init__(self, pid=None):
- self.pid = pid
-
-class ServerStatus(BaseObject):
- __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect']
-
- def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None):
- self.pause = pause
- self.active = active
- self.queue = queue
- self.total = total
- self.speed = speed
- self.download = download
- self.reconnect = reconnect
-
-class ServiceCall(BaseObject):
- __slots__ = ['plugin', 'func', 'arguments', 'parseArguments']
-
- def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None):
- self.plugin = plugin
- self.func = func
- self.arguments = arguments
- self.parseArguments = parseArguments
-
-class ServiceDoesNotExists(Exception):
- __slots__ = ['plugin', 'func']
-
- def __init__(self, plugin=None, func=None):
- self.plugin = plugin
- self.func = func
-
-class ServiceException(Exception):
- __slots__ = ['msg']
-
- def __init__(self, msg=None):
- self.msg = msg
-
-class UserData(BaseObject):
- __slots__ = ['name', 'email', 'role', 'permission', 'templateName']
-
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None):
- self.name = name
- self.email = email
- self.role = role
- self.permission = permission
- self.templateName = templateName
-
-class Iface:
- def addFiles(self, pid, links):
- pass
- def addPackage(self, name, links, dest):
- pass
- def call(self, info):
- pass
- def checkOnlineStatus(self, urls):
- pass
- def checkOnlineStatusContainer(self, urls, filename, data):
- pass
- def checkURLs(self, urls):
- pass
- def deleteFiles(self, fids):
- pass
- def deleteFinished(self):
- pass
- def deletePackages(self, pids):
- pass
- def freeSpace(self):
- pass
- def generateAndAddPackages(self, links, dest):
- pass
- def generatePackages(self, links):
- pass
- def getAccountTypes(self):
- pass
- def getAccounts(self, refresh):
- pass
- def getAllInfo(self):
- pass
- def getAllUserData(self):
- pass
- def getCaptchaTask(self, exclusive):
- pass
- def getCaptchaTaskStatus(self, tid):
- pass
- def getCollector(self):
- pass
- def getCollectorData(self):
- pass
- def getConfig(self):
- pass
- def getConfigValue(self, category, option, section):
- pass
- def getEvents(self, uuid):
- pass
- def getFileData(self, fid):
- pass
- def getFileOrder(self, pid):
- pass
- def getInfoByPlugin(self, plugin):
- pass
- def getLog(self, offset):
- pass
- def getPackageData(self, pid):
- pass
- def getPackageInfo(self, pid):
- pass
- def getPackageOrder(self, destination):
- pass
- def getPluginConfig(self):
- pass
- def getQueue(self):
- pass
- def getQueueData(self):
- pass
- def getServerVersion(self):
- pass
- def getServices(self):
- pass
- def getUserData(self, username, password):
- pass
- def hasService(self, plugin, func):
- pass
- def isCaptchaWaiting(self):
- pass
- def isTimeDownload(self):
- pass
- def isTimeReconnect(self):
- pass
- def kill(self):
- pass
- def login(self, username, password):
- pass
- def moveFiles(self, fids, pid):
- pass
- def movePackage(self, destination, pid):
- pass
- def orderFile(self, fid, position):
- pass
- def orderPackage(self, pid, position):
- pass
- def parseURLs(self, html, url):
- pass
- def pauseServer(self):
- pass
- def pollResults(self, rid):
- pass
- def pullFromQueue(self, pid):
- pass
- def pushToQueue(self, pid):
- pass
- def recheckPackage(self, pid):
- pass
- def removeAccount(self, plugin, account):
- pass
- def restart(self):
- pass
- def restartFailed(self):
- pass
- def restartFile(self, fid):
- pass
- def restartPackage(self, pid):
- pass
- def setCaptchaResult(self, tid, result):
- pass
- def setConfigValue(self, category, option, value, section):
- pass
- def setPackageData(self, pid, data):
- pass
- def setPackageName(self, pid, name):
- pass
- def statusDownloads(self):
- pass
- def statusServer(self):
- pass
- def stopAllDownloads(self):
- pass
- def stopDownloads(self, fids):
- pass
- def togglePause(self):
- pass
- def toggleReconnect(self):
- pass
- def unpauseServer(self):
- pass
- def updateAccount(self, plugin, account, password, options):
- pass
- def uploadContainer(self, filename, data):
- pass
-
diff --git a/module/remote/thriftbackend/Protocol.py b/module/remote/thriftbackend/Protocol.py
deleted file mode 100644
index c42d01459..000000000
--- a/module/remote/thriftbackend/Protocol.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thrift.protocol import TBinaryProtocol
-
-class Protocol(TBinaryProtocol.TBinaryProtocol):
- def writeString(self, str):
- try:
- str = str.encode("utf8", "ignore")
- except Exception, e:
- pass
-
- self.writeI32(len(str))
- self.trans.write(str)
-
- def readString(self):
- len = self.readI32()
- str = self.trans.readAll(len)
- try:
- str = str.decode("utf8", "ignore")
- except:
- pass
-
- return str
-
-
-class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory):
-
- def getProtocol(self, trans):
- prot = Protocol(trans, self.strictRead, self.strictWrite)
- return prot \ No newline at end of file
diff --git a/module/remote/thriftbackend/Socket.py b/module/remote/thriftbackend/Socket.py
deleted file mode 100644
index 2243f9df2..000000000
--- a/module/remote/thriftbackend/Socket.py
+++ /dev/null
@@ -1,129 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-import socket
-import errno
-
-from time import sleep
-
-from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException
-
-WantReadError = Exception #overwritten when ssl is used
-
-class SecureSocketConnection:
- def __init__(self, connection):
- self.__dict__["connection"] = connection
-
- def __getattr__(self, name):
- return getattr(self.__dict__["connection"], name)
-
- def __setattr__(self, name, value):
- setattr(self.__dict__["connection"], name, value)
-
- def shutdown(self, how=1):
- self.__dict__["connection"].shutdown()
-
- def accept(self):
- connection, address = self.__dict__["connection"].accept()
- return SecureSocketConnection(connection), address
-
- def send(self, buff):
- try:
- return self.__dict__["connection"].send(buff)
- except WantReadError:
- sleep(0.1)
- return self.send(buff)
-
- def recv(self, buff):
- try:
- return self.__dict__["connection"].recv(buff)
- except WantReadError:
- sleep(0.1)
- return self.recv(buff)
-
-class Socket(TSocket):
- def __init__(self, host='localhost', port=7228, ssl=False):
- TSocket.__init__(self, host, port)
- self.ssl = ssl
-
- def open(self):
- if self.ssl:
- SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
- WantReadError = SSL.WantReadError
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
- c.set_connect_state()
- self.handle = SecureSocketConnection(c)
- else:
- self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
- #errno 104 connection reset
-
- self.handle.settimeout(self._timeout)
- self.handle.connect((self.host, self.port))
-
- def read(self, sz):
- try:
- buff = self.handle.recv(sz)
- except socket.error, e:
- if (e.args[0] == errno.ECONNRESET and
- (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
- # freebsd and Mach don't follow POSIX semantic of recv
- # and fail with ECONNRESET if peer performed shutdown.
- # See corresponding comment and code in TSocket::read()
- # in lib/cpp/src/transport/TSocket.cpp.
- self.close()
- # Trigger the check to raise the END_OF_FILE exception below.
- buff = ''
- else:
- raise
- except Exception, e:
- # SSL connection was closed
- if e.args == (-1, 'Unexpected EOF'):
- buff = ''
- elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],):
- #a socket not using ssl tried to connect
- buff = ''
- else:
- raise
-
- if not len(buff):
- raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes')
- return buff
-
-
-class ServerSocket(TServerSocket, Socket):
- def __init__(self, port=7228, host="0.0.0.0", key="", cert=""):
- self.host = host
- self.port = port
- self.key = key
- self.cert = cert
- self.handle = None
-
- def listen(self):
- if self.cert and self.key:
- SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
- WantReadError = SSL.WantReadError
- ctx = SSL.Context(SSL.SSLv23_METHOD)
- ctx.use_privatekey_file(self.key)
- ctx.use_certificate_file(self.cert)
-
- tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
- tmpConnection.set_accept_state()
- self.handle = SecureSocketConnection(tmpConnection)
-
- else:
- self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-
- self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- if hasattr(self.handle, 'set_timeout'):
- self.handle.set_timeout(None)
- self.handle.bind((self.host, self.port))
- self.handle.listen(128)
-
- def accept(self):
- client, addr = self.handle.accept()
- result = Socket()
- result.setHandle(client)
- return result
diff --git a/module/remote/thriftbackend/ThriftClient.py b/module/remote/thriftbackend/ThriftClient.py
deleted file mode 100644
index 74363cf62..000000000
--- a/module/remote/thriftbackend/ThriftClient.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import sys
-from socket import error
-from os.path import dirname, abspath, join
-from traceback import print_exc
-
-try:
- import thrift
-except ImportError:
- sys.path.append(abspath(join(dirname(abspath(__file__)), "..", "..", "lib")))
-
-from thrift.transport import TTransport
-#from thrift.transport.TZlibTransport import TZlibTransport
-from Socket import Socket
-from Protocol import Protocol
-
-# modules should import ttypes from here, when want to avoid importing API
-
-from thriftgen.pyload import Pyload
-from thriftgen.pyload.ttypes import *
-
-ConnectionClosed = TTransport.TTransportException
-
-class WrongLogin(Exception):
- pass
-
-class NoConnection(Exception):
- pass
-
-class NoSSL(Exception):
- pass
-
-class ThriftClient:
- def __init__(self, host="localhost", port=7227, user="", password=""):
-
- self.createConnection(host, port)
- try:
- self.transport.open()
- except error, e:
- if e.args and e.args[0] in (111, 10061):
- raise NoConnection
- else:
- print_exc()
- raise NoConnection
-
- try:
- correct = self.client.login(user, password)
- except error, e:
- if e.args and e.args[0] == 104:
- #connection reset by peer, probably wants ssl
- try:
- self.createConnection(host, port, True)
- #set timeout or a ssl socket will block when querying none ssl server
- self.socket.setTimeout(10)
-
- except ImportError:
- #@TODO untested
- raise NoSSL
- try:
- self.transport.open()
- correct = self.client.login(user, password)
- finally:
- self.socket.setTimeout(None)
- elif e.args and e.args[0] == 32:
- raise NoConnection
- else:
- print_exc()
- raise NoConnection
-
- if not correct:
- self.transport.close()
- raise WrongLogin
-
- def createConnection(self, host, port, ssl=False):
- self.socket = Socket(host, port, ssl)
- self.transport = TTransport.TBufferedTransport(self.socket)
-# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket))
-
- protocol = Protocol(self.transport)
- self.client = Pyload.Client(protocol)
-
- def close(self):
- self.transport.close()
-
- def __getattr__(self, item):
- return getattr(self.client, item)
-
-if __name__ == "__main__":
-
- client = ThriftClient(user="User", password="pwhere")
-
- print client.getServerVersion()
- print client.statusServer()
- print client.statusDownloads()
- q = client.getQueue()
-
-# for p in q:
-# data = client.getPackageData(p.pid)
-# print data
-# print "Package Name: ", data.name
-
-
- print client.getServices()
- print client.call(Pyload.ServiceCall("UpdateManager", "recheckForUpdates"))
-
- print client.getConfigValue("download", "limit_speed", "core")
-
- client.close() \ No newline at end of file
diff --git a/module/remote/thriftbackend/ThriftTest.py b/module/remote/thriftbackend/ThriftTest.py
deleted file mode 100644
index 69ac6a745..000000000
--- a/module/remote/thriftbackend/ThriftTest.py
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import sys
-from os.path import join,abspath,dirname
-
-path = join((abspath(dirname(__file__))), "..","..", "lib")
-sys.path.append(path)
-
-from thriftgen.pyload import Pyload
-from thriftgen.pyload.ttypes import *
-from Socket import Socket
-
-from thrift import Thrift
-from thrift.transport import TTransport
-
-from Protocol import Protocol
-
-from time import time
-
-import xmlrpclib
-
-def bench(f, *args, **kwargs):
- s = time()
- ret = [f(*args, **kwargs) for i in range(0,100)]
- e = time()
- try:
- print "%s: %f s" % (f._Method__name, e-s)
- except :
- print "%s: %f s" % (f.__name__, e-s)
- return ret
-
-from getpass import getpass
-user = raw_input("user ")
-passwd = getpass("password ")
-
-server_url = "http%s://%s:%s@%s:%s/" % (
- "",
- user,
- passwd,
- "127.0.0.1",
- 7227
-)
-proxy = xmlrpclib.ServerProxy(server_url, allow_none=True)
-
-bench(proxy.get_server_version)
-bench(proxy.status_server)
-bench(proxy.status_downloads)
-#bench(proxy.get_queue)
-#bench(proxy.get_collector)
-print
-try:
-
- # Make socket
- transport = Socket('localhost', 7228, False)
-
- # Buffering is critical. Raw sockets are very slow
- transport = TTransport.TBufferedTransport(transport)
-
- # Wrap in a protocol
- protocol = Protocol(transport)
-
- # Create a client to use the protocol encoder
- client = Pyload.Client(protocol)
-
- # Connect!
- transport.open()
-
- print "Login", client.login(user, passwd)
-
- bench(client.getServerVersion)
- bench(client.statusServer)
- bench(client.statusDownloads)
- #bench(client.getQueue)
- #bench(client.getCollector)
-
- print
- print client.getServerVersion()
- print client.statusServer()
- print client.statusDownloads()
- q = client.getQueue()
-
- for p in q:
- data = client.getPackageData(p.pid)
- print data
- print "Package Name: ", data.name
-
- # Close!
- transport.close()
-
-except Thrift.TException, tx:
- print 'ThriftExpection: %s' % tx.message
diff --git a/module/remote/thriftbackend/Transport.py b/module/remote/thriftbackend/Transport.py
deleted file mode 100644
index 5772c5a9e..000000000
--- a/module/remote/thriftbackend/Transport.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from thrift.transport.TTransport import TBufferedTransport
-from thrift.transport.TZlibTransport import TZlibTransport
-
-class Transport(TBufferedTransport):
- DEFAULT_BUFFER = 4096
-
- def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
- TBufferedTransport.__init__(self, trans, rbuf_size)
- self.handle = trans.handle
- self.remoteaddr = trans.handle.getpeername()
-
-class TransportCompressed(TZlibTransport):
- DEFAULT_BUFFER = 4096
-
- def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
- TZlibTransport.__init__(self, trans, rbuf_size)
- self.handle = trans.handle
- self.remoteaddr = trans.handle.getpeername()
-
-class TransportFactory:
- def getTransport(self, trans):
- buffered = Transport(trans)
- return buffered
-
-class TransportFactoryCompressed:
- _last_trans = None
- _last_z = None
-
- def getTransport(self, trans, compresslevel=9):
- if trans == self._last_trans:
- return self._last_z
- ztrans = TransportCompressed(Transport(trans), compresslevel)
- self._last_trans = trans
- self._last_z = ztrans
- return ztrans \ No newline at end of file
diff --git a/module/remote/thriftbackend/pyload.thrift b/module/remote/thriftbackend/pyload.thrift
deleted file mode 100644
index 1542e651a..000000000
--- a/module/remote/thriftbackend/pyload.thrift
+++ /dev/null
@@ -1,337 +0,0 @@
-namespace java org.pyload.thrift
-
-typedef i32 FileID
-typedef i32 PackageID
-typedef i32 TaskID
-typedef i32 ResultID
-typedef i32 InteractionID
-typedef list<string> LinkList
-typedef string PluginName
-typedef byte Progress
-typedef byte Priority
-
-
-enum DownloadStatus {
- Finished
- Offline,
- Online,
- Queued,
- Skipped,
- Waiting,
- TempOffline,
- Starting,
- Failed,
- Aborted,
- Decrypting,
- Custom,
- Downloading,
- Processing,
- Unknown
-}
-
-enum Destination {
- Collector,
- Queue
-}
-
-enum ElementType {
- Package,
- File
-}
-
-// types for user interaction
-// some may only be place holder currently not supported
-// also all input - output combination are not reasonable, see InteractionManager for further info
-enum Input {
- NONE,
- TEXT,
- TEXTBOX,
- PASSWORD,
- BOOL, // confirm like, yes or no dialog
- CLICK, // for positional captchas
- CHOICE, // choice from list
- MULTIPLE, // multiple choice from list of elements
- LIST, // arbitary list of elements
- TABLE // table like data structure
-}
-// more can be implemented by need
-
-// this describes the type of the outgoing interaction
-// ensure they can be logcial or'ed
-enum Output {
- CAPTCHA = 1,
- QUESTION = 2,
- NOTIFICATION = 4,
-}
-
-struct DownloadInfo {
- 1: FileID fid,
- 2: string name,
- 3: i64 speed,
- 4: i32 eta,
- 5: string format_eta,
- 6: i64 bleft,
- 7: i64 size,
- 8: string format_size,
- 9: Progress percent,
- 10: DownloadStatus status,
- 11: string statusmsg,
- 12: string format_wait,
- 13: i64 wait_until,
- 14: PackageID packageID,
- 15: string packageName,
- 16: PluginName plugin,
-}
-
-struct ServerStatus {
- 1: bool pause,
- 2: i16 active,
- 3: i16 queue,
- 4: i16 total,
- 5: i64 speed,
- 6: bool download,
- 7: bool reconnect
-}
-
-struct ConfigItem {
- 1: string name,
- 2: string description,
- 3: string value,
- 4: string type,
-}
-
-struct ConfigSection {
- 1: string name,
- 2: string description,
- 3: list<ConfigItem> items,
- 4: optional string outline
-}
-
-struct FileData {
- 1: FileID fid,
- 2: string url,
- 3: string name,
- 4: PluginName plugin,
- 5: i64 size,
- 6: string format_size,
- 7: DownloadStatus status,
- 8: string statusmsg,
- 9: PackageID packageID,
- 10: string error,
- 11: i16 order
-}
-
-struct PackageData {
- 1: PackageID pid,
- 2: string name,
- 3: string folder,
- 4: string site,
- 5: string password,
- 6: Destination dest,
- 7: i16 order,
- 8: optional i16 linksdone,
- 9: optional i64 sizedone,
- 10: optional i64 sizetotal,
- 11: optional i16 linkstotal,
- 12: optional list<FileData> links,
- 13: optional list<FileID> fids
-}
-
-struct InteractionTask {
- 1: InteractionID iid,
- 2: Input input,
- 3: list<string> structure,
- 4: list<string> preset,
- 5: Output output,
- 6: list<string> data,
- 7: string title,
- 8: string description,
- 9: string plugin,
-}
-
-struct CaptchaTask {
- 1: i16 tid,
- 2: binary data,
- 3: string type,
- 4: string resultType
-}
-
-struct EventInfo {
- 1: string eventname,
- 2: optional i32 id,
- 3: optional ElementType type,
- 4: optional Destination destination
-}
-
-struct UserData {
- 1: string name,
- 2: string email,
- 3: i32 role,
- 4: i32 permission,
- 5: string templateName
-}
-
-struct AccountInfo {
- 1: i64 validuntil,
- 2: string login,
- 3: map<string, list<string>> options,
- 4: bool valid,
- 5: i64 trafficleft,
- 6: i64 maxtraffic,
- 7: bool premium,
- 8: string type,
-}
-
-struct ServiceCall {
- 1: PluginName plugin,
- 2: string func,
- 3: optional list<string> arguments,
- 4: optional bool parseArguments, //default False
-}
-
-struct OnlineStatus {
- 1: string name,
- 2: PluginName plugin,
- 3: string packagename,
- 4: DownloadStatus status,
- 5: i64 size, // size <= 0 : unknown
-}
-
-struct OnlineCheck {
- 1: ResultID rid, // -1 -> nothing more to get
- 2: map<string, OnlineStatus> data, //url to result
-}
-
-
-// exceptions
-
-exception PackageDoesNotExists{
- 1: PackageID pid
-}
-
-exception FileDoesNotExists{
- 1: FileID fid
-}
-
-exception ServiceDoesNotExists{
- 1: string plugin
- 2: string func
-}
-
-exception ServiceException{
- 1: string msg
-}
-
-service Pyload {
-
- //config
- string getConfigValue(1: string category, 2: string option, 3: string section),
- void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section),
- map<string, ConfigSection> getConfig(),
- map<string, ConfigSection> getPluginConfig(),
-
- // server status
- void pauseServer(),
- void unpauseServer(),
- bool togglePause(),
- ServerStatus statusServer(),
- i64 freeSpace(),
- string getServerVersion(),
- void kill(),
- void restart(),
- list<string> getLog(1: i32 offset),
- bool isTimeDownload(),
- bool isTimeReconnect(),
- bool toggleReconnect(),
-
- // download preparing
-
- // packagename - urls
- map<string, LinkList> generatePackages(1: LinkList links),
- map<PluginName, LinkList> checkURLs(1: LinkList urls),
- map<PluginName, LinkList> parseURLs(1: string html, 2: string url),
-
- // parses results and generates packages
- OnlineCheck checkOnlineStatus(1: LinkList urls),
- OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
-
- // poll results from previosly started online check
- OnlineCheck pollResults(1: ResultID rid),
-
- // downloads - information
- list<DownloadInfo> statusDownloads(),
- PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e),
- PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
- FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e),
- list<PackageData> getQueue(),
- list<PackageData> getCollector(),
- list<PackageData> getQueueData(),
- list<PackageData> getCollectorData(),
- map<i16, PackageID> getPackageOrder(1: Destination destination),
- map<i16, FileID> getFileOrder(1: PackageID pid)
-
- // downloads - adding/deleting
- list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest),
- PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest),
- void addFiles(1: PackageID pid, 2: LinkList links),
- void uploadContainer(1: string filename, 2: binary data),
- void deleteFiles(1: list<FileID> fids),
- void deletePackages(1: list<PackageID> pids),
-
- // downloads - modifying
- void pushToQueue(1: PackageID pid),
- void pullFromQueue(1: PackageID pid),
- void restartPackage(1: PackageID pid),
- void restartFile(1: FileID fid),
- void recheckPackage(1: PackageID pid),
- void stopAllDownloads(),
- void stopDownloads(1: list<FileID> fids),
- void setPackageName(1: PackageID pid, 2: string name),
- void movePackage(1: Destination destination, 2: PackageID pid),
- void moveFiles(1: list<FileID> fids, 2: PackageID pid),
- void orderPackage(1: PackageID pid, 2: i16 position),
- void orderFile(1: FileID fid, 2: i16 position),
- void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
- list<PackageID> deleteFinished(),
- void restartFailed(),
-
- //events
- list<EventInfo> getEvents(1: string uuid)
-
- //accounts
- list<AccountInfo> getAccounts(1: bool refresh),
- list<string> getAccountTypes()
- void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options),
- void removeAccount(1: PluginName plugin, 2: string account),
-
- //auth
- bool login(1: string username, 2: string password),
- UserData getUserData(1: string username, 2:string password),
- map<string, UserData> getAllUserData(),
-
- //services
-
- // servicename : description
- map<PluginName, map<string, string>> getServices(),
- bool hasService(1: PluginName plugin, 2: string func),
- string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e),
-
-
- //info
- // {plugin: {name: value}}
- map<PluginName, map<string,string>> getAllInfo(),
- map<string, string> getInfoByPlugin(1: PluginName plugin),
-
- //scheduler
-
- // TODO
-
-
- // User interaction
-
- //captcha
- bool isCaptchaWaiting(),
- CaptchaTask getCaptchaTask(1: bool exclusive),
- string getCaptchaTaskStatus(1: TaskID tid),
- void setCaptchaResult(1: TaskID tid, 2: string result),
-}
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
deleted file mode 100755
index bfaf5b078..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload-remote
+++ /dev/null
@@ -1,571 +0,0 @@
-#!/usr/bin/env python
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-import sys
-import pprint
-from urlparse import urlparse
-from thrift.transport import TTransport
-from thrift.transport import TSocket
-from thrift.transport import THttpClient
-from thrift.protocol import TBinaryProtocol
-
-import Pyload
-from ttypes import *
-
-if len(sys.argv) <= 1 or sys.argv[1] == '--help':
- print ''
- print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]'
- print ''
- print 'Functions:'
- print ' string getConfigValue(string category, string option, string section)'
- print ' void setConfigValue(string category, string option, string value, string section)'
- print ' getConfig()'
- print ' getPluginConfig()'
- print ' void pauseServer()'
- print ' void unpauseServer()'
- print ' bool togglePause()'
- print ' ServerStatus statusServer()'
- print ' i64 freeSpace()'
- print ' string getServerVersion()'
- print ' void kill()'
- print ' void restart()'
- print ' getLog(i32 offset)'
- print ' bool isTimeDownload()'
- print ' bool isTimeReconnect()'
- print ' bool toggleReconnect()'
- print ' generatePackages(LinkList links)'
- print ' checkURLs(LinkList urls)'
- print ' parseURLs(string html, string url)'
- print ' OnlineCheck checkOnlineStatus(LinkList urls)'
- print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)'
- print ' OnlineCheck pollResults(ResultID rid)'
- print ' statusDownloads()'
- print ' PackageData getPackageData(PackageID pid)'
- print ' PackageData getPackageInfo(PackageID pid)'
- print ' FileData getFileData(FileID fid)'
- print ' getQueue()'
- print ' getCollector()'
- print ' getQueueData()'
- print ' getCollectorData()'
- print ' getPackageOrder(Destination destination)'
- print ' getFileOrder(PackageID pid)'
- print ' generateAndAddPackages(LinkList links, Destination dest)'
- print ' PackageID addPackage(string name, LinkList links, Destination dest)'
- print ' void addFiles(PackageID pid, LinkList links)'
- print ' void uploadContainer(string filename, string data)'
- print ' void deleteFiles( fids)'
- print ' void deletePackages( pids)'
- print ' void pushToQueue(PackageID pid)'
- print ' void pullFromQueue(PackageID pid)'
- print ' void restartPackage(PackageID pid)'
- print ' void restartFile(FileID fid)'
- print ' void recheckPackage(PackageID pid)'
- print ' void stopAllDownloads()'
- print ' void stopDownloads( fids)'
- print ' void setPackageName(PackageID pid, string name)'
- print ' void movePackage(Destination destination, PackageID pid)'
- print ' void moveFiles( fids, PackageID pid)'
- print ' void orderPackage(PackageID pid, i16 position)'
- print ' void orderFile(FileID fid, i16 position)'
- print ' void setPackageData(PackageID pid, data)'
- print ' deleteFinished()'
- print ' void restartFailed()'
- print ' getEvents(string uuid)'
- print ' getAccounts(bool refresh)'
- print ' getAccountTypes()'
- print ' void updateAccount(PluginName plugin, string account, string password, options)'
- print ' void removeAccount(PluginName plugin, string account)'
- print ' bool login(string username, string password)'
- print ' UserData getUserData(string username, string password)'
- print ' getAllUserData()'
- print ' getServices()'
- print ' bool hasService(PluginName plugin, string func)'
- print ' string call(ServiceCall info)'
- print ' getAllInfo()'
- print ' getInfoByPlugin(PluginName plugin)'
- print ' bool isCaptchaWaiting()'
- print ' CaptchaTask getCaptchaTask(bool exclusive)'
- print ' string getCaptchaTaskStatus(TaskID tid)'
- print ' void setCaptchaResult(TaskID tid, string result)'
- print ''
- sys.exit(0)
-
-pp = pprint.PrettyPrinter(indent = 2)
-host = 'localhost'
-port = 9090
-uri = ''
-framed = False
-http = False
-argi = 1
-
-if sys.argv[argi] == '-h':
- parts = sys.argv[argi+1].split(':')
- host = parts[0]
- if len(parts) > 1:
- port = int(parts[1])
- argi += 2
-
-if sys.argv[argi] == '-u':
- url = urlparse(sys.argv[argi+1])
- parts = url[1].split(':')
- host = parts[0]
- if len(parts) > 1:
- port = int(parts[1])
- else:
- port = 80
- uri = url[2]
- if url[4]:
- uri += '?%s' % url[4]
- http = True
- argi += 2
-
-if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':
- framed = True
- argi += 1
-
-cmd = sys.argv[argi]
-args = sys.argv[argi+1:]
-
-if http:
- transport = THttpClient.THttpClient(host, port, uri)
-else:
- socket = TSocket.TSocket(host, port)
- if framed:
- transport = TTransport.TFramedTransport(socket)
- else:
- transport = TTransport.TBufferedTransport(socket)
-protocol = TBinaryProtocol.TBinaryProtocol(transport)
-client = Pyload.Client(protocol)
-transport.open()
-
-if cmd == 'getConfigValue':
- if len(args) != 3:
- print 'getConfigValue requires 3 args'
- sys.exit(1)
- pp.pprint(client.getConfigValue(args[0],args[1],args[2],))
-
-elif cmd == 'setConfigValue':
- if len(args) != 4:
- print 'setConfigValue requires 4 args'
- sys.exit(1)
- pp.pprint(client.setConfigValue(args[0],args[1],args[2],args[3],))
-
-elif cmd == 'getConfig':
- if len(args) != 0:
- print 'getConfig requires 0 args'
- sys.exit(1)
- pp.pprint(client.getConfig())
-
-elif cmd == 'getPluginConfig':
- if len(args) != 0:
- print 'getPluginConfig requires 0 args'
- sys.exit(1)
- pp.pprint(client.getPluginConfig())
-
-elif cmd == 'pauseServer':
- if len(args) != 0:
- print 'pauseServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.pauseServer())
-
-elif cmd == 'unpauseServer':
- if len(args) != 0:
- print 'unpauseServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.unpauseServer())
-
-elif cmd == 'togglePause':
- if len(args) != 0:
- print 'togglePause requires 0 args'
- sys.exit(1)
- pp.pprint(client.togglePause())
-
-elif cmd == 'statusServer':
- if len(args) != 0:
- print 'statusServer requires 0 args'
- sys.exit(1)
- pp.pprint(client.statusServer())
-
-elif cmd == 'freeSpace':
- if len(args) != 0:
- print 'freeSpace requires 0 args'
- sys.exit(1)
- pp.pprint(client.freeSpace())
-
-elif cmd == 'getServerVersion':
- if len(args) != 0:
- print 'getServerVersion requires 0 args'
- sys.exit(1)
- pp.pprint(client.getServerVersion())
-
-elif cmd == 'kill':
- if len(args) != 0:
- print 'kill requires 0 args'
- sys.exit(1)
- pp.pprint(client.kill())
-
-elif cmd == 'restart':
- if len(args) != 0:
- print 'restart requires 0 args'
- sys.exit(1)
- pp.pprint(client.restart())
-
-elif cmd == 'getLog':
- if len(args) != 1:
- print 'getLog requires 1 args'
- sys.exit(1)
- pp.pprint(client.getLog(eval(args[0]),))
-
-elif cmd == 'isTimeDownload':
- if len(args) != 0:
- print 'isTimeDownload requires 0 args'
- sys.exit(1)
- pp.pprint(client.isTimeDownload())
-
-elif cmd == 'isTimeReconnect':
- if len(args) != 0:
- print 'isTimeReconnect requires 0 args'
- sys.exit(1)
- pp.pprint(client.isTimeReconnect())
-
-elif cmd == 'toggleReconnect':
- if len(args) != 0:
- print 'toggleReconnect requires 0 args'
- sys.exit(1)
- pp.pprint(client.toggleReconnect())
-
-elif cmd == 'generatePackages':
- if len(args) != 1:
- print 'generatePackages requires 1 args'
- sys.exit(1)
- pp.pprint(client.generatePackages(eval(args[0]),))
-
-elif cmd == 'checkURLs':
- if len(args) != 1:
- print 'checkURLs requires 1 args'
- sys.exit(1)
- pp.pprint(client.checkURLs(eval(args[0]),))
-
-elif cmd == 'parseURLs':
- if len(args) != 2:
- print 'parseURLs requires 2 args'
- sys.exit(1)
- pp.pprint(client.parseURLs(args[0],args[1],))
-
-elif cmd == 'checkOnlineStatus':
- if len(args) != 1:
- print 'checkOnlineStatus requires 1 args'
- sys.exit(1)
- pp.pprint(client.checkOnlineStatus(eval(args[0]),))
-
-elif cmd == 'checkOnlineStatusContainer':
- if len(args) != 3:
- print 'checkOnlineStatusContainer requires 3 args'
- sys.exit(1)
- pp.pprint(client.checkOnlineStatusContainer(eval(args[0]),args[1],args[2],))
-
-elif cmd == 'pollResults':
- if len(args) != 1:
- print 'pollResults requires 1 args'
- sys.exit(1)
- pp.pprint(client.pollResults(eval(args[0]),))
-
-elif cmd == 'statusDownloads':
- if len(args) != 0:
- print 'statusDownloads requires 0 args'
- sys.exit(1)
- pp.pprint(client.statusDownloads())
-
-elif cmd == 'getPackageData':
- if len(args) != 1:
- print 'getPackageData requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageData(eval(args[0]),))
-
-elif cmd == 'getPackageInfo':
- if len(args) != 1:
- print 'getPackageInfo requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageInfo(eval(args[0]),))
-
-elif cmd == 'getFileData':
- if len(args) != 1:
- print 'getFileData requires 1 args'
- sys.exit(1)
- pp.pprint(client.getFileData(eval(args[0]),))
-
-elif cmd == 'getQueue':
- if len(args) != 0:
- print 'getQueue requires 0 args'
- sys.exit(1)
- pp.pprint(client.getQueue())
-
-elif cmd == 'getCollector':
- if len(args) != 0:
- print 'getCollector requires 0 args'
- sys.exit(1)
- pp.pprint(client.getCollector())
-
-elif cmd == 'getQueueData':
- if len(args) != 0:
- print 'getQueueData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getQueueData())
-
-elif cmd == 'getCollectorData':
- if len(args) != 0:
- print 'getCollectorData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getCollectorData())
-
-elif cmd == 'getPackageOrder':
- if len(args) != 1:
- print 'getPackageOrder requires 1 args'
- sys.exit(1)
- pp.pprint(client.getPackageOrder(eval(args[0]),))
-
-elif cmd == 'getFileOrder':
- if len(args) != 1:
- print 'getFileOrder requires 1 args'
- sys.exit(1)
- pp.pprint(client.getFileOrder(eval(args[0]),))
-
-elif cmd == 'generateAndAddPackages':
- if len(args) != 2:
- print 'generateAndAddPackages requires 2 args'
- sys.exit(1)
- pp.pprint(client.generateAndAddPackages(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'addPackage':
- if len(args) != 3:
- print 'addPackage requires 3 args'
- sys.exit(1)
- pp.pprint(client.addPackage(args[0],eval(args[1]),eval(args[2]),))
-
-elif cmd == 'addFiles':
- if len(args) != 2:
- print 'addFiles requires 2 args'
- sys.exit(1)
- pp.pprint(client.addFiles(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'uploadContainer':
- if len(args) != 2:
- print 'uploadContainer requires 2 args'
- sys.exit(1)
- pp.pprint(client.uploadContainer(args[0],args[1],))
-
-elif cmd == 'deleteFiles':
- if len(args) != 1:
- print 'deleteFiles requires 1 args'
- sys.exit(1)
- pp.pprint(client.deleteFiles(eval(args[0]),))
-
-elif cmd == 'deletePackages':
- if len(args) != 1:
- print 'deletePackages requires 1 args'
- sys.exit(1)
- pp.pprint(client.deletePackages(eval(args[0]),))
-
-elif cmd == 'pushToQueue':
- if len(args) != 1:
- print 'pushToQueue requires 1 args'
- sys.exit(1)
- pp.pprint(client.pushToQueue(eval(args[0]),))
-
-elif cmd == 'pullFromQueue':
- if len(args) != 1:
- print 'pullFromQueue requires 1 args'
- sys.exit(1)
- pp.pprint(client.pullFromQueue(eval(args[0]),))
-
-elif cmd == 'restartPackage':
- if len(args) != 1:
- print 'restartPackage requires 1 args'
- sys.exit(1)
- pp.pprint(client.restartPackage(eval(args[0]),))
-
-elif cmd == 'restartFile':
- if len(args) != 1:
- print 'restartFile requires 1 args'
- sys.exit(1)
- pp.pprint(client.restartFile(eval(args[0]),))
-
-elif cmd == 'recheckPackage':
- if len(args) != 1:
- print 'recheckPackage requires 1 args'
- sys.exit(1)
- pp.pprint(client.recheckPackage(eval(args[0]),))
-
-elif cmd == 'stopAllDownloads':
- if len(args) != 0:
- print 'stopAllDownloads requires 0 args'
- sys.exit(1)
- pp.pprint(client.stopAllDownloads())
-
-elif cmd == 'stopDownloads':
- if len(args) != 1:
- print 'stopDownloads requires 1 args'
- sys.exit(1)
- pp.pprint(client.stopDownloads(eval(args[0]),))
-
-elif cmd == 'setPackageName':
- if len(args) != 2:
- print 'setPackageName requires 2 args'
- sys.exit(1)
- pp.pprint(client.setPackageName(eval(args[0]),args[1],))
-
-elif cmd == 'movePackage':
- if len(args) != 2:
- print 'movePackage requires 2 args'
- sys.exit(1)
- pp.pprint(client.movePackage(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'moveFiles':
- if len(args) != 2:
- print 'moveFiles requires 2 args'
- sys.exit(1)
- pp.pprint(client.moveFiles(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'orderPackage':
- if len(args) != 2:
- print 'orderPackage requires 2 args'
- sys.exit(1)
- pp.pprint(client.orderPackage(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'orderFile':
- if len(args) != 2:
- print 'orderFile requires 2 args'
- sys.exit(1)
- pp.pprint(client.orderFile(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'setPackageData':
- if len(args) != 2:
- print 'setPackageData requires 2 args'
- sys.exit(1)
- pp.pprint(client.setPackageData(eval(args[0]),eval(args[1]),))
-
-elif cmd == 'deleteFinished':
- if len(args) != 0:
- print 'deleteFinished requires 0 args'
- sys.exit(1)
- pp.pprint(client.deleteFinished())
-
-elif cmd == 'restartFailed':
- if len(args) != 0:
- print 'restartFailed requires 0 args'
- sys.exit(1)
- pp.pprint(client.restartFailed())
-
-elif cmd == 'getEvents':
- if len(args) != 1:
- print 'getEvents requires 1 args'
- sys.exit(1)
- pp.pprint(client.getEvents(args[0],))
-
-elif cmd == 'getAccounts':
- if len(args) != 1:
- print 'getAccounts requires 1 args'
- sys.exit(1)
- pp.pprint(client.getAccounts(eval(args[0]),))
-
-elif cmd == 'getAccountTypes':
- if len(args) != 0:
- print 'getAccountTypes requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAccountTypes())
-
-elif cmd == 'updateAccount':
- if len(args) != 4:
- print 'updateAccount requires 4 args'
- sys.exit(1)
- pp.pprint(client.updateAccount(eval(args[0]),args[1],args[2],eval(args[3]),))
-
-elif cmd == 'removeAccount':
- if len(args) != 2:
- print 'removeAccount requires 2 args'
- sys.exit(1)
- pp.pprint(client.removeAccount(eval(args[0]),args[1],))
-
-elif cmd == 'login':
- if len(args) != 2:
- print 'login requires 2 args'
- sys.exit(1)
- pp.pprint(client.login(args[0],args[1],))
-
-elif cmd == 'getUserData':
- if len(args) != 2:
- print 'getUserData requires 2 args'
- sys.exit(1)
- pp.pprint(client.getUserData(args[0],args[1],))
-
-elif cmd == 'getAllUserData':
- if len(args) != 0:
- print 'getAllUserData requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAllUserData())
-
-elif cmd == 'getServices':
- if len(args) != 0:
- print 'getServices requires 0 args'
- sys.exit(1)
- pp.pprint(client.getServices())
-
-elif cmd == 'hasService':
- if len(args) != 2:
- print 'hasService requires 2 args'
- sys.exit(1)
- pp.pprint(client.hasService(eval(args[0]),args[1],))
-
-elif cmd == 'call':
- if len(args) != 1:
- print 'call requires 1 args'
- sys.exit(1)
- pp.pprint(client.call(eval(args[0]),))
-
-elif cmd == 'getAllInfo':
- if len(args) != 0:
- print 'getAllInfo requires 0 args'
- sys.exit(1)
- pp.pprint(client.getAllInfo())
-
-elif cmd == 'getInfoByPlugin':
- if len(args) != 1:
- print 'getInfoByPlugin requires 1 args'
- sys.exit(1)
- pp.pprint(client.getInfoByPlugin(eval(args[0]),))
-
-elif cmd == 'isCaptchaWaiting':
- if len(args) != 0:
- print 'isCaptchaWaiting requires 0 args'
- sys.exit(1)
- pp.pprint(client.isCaptchaWaiting())
-
-elif cmd == 'getCaptchaTask':
- if len(args) != 1:
- print 'getCaptchaTask requires 1 args'
- sys.exit(1)
- pp.pprint(client.getCaptchaTask(eval(args[0]),))
-
-elif cmd == 'getCaptchaTaskStatus':
- if len(args) != 1:
- print 'getCaptchaTaskStatus requires 1 args'
- sys.exit(1)
- pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),))
-
-elif cmd == 'setCaptchaResult':
- if len(args) != 2:
- print 'setCaptchaResult requires 2 args'
- sys.exit(1)
- pp.pprint(client.setCaptchaResult(eval(args[0]),args[1],))
-
-else:
- print 'Unrecognized method %s' % cmd
- sys.exit(1)
-
-transport.close()
diff --git a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py b/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
deleted file mode 100644
index 78a42f16a..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/Pyload.py
+++ /dev/null
@@ -1,5534 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-from ttypes import *
-from thrift.Thrift import TProcessor
-from thrift.protocol.TBase import TBase, TExceptionBase
-
-
-class Iface(object):
- def getConfigValue(self, category, option, section):
- """
- Parameters:
- - category
- - option
- - section
- """
- pass
-
- def setConfigValue(self, category, option, value, section):
- """
- Parameters:
- - category
- - option
- - value
- - section
- """
- pass
-
- def getConfig(self, ):
- pass
-
- def getPluginConfig(self, ):
- pass
-
- def pauseServer(self, ):
- pass
-
- def unpauseServer(self, ):
- pass
-
- def togglePause(self, ):
- pass
-
- def statusServer(self, ):
- pass
-
- def freeSpace(self, ):
- pass
-
- def getServerVersion(self, ):
- pass
-
- def kill(self, ):
- pass
-
- def restart(self, ):
- pass
-
- def getLog(self, offset):
- """
- Parameters:
- - offset
- """
- pass
-
- def isTimeDownload(self, ):
- pass
-
- def isTimeReconnect(self, ):
- pass
-
- def toggleReconnect(self, ):
- pass
-
- def generatePackages(self, links):
- """
- Parameters:
- - links
- """
- pass
-
- def checkURLs(self, urls):
- """
- Parameters:
- - urls
- """
- pass
-
- def parseURLs(self, html, url):
- """
- Parameters:
- - html
- - url
- """
- pass
-
- def checkOnlineStatus(self, urls):
- """
- Parameters:
- - urls
- """
- pass
-
- def checkOnlineStatusContainer(self, urls, filename, data):
- """
- Parameters:
- - urls
- - filename
- - data
- """
- pass
-
- def pollResults(self, rid):
- """
- Parameters:
- - rid
- """
- pass
-
- def statusDownloads(self, ):
- pass
-
- def getPackageData(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def getPackageInfo(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def getFileData(self, fid):
- """
- Parameters:
- - fid
- """
- pass
-
- def getQueue(self, ):
- pass
-
- def getCollector(self, ):
- pass
-
- def getQueueData(self, ):
- pass
-
- def getCollectorData(self, ):
- pass
-
- def getPackageOrder(self, destination):
- """
- Parameters:
- - destination
- """
- pass
-
- def getFileOrder(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def generateAndAddPackages(self, links, dest):
- """
- Parameters:
- - links
- - dest
- """
- pass
-
- def addPackage(self, name, links, dest):
- """
- Parameters:
- - name
- - links
- - dest
- """
- pass
-
- def addFiles(self, pid, links):
- """
- Parameters:
- - pid
- - links
- """
- pass
-
- def uploadContainer(self, filename, data):
- """
- Parameters:
- - filename
- - data
- """
- pass
-
- def deleteFiles(self, fids):
- """
- Parameters:
- - fids
- """
- pass
-
- def deletePackages(self, pids):
- """
- Parameters:
- - pids
- """
- pass
-
- def pushToQueue(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def pullFromQueue(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def restartPackage(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def restartFile(self, fid):
- """
- Parameters:
- - fid
- """
- pass
-
- def recheckPackage(self, pid):
- """
- Parameters:
- - pid
- """
- pass
-
- def stopAllDownloads(self, ):
- pass
-
- def stopDownloads(self, fids):
- """
- Parameters:
- - fids
- """
- pass
-
- def setPackageName(self, pid, name):
- """
- Parameters:
- - pid
- - name
- """
- pass
-
- def movePackage(self, destination, pid):
- """
- Parameters:
- - destination
- - pid
- """
- pass
-
- def moveFiles(self, fids, pid):
- """
- Parameters:
- - fids
- - pid
- """
- pass
-
- def orderPackage(self, pid, position):
- """
- Parameters:
- - pid
- - position
- """
- pass
-
- def orderFile(self, fid, position):
- """
- Parameters:
- - fid
- - position
- """
- pass
-
- def setPackageData(self, pid, data):
- """
- Parameters:
- - pid
- - data
- """
- pass
-
- def deleteFinished(self, ):
- pass
-
- def restartFailed(self, ):
- pass
-
- def getEvents(self, uuid):
- """
- Parameters:
- - uuid
- """
- pass
-
- def getAccounts(self, refresh):
- """
- Parameters:
- - refresh
- """
- pass
-
- def getAccountTypes(self, ):
- pass
-
- def updateAccount(self, plugin, account, password, options):
- """
- Parameters:
- - plugin
- - account
- - password
- - options
- """
- pass
-
- def removeAccount(self, plugin, account):
- """
- Parameters:
- - plugin
- - account
- """
- pass
-
- def login(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- pass
-
- def getUserData(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- pass
-
- def getAllUserData(self, ):
- pass
-
- def getServices(self, ):
- pass
-
- def hasService(self, plugin, func):
- """
- Parameters:
- - plugin
- - func
- """
- pass
-
- def call(self, info):
- """
- Parameters:
- - info
- """
- pass
-
- def getAllInfo(self, ):
- pass
-
- def getInfoByPlugin(self, plugin):
- """
- Parameters:
- - plugin
- """
- pass
-
- def isCaptchaWaiting(self, ):
- pass
-
- def getCaptchaTask(self, exclusive):
- """
- Parameters:
- - exclusive
- """
- pass
-
- def getCaptchaTaskStatus(self, tid):
- """
- Parameters:
- - tid
- """
- pass
-
- def setCaptchaResult(self, tid, result):
- """
- Parameters:
- - tid
- - result
- """
- pass
-
-
-class Client(Iface):
- def __init__(self, iprot, oprot=None):
- self._iprot = self._oprot = iprot
- if oprot is not None:
- self._oprot = oprot
- self._seqid = 0
-
- def getConfigValue(self, category, option, section):
- """
- Parameters:
- - category
- - option
- - section
- """
- self.send_getConfigValue(category, option, section)
- return self.recv_getConfigValue()
-
- def send_getConfigValue(self, category, option, section):
- self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid)
- args = getConfigValue_args()
- args.category = category
- args.option = option
- args.section = section
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getConfigValue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getConfigValue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result");
-
- def setConfigValue(self, category, option, value, section):
- """
- Parameters:
- - category
- - option
- - value
- - section
- """
- self.send_setConfigValue(category, option, value, section)
- self.recv_setConfigValue()
-
- def send_setConfigValue(self, category, option, value, section):
- self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid)
- args = setConfigValue_args()
- args.category = category
- args.option = option
- args.value = value
- args.section = section
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setConfigValue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setConfigValue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getConfig(self, ):
- self.send_getConfig()
- return self.recv_getConfig()
-
- def send_getConfig(self, ):
- self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid)
- args = getConfig_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getConfig(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getConfig_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result");
-
- def getPluginConfig(self, ):
- self.send_getPluginConfig()
- return self.recv_getPluginConfig()
-
- def send_getPluginConfig(self, ):
- self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid)
- args = getPluginConfig_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPluginConfig(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPluginConfig_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result");
-
- def pauseServer(self, ):
- self.send_pauseServer()
- self.recv_pauseServer()
-
- def send_pauseServer(self, ):
- self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid)
- args = pauseServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pauseServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pauseServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def unpauseServer(self, ):
- self.send_unpauseServer()
- self.recv_unpauseServer()
-
- def send_unpauseServer(self, ):
- self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid)
- args = unpauseServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_unpauseServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = unpauseServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def togglePause(self, ):
- self.send_togglePause()
- return self.recv_togglePause()
-
- def send_togglePause(self, ):
- self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid)
- args = togglePause_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_togglePause(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = togglePause_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result");
-
- def statusServer(self, ):
- self.send_statusServer()
- return self.recv_statusServer()
-
- def send_statusServer(self, ):
- self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid)
- args = statusServer_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_statusServer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = statusServer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result");
-
- def freeSpace(self, ):
- self.send_freeSpace()
- return self.recv_freeSpace()
-
- def send_freeSpace(self, ):
- self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid)
- args = freeSpace_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_freeSpace(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = freeSpace_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result");
-
- def getServerVersion(self, ):
- self.send_getServerVersion()
- return self.recv_getServerVersion()
-
- def send_getServerVersion(self, ):
- self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid)
- args = getServerVersion_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getServerVersion(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getServerVersion_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result");
-
- def kill(self, ):
- self.send_kill()
- self.recv_kill()
-
- def send_kill(self, ):
- self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid)
- args = kill_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_kill(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = kill_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restart(self, ):
- self.send_restart()
- self.recv_restart()
-
- def send_restart(self, ):
- self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid)
- args = restart_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restart(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restart_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getLog(self, offset):
- """
- Parameters:
- - offset
- """
- self.send_getLog(offset)
- return self.recv_getLog()
-
- def send_getLog(self, offset):
- self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid)
- args = getLog_args()
- args.offset = offset
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getLog(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getLog_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result");
-
- def isTimeDownload(self, ):
- self.send_isTimeDownload()
- return self.recv_isTimeDownload()
-
- def send_isTimeDownload(self, ):
- self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid)
- args = isTimeDownload_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isTimeDownload(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isTimeDownload_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result");
-
- def isTimeReconnect(self, ):
- self.send_isTimeReconnect()
- return self.recv_isTimeReconnect()
-
- def send_isTimeReconnect(self, ):
- self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid)
- args = isTimeReconnect_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isTimeReconnect(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isTimeReconnect_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result");
-
- def toggleReconnect(self, ):
- self.send_toggleReconnect()
- return self.recv_toggleReconnect()
-
- def send_toggleReconnect(self, ):
- self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid)
- args = toggleReconnect_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_toggleReconnect(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = toggleReconnect_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result");
-
- def generatePackages(self, links):
- """
- Parameters:
- - links
- """
- self.send_generatePackages(links)
- return self.recv_generatePackages()
-
- def send_generatePackages(self, links):
- self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid)
- args = generatePackages_args()
- args.links = links
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_generatePackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = generatePackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result");
-
- def checkURLs(self, urls):
- """
- Parameters:
- - urls
- """
- self.send_checkURLs(urls)
- return self.recv_checkURLs()
-
- def send_checkURLs(self, urls):
- self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid)
- args = checkURLs_args()
- args.urls = urls
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkURLs(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkURLs_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result");
-
- def parseURLs(self, html, url):
- """
- Parameters:
- - html
- - url
- """
- self.send_parseURLs(html, url)
- return self.recv_parseURLs()
-
- def send_parseURLs(self, html, url):
- self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid)
- args = parseURLs_args()
- args.html = html
- args.url = url
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_parseURLs(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = parseURLs_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result");
-
- def checkOnlineStatus(self, urls):
- """
- Parameters:
- - urls
- """
- self.send_checkOnlineStatus(urls)
- return self.recv_checkOnlineStatus()
-
- def send_checkOnlineStatus(self, urls):
- self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid)
- args = checkOnlineStatus_args()
- args.urls = urls
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkOnlineStatus(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkOnlineStatus_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result");
-
- def checkOnlineStatusContainer(self, urls, filename, data):
- """
- Parameters:
- - urls
- - filename
- - data
- """
- self.send_checkOnlineStatusContainer(urls, filename, data)
- return self.recv_checkOnlineStatusContainer()
-
- def send_checkOnlineStatusContainer(self, urls, filename, data):
- self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid)
- args = checkOnlineStatusContainer_args()
- args.urls = urls
- args.filename = filename
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_checkOnlineStatusContainer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = checkOnlineStatusContainer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result");
-
- def pollResults(self, rid):
- """
- Parameters:
- - rid
- """
- self.send_pollResults(rid)
- return self.recv_pollResults()
-
- def send_pollResults(self, rid):
- self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid)
- args = pollResults_args()
- args.rid = rid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pollResults(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pollResults_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result");
-
- def statusDownloads(self, ):
- self.send_statusDownloads()
- return self.recv_statusDownloads()
-
- def send_statusDownloads(self, ):
- self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid)
- args = statusDownloads_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_statusDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = statusDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result");
-
- def getPackageData(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getPackageData(pid)
- return self.recv_getPackageData()
-
- def send_getPackageData(self, pid):
- self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid)
- args = getPackageData_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result");
-
- def getPackageInfo(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getPackageInfo(pid)
- return self.recv_getPackageInfo()
-
- def send_getPackageInfo(self, pid):
- self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid)
- args = getPackageInfo_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageInfo(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageInfo_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result");
-
- def getFileData(self, fid):
- """
- Parameters:
- - fid
- """
- self.send_getFileData(fid)
- return self.recv_getFileData()
-
- def send_getFileData(self, fid):
- self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid)
- args = getFileData_args()
- args.fid = fid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getFileData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getFileData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result");
-
- def getQueue(self, ):
- self.send_getQueue()
- return self.recv_getQueue()
-
- def send_getQueue(self, ):
- self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid)
- args = getQueue_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result");
-
- def getCollector(self, ):
- self.send_getCollector()
- return self.recv_getCollector()
-
- def send_getCollector(self, ):
- self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid)
- args = getCollector_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCollector(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCollector_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result");
-
- def getQueueData(self, ):
- self.send_getQueueData()
- return self.recv_getQueueData()
-
- def send_getQueueData(self, ):
- self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid)
- args = getQueueData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getQueueData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getQueueData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result");
-
- def getCollectorData(self, ):
- self.send_getCollectorData()
- return self.recv_getCollectorData()
-
- def send_getCollectorData(self, ):
- self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid)
- args = getCollectorData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCollectorData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCollectorData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result");
-
- def getPackageOrder(self, destination):
- """
- Parameters:
- - destination
- """
- self.send_getPackageOrder(destination)
- return self.recv_getPackageOrder()
-
- def send_getPackageOrder(self, destination):
- self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid)
- args = getPackageOrder_args()
- args.destination = destination
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getPackageOrder(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getPackageOrder_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result");
-
- def getFileOrder(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_getFileOrder(pid)
- return self.recv_getFileOrder()
-
- def send_getFileOrder(self, pid):
- self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid)
- args = getFileOrder_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getFileOrder(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getFileOrder_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result");
-
- def generateAndAddPackages(self, links, dest):
- """
- Parameters:
- - links
- - dest
- """
- self.send_generateAndAddPackages(links, dest)
- return self.recv_generateAndAddPackages()
-
- def send_generateAndAddPackages(self, links, dest):
- self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
- args = generateAndAddPackages_args()
- args.links = links
- args.dest = dest
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_generateAndAddPackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = generateAndAddPackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result");
-
- def addPackage(self, name, links, dest):
- """
- Parameters:
- - name
- - links
- - dest
- """
- self.send_addPackage(name, links, dest)
- return self.recv_addPackage()
-
- def send_addPackage(self, name, links, dest):
- self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
- args = addPackage_args()
- args.name = name
- args.links = links
- args.dest = dest
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_addPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = addPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result");
-
- def addFiles(self, pid, links):
- """
- Parameters:
- - pid
- - links
- """
- self.send_addFiles(pid, links)
- self.recv_addFiles()
-
- def send_addFiles(self, pid, links):
- self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid)
- args = addFiles_args()
- args.pid = pid
- args.links = links
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_addFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = addFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def uploadContainer(self, filename, data):
- """
- Parameters:
- - filename
- - data
- """
- self.send_uploadContainer(filename, data)
- self.recv_uploadContainer()
-
- def send_uploadContainer(self, filename, data):
- self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid)
- args = uploadContainer_args()
- args.filename = filename
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_uploadContainer(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = uploadContainer_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def deleteFiles(self, fids):
- """
- Parameters:
- - fids
- """
- self.send_deleteFiles(fids)
- self.recv_deleteFiles()
-
- def send_deleteFiles(self, fids):
- self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid)
- args = deleteFiles_args()
- args.fids = fids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deleteFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deleteFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def deletePackages(self, pids):
- """
- Parameters:
- - pids
- """
- self.send_deletePackages(pids)
- self.recv_deletePackages()
-
- def send_deletePackages(self, pids):
- self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid)
- args = deletePackages_args()
- args.pids = pids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deletePackages(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deletePackages_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def pushToQueue(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_pushToQueue(pid)
- self.recv_pushToQueue()
-
- def send_pushToQueue(self, pid):
- self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid)
- args = pushToQueue_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pushToQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pushToQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def pullFromQueue(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_pullFromQueue(pid)
- self.recv_pullFromQueue()
-
- def send_pullFromQueue(self, pid):
- self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid)
- args = pullFromQueue_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_pullFromQueue(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = pullFromQueue_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restartPackage(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_restartPackage(pid)
- self.recv_restartPackage()
-
- def send_restartPackage(self, pid):
- self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid)
- args = restartPackage_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def restartFile(self, fid):
- """
- Parameters:
- - fid
- """
- self.send_restartFile(fid)
- self.recv_restartFile()
-
- def send_restartFile(self, fid):
- self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid)
- args = restartFile_args()
- args.fid = fid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartFile(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartFile_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def recheckPackage(self, pid):
- """
- Parameters:
- - pid
- """
- self.send_recheckPackage(pid)
- self.recv_recheckPackage()
-
- def send_recheckPackage(self, pid):
- self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid)
- args = recheckPackage_args()
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_recheckPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = recheckPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def stopAllDownloads(self, ):
- self.send_stopAllDownloads()
- self.recv_stopAllDownloads()
-
- def send_stopAllDownloads(self, ):
- self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid)
- args = stopAllDownloads_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_stopAllDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = stopAllDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def stopDownloads(self, fids):
- """
- Parameters:
- - fids
- """
- self.send_stopDownloads(fids)
- self.recv_stopDownloads()
-
- def send_stopDownloads(self, fids):
- self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid)
- args = stopDownloads_args()
- args.fids = fids
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_stopDownloads(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = stopDownloads_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def setPackageName(self, pid, name):
- """
- Parameters:
- - pid
- - name
- """
- self.send_setPackageName(pid, name)
- self.recv_setPackageName()
-
- def send_setPackageName(self, pid, name):
- self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid)
- args = setPackageName_args()
- args.pid = pid
- args.name = name
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setPackageName(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setPackageName_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def movePackage(self, destination, pid):
- """
- Parameters:
- - destination
- - pid
- """
- self.send_movePackage(destination, pid)
- self.recv_movePackage()
-
- def send_movePackage(self, destination, pid):
- self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid)
- args = movePackage_args()
- args.destination = destination
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_movePackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = movePackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def moveFiles(self, fids, pid):
- """
- Parameters:
- - fids
- - pid
- """
- self.send_moveFiles(fids, pid)
- self.recv_moveFiles()
-
- def send_moveFiles(self, fids, pid):
- self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid)
- args = moveFiles_args()
- args.fids = fids
- args.pid = pid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_moveFiles(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = moveFiles_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def orderPackage(self, pid, position):
- """
- Parameters:
- - pid
- - position
- """
- self.send_orderPackage(pid, position)
- self.recv_orderPackage()
-
- def send_orderPackage(self, pid, position):
- self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid)
- args = orderPackage_args()
- args.pid = pid
- args.position = position
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_orderPackage(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = orderPackage_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def orderFile(self, fid, position):
- """
- Parameters:
- - fid
- - position
- """
- self.send_orderFile(fid, position)
- self.recv_orderFile()
-
- def send_orderFile(self, fid, position):
- self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid)
- args = orderFile_args()
- args.fid = fid
- args.position = position
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_orderFile(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = orderFile_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def setPackageData(self, pid, data):
- """
- Parameters:
- - pid
- - data
- """
- self.send_setPackageData(pid, data)
- self.recv_setPackageData()
-
- def send_setPackageData(self, pid, data):
- self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid)
- args = setPackageData_args()
- args.pid = pid
- args.data = data
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setPackageData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setPackageData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.e is not None:
- raise result.e
- return
-
- def deleteFinished(self, ):
- self.send_deleteFinished()
- return self.recv_deleteFinished()
-
- def send_deleteFinished(self, ):
- self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid)
- args = deleteFinished_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_deleteFinished(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = deleteFinished_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result");
-
- def restartFailed(self, ):
- self.send_restartFailed()
- self.recv_restartFailed()
-
- def send_restartFailed(self, ):
- self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid)
- args = restartFailed_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_restartFailed(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = restartFailed_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def getEvents(self, uuid):
- """
- Parameters:
- - uuid
- """
- self.send_getEvents(uuid)
- return self.recv_getEvents()
-
- def send_getEvents(self, uuid):
- self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid)
- args = getEvents_args()
- args.uuid = uuid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getEvents(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getEvents_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result");
-
- def getAccounts(self, refresh):
- """
- Parameters:
- - refresh
- """
- self.send_getAccounts(refresh)
- return self.recv_getAccounts()
-
- def send_getAccounts(self, refresh):
- self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid)
- args = getAccounts_args()
- args.refresh = refresh
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAccounts(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAccounts_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result");
-
- def getAccountTypes(self, ):
- self.send_getAccountTypes()
- return self.recv_getAccountTypes()
-
- def send_getAccountTypes(self, ):
- self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid)
- args = getAccountTypes_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAccountTypes(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAccountTypes_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result");
-
- def updateAccount(self, plugin, account, password, options):
- """
- Parameters:
- - plugin
- - account
- - password
- - options
- """
- self.send_updateAccount(plugin, account, password, options)
- self.recv_updateAccount()
-
- def send_updateAccount(self, plugin, account, password, options):
- self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid)
- args = updateAccount_args()
- args.plugin = plugin
- args.account = account
- args.password = password
- args.options = options
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_updateAccount(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = updateAccount_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def removeAccount(self, plugin, account):
- """
- Parameters:
- - plugin
- - account
- """
- self.send_removeAccount(plugin, account)
- self.recv_removeAccount()
-
- def send_removeAccount(self, plugin, account):
- self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid)
- args = removeAccount_args()
- args.plugin = plugin
- args.account = account
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_removeAccount(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = removeAccount_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
- def login(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- self.send_login(username, password)
- return self.recv_login()
-
- def send_login(self, username, password):
- self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid)
- args = login_args()
- args.username = username
- args.password = password
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_login(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = login_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result");
-
- def getUserData(self, username, password):
- """
- Parameters:
- - username
- - password
- """
- self.send_getUserData(username, password)
- return self.recv_getUserData()
-
- def send_getUserData(self, username, password):
- self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid)
- args = getUserData_args()
- args.username = username
- args.password = password
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getUserData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getUserData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result");
-
- def getAllUserData(self, ):
- self.send_getAllUserData()
- return self.recv_getAllUserData()
-
- def send_getAllUserData(self, ):
- self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid)
- args = getAllUserData_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAllUserData(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAllUserData_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result");
-
- def getServices(self, ):
- self.send_getServices()
- return self.recv_getServices()
-
- def send_getServices(self, ):
- self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid)
- args = getServices_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getServices(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getServices_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result");
-
- def hasService(self, plugin, func):
- """
- Parameters:
- - plugin
- - func
- """
- self.send_hasService(plugin, func)
- return self.recv_hasService()
-
- def send_hasService(self, plugin, func):
- self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid)
- args = hasService_args()
- args.plugin = plugin
- args.func = func
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_hasService(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = hasService_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result");
-
- def call(self, info):
- """
- Parameters:
- - info
- """
- self.send_call(info)
- return self.recv_call()
-
- def send_call(self, info):
- self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid)
- args = call_args()
- args.info = info
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_call(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = call_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- if result.ex is not None:
- raise result.ex
- if result.e is not None:
- raise result.e
- raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result");
-
- def getAllInfo(self, ):
- self.send_getAllInfo()
- return self.recv_getAllInfo()
-
- def send_getAllInfo(self, ):
- self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid)
- args = getAllInfo_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getAllInfo(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getAllInfo_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result");
-
- def getInfoByPlugin(self, plugin):
- """
- Parameters:
- - plugin
- """
- self.send_getInfoByPlugin(plugin)
- return self.recv_getInfoByPlugin()
-
- def send_getInfoByPlugin(self, plugin):
- self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid)
- args = getInfoByPlugin_args()
- args.plugin = plugin
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getInfoByPlugin(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getInfoByPlugin_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result");
-
- def isCaptchaWaiting(self, ):
- self.send_isCaptchaWaiting()
- return self.recv_isCaptchaWaiting()
-
- def send_isCaptchaWaiting(self, ):
- self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid)
- args = isCaptchaWaiting_args()
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_isCaptchaWaiting(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = isCaptchaWaiting_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result");
-
- def getCaptchaTask(self, exclusive):
- """
- Parameters:
- - exclusive
- """
- self.send_getCaptchaTask(exclusive)
- return self.recv_getCaptchaTask()
-
- def send_getCaptchaTask(self, exclusive):
- self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid)
- args = getCaptchaTask_args()
- args.exclusive = exclusive
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCaptchaTask(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCaptchaTask_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result");
-
- def getCaptchaTaskStatus(self, tid):
- """
- Parameters:
- - tid
- """
- self.send_getCaptchaTaskStatus(tid)
- return self.recv_getCaptchaTaskStatus()
-
- def send_getCaptchaTaskStatus(self, tid):
- self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid)
- args = getCaptchaTaskStatus_args()
- args.tid = tid
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_getCaptchaTaskStatus(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = getCaptchaTaskStatus_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- if result.success is not None:
- return result.success
- raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result");
-
- def setCaptchaResult(self, tid, result):
- """
- Parameters:
- - tid
- - result
- """
- self.send_setCaptchaResult(tid, result)
- self.recv_setCaptchaResult()
-
- def send_setCaptchaResult(self, tid, result):
- self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid)
- args = setCaptchaResult_args()
- args.tid = tid
- args.result = result
- args.write(self._oprot)
- self._oprot.writeMessageEnd()
- self._oprot.trans.flush()
-
- def recv_setCaptchaResult(self, ):
- (fname, mtype, rseqid) = self._iprot.readMessageBegin()
- if mtype == TMessageType.EXCEPTION:
- x = TApplicationException()
- x.read(self._iprot)
- self._iprot.readMessageEnd()
- raise x
- result = setCaptchaResult_result()
- result.read(self._iprot)
- self._iprot.readMessageEnd()
- return
-
-
-class Processor(Iface, TProcessor):
- def __init__(self, handler):
- self._handler = handler
- self._processMap = {}
- self._processMap["getConfigValue"] = Processor.process_getConfigValue
- self._processMap["setConfigValue"] = Processor.process_setConfigValue
- self._processMap["getConfig"] = Processor.process_getConfig
- self._processMap["getPluginConfig"] = Processor.process_getPluginConfig
- self._processMap["pauseServer"] = Processor.process_pauseServer
- self._processMap["unpauseServer"] = Processor.process_unpauseServer
- self._processMap["togglePause"] = Processor.process_togglePause
- self._processMap["statusServer"] = Processor.process_statusServer
- self._processMap["freeSpace"] = Processor.process_freeSpace
- self._processMap["getServerVersion"] = Processor.process_getServerVersion
- self._processMap["kill"] = Processor.process_kill
- self._processMap["restart"] = Processor.process_restart
- self._processMap["getLog"] = Processor.process_getLog
- self._processMap["isTimeDownload"] = Processor.process_isTimeDownload
- self._processMap["isTimeReconnect"] = Processor.process_isTimeReconnect
- self._processMap["toggleReconnect"] = Processor.process_toggleReconnect
- self._processMap["generatePackages"] = Processor.process_generatePackages
- self._processMap["checkURLs"] = Processor.process_checkURLs
- self._processMap["parseURLs"] = Processor.process_parseURLs
- self._processMap["checkOnlineStatus"] = Processor.process_checkOnlineStatus
- self._processMap["checkOnlineStatusContainer"] = Processor.process_checkOnlineStatusContainer
- self._processMap["pollResults"] = Processor.process_pollResults
- self._processMap["statusDownloads"] = Processor.process_statusDownloads
- self._processMap["getPackageData"] = Processor.process_getPackageData
- self._processMap["getPackageInfo"] = Processor.process_getPackageInfo
- self._processMap["getFileData"] = Processor.process_getFileData
- self._processMap["getQueue"] = Processor.process_getQueue
- self._processMap["getCollector"] = Processor.process_getCollector
- self._processMap["getQueueData"] = Processor.process_getQueueData
- self._processMap["getCollectorData"] = Processor.process_getCollectorData
- self._processMap["getPackageOrder"] = Processor.process_getPackageOrder
- self._processMap["getFileOrder"] = Processor.process_getFileOrder
- self._processMap["generateAndAddPackages"] = Processor.process_generateAndAddPackages
- self._processMap["addPackage"] = Processor.process_addPackage
- self._processMap["addFiles"] = Processor.process_addFiles
- self._processMap["uploadContainer"] = Processor.process_uploadContainer
- self._processMap["deleteFiles"] = Processor.process_deleteFiles
- self._processMap["deletePackages"] = Processor.process_deletePackages
- self._processMap["pushToQueue"] = Processor.process_pushToQueue
- self._processMap["pullFromQueue"] = Processor.process_pullFromQueue
- self._processMap["restartPackage"] = Processor.process_restartPackage
- self._processMap["restartFile"] = Processor.process_restartFile
- self._processMap["recheckPackage"] = Processor.process_recheckPackage
- self._processMap["stopAllDownloads"] = Processor.process_stopAllDownloads
- self._processMap["stopDownloads"] = Processor.process_stopDownloads
- self._processMap["setPackageName"] = Processor.process_setPackageName
- self._processMap["movePackage"] = Processor.process_movePackage
- self._processMap["moveFiles"] = Processor.process_moveFiles
- self._processMap["orderPackage"] = Processor.process_orderPackage
- self._processMap["orderFile"] = Processor.process_orderFile
- self._processMap["setPackageData"] = Processor.process_setPackageData
- self._processMap["deleteFinished"] = Processor.process_deleteFinished
- self._processMap["restartFailed"] = Processor.process_restartFailed
- self._processMap["getEvents"] = Processor.process_getEvents
- self._processMap["getAccounts"] = Processor.process_getAccounts
- self._processMap["getAccountTypes"] = Processor.process_getAccountTypes
- self._processMap["updateAccount"] = Processor.process_updateAccount
- self._processMap["removeAccount"] = Processor.process_removeAccount
- self._processMap["login"] = Processor.process_login
- self._processMap["getUserData"] = Processor.process_getUserData
- self._processMap["getAllUserData"] = Processor.process_getAllUserData
- self._processMap["getServices"] = Processor.process_getServices
- self._processMap["hasService"] = Processor.process_hasService
- self._processMap["call"] = Processor.process_call
- self._processMap["getAllInfo"] = Processor.process_getAllInfo
- self._processMap["getInfoByPlugin"] = Processor.process_getInfoByPlugin
- self._processMap["isCaptchaWaiting"] = Processor.process_isCaptchaWaiting
- self._processMap["getCaptchaTask"] = Processor.process_getCaptchaTask
- self._processMap["getCaptchaTaskStatus"] = Processor.process_getCaptchaTaskStatus
- self._processMap["setCaptchaResult"] = Processor.process_setCaptchaResult
-
- def process(self, iprot, oprot):
- (name, type, seqid) = iprot.readMessageBegin()
- if name not in self._processMap:
- iprot.skip(TType.STRUCT)
- iprot.readMessageEnd()
- x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
- oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
- x.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
- return
- else:
- self._processMap[name](self, seqid, iprot, oprot)
- return True
-
- def process_getConfigValue(self, seqid, iprot, oprot):
- args = getConfigValue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getConfigValue_result()
- result.success = self._handler.getConfigValue(args.category, args.option, args.section)
- oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setConfigValue(self, seqid, iprot, oprot):
- args = setConfigValue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setConfigValue_result()
- self._handler.setConfigValue(args.category, args.option, args.value, args.section)
- oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getConfig(self, seqid, iprot, oprot):
- args = getConfig_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getConfig_result()
- result.success = self._handler.getConfig()
- oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPluginConfig(self, seqid, iprot, oprot):
- args = getPluginConfig_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPluginConfig_result()
- result.success = self._handler.getPluginConfig()
- oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pauseServer(self, seqid, iprot, oprot):
- args = pauseServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pauseServer_result()
- self._handler.pauseServer()
- oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_unpauseServer(self, seqid, iprot, oprot):
- args = unpauseServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = unpauseServer_result()
- self._handler.unpauseServer()
- oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_togglePause(self, seqid, iprot, oprot):
- args = togglePause_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = togglePause_result()
- result.success = self._handler.togglePause()
- oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_statusServer(self, seqid, iprot, oprot):
- args = statusServer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = statusServer_result()
- result.success = self._handler.statusServer()
- oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_freeSpace(self, seqid, iprot, oprot):
- args = freeSpace_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = freeSpace_result()
- result.success = self._handler.freeSpace()
- oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getServerVersion(self, seqid, iprot, oprot):
- args = getServerVersion_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getServerVersion_result()
- result.success = self._handler.getServerVersion()
- oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_kill(self, seqid, iprot, oprot):
- args = kill_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = kill_result()
- self._handler.kill()
- oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restart(self, seqid, iprot, oprot):
- args = restart_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restart_result()
- self._handler.restart()
- oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getLog(self, seqid, iprot, oprot):
- args = getLog_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getLog_result()
- result.success = self._handler.getLog(args.offset)
- oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isTimeDownload(self, seqid, iprot, oprot):
- args = isTimeDownload_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isTimeDownload_result()
- result.success = self._handler.isTimeDownload()
- oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isTimeReconnect(self, seqid, iprot, oprot):
- args = isTimeReconnect_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isTimeReconnect_result()
- result.success = self._handler.isTimeReconnect()
- oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_toggleReconnect(self, seqid, iprot, oprot):
- args = toggleReconnect_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = toggleReconnect_result()
- result.success = self._handler.toggleReconnect()
- oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_generatePackages(self, seqid, iprot, oprot):
- args = generatePackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = generatePackages_result()
- result.success = self._handler.generatePackages(args.links)
- oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkURLs(self, seqid, iprot, oprot):
- args = checkURLs_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkURLs_result()
- result.success = self._handler.checkURLs(args.urls)
- oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_parseURLs(self, seqid, iprot, oprot):
- args = parseURLs_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = parseURLs_result()
- result.success = self._handler.parseURLs(args.html, args.url)
- oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkOnlineStatus(self, seqid, iprot, oprot):
- args = checkOnlineStatus_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkOnlineStatus_result()
- result.success = self._handler.checkOnlineStatus(args.urls)
- oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_checkOnlineStatusContainer(self, seqid, iprot, oprot):
- args = checkOnlineStatusContainer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = checkOnlineStatusContainer_result()
- result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data)
- oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pollResults(self, seqid, iprot, oprot):
- args = pollResults_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pollResults_result()
- result.success = self._handler.pollResults(args.rid)
- oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_statusDownloads(self, seqid, iprot, oprot):
- args = statusDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = statusDownloads_result()
- result.success = self._handler.statusDownloads()
- oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageData(self, seqid, iprot, oprot):
- args = getPackageData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageData_result()
- try:
- result.success = self._handler.getPackageData(args.pid)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageInfo(self, seqid, iprot, oprot):
- args = getPackageInfo_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageInfo_result()
- try:
- result.success = self._handler.getPackageInfo(args.pid)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getFileData(self, seqid, iprot, oprot):
- args = getFileData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getFileData_result()
- try:
- result.success = self._handler.getFileData(args.fid)
- except FileDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getQueue(self, seqid, iprot, oprot):
- args = getQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getQueue_result()
- result.success = self._handler.getQueue()
- oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCollector(self, seqid, iprot, oprot):
- args = getCollector_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCollector_result()
- result.success = self._handler.getCollector()
- oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getQueueData(self, seqid, iprot, oprot):
- args = getQueueData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getQueueData_result()
- result.success = self._handler.getQueueData()
- oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCollectorData(self, seqid, iprot, oprot):
- args = getCollectorData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCollectorData_result()
- result.success = self._handler.getCollectorData()
- oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getPackageOrder(self, seqid, iprot, oprot):
- args = getPackageOrder_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getPackageOrder_result()
- result.success = self._handler.getPackageOrder(args.destination)
- oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getFileOrder(self, seqid, iprot, oprot):
- args = getFileOrder_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getFileOrder_result()
- result.success = self._handler.getFileOrder(args.pid)
- oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_generateAndAddPackages(self, seqid, iprot, oprot):
- args = generateAndAddPackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = generateAndAddPackages_result()
- result.success = self._handler.generateAndAddPackages(args.links, args.dest)
- oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_addPackage(self, seqid, iprot, oprot):
- args = addPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = addPackage_result()
- result.success = self._handler.addPackage(args.name, args.links, args.dest)
- oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_addFiles(self, seqid, iprot, oprot):
- args = addFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = addFiles_result()
- self._handler.addFiles(args.pid, args.links)
- oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_uploadContainer(self, seqid, iprot, oprot):
- args = uploadContainer_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = uploadContainer_result()
- self._handler.uploadContainer(args.filename, args.data)
- oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deleteFiles(self, seqid, iprot, oprot):
- args = deleteFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deleteFiles_result()
- self._handler.deleteFiles(args.fids)
- oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deletePackages(self, seqid, iprot, oprot):
- args = deletePackages_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deletePackages_result()
- self._handler.deletePackages(args.pids)
- oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pushToQueue(self, seqid, iprot, oprot):
- args = pushToQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pushToQueue_result()
- self._handler.pushToQueue(args.pid)
- oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_pullFromQueue(self, seqid, iprot, oprot):
- args = pullFromQueue_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = pullFromQueue_result()
- self._handler.pullFromQueue(args.pid)
- oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartPackage(self, seqid, iprot, oprot):
- args = restartPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartPackage_result()
- self._handler.restartPackage(args.pid)
- oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartFile(self, seqid, iprot, oprot):
- args = restartFile_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartFile_result()
- self._handler.restartFile(args.fid)
- oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_recheckPackage(self, seqid, iprot, oprot):
- args = recheckPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = recheckPackage_result()
- self._handler.recheckPackage(args.pid)
- oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_stopAllDownloads(self, seqid, iprot, oprot):
- args = stopAllDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = stopAllDownloads_result()
- self._handler.stopAllDownloads()
- oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_stopDownloads(self, seqid, iprot, oprot):
- args = stopDownloads_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = stopDownloads_result()
- self._handler.stopDownloads(args.fids)
- oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setPackageName(self, seqid, iprot, oprot):
- args = setPackageName_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setPackageName_result()
- self._handler.setPackageName(args.pid, args.name)
- oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_movePackage(self, seqid, iprot, oprot):
- args = movePackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = movePackage_result()
- self._handler.movePackage(args.destination, args.pid)
- oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_moveFiles(self, seqid, iprot, oprot):
- args = moveFiles_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = moveFiles_result()
- self._handler.moveFiles(args.fids, args.pid)
- oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_orderPackage(self, seqid, iprot, oprot):
- args = orderPackage_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = orderPackage_result()
- self._handler.orderPackage(args.pid, args.position)
- oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_orderFile(self, seqid, iprot, oprot):
- args = orderFile_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = orderFile_result()
- self._handler.orderFile(args.fid, args.position)
- oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setPackageData(self, seqid, iprot, oprot):
- args = setPackageData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setPackageData_result()
- try:
- self._handler.setPackageData(args.pid, args.data)
- except PackageDoesNotExists, e:
- result.e = e
- oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_deleteFinished(self, seqid, iprot, oprot):
- args = deleteFinished_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = deleteFinished_result()
- result.success = self._handler.deleteFinished()
- oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_restartFailed(self, seqid, iprot, oprot):
- args = restartFailed_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = restartFailed_result()
- self._handler.restartFailed()
- oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getEvents(self, seqid, iprot, oprot):
- args = getEvents_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getEvents_result()
- result.success = self._handler.getEvents(args.uuid)
- oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAccounts(self, seqid, iprot, oprot):
- args = getAccounts_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAccounts_result()
- result.success = self._handler.getAccounts(args.refresh)
- oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAccountTypes(self, seqid, iprot, oprot):
- args = getAccountTypes_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAccountTypes_result()
- result.success = self._handler.getAccountTypes()
- oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_updateAccount(self, seqid, iprot, oprot):
- args = updateAccount_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = updateAccount_result()
- self._handler.updateAccount(args.plugin, args.account, args.password, args.options)
- oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_removeAccount(self, seqid, iprot, oprot):
- args = removeAccount_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = removeAccount_result()
- self._handler.removeAccount(args.plugin, args.account)
- oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_login(self, seqid, iprot, oprot):
- args = login_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = login_result()
- result.success = self._handler.login(args.username, args.password)
- oprot.writeMessageBegin("login", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getUserData(self, seqid, iprot, oprot):
- args = getUserData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getUserData_result()
- result.success = self._handler.getUserData(args.username, args.password)
- oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAllUserData(self, seqid, iprot, oprot):
- args = getAllUserData_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAllUserData_result()
- result.success = self._handler.getAllUserData()
- oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getServices(self, seqid, iprot, oprot):
- args = getServices_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getServices_result()
- result.success = self._handler.getServices()
- oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_hasService(self, seqid, iprot, oprot):
- args = hasService_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = hasService_result()
- result.success = self._handler.hasService(args.plugin, args.func)
- oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_call(self, seqid, iprot, oprot):
- args = call_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = call_result()
- try:
- result.success = self._handler.call(args.info)
- except ServiceDoesNotExists, ex:
- result.ex = ex
- except ServiceException, e:
- result.e = e
- oprot.writeMessageBegin("call", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getAllInfo(self, seqid, iprot, oprot):
- args = getAllInfo_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getAllInfo_result()
- result.success = self._handler.getAllInfo()
- oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getInfoByPlugin(self, seqid, iprot, oprot):
- args = getInfoByPlugin_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getInfoByPlugin_result()
- result.success = self._handler.getInfoByPlugin(args.plugin)
- oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_isCaptchaWaiting(self, seqid, iprot, oprot):
- args = isCaptchaWaiting_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = isCaptchaWaiting_result()
- result.success = self._handler.isCaptchaWaiting()
- oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCaptchaTask(self, seqid, iprot, oprot):
- args = getCaptchaTask_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCaptchaTask_result()
- result.success = self._handler.getCaptchaTask(args.exclusive)
- oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_getCaptchaTaskStatus(self, seqid, iprot, oprot):
- args = getCaptchaTaskStatus_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = getCaptchaTaskStatus_result()
- result.success = self._handler.getCaptchaTaskStatus(args.tid)
- oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
- def process_setCaptchaResult(self, seqid, iprot, oprot):
- args = setCaptchaResult_args()
- args.read(iprot)
- iprot.readMessageEnd()
- result = setCaptchaResult_result()
- self._handler.setCaptchaResult(args.tid, args.result)
- oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid)
- result.write(oprot)
- oprot.writeMessageEnd()
- oprot.trans.flush()
-
-
-# HELPER FUNCTIONS AND STRUCTURES
-
-class getConfigValue_args(TBase):
- """
- Attributes:
- - category
- - option
- - section
- """
-
- __slots__ = [
- 'category',
- 'option',
- 'section',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
- (2, TType.STRING, 'option', None, None, ), # 2
- (3, TType.STRING, 'section', None, None, ), # 3
- )
-
- def __init__(self, category=None, option=None, section=None,):
- self.category = category
- self.option = option
- self.section = section
-
-
-class getConfigValue_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class setConfigValue_args(TBase):
- """
- Attributes:
- - category
- - option
- - value
- - section
- """
-
- __slots__ = [
- 'category',
- 'option',
- 'value',
- 'section',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'category', None, None, ), # 1
- (2, TType.STRING, 'option', None, None, ), # 2
- (3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'section', None, None, ), # 4
- )
-
- def __init__(self, category=None, option=None, value=None, section=None,):
- self.category = category
- self.option = option
- self.value = value
- self.section = section
-
-
-class setConfigValue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getConfig_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getConfig_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPluginConfig_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getPluginConfig_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(ConfigSection, ConfigSection.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class pauseServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pauseServer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class unpauseServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class unpauseServer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class togglePause_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class togglePause_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class statusServer_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class statusServer_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class freeSpace_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class freeSpace_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.I64, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getServerVersion_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getServerVersion_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class kill_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class kill_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restart_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restart_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getLog_args(TBase):
- """
- Attributes:
- - offset
- """
-
- __slots__ = [
- 'offset',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'offset', None, None, ), # 1
- )
-
- def __init__(self, offset=None,):
- self.offset = offset
-
-
-class getLog_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isTimeDownload_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isTimeDownload_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isTimeReconnect_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isTimeReconnect_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class toggleReconnect_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class toggleReconnect_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class generatePackages_args(TBase):
- """
- Attributes:
- - links
- """
-
- __slots__ = [
- 'links',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, links=None,):
- self.links = links
-
-
-class generatePackages_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkURLs_args(TBase):
- """
- Attributes:
- - urls
- """
-
- __slots__ = [
- 'urls',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, urls=None,):
- self.urls = urls
-
-
-class checkURLs_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class parseURLs_args(TBase):
- """
- Attributes:
- - html
- - url
- """
-
- __slots__ = [
- 'html',
- 'url',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'html', None, None, ), # 1
- (2, TType.STRING, 'url', None, None, ), # 2
- )
-
- def __init__(self, html=None, url=None,):
- self.html = html
- self.url = url
-
-
-class parseURLs_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkOnlineStatus_args(TBase):
- """
- Attributes:
- - urls
- """
-
- __slots__ = [
- 'urls',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- )
-
- def __init__(self, urls=None,):
- self.urls = urls
-
-
-class checkOnlineStatus_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class checkOnlineStatusContainer_args(TBase):
- """
- Attributes:
- - urls
- - filename
- - data
- """
-
- __slots__ = [
- 'urls',
- 'filename',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'urls', (TType.STRING,None), None, ), # 1
- (2, TType.STRING, 'filename', None, None, ), # 2
- (3, TType.STRING, 'data', None, None, ), # 3
- )
-
- def __init__(self, urls=None, filename=None, data=None,):
- self.urls = urls
- self.filename = filename
- self.data = data
-
-
-class checkOnlineStatusContainer_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class pollResults_args(TBase):
- """
- Attributes:
- - rid
- """
-
- __slots__ = [
- 'rid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'rid', None, None, ), # 1
- )
-
- def __init__(self, rid=None,):
- self.rid = rid
-
-
-class pollResults_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class statusDownloads_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class statusDownloads_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(DownloadInfo, DownloadInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPackageData_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getPackageData_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getPackageInfo_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getPackageInfo_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getFileData_args(TBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
-
-class getFileData_result(TBase):
- """
- Attributes:
- - success
- - e
- """
-
- __slots__ = [
- 'success',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None, ), # 0
- (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, success=None, e=None,):
- self.success = success
- self.e = e
-
-
-class getQueue_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getQueue_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCollector_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getCollector_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getQueueData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getQueueData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCollectorData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getCollectorData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(PackageData, PackageData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getPackageOrder_args(TBase):
- """
- Attributes:
- - destination
- """
-
- __slots__ = [
- 'destination',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
- )
-
- def __init__(self, destination=None,):
- self.destination = destination
-
-
-class getPackageOrder_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getFileOrder_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class getFileOrder_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.I16,None,TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class generateAndAddPackages_args(TBase):
- """
- Attributes:
- - links
- - dest
- """
-
- __slots__ = [
- 'links',
- 'dest',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'links', (TType.STRING,None), None, ), # 1
- (2, TType.I32, 'dest', None, None, ), # 2
- )
-
- def __init__(self, links=None, dest=None,):
- self.links = links
- self.dest = dest
-
-
-class generateAndAddPackages_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class addPackage_args(TBase):
- """
- Attributes:
- - name
- - links
- - dest
- """
-
- __slots__ = [
- 'name',
- 'links',
- 'dest',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
- (3, TType.I32, 'dest', None, None, ), # 3
- )
-
- def __init__(self, name=None, links=None, dest=None,):
- self.name = name
- self.links = links
- self.dest = dest
-
-
-class addPackage_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.I32, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class addFiles_args(TBase):
- """
- Attributes:
- - pid
- - links
- """
-
- __slots__ = [
- 'pid',
- 'links',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.LIST, 'links', (TType.STRING,None), None, ), # 2
- )
-
- def __init__(self, pid=None, links=None,):
- self.pid = pid
- self.links = links
-
-
-class addFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class uploadContainer_args(TBase):
- """
- Attributes:
- - filename
- - data
- """
-
- __slots__ = [
- 'filename',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'filename', None, None, ), # 1
- (2, TType.STRING, 'data', None, None, ), # 2
- )
-
- def __init__(self, filename=None, data=None,):
- self.filename = filename
- self.data = data
-
-
-class uploadContainer_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deleteFiles_args(TBase):
- """
- Attributes:
- - fids
- """
-
- __slots__ = [
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, fids=None,):
- self.fids = fids
-
-
-class deleteFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deletePackages_args(TBase):
- """
- Attributes:
- - pids
- """
-
- __slots__ = [
- 'pids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'pids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, pids=None,):
- self.pids = pids
-
-
-class deletePackages_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pushToQueue_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class pushToQueue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class pullFromQueue_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class pullFromQueue_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartPackage_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class restartPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartFile_args(TBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
-
-class restartFile_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class recheckPackage_args(TBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
-
-class recheckPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopAllDownloads_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopAllDownloads_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class stopDownloads_args(TBase):
- """
- Attributes:
- - fids
- """
-
- __slots__ = [
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- )
-
- def __init__(self, fids=None,):
- self.fids = fids
-
-
-class stopDownloads_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class setPackageName_args(TBase):
- """
- Attributes:
- - pid
- - name
- """
-
- __slots__ = [
- 'pid',
- 'name',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- )
-
- def __init__(self, pid=None, name=None,):
- self.pid = pid
- self.name = name
-
-
-class setPackageName_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class movePackage_args(TBase):
- """
- Attributes:
- - destination
- - pid
- """
-
- __slots__ = [
- 'destination',
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'destination', None, None, ), # 1
- (2, TType.I32, 'pid', None, None, ), # 2
- )
-
- def __init__(self, destination=None, pid=None,):
- self.destination = destination
- self.pid = pid
-
-
-class movePackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class moveFiles_args(TBase):
- """
- Attributes:
- - fids
- - pid
- """
-
- __slots__ = [
- 'fids',
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.LIST, 'fids', (TType.I32,None), None, ), # 1
- (2, TType.I32, 'pid', None, None, ), # 2
- )
-
- def __init__(self, fids=None, pid=None,):
- self.fids = fids
- self.pid = pid
-
-
-class moveFiles_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class orderPackage_args(TBase):
- """
- Attributes:
- - pid
- - position
- """
-
- __slots__ = [
- 'pid',
- 'position',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.I16, 'position', None, None, ), # 2
- )
-
- def __init__(self, pid=None, position=None,):
- self.pid = pid
- self.position = position
-
-
-class orderPackage_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class orderFile_args(TBase):
- """
- Attributes:
- - fid
- - position
- """
-
- __slots__ = [
- 'fid',
- 'position',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.I16, 'position', None, None, ), # 2
- )
-
- def __init__(self, fid=None, position=None,):
- self.fid = fid
- self.position = position
-
-
-class orderFile_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class setPackageData_args(TBase):
- """
- Attributes:
- - pid
- - data
- """
-
- __slots__ = [
- 'pid',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.MAP, 'data', (TType.STRING,None,TType.STRING,None), None, ), # 2
- )
-
- def __init__(self, pid=None, data=None,):
- self.pid = pid
- self.data = data
-
-
-class setPackageData_result(TBase):
- """
- Attributes:
- - e
- """
-
- __slots__ = [
- 'e',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None, ), # 1
- )
-
- def __init__(self, e=None,):
- self.e = e
-
-
-class deleteFinished_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class deleteFinished_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.I32,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class restartFailed_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class restartFailed_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getEvents_args(TBase):
- """
- Attributes:
- - uuid
- """
-
- __slots__ = [
- 'uuid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'uuid', None, None, ), # 1
- )
-
- def __init__(self, uuid=None,):
- self.uuid = uuid
-
-
-class getEvents_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(EventInfo, EventInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAccounts_args(TBase):
- """
- Attributes:
- - refresh
- """
-
- __slots__ = [
- 'refresh',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'refresh', None, None, ), # 1
- )
-
- def __init__(self, refresh=None,):
- self.refresh = refresh
-
-
-class getAccounts_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRUCT,(AccountInfo, AccountInfo.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAccountTypes_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAccountTypes_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.LIST, 'success', (TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class updateAccount_args(TBase):
- """
- Attributes:
- - plugin
- - account
- - password
- - options
- """
-
- __slots__ = [
- 'plugin',
- 'account',
- 'password',
- 'options',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'account', None, None, ), # 2
- (3, TType.STRING, 'password', None, None, ), # 3
- (4, TType.MAP, 'options', (TType.STRING,None,TType.STRING,None), None, ), # 4
- )
-
- def __init__(self, plugin=None, account=None, password=None, options=None,):
- self.plugin = plugin
- self.account = account
- self.password = password
- self.options = options
-
-
-class updateAccount_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class removeAccount_args(TBase):
- """
- Attributes:
- - plugin
- - account
- """
-
- __slots__ = [
- 'plugin',
- 'account',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'account', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, account=None,):
- self.plugin = plugin
- self.account = account
-
-
-class removeAccount_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class login_args(TBase):
- """
- Attributes:
- - username
- - password
- """
-
- __slots__ = [
- 'username',
- 'password',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'username', None, None, ), # 1
- (2, TType.STRING, 'password', None, None, ), # 2
- )
-
- def __init__(self, username=None, password=None,):
- self.username = username
- self.password = password
-
-
-class login_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getUserData_args(TBase):
- """
- Attributes:
- - username
- - password
- """
-
- __slots__ = [
- 'username',
- 'password',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'username', None, None, ), # 1
- (2, TType.STRING, 'password', None, None, ), # 2
- )
-
- def __init__(self, username=None, password=None,):
- self.username = username
- self.password = password
-
-
-class getUserData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getAllUserData_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAllUserData_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRUCT,(UserData, UserData.thrift_spec)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getServices_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getServices_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class hasService_args(TBase):
- """
- Attributes:
- - plugin
- - func
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, func=None,):
- self.plugin = plugin
- self.func = func
-
-
-class hasService_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class call_args(TBase):
- """
- Attributes:
- - info
- """
-
- __slots__ = [
- 'info',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None, ), # 1
- )
-
- def __init__(self, info=None,):
- self.info = info
-
-
-class call_result(TBase):
- """
- Attributes:
- - success
- - ex
- - e
- """
-
- __slots__ = [
- 'success',
- 'ex',
- 'e',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None, ), # 1
- (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None, ), # 2
- )
-
- def __init__(self, success=None, ex=None, e=None,):
- self.success = success
- self.ex = ex
- self.e = e
-
-
-class getAllInfo_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class getAllInfo_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.MAP,(TType.STRING,None,TType.STRING,None)), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getInfoByPlugin_args(TBase):
- """
- Attributes:
- - plugin
- """
-
- __slots__ = [
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- )
-
- def __init__(self, plugin=None,):
- self.plugin = plugin
-
-
-class getInfoByPlugin_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.MAP, 'success', (TType.STRING,None,TType.STRING,None), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class isCaptchaWaiting_args(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
-
-class isCaptchaWaiting_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.BOOL, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCaptchaTask_args(TBase):
- """
- Attributes:
- - exclusive
- """
-
- __slots__ = [
- 'exclusive',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'exclusive', None, None, ), # 1
- )
-
- def __init__(self, exclusive=None,):
- self.exclusive = exclusive
-
-
-class getCaptchaTask_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class getCaptchaTaskStatus_args(TBase):
- """
- Attributes:
- - tid
- """
-
- __slots__ = [
- 'tid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
- )
-
- def __init__(self, tid=None,):
- self.tid = tid
-
-
-class getCaptchaTaskStatus_result(TBase):
- """
- Attributes:
- - success
- """
-
- __slots__ = [
- 'success',
- ]
-
- thrift_spec = (
- (0, TType.STRING, 'success', None, None, ), # 0
- )
-
- def __init__(self, success=None,):
- self.success = success
-
-
-class setCaptchaResult_args(TBase):
- """
- Attributes:
- - tid
- - result
- """
-
- __slots__ = [
- 'tid',
- 'result',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'tid', None, None, ), # 1
- (2, TType.STRING, 'result', None, None, ), # 2
- )
-
- def __init__(self, tid=None, result=None,):
- self.tid = tid
- self.result = result
-
-
-class setCaptchaResult_result(TBase):
-
- __slots__ = [
- ]
-
- thrift_spec = (
- )
-
diff --git a/module/remote/thriftbackend/thriftgen/pyload/__init__.py b/module/remote/thriftbackend/thriftgen/pyload/__init__.py
deleted file mode 100644
index ce7f52598..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__all__ = ['ttypes', 'constants', 'Pyload']
diff --git a/module/remote/thriftbackend/thriftgen/pyload/constants.py b/module/remote/thriftbackend/thriftgen/pyload/constants.py
deleted file mode 100644
index f8960dc63..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/constants.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-from ttypes import *
-
diff --git a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py b/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
deleted file mode 100644
index 1299b515d..000000000
--- a/module/remote/thriftbackend/thriftgen/pyload/ttypes.py
+++ /dev/null
@@ -1,835 +0,0 @@
-#
-# Autogenerated by Thrift Compiler (0.9.0-dev)
-#
-# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
-#
-# options string: py:slots,dynamic
-#
-
-from thrift.Thrift import TType, TMessageType, TException
-
-from thrift.protocol.TBase import TBase, TExceptionBase
-
-
-class DownloadStatus(TBase):
- Finished = 0
- Offline = 1
- Online = 2
- Queued = 3
- Skipped = 4
- Waiting = 5
- TempOffline = 6
- Starting = 7
- Failed = 8
- Aborted = 9
- Decrypting = 10
- Custom = 11
- Downloading = 12
- Processing = 13
- Unknown = 14
-
- _VALUES_TO_NAMES = {
- 0: "Finished",
- 1: "Offline",
- 2: "Online",
- 3: "Queued",
- 4: "Skipped",
- 5: "Waiting",
- 6: "TempOffline",
- 7: "Starting",
- 8: "Failed",
- 9: "Aborted",
- 10: "Decrypting",
- 11: "Custom",
- 12: "Downloading",
- 13: "Processing",
- 14: "Unknown",
- }
-
- _NAMES_TO_VALUES = {
- "Finished": 0,
- "Offline": 1,
- "Online": 2,
- "Queued": 3,
- "Skipped": 4,
- "Waiting": 5,
- "TempOffline": 6,
- "Starting": 7,
- "Failed": 8,
- "Aborted": 9,
- "Decrypting": 10,
- "Custom": 11,
- "Downloading": 12,
- "Processing": 13,
- "Unknown": 14,
- }
-
-class Destination(TBase):
- Collector = 0
- Queue = 1
-
- _VALUES_TO_NAMES = {
- 0: "Collector",
- 1: "Queue",
- }
-
- _NAMES_TO_VALUES = {
- "Collector": 0,
- "Queue": 1,
- }
-
-class ElementType(TBase):
- Package = 0
- File = 1
-
- _VALUES_TO_NAMES = {
- 0: "Package",
- 1: "File",
- }
-
- _NAMES_TO_VALUES = {
- "Package": 0,
- "File": 1,
- }
-
-class Input(TBase):
- NONE = 0
- TEXT = 1
- TEXTBOX = 2
- PASSWORD = 3
- BOOL = 4
- CLICK = 5
- CHOICE = 6
- MULTIPLE = 7
- LIST = 8
- TABLE = 9
-
- _VALUES_TO_NAMES = {
- 0: "NONE",
- 1: "TEXT",
- 2: "TEXTBOX",
- 3: "PASSWORD",
- 4: "BOOL",
- 5: "CLICK",
- 6: "CHOICE",
- 7: "MULTIPLE",
- 8: "LIST",
- 9: "TABLE",
- }
-
- _NAMES_TO_VALUES = {
- "NONE": 0,
- "TEXT": 1,
- "TEXTBOX": 2,
- "PASSWORD": 3,
- "BOOL": 4,
- "CLICK": 5,
- "CHOICE": 6,
- "MULTIPLE": 7,
- "LIST": 8,
- "TABLE": 9,
- }
-
-class Output(TBase):
- CAPTCHA = 1
- QUESTION = 2
- NOTIFICATION = 4
-
- _VALUES_TO_NAMES = {
- 1: "CAPTCHA",
- 2: "QUESTION",
- 4: "NOTIFICATION",
- }
-
- _NAMES_TO_VALUES = {
- "CAPTCHA": 1,
- "QUESTION": 2,
- "NOTIFICATION": 4,
- }
-
-
-class DownloadInfo(TBase):
- """
- Attributes:
- - fid
- - name
- - speed
- - eta
- - format_eta
- - bleft
- - size
- - format_size
- - percent
- - status
- - statusmsg
- - format_wait
- - wait_until
- - packageID
- - packageName
- - plugin
- """
-
- __slots__ = [
- 'fid',
- 'name',
- 'speed',
- 'eta',
- 'format_eta',
- 'bleft',
- 'size',
- 'format_size',
- 'percent',
- 'status',
- 'statusmsg',
- 'format_wait',
- 'wait_until',
- 'packageID',
- 'packageName',
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- (3, TType.I64, 'speed', None, None, ), # 3
- (4, TType.I32, 'eta', None, None, ), # 4
- (5, TType.STRING, 'format_eta', None, None, ), # 5
- (6, TType.I64, 'bleft', None, None, ), # 6
- (7, TType.I64, 'size', None, None, ), # 7
- (8, TType.STRING, 'format_size', None, None, ), # 8
- (9, TType.BYTE, 'percent', None, None, ), # 9
- (10, TType.I32, 'status', None, None, ), # 10
- (11, TType.STRING, 'statusmsg', None, None, ), # 11
- (12, TType.STRING, 'format_wait', None, None, ), # 12
- (13, TType.I64, 'wait_until', None, None, ), # 13
- (14, TType.I32, 'packageID', None, None, ), # 14
- (15, TType.STRING, 'packageName', None, None, ), # 15
- (16, TType.STRING, 'plugin', None, None, ), # 16
- )
-
- def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,):
- self.fid = fid
- self.name = name
- self.speed = speed
- self.eta = eta
- self.format_eta = format_eta
- self.bleft = bleft
- self.size = size
- self.format_size = format_size
- self.percent = percent
- self.status = status
- self.statusmsg = statusmsg
- self.format_wait = format_wait
- self.wait_until = wait_until
- self.packageID = packageID
- self.packageName = packageName
- self.plugin = plugin
-
-
-class ServerStatus(TBase):
- """
- Attributes:
- - pause
- - active
- - queue
- - total
- - speed
- - download
- - reconnect
- """
-
- __slots__ = [
- 'pause',
- 'active',
- 'queue',
- 'total',
- 'speed',
- 'download',
- 'reconnect',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.BOOL, 'pause', None, None, ), # 1
- (2, TType.I16, 'active', None, None, ), # 2
- (3, TType.I16, 'queue', None, None, ), # 3
- (4, TType.I16, 'total', None, None, ), # 4
- (5, TType.I64, 'speed', None, None, ), # 5
- (6, TType.BOOL, 'download', None, None, ), # 6
- (7, TType.BOOL, 'reconnect', None, None, ), # 7
- )
-
- def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,):
- self.pause = pause
- self.active = active
- self.queue = queue
- self.total = total
- self.speed = speed
- self.download = download
- self.reconnect = reconnect
-
-
-class ConfigItem(TBase):
- """
- Attributes:
- - name
- - description
- - value
- - type
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'value',
- 'type',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.STRING, 'value', None, None, ), # 3
- (4, TType.STRING, 'type', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, value=None, type=None,):
- self.name = name
- self.description = description
- self.value = value
- self.type = type
-
-
-class ConfigSection(TBase):
- """
- Attributes:
- - name
- - description
- - items
- - outline
- """
-
- __slots__ = [
- 'name',
- 'description',
- 'items',
- 'outline',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'description', None, None, ), # 2
- (3, TType.LIST, 'items', (TType.STRUCT,(ConfigItem, ConfigItem.thrift_spec)), None, ), # 3
- (4, TType.STRING, 'outline', None, None, ), # 4
- )
-
- def __init__(self, name=None, description=None, items=None, outline=None,):
- self.name = name
- self.description = description
- self.items = items
- self.outline = outline
-
-
-class FileData(TBase):
- """
- Attributes:
- - fid
- - url
- - name
- - plugin
- - size
- - format_size
- - status
- - statusmsg
- - packageID
- - error
- - order
- """
-
- __slots__ = [
- 'fid',
- 'url',
- 'name',
- 'plugin',
- 'size',
- 'format_size',
- 'status',
- 'statusmsg',
- 'packageID',
- 'error',
- 'order',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- (2, TType.STRING, 'url', None, None, ), # 2
- (3, TType.STRING, 'name', None, None, ), # 3
- (4, TType.STRING, 'plugin', None, None, ), # 4
- (5, TType.I64, 'size', None, None, ), # 5
- (6, TType.STRING, 'format_size', None, None, ), # 6
- (7, TType.I32, 'status', None, None, ), # 7
- (8, TType.STRING, 'statusmsg', None, None, ), # 8
- (9, TType.I32, 'packageID', None, None, ), # 9
- (10, TType.STRING, 'error', None, None, ), # 10
- (11, TType.I16, 'order', None, None, ), # 11
- )
-
- def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,):
- self.fid = fid
- self.url = url
- self.name = name
- self.plugin = plugin
- self.size = size
- self.format_size = format_size
- self.status = status
- self.statusmsg = statusmsg
- self.packageID = packageID
- self.error = error
- self.order = order
-
-
-class PackageData(TBase):
- """
- Attributes:
- - pid
- - name
- - folder
- - site
- - password
- - dest
- - order
- - linksdone
- - sizedone
- - sizetotal
- - linkstotal
- - links
- - fids
- """
-
- __slots__ = [
- 'pid',
- 'name',
- 'folder',
- 'site',
- 'password',
- 'dest',
- 'order',
- 'linksdone',
- 'sizedone',
- 'sizetotal',
- 'linkstotal',
- 'links',
- 'fids',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- (2, TType.STRING, 'name', None, None, ), # 2
- (3, TType.STRING, 'folder', None, None, ), # 3
- (4, TType.STRING, 'site', None, None, ), # 4
- (5, TType.STRING, 'password', None, None, ), # 5
- (6, TType.I32, 'dest', None, None, ), # 6
- (7, TType.I16, 'order', None, None, ), # 7
- (8, TType.I16, 'linksdone', None, None, ), # 8
- (9, TType.I64, 'sizedone', None, None, ), # 9
- (10, TType.I64, 'sizetotal', None, None, ), # 10
- (11, TType.I16, 'linkstotal', None, None, ), # 11
- (12, TType.LIST, 'links', (TType.STRUCT,(FileData, FileData.thrift_spec)), None, ), # 12
- (13, TType.LIST, 'fids', (TType.I32,None), None, ), # 13
- )
-
- def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,):
- self.pid = pid
- self.name = name
- self.folder = folder
- self.site = site
- self.password = password
- self.dest = dest
- self.order = order
- self.linksdone = linksdone
- self.sizedone = sizedone
- self.sizetotal = sizetotal
- self.linkstotal = linkstotal
- self.links = links
- self.fids = fids
-
-
-class InteractionTask(TBase):
- """
- Attributes:
- - iid
- - input
- - structure
- - preset
- - output
- - data
- - title
- - description
- - plugin
- """
-
- __slots__ = [
- 'iid',
- 'input',
- 'structure',
- 'preset',
- 'output',
- 'data',
- 'title',
- 'description',
- 'plugin',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'iid', None, None, ), # 1
- (2, TType.I32, 'input', None, None, ), # 2
- (3, TType.LIST, 'structure', (TType.STRING,None), None, ), # 3
- (4, TType.LIST, 'preset', (TType.STRING,None), None, ), # 4
- (5, TType.I32, 'output', None, None, ), # 5
- (6, TType.LIST, 'data', (TType.STRING,None), None, ), # 6
- (7, TType.STRING, 'title', None, None, ), # 7
- (8, TType.STRING, 'description', None, None, ), # 8
- (9, TType.STRING, 'plugin', None, None, ), # 9
- )
-
- def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,):
- self.iid = iid
- self.input = input
- self.structure = structure
- self.preset = preset
- self.output = output
- self.data = data
- self.title = title
- self.description = description
- self.plugin = plugin
-
-
-class CaptchaTask(TBase):
- """
- Attributes:
- - tid
- - data
- - type
- - resultType
- """
-
- __slots__ = [
- 'tid',
- 'data',
- 'type',
- 'resultType',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I16, 'tid', None, None, ), # 1
- (2, TType.STRING, 'data', None, None, ), # 2
- (3, TType.STRING, 'type', None, None, ), # 3
- (4, TType.STRING, 'resultType', None, None, ), # 4
- )
-
- def __init__(self, tid=None, data=None, type=None, resultType=None,):
- self.tid = tid
- self.data = data
- self.type = type
- self.resultType = resultType
-
-
-class EventInfo(TBase):
- """
- Attributes:
- - eventname
- - id
- - type
- - destination
- """
-
- __slots__ = [
- 'eventname',
- 'id',
- 'type',
- 'destination',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'eventname', None, None, ), # 1
- (2, TType.I32, 'id', None, None, ), # 2
- (3, TType.I32, 'type', None, None, ), # 3
- (4, TType.I32, 'destination', None, None, ), # 4
- )
-
- def __init__(self, eventname=None, id=None, type=None, destination=None,):
- self.eventname = eventname
- self.id = id
- self.type = type
- self.destination = destination
-
-
-class UserData(TBase):
- """
- Attributes:
- - name
- - email
- - role
- - permission
- - templateName
- """
-
- __slots__ = [
- 'name',
- 'email',
- 'role',
- 'permission',
- 'templateName',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'email', None, None, ), # 2
- (3, TType.I32, 'role', None, None, ), # 3
- (4, TType.I32, 'permission', None, None, ), # 4
- (5, TType.STRING, 'templateName', None, None, ), # 5
- )
-
- def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,):
- self.name = name
- self.email = email
- self.role = role
- self.permission = permission
- self.templateName = templateName
-
-
-class AccountInfo(TBase):
- """
- Attributes:
- - validuntil
- - login
- - options
- - valid
- - trafficleft
- - maxtraffic
- - premium
- - type
- """
-
- __slots__ = [
- 'validuntil',
- 'login',
- 'options',
- 'valid',
- 'trafficleft',
- 'maxtraffic',
- 'premium',
- 'type',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I64, 'validuntil', None, None, ), # 1
- (2, TType.STRING, 'login', None, None, ), # 2
- (3, TType.MAP, 'options', (TType.STRING,None,TType.LIST,(TType.STRING,None)), None, ), # 3
- (4, TType.BOOL, 'valid', None, None, ), # 4
- (5, TType.I64, 'trafficleft', None, None, ), # 5
- (6, TType.I64, 'maxtraffic', None, None, ), # 6
- (7, TType.BOOL, 'premium', None, None, ), # 7
- (8, TType.STRING, 'type', None, None, ), # 8
- )
-
- def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,):
- self.validuntil = validuntil
- self.login = login
- self.options = options
- self.valid = valid
- self.trafficleft = trafficleft
- self.maxtraffic = maxtraffic
- self.premium = premium
- self.type = type
-
-
-class ServiceCall(TBase):
- """
- Attributes:
- - plugin
- - func
- - arguments
- - parseArguments
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- 'arguments',
- 'parseArguments',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- (3, TType.LIST, 'arguments', (TType.STRING,None), None, ), # 3
- (4, TType.BOOL, 'parseArguments', None, None, ), # 4
- )
-
- def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,):
- self.plugin = plugin
- self.func = func
- self.arguments = arguments
- self.parseArguments = parseArguments
-
-
-class OnlineStatus(TBase):
- """
- Attributes:
- - name
- - plugin
- - packagename
- - status
- - size
- """
-
- __slots__ = [
- 'name',
- 'plugin',
- 'packagename',
- 'status',
- 'size',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'name', None, None, ), # 1
- (2, TType.STRING, 'plugin', None, None, ), # 2
- (3, TType.STRING, 'packagename', None, None, ), # 3
- (4, TType.I32, 'status', None, None, ), # 4
- (5, TType.I64, 'size', None, None, ), # 5
- )
-
- def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,):
- self.name = name
- self.plugin = plugin
- self.packagename = packagename
- self.status = status
- self.size = size
-
-
-class OnlineCheck(TBase):
- """
- Attributes:
- - rid
- - data
- """
-
- __slots__ = [
- 'rid',
- 'data',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'rid', None, None, ), # 1
- (2, TType.MAP, 'data', (TType.STRING,None,TType.STRUCT,(OnlineStatus, OnlineStatus.thrift_spec)), None, ), # 2
- )
-
- def __init__(self, rid=None, data=None,):
- self.rid = rid
- self.data = data
-
-
-class PackageDoesNotExists(TExceptionBase):
- """
- Attributes:
- - pid
- """
-
- __slots__ = [
- 'pid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'pid', None, None, ), # 1
- )
-
- def __init__(self, pid=None,):
- self.pid = pid
-
- def __str__(self):
- return repr(self)
-
-
-class FileDoesNotExists(TExceptionBase):
- """
- Attributes:
- - fid
- """
-
- __slots__ = [
- 'fid',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.I32, 'fid', None, None, ), # 1
- )
-
- def __init__(self, fid=None,):
- self.fid = fid
-
- def __str__(self):
- return repr(self)
-
-
-class ServiceDoesNotExists(TExceptionBase):
- """
- Attributes:
- - plugin
- - func
- """
-
- __slots__ = [
- 'plugin',
- 'func',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'plugin', None, None, ), # 1
- (2, TType.STRING, 'func', None, None, ), # 2
- )
-
- def __init__(self, plugin=None, func=None,):
- self.plugin = plugin
- self.func = func
-
- def __str__(self):
- return repr(self)
-
-
-class ServiceException(TExceptionBase):
- """
- Attributes:
- - msg
- """
-
- __slots__ = [
- 'msg',
- ]
-
- thrift_spec = (
- None, # 0
- (1, TType.STRING, 'msg', None, None, ), # 1
- )
-
- def __init__(self, msg=None,):
- self.msg = msg
-
- def __str__(self):
- return repr(self)
-
diff --git a/module/setup.py b/module/setup.py
deleted file mode 100644
index 42b24859f..000000000
--- a/module/setup.py
+++ /dev/null
@@ -1,514 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from getpass import getpass
-import module.common.pylgettext as gettext
-import os
-from os import makedirs
-from os.path import abspath
-from os.path import dirname
-from os.path import exists
-from os.path import join
-from subprocess import PIPE
-from subprocess import call
-import sys
-from sys import exit
-from module.utils import get_console_encoding
-
-class Setup():
- """
- pyLoads initial setup configuration assistent
- """
-
- def __init__(self, path, config):
- self.path = path
- self.config = config
- self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
-
- def start(self):
- langs = self.config.getMetaData("general", "language")["type"].split(";")
- lang = self.ask(u"Choose your Language / WÀhle deine Sprache", "en", langs)
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"), languages=[lang, "en"], fallback=True)
- translation.install(True)
-
- #Input shorthand for yes
- self.yes = _("y")
- #Input shorthand for no
- self.no = _("n")
-
- # print ""
- # print _("Would you like to configure pyLoad via Webinterface?")
- # print _("You need a Browser and a connection to this PC for it.")
- # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True)
- # if viaweb:
- # try:
- # from module.web import ServerThread
- # ServerThread.setup = self
- # from module.web import webinterface
- # webinterface.run_simple()
- # return False
- # except Exception, e:
- # print "Setup failed with this error: ", e
- # print "Falling back to commandline setup."
-
-
- print ""
- print _("Welcome to the pyLoad Configuration Assistent.")
- print _("It will check your system and make a basic setup in order to run pyLoad.")
- print ""
- print _("The value in brackets [] always is the default value,")
- print _("in case you don't want to change it or you are unsure what to choose, just hit enter.")
- print _(
- "Don't forget: You can always rerun this assistent with --setup or -s parameter, when you start pyLoadCore.")
- print _("If you have any problems with this assistent hit STRG-C,")
- print _("to abort and don't let him start with pyLoadCore automatically anymore.")
- print ""
- print _("When you are ready for system check, hit enter.")
- raw_input()
-
- basic, ssl, captcha, gui, web, js = self.system_check()
- print ""
-
- if not basic:
- print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.")
- print _("Please correct this and re-run pyLoad.")
- print _("Setup will now close.")
- raw_input()
- return False
-
- raw_input(_("System check finished, hit enter to see your status report."))
- print ""
- print _("## Status ##")
- print ""
-
- avail = []
- if self.check_module("Crypto"): avail.append(_("container decrypting"))
- if ssl: avail.append(_("ssl connection"))
- if captcha: avail.append(_("automatic captcha decryption"))
- if gui: avail.append(_("GUI"))
- if web: avail.append(_("Webinterface"))
- if js: avail.append(_("extended Click'N'Load"))
-
- string = ""
-
- for av in avail:
- string += ", " + av
-
- print _("Features available:") + string[1:]
- print ""
-
- if len(avail) < 5:
- print _("Featues missing: ")
- print
-
- if not self.check_module("Crypto"):
- print _("no py-crypto available")
- print _("You need this if you want to decrypt container files.")
- print ""
-
- if not ssl:
- print _("no SSL available")
- print _("This is needed if you want to establish a secure connection to core or webinterface.")
- print _("If you only want to access locally to pyLoad ssl is not usefull.")
- print ""
-
- if not captcha:
- print _("no Captcha Recognition available")
- print _("Only needed for some hosters and as freeuser.")
- print ""
-
- if not gui:
- print _("Gui not available")
- print _("The Graphical User Interface.")
- print ""
-
- if not js:
- print _("no JavaScript engine found")
- print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino")
-
- print _("You can abort the setup now and fix some dependicies if you want.")
-
- con = self.ask(_("Continue with setup?"), self.yes, bool=True)
-
- if not con:
- return False
-
- print ""
- print _("Do you want to change the config path? Current is %s") % abspath("")
- print _(
- "If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.")
- path = self.ask(_("Change config path?"), self.no, bool=True)
- if path:
- self.conf_path()
- #calls exit when changed
-
- print ""
- print _("Do you want to configure login data and basic settings?")
- print _("This is recommend for first run.")
- con = self.ask(_("Make basic setup?"), self.yes, bool=True)
-
- if con:
- self.conf_basic()
-
- if ssl:
- print ""
- print _("Do you want to configure ssl?")
- ssl = self.ask(_("Configure ssl?"), self.no, bool=True)
- if ssl:
- self.conf_ssl()
-
- if web:
- print ""
- print _("Do you want to configure webinterface?")
- web = self.ask(_("Configure webinterface?"), self.yes, bool=True)
- if web:
- self.conf_web()
-
- print ""
- print _("Setup finished successfully.")
- print _("Hit enter to exit and restart pyLoad")
- raw_input()
- return True
-
- def system_check(self):
- """ make a systemcheck and return the results"""
- print _("## System Check ##")
-
- if sys.version_info[:2] > (2, 7):
- print _("Your python version is to new, Please use Python 2.6/2.7")
- python = False
- elif sys.version_info[:2] < (2, 5):
- print _("Your python version is to old, Please use at least Python 2.5")
- python = False
- else:
- print _("Python Version: OK")
- python = True
-
- curl = self.check_module("pycurl")
- self.print_dep("pycurl", curl)
-
- sqlite = self.check_module("sqlite3")
- self.print_dep("sqlite3", sqlite)
-
- basic = python and curl and sqlite
-
- print ""
-
- crypto = self.check_module("Crypto")
- self.print_dep("pycrypto", crypto)
-
- ssl = self.check_module("OpenSSL")
- self.print_dep("py-OpenSSL", ssl)
-
- print ""
-
- pil = self.check_module("Image")
- self.print_dep("py-imaging", pil)
-
- if os.name == "nt":
- tesser = self.check_prog([join(pypath, "tesseract", "tesseract.exe"), "-v"])
- else:
- tesser = self.check_prog(["tesseract", "-v"])
-
- self.print_dep("tesseract", tesser)
-
- captcha = pil and tesser
-
- print ""
-
- gui = self.check_module("PyQt4")
- self.print_dep("PyQt4", gui)
-
- print ""
- jinja = True
-
- try:
- import jinja2
-
- v = jinja2.__version__
- if v and "unknown" not in v:
- if not v.startswith("2.5") and not v.startswith("2.6"):
- print _("Your installed jinja2 version %s seems too old.") % jinja2.__version__
- print _("You can safely continue but if the webinterface is not working,")
- print _("please upgrade or deinstall it, pyLoad includes a sufficient jinja2 libary.")
- print
- jinja = False
- except:
- pass
-
- self.print_dep("jinja2", jinja)
- beaker = self.check_module("beaker")
- self.print_dep("beaker", beaker)
-
- web = sqlite and beaker
-
- from module.common import JsEngine
-
- js = True if JsEngine.ENGINE else False
- self.print_dep(_("JS engine"), js)
-
- return basic, ssl, captcha, gui, web, js
-
- def conf_basic(self):
- print ""
- print _("## Basic Setup ##")
-
- print ""
- print _("The following logindata is valid for CLI, GUI and webinterface.")
-
- from module.database import DatabaseBackend
-
- db = DatabaseBackend(None)
- db.setup()
- username = self.ask(_("Username"), "User")
- password = self.ask("", "", password=True)
- db.addUser(username, password)
- db.shutdown()
-
- print ""
- print _("External clients (GUI, CLI or other) need remote access to work over the network.")
- print _("However, if you only want to use the webinterface you may disable it to save ram.")
- self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.yes, bool=True)
-
- print ""
- langs = self.config.getMetaData("general", "language")
- self.config["general"]["language"] = self.ask(_("Language"), "en", langs["type"].split(";"))
-
- self.config["general"]["download_folder"] = self.ask(_("Downloadfolder"), "Downloads")
- self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3")
- #print _("You should disable checksum proofing, if you have low hardware requirements.")
- #self.config["general"]["checksum"] = self.ask(_("Proof checksum?"), "y", bool=True)
-
- reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True)
- self.config["reconnect"]["activated"] = reconnect
- if reconnect:
- self.config["reconnect"]["method"] = self.ask(_("Reconnect script location"), "./reconnect.sh")
-
-
- def conf_web(self):
- print ""
- print _("## Webinterface Setup ##")
-
- print ""
- self.config["webinterface"]["activated"] = self.ask(_("Activate webinterface?"), self.yes, bool=True)
- print ""
- print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.")
- self.config["webinterface"]["host"] = self.ask(_("Address"), "0.0.0.0")
- self.config["webinterface"]["port"] = self.ask(_("Port"), "8000")
- print ""
- print _("pyLoad offers several server backends, now following a short explanation.")
- print "builtin:", _("Default server, best choice if you dont know which one to choose.")
- print "threaded:", _("This server offers SSL and is a good alternative to builtin.")
- print "fastcgi:", _(
- "Can be used by apache, lighttpd, requires you to configure them, which is not too easy job.")
- print "lightweight:", _("Very fast alternative written in C, requires libev and linux knowlegde.")
- print "\t", _("Get it from here: https://github.com/jonashaag/bjoern, compile it")
- print "\t", _("and copy bjoern.so to module/lib")
-
- print
- print _(
- "Attention: In some rare cases the builtin server is not working, if you notice problems with the webinterface")
- print _("come back here and change the builtin server to the threaded one here.")
-
- self.config["webinterface"]["server"] = self.ask(_("Server"), "builtin",
- ["builtin", "threaded", "fastcgi", "lightweight"])
-
- def conf_ssl(self):
- print ""
- print _("## SSL Setup ##")
- print ""
- print _("Execute these commands from pyLoad config folder to make ssl certificates:")
- print ""
- print "openssl genrsa -out ssl.key 1024"
- print "openssl req -new -key ssl.key -out ssl.csr"
- print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt "
- print ""
- print _("If you're done and everything went fine, you can activate ssl now.")
-
- self.config["ssl"]["activated"] = self.ask(_("Activate SSL?"), self.yes, bool=True)
-
- def set_user(self):
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"),
- languages=[self.config["general"]["language"], "en"], fallback=True)
- translation.install(True)
-
- from module.database import DatabaseBackend
-
- db = DatabaseBackend(None)
- db.setup()
-
- noaction = True
- try:
- while True:
- print _("Select action")
- print _("1 - Create/Edit user")
- print _("2 - List users")
- print _("3 - Remove user")
- print _("4 - Quit")
- action = raw_input("[1]/2/3/4: ")
- if not action in ("1", "2", "3", "4"):
- continue
- elif action == "1":
- print ""
- username = self.ask(_("Username"), "User")
- password = self.ask("", "", password=True)
- db.addUser(username, password)
- noaction = False
- elif action == "2":
- print ""
- print _("Users")
- print "-----"
- users = db.listUsers()
- noaction = False
- for user in users:
- print user
- print "-----"
- print ""
- elif action == "3":
- print ""
- username = self.ask(_("Username"), "")
- if username:
- db.removeUser(username)
- noaction = False
- elif action == "4":
- break
- finally:
- if not noaction:
- db.shutdown()
-
- def conf_path(self, trans=False):
- if trans:
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("setup", join(self.path, "locale"),
- languages=[self.config["general"]["language"], "en"], fallback=True)
- translation.install(True)
-
- print _("Setting new configpath, current configuration will not be transfered!")
- path = self.ask(_("Configpath"), abspath(""))
- try:
- path = join(pypath, path)
- if not exists(path):
- makedirs(path)
- f = open(join(pypath, "module", "config", "configdir"), "wb")
- f.write(path)
- f.close()
- print _("Configpath changed, setup will now close, please restart to go on.")
- print _("Press Enter to exit.")
- raw_input()
- exit()
- except Exception, e:
- print _("Setting config path failed: %s") % str(e)
-
- def print_dep(self, name, value):
- """Print Status of dependency"""
- if value:
- print _("%s: OK") % name
- else:
- print _("%s: missing") % name
-
-
- def check_module(self, module):
- try:
- __import__(module)
- return True
- except:
- return False
-
- def check_prog(self, command):
- pipe = PIPE
- try:
- call(command, stdout=pipe, stderr=pipe)
- return True
- except:
- return False
-
- def ask(self, qst, default, answers=[], bool=False, password=False):
- """produce one line to asking for input"""
- if answers:
- info = "("
-
- for i, answer in enumerate(answers):
- info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer)
-
- info += ")"
- elif bool:
- if default == self.yes:
- info = "([%s]/%s)" % (self.yes, self.no)
- else:
- info = "(%s/[%s])" % (self.yes, self.no)
- else:
- info = "[%s]" % default
-
- if password:
- p1 = True
- p2 = False
- while p1 != p2:
- # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS)
- sys.stdout.write(_("Password: "))
- p1 = getpass("")
-
- if len(p1) < 4:
- print _("Password too short. Use at least 4 symbols.")
- continue
-
- sys.stdout.write(_("Password (again): "))
- p2 = getpass("")
-
- if p1 == p2:
- return p1
- else:
- print _("Passwords did not match.")
-
- while True:
- try:
- input = raw_input(qst + " %s: " % info)
- except KeyboardInterrupt:
- print "\nSetup interrupted"
- exit()
-
- input = input.decode(self.stdin_encoding)
-
- if input.strip() == "":
- input = default
-
- if bool:
- # yes, true,t are inputs for booleans with value true
- if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]:
- return True
- # no, false,f are inputs for booleans with value false
- elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]:
- return False
- else:
- print _("Invalid Input")
- continue
-
- if not answers:
- return input
-
- else:
- if input in answers:
- return input
- else:
- print _("Invalid Input")
-
-
-if __name__ == "__main__":
- test = Setup(join(abspath(dirname(__file__)), ".."), None)
- test.start()
diff --git a/module/unescape.py b/module/unescape.py
deleted file mode 100644
index d8999e077..000000000
--- a/module/unescape.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from module.utils import html_unescape
-#deprecated
-unescape = html_unescape \ No newline at end of file
diff --git a/module/utils.py b/module/utils.py
deleted file mode 100644
index 8748b7693..000000000
--- a/module/utils.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# -*- coding: utf-8 -*-
-
-""" Store all usefull functions here """
-
-import os
-import sys
-import time
-import re
-from os.path import join
-from string import maketrans
-from htmlentitydefs import name2codepoint
-
-def chmod(*args):
- try:
- os.chmod(*args)
- except:
- pass
-
-
-def decode(string):
- """ decode string with utf if possible """
- try:
- return string.decode("utf8", "replace")
- except:
- return string
-
-
-def remove_chars(string, repl):
- """ removes all chars in repl from string"""
- if type(string) == str:
- return string.translate(maketrans("", ""), repl)
- elif type(string) == unicode:
- return string.translate(dict([(ord(s), None) for s in repl]))
-
-
-def save_path(name):
- #remove some chars
- if os.name == 'nt':
- return remove_chars(name, '/\\?%*:|"<>')
- else:
- return remove_chars(name, '/\\"')
-
-
-def save_join(*args):
- """ joins a path, encoding aware """
- return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args]))
-
-
-# File System Encoding functions:
-# Use fs_encode before accesing files on disk, it will encode the string properly
-
-if sys.getfilesystemencoding().startswith('ANSI'):
- def fs_encode(string):
- try:
- string = string.encode('utf-8')
- finally:
- return string
-
- fs_decode = decode #decode utf8
-
-else:
- fs_encode = fs_decode = lambda x: x # do nothing
-
-def get_console_encoding(enc):
- if os.name == "nt":
- if enc == "cp65001": # aka UTF-8
- print "WARNING: Windows codepage 65001 is not supported."
- enc = "cp850"
- else:
- enc = "utf8"
-
- return enc
-
-def compare_time(start, end):
- start = map(int, start)
- end = map(int, end)
-
- if start == end: return True
-
- now = list(time.localtime()[3:5])
- if start < now < end: return True
- elif start > end and (now > start or now < end): return True
- elif start < now > end < start: return True
- else: return False
-
-
-def formatSize(size):
- """formats size of bytes"""
- size = int(size)
- steps = 0
- sizes = ["B", "KiB", "MiB", "GiB", "TiB"]
- while size > 1000:
- size /= 1024.0
- steps += 1
- return "%.2f %s" % (size, sizes[steps])
-
-
-def formatSpeed(speed):
- return formatSize(speed) + "/s"
-
-
-def freeSpace(folder):
- if os.name == "nt":
- import ctypes
-
- free_bytes = ctypes.c_ulonglong(0)
- ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
- return free_bytes.value
- else:
- from os import statvfs
-
- s = statvfs(folder)
- return s.f_bsize * s.f_bavail
-
-
-def uniqify(seq, idfun=None):
-# order preserving
- if idfun is None:
- def idfun(x): return x
- seen = {}
- result = []
- for item in seq:
- marker = idfun(item)
- # in old Python versions:
- # if seen.has_key(marker)
- # but in new ones:
- if marker in seen: continue
- seen[marker] = 1
- result.append(item)
- return result
-
-
-def parseFileSize(string, unit=None): #returns bytes
- if not unit:
- m = re.match(r"(\d*[\.,]?\d+)(.*)", string.strip().lower())
- if m:
- traffic = float(m.group(1).replace(",", "."))
- unit = m.group(2)
- else:
- return 0
- else:
- if isinstance(string, basestring):
- traffic = float(string.replace(",", "."))
- else:
- traffic = string
-
- #ignore case
- unit = unit.lower().strip()
-
- if unit in ("gb", "gig", "gbyte", "gigabyte", "gib", "g"):
- traffic *= 1 << 30
- elif unit in ("mb", "mbyte", "megabyte", "mib", "m"):
- traffic *= 1 << 20
- elif unit in ("kb", "kib", "kilobyte", "kbyte", "k"):
- traffic *= 1 << 10
-
- return traffic
-
-
-def lock(func):
- def new(*args):
- #print "Handler: %s args: %s" % (func,args[1:])
- args[0].lock.acquire()
- try:
- return func(*args)
- finally:
- args[0].lock.release()
-
- return new
-
-
-def fixup(m):
- text = m.group(0)
- if text[:2] == "&#":
- # character reference
- try:
- if text[:3] == "&#x":
- return unichr(int(text[3:-1], 16))
- else:
- return unichr(int(text[2:-1]))
- except ValueError:
- pass
- else:
- # named entity
- try:
- name = text[1:-1]
- text = unichr(name2codepoint[name])
- except KeyError:
- pass
-
- return text # leave as is
-
-
-def html_unescape(text):
- """Removes HTML or XML character references and entities from a text string"""
- return re.sub("&#?\w+;", fixup, text)
-
-if __name__ == "__main__":
- print freeSpace(".")
-
- print remove_chars("ab'cdgdsf''ds'", "'ghd")
diff --git a/module/web/ServerThread.py b/module/web/ServerThread.py
deleted file mode 100644
index 84667e5f6..000000000
--- a/module/web/ServerThread.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-from __future__ import with_statement
-from os.path import exists
-
-import os
-import threading
-import logging
-
-core = None
-setup = None
-log = logging.getLogger("log")
-
-class WebServer(threading.Thread):
- def __init__(self, pycore):
- global core
- threading.Thread.__init__(self)
- self.core = pycore
- core = pycore
- self.running = True
- self.server = pycore.config['webinterface']['server']
- self.https = pycore.config['webinterface']['https']
- self.cert = pycore.config["ssl"]["cert"]
- self.key = pycore.config["ssl"]["key"]
- self.host = pycore.config['webinterface']['host']
- self.port = pycore.config['webinterface']['port']
-
- self.setDaemon(True)
-
- def run(self):
- import webinterface
- global webinterface
-
- if self.https:
- if not exists(self.cert) or not exists(self.key):
- log.warning(_("SSL certificates not found."))
- self.https = False
-
- if self.server in ("lighttpd", "nginx"):
- log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server)
- log.warning(_("You can use the threaded server which offers good performance and ssl,"))
- log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server)
- log.warning(_("sample configs are located in the module/web/servers directory"))
- self.server = "builtin"
-
- if self.server == "fastcgi":
- try:
- import flup
- except:
- log.warning(_("Can't use %(server)s, python-flup is not installed!") % {
- "server": self.server})
- self.server = "builtin"
- elif self.server == "lightweight":
- try:
- import bjoern
- except Exception, e:
- log.error(_("Error importing lightweight server: %s") % e)
- log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern"))
- log.warning(_("Copy the boern.so to module/lib folder or use setup.py install"))
- log.warning(_("Of course you need to be familiar with linux and know how to compile software"))
- self.server = "builtin"
-
- if os.name == "nt":
- self.core.log.info(_("Server set to threaded, due to known performance problems on windows."))
- self.core.config['webinterface']['server'] = "threaded"
- self.server = "threaded"
-
-
- if self.server == "fastcgi":
- self.start_fcgi()
- elif self.server == "threaded":
- self.start_threaded()
- elif self.server == "lightweight":
- self.start_lightweight()
- else:
- self.start_builtin()
-
- def start_builtin(self):
-
- if self.https:
- log.warning(_("This server offers no SSL, please consider using threaded instead"))
-
- self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_simple(host=self.host, port=self.port)
-
- def start_threaded(self):
- if self.https:
- self.core.log.info(_("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- else:
- self.cert = ""
- self.key = ""
- self.core.log.info(_("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
-
- webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key)
-
- def start_fcgi(self):
-
- self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_fcgi(host=self.host, port=self.port)
-
-
- def start_lightweight(self):
- if self.https:
- log.warning(_("This server offers no SSL, please consider using threaded instead"))
-
- self.core.log.info(_("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port})
- webinterface.run_lightweight(host=self.host, port=self.port)
-
- def quit(self):
- self.running = False
diff --git a/module/web/api_app.py b/module/web/api_app.py
deleted file mode 100644
index 1629c1677..000000000
--- a/module/web/api_app.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from urllib import unquote
-from itertools import chain
-from traceback import format_exc, print_exc
-
-from bottle import route, request, response, HTTPError
-
-from utils import toDict, set_session
-from webinterface import PYLOAD
-
-from module.common.json_layer import json
-from module.lib.SafeEval import const_eval as literal_eval
-from module.Api import BaseObject
-
-# json encoder that accepts TBase objects
-class TBaseEncoder(json.JSONEncoder):
-
- def default(self, o):
- if isinstance(o, BaseObject):
- return toDict(o)
- return json.JSONEncoder.default(self, o)
-
-
-# accepting positional arguments, as well as kwargs via post and get
-
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#")
-@route("/api/:func:args#[a-zA-Z0-9\-_/\"'\[\]%{}]*#", method="POST")
-def call_api(func, args=""):
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- s = request.environ.get('beaker.session')
- if 'session' in request.POST:
- s = s.get_by_id(request.POST['session'])
-
- if not s or not s.get("authenticated", False):
- return HTTPError(403, json.dumps("Forbidden"))
-
- if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}):
- return HTTPError(401, json.dumps("Unauthorized"))
-
- args = args.split("/")[1:]
- kwargs = {}
-
- for x, y in chain(request.GET.iteritems(), request.POST.iteritems()):
- if x == "session": continue
- kwargs[x] = unquote(y)
-
- try:
- return callApi(func, *args, **kwargs)
- except Exception, e:
- print_exc()
- return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()}))
-
-
-def callApi(func, *args, **kwargs):
- if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
- print "Invalid API call", func
- return HTTPError(404, json.dumps("Not Found"))
-
- result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args],
- **dict([(x, literal_eval(y)) for x, y in kwargs.iteritems()]))
-
- # null is invalid json response
- if result is None: result = True
-
- return json.dumps(result, cls=TBaseEncoder)
-
-
-#post -> username, password
-@route("/api/login", method="POST")
-def login():
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- user = request.forms.get("username")
- password = request.forms.get("password")
-
- info = PYLOAD.checkAuth(user, password)
-
- if not info:
- return json.dumps(False)
-
- s = set_session(request, info)
-
- # get the session id by dirty way, documentations seems wrong
- try:
- sid = s._headers["cookie_out"].split("=")[1].split(";")[0]
- return json.dumps(sid)
- except:
- return json.dumps(True)
-
-
-@route("/api/logout")
-def logout():
- response.headers.replace("Content-type", "application/json")
- response.headers.append("Cache-Control", "no-cache, must-revalidate")
-
- s = request.environ.get('beaker.session')
- s.delete()
diff --git a/module/web/cnl_app.py b/module/web/cnl_app.py
deleted file mode 100644
index d8f7c1180..000000000
--- a/module/web/cnl_app.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-from os.path import join
-import re
-from urllib import unquote
-from base64 import standard_b64decode
-from binascii import unhexlify
-
-from bottle import route, request, HTTPError
-from webinterface import PYLOAD, DL_ROOT, JS
-
-try:
- from Crypto.Cipher import AES
-except:
- pass
-
-
-def local_check(function):
- def _view(*args, **kwargs):
- if request.environ.get('REMOTE_ADDR', "0") in ('127.0.0.1', 'localhost') \
- or request.environ.get('HTTP_HOST','0') == '127.0.0.1:9666':
- return function(*args, **kwargs)
- else:
- return HTTPError(403, "Forbidden")
-
- return _view
-
-
-@route("/flash")
-@route("/flash/:id")
-@route("/flash", method="POST")
-@local_check
-def flash(id="0"):
- return "JDownloader\r\n"
-
-@route("/flash/add", method="POST")
-@local_check
-def add(request):
- package = request.POST.get('referer', None)
- urls = filter(lambda x: x != "", request.POST['urls'].split("\n"))
-
- if package:
- PYLOAD.addPackage(package, urls, 0)
- else:
- PYLOAD.generateAndAddPackages(urls, 0)
-
- return ""
-
-@route("/flash/addcrypted", method="POST")
-@local_check
-def addcrypted():
-
- package = request.forms.get('referer', 'ClickAndLoad Package')
- dlc = request.forms['crypted'].replace(" ", "+")
-
- dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc")
- dlc_file = open(dlc_path, "wb")
- dlc_file.write(dlc)
- dlc_file.close()
-
- try:
- PYLOAD.addPackage(package, [dlc_path], 0)
- except:
- return HTTPError()
- else:
- return "success\r\n"
-
-@route("/flash/addcrypted2", method="POST")
-@local_check
-def addcrypted2():
-
- package = request.forms.get("source", None)
- crypted = request.forms["crypted"]
- jk = request.forms["jk"]
-
- crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
- if JS:
- jk = "%s f()" % jk
- jk = JS.eval(jk)
-
- else:
- try:
- jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1]
- except:
- ## Test for some known js functions to decode
- if jk.find("dec") > -1 and jk.find("org") > -1:
- org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1]
- jk = list(org)
- jk.reverse()
- jk = "".join(jk)
- else:
- print "Could not decrypt key, please install py-spidermonkey or ossp-js"
-
- try:
- Key = unhexlify(jk)
- except:
- print "Could not decrypt key, please install py-spidermonkey or ossp-js"
- return "failed"
-
- IV = Key
-
- obj = AES.new(Key, AES.MODE_CBC, IV)
- result = obj.decrypt(crypted).replace("\x00", "").replace("\r","").split("\n")
-
- result = filter(lambda x: x != "", result)
-
- try:
- if package:
- PYLOAD.addPackage(package, result, 0)
- else:
- PYLOAD.generateAndAddPackages(result, 0)
- except:
- return "failed can't add"
- else:
- return "success\r\n"
-
-@route("/flashgot_pyload")
-@route("/flashgot_pyload", method="POST")
-@route("/flashgot")
-@route("/flashgot", method="POST")
-@local_check
-def flashgot():
- if request.environ['HTTP_REFERER'] != "http://localhost:9666/flashgot" and request.environ['HTTP_REFERER'] != "http://127.0.0.1:9666/flashgot":
- return HTTPError()
-
- autostart = int(request.forms.get('autostart', 0))
- package = request.forms.get('package', None)
- urls = filter(lambda x: x != "", request.forms['urls'].split("\n"))
- folder = request.forms.get('dir', None)
-
- if package:
- PYLOAD.addPackage(package, urls, autostart)
- else:
- PYLOAD.generateAndAddPackages(urls, autostart)
-
- return ""
-
-@route("/crossdomain.xml")
-@local_check
-def crossdomain():
- rep = "<?xml version=\"1.0\"?>\n"
- rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
- rep += "<cross-domain-policy>\n"
- rep += "<allow-access-from domain=\"*\" />\n"
- rep += "</cross-domain-policy>"
- return rep
-
-
-@route("/flash/checkSupportForUrl")
-@local_check
-def checksupport():
-
- url = request.GET.get("url")
- res = PYLOAD.checkURLs([url])
- supported = (not res[0][1] is None)
-
- return str(supported).lower()
-
-@route("/jdcheck.js")
-@local_check
-def jdcheck():
- rep = "jdownloader=true;\n"
- rep += "var version='9.581;'"
- return rep
diff --git a/module/web/filters.py b/module/web/filters.py
deleted file mode 100644
index 13b8345fc..000000000
--- a/module/web/filters.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import os
-from os.path import abspath, commonprefix, join
-
-quotechar = "::/"
-
-try:
- from os.path import relpath
-except:
- from posixpath import curdir, sep, pardir
- def relpath(path, start=curdir):
- """Return a relative version of a path"""
- if not path:
- raise ValueError("no path specified")
- start_list = abspath(start).split(sep)
- path_list = abspath(path).split(sep)
- # Work out how much of the filepath is shared by start and path.
- i = len(commonprefix([start_list, path_list]))
- rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
- if not rel_list:
- return curdir
- return join(*rel_list)
-
-
-def quotepath(path):
- try:
- return path.replace("../", quotechar)
- except AttributeError:
- return path
- except:
- return ""
-
-def unquotepath(path):
- try:
- return path.replace(quotechar, "../")
- except AttributeError:
- return path
- except:
- return ""
-
-def path_make_absolute(path):
- p = os.path.abspath(path)
- if p[-1] == os.path.sep:
- return p
- else:
- return p + os.path.sep
-
-def path_make_relative(path):
- p = relpath(path)
- if p[-1] == os.path.sep:
- return p
- else:
- return p + os.path.sep
-
-def truncate(value, n):
- if (n - len(value)) < 3:
- return value[:n]+"..."
- return value
-
-def date(date, format):
- return date \ No newline at end of file
diff --git a/module/web/json_app.py b/module/web/json_app.py
deleted file mode 100644
index f3626405c..000000000
--- a/module/web/json_app.py
+++ /dev/null
@@ -1,312 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-from os.path import join
-from traceback import print_exc
-from shutil import copyfileobj
-
-from bottle import route, request, HTTPError
-
-from webinterface import PYLOAD
-
-from utils import login_required, render_to_response, toDict
-
-from module.utils import decode, formatSize
-
-
-def format_time(seconds):
- seconds = int(seconds)
-
- hours, seconds = divmod(seconds, 3600)
- minutes, seconds = divmod(seconds, 60)
- return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
-
-
-def get_sort_key(item):
- return item["order"]
-
-
-@route("/json/status")
-@route("/json/status", method="POST")
-@login_required('LIST')
-def status():
- try:
- status = toDict(PYLOAD.statusServer())
- status['captcha'] = PYLOAD.isCaptchaWaiting()
- return status
- except:
- return HTTPError()
-
-
-@route("/json/links")
-@route("/json/links", method="POST")
-@login_required('LIST')
-def links():
- try:
- links = [toDict(x) for x in PYLOAD.statusDownloads()]
- ids = []
- for link in links:
- ids.append(link['fid'])
-
- if link['status'] == 12:
- link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed']))
- elif link['status'] == 5:
- link['percent'] = 0
- link['size'] = 0
- link['bleft'] = 0
- link['info'] = _("waiting %s") % link['format_wait']
- else:
- link['info'] = ""
-
- data = {'links': links, 'ids': ids}
- return data
- except Exception, e:
- print_exc()
- return HTTPError()
-
-
-@route("/json/packages")
-@login_required('LIST')
-def packages():
- print "/json/packages"
- try:
- data = PYLOAD.getQueue()
-
- for package in data:
- package['links'] = []
- for file in PYLOAD.get_package_files(package['id']):
- package['links'].append(PYLOAD.get_file_info(file))
-
- return data
-
- except:
- return HTTPError()
-
-
-@route("/json/package/<id:int>")
-@login_required('LIST')
-def package(id):
- try:
- data = toDict(PYLOAD.getPackageData(id))
- data["links"] = [toDict(x) for x in data["links"]]
-
- for pyfile in data["links"]:
- if pyfile["status"] == 0:
- pyfile["icon"] = "status_finished.png"
- elif pyfile["status"] in (2, 3):
- pyfile["icon"] = "status_queue.png"
- elif pyfile["status"] in (9, 1):
- pyfile["icon"] = "status_offline.png"
- elif pyfile["status"] == 5:
- pyfile["icon"] = "status_waiting.png"
- elif pyfile["status"] == 8:
- pyfile["icon"] = "status_failed.png"
- elif pyfile["status"] == 4:
- pyfile["icon"] = "arrow_right.png"
- elif pyfile["status"] in (11, 13):
- pyfile["icon"] = "status_proc.png"
- else:
- pyfile["icon"] = "status_downloading.png"
-
- tmp = data["links"]
- tmp.sort(key=get_sort_key)
- data["links"] = tmp
- return data
-
- except:
- print_exc()
- return HTTPError()
-
-
-@route("/json/package_order/:ids")
-@login_required('ADD')
-def package_order(ids):
- try:
- pid, pos = ids.split("|")
- PYLOAD.orderPackage(int(pid), int(pos))
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/abort_link/<id:int>")
-@login_required('DELETE')
-def abort_link(id):
- try:
- PYLOAD.stopDownloads([id])
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/link_order/:ids")
-@login_required('ADD')
-def link_order(ids):
- try:
- pid, pos = ids.split("|")
- PYLOAD.orderFile(int(pid), int(pos))
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/add_package")
-@route("/json/add_package", method="POST")
-@login_required('ADD')
-def add_package():
- name = request.forms.get("add_name", "New Package").strip()
- queue = int(request.forms['add_dest'])
- links = decode(request.forms['add_links'])
- links = links.split("\n")
- pw = request.forms.get("add_password", "").strip("\n\r")
-
- try:
- f = request.files['add_file']
-
- if not name or name == "New Package":
- name = f.name
-
- fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename)
- destination = open(fpath, 'wb')
- copyfileobj(f.file, destination)
- destination.close()
- links.insert(0, fpath)
- except:
- pass
-
- name = name.decode("utf8", "ignore")
-
- links = map(lambda x: x.strip(), links)
- links = filter(lambda x: x != "", links)
-
- pack = PYLOAD.addPackage(name, links, queue)
- if pw:
- pw = pw.decode("utf8", "ignore")
- data = {"password": pw}
- PYLOAD.setPackageData(pack, data)
-
-
-@route("/json/move_package/<dest:int>/<id:int>")
-@login_required('MODIFY')
-def move_package(dest, id):
- try:
- PYLOAD.movePackage(dest, id)
- return {"response": "success"}
- except:
- return HTTPError()
-
-
-@route("/json/edit_package", method="POST")
-@login_required('MODIFY')
-def edit_package():
- try:
- id = int(request.forms.get("pack_id"))
- data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"),
- "folder": request.forms.get("pack_folder").decode("utf8", "ignore"),
- "password": request.forms.get("pack_pws").decode("utf8", "ignore")}
-
- PYLOAD.setPackageData(id, data)
- return {"response": "success"}
-
- except:
- return HTTPError()
-
-
-@route("/json/set_captcha")
-@route("/json/set_captcha", method="POST")
-@login_required('ADD')
-def set_captcha():
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- try:
- PYLOAD.setCaptchaResult(request.forms["cap_id"], request.forms["cap_result"])
- except:
- pass
-
- task = PYLOAD.getCaptchaTask()
-
- if task.tid >= 0:
- src = "data:image/%s;base64,%s" % (task.type, task.data)
-
- return {'captcha': True, 'id': task.tid, 'src': src, 'result_type' : task.resultType}
- else:
- return {'captcha': False}
-
-
-@route("/json/load_config/:category/:section")
-@login_required("SETTINGS")
-def load_config(category, section):
- conf = None
- if category == "general":
- conf = PYLOAD.getConfigDict()
- elif category == "plugin":
- conf = PYLOAD.getPluginConfigDict()
-
- for key, option in conf[section].iteritems():
- if key in ("desc","outline"): continue
-
- if ";" in option["type"]:
- option["list"] = option["type"].split(";")
-
- option["value"] = decode(option["value"])
-
- return render_to_response("settings_item.html", {"skey": section, "section": conf[section]})
-
-
-@route("/json/save_config/:category", method="POST")
-@login_required("SETTINGS")
-def save_config(category):
- for key, value in request.POST.iteritems():
- try:
- section, option = key.split("|")
- except:
- continue
-
- if category == "general": category = "core"
-
- PYLOAD.setConfigValue(section, option, decode(value), category)
-
-
-@route("/json/add_account", method="POST")
-@login_required("ACCOUNTS")
-def add_account():
- login = request.POST["account_login"]
- password = request.POST["account_password"]
- type = request.POST["account_type"]
-
- PYLOAD.updateAccount(type, login, password)
-
-
-@route("/json/update_accounts", method="POST")
-@login_required("ACCOUNTS")
-def update_accounts():
- deleted = [] #dont update deleted accs or they will be created again
-
- for name, value in request.POST.iteritems():
- value = value.strip()
- if not value: continue
-
- tmp, user = name.split(";")
- plugin, action = tmp.split("|")
-
- if (plugin, user) in deleted: continue
-
- if action == "password":
- PYLOAD.updateAccount(plugin, user, value)
- elif action == "time" and "-" in value:
- PYLOAD.updateAccount(plugin, user, options={"time": [value]})
- elif action == "limitdl" and value.isdigit():
- PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]})
- elif action == "delete":
- deleted.append((plugin,user))
- PYLOAD.removeAccount(plugin, user)
-
-@route("/json/change_password", method="POST")
-def change_password():
-
- user = request.POST["user_login"]
- oldpw = request.POST["login_current_password"]
- newpw = request.POST["login_new_password"]
-
- if not PYLOAD.changePassword(user, oldpw, newpw):
- print "Wrong password"
- return HTTPError()
diff --git a/module/web/media/default/css/MooDialog.css b/module/web/media/default/css/MooDialog.css
deleted file mode 100644
index 48c9166ad..000000000
--- a/module/web/media/default/css/MooDialog.css
+++ /dev/null
@@ -1,92 +0,0 @@
-/* Created by Arian Stolwijk <http://www.aryweb.nl> */
-
-.MooDialog {
-/* position: fixed;*/
- margin: 0 auto 0 -350px;
- width:600px;
- padding:14px;
- left:50%;
- top: 100px;
-
- position: absolute;
- left: 50%;
- z-index: 50000;
-
- background: #eef5f8;
- color: black;
- border-radius: 7px;
- -moz-border-radius: 7px;
- -webkit-border-radius: 7px;
- border-radius: 7px;
- -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
- -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
- box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
-}
-
-.MooDialogTitle {
- padding-top: 30px;
-}
-
-.MooDialog .title {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- padding: 3px 20px;
- background: #b7c4dc;
- border-bottom: 1px solid #a1aec5;
- font-weight: bold;
- text-shadow: 1px 1px 0 #fff;
- color: black;
- border-radius: 7px;
- -moz-border-radius: 7px;
- -webkit-border-radius: 7px;
-}
-
-.MooDialog .close {
- background: url(/media/img/dialog-close.png) no-repeat;
- width: 16px;
- height: 16px;
- display: block;
- cursor: pointer;
- top: -5px;
- left: -5px;
- position: absolute;
-}
-
-.MooDialog .buttons {
- text-align: right;
- margin: 0;
- padding: 0;
- border: 0;
- background: none;
-}
-
-.MooDialog .iframe {
- width: 100%;
- height: 100%;
-}
-
-.MooDialog .textInput {
- width: 200px;
- float: left;
-}
-
-.MooDialog .MooDialogAlert,
-.MooDialog .MooDialogConfirm,
-.MooDialog .MooDialogPrompt,
-.MooDialog .MooDialogError {
- background: url(/media/img/dialog-warning.png) no-repeat;
- padding-left: 40px;
- min-height: 40px;
-}
-
-.MooDialog .MooDialogConfirm,
-.MooDialog .MooDialogPromt {
- background: url(/media/img/dialog-question.png) no-repeat;
-}
-
-.MooDialog .MooDialogError {
- background: url(/media/img/dialog-error.png) no-repeat;
-}
-
diff --git a/module/web/media/default/css/default.css b/module/web/media/default/css/default.css
deleted file mode 100644
index 116f9725a..000000000
--- a/module/web/media/default/css/default.css
+++ /dev/null
@@ -1,908 +0,0 @@
-.hidden {
- display:none;
-}
-.leftalign {
- text-align:left;
-}
-.centeralign {
- text-align:center;
-}
-.rightalign {
- text-align:right;
-}
-
-
-.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
- background-color:#000080;
- color:#fff !important;
- text-decoration:none;
- padding:0 0.2em;
- margin:0.1em 0.2em;
- border:none !important;
-}
-.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
- background-color:#808080;
- color:#fff !important;
- text-decoration:none;
- padding:0 0.2em;
- margin:0.1em 0.2em;
- border:none !important;
-}
-
-.dokuwiki div.plugin_translation ul li a:hover img {
- opacity:1.0;
- height:15px;
-}
-
-body {
- margin:0;
- padding:0;
- background-color:white;
- color:black;
- font-size:12px;
- font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
- font-family:sans-serif;
- font-size:99, 96%;
- font-size-adjust:none;
- font-style:normal;
- font-variant:normal;
- font-weight:normal;
- line-height:normal;
-}
-hr {
- border-width:0;
- border-bottom:1px #aaa dotted;
-}
-img {
- border:none;
-}
-form {
- margin:0px;
- padding:0px;
- border:none;
- display:inline;
- background:transparent;
-}
-ul li {
- margin:5px;
-}
-textarea {
- font-family:monospace;
-}
-table {
- margin:0.5em 0;
- border-collapse:collapse;
-}
-td {
- padding:0.25em;
- border:1pt solid #ADB9CC;
-}
-a {
- color:#3465a4;
- text-decoration:none;
-}
-a:hover {
- text-decoration:underline;
-}
-
-option {
- border:0 none #fff;
-}
-strong.highlight {
- background-color:#fc9;
- padding:1pt;
-}
-#pagebottom {
- clear:both;
-}
-hr {
- height:1px;
- color:#c0c0c0;
- background-color:#c0c0c0;
- border:none;
- margin:.2em 0 .2em 0;
-}
-
-.invisible {
- margin:0px;
- border:0px;
- padding:0px;
- height:0px;
- visibility:hidden;
-}
-.left {
- float:left !important;
-}
-.right {
- float:right !important;
-}
-.center {
- text-align:center;
-}
-div#body-wrapper {
- padding:40px 40px 10px 40px;
- font-size:127%;
-}
-div#content {
- margin-top:-20px;
- padding:0;
- font-size:14px;
- color:black;
- line-height:1.5em;
-}
-h1, h2, h3, h4, h5, h6 {
- background:transparent none repeat scroll 0 0;
- border-bottom:1px solid #aaa;
- color:black;
- font-weight:normal;
- margin:0;
- padding:0;
- padding-bottom:0.17em;
- padding-top:0.5em;
-}
-h1 {
- font-size:188%;
- line-height:1.2em;
- margin-bottom:0.1em;
- padding-bottom:0;
-}
-h2 {
- font-size:150%;
-}
-h3, h4, h5, h6 {
- border-bottom:none;
- font-weight:bold;
-}
-h3 {
- font-size:132%;
-}
-h4 {
- font-size:116%;
-}
-h5 {
- font-size:100%;
-}
-h6 {
- font-size:80%;
-}
-ul#page-actions, ul#page-actions-more {
- float:right;
- margin:10px 10px 0 10px;
- padding:6px;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- white-space: nowrap;
- border-radius:5px;
- -moz-border-radius:5px;
-}
-ul#user-actions {
- padding:5px;
- margin:0;
- display:inline;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- -moz-border-radius:3px;
- border-radius:3px;
-}
-ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
- display:inline;
-}
-ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
- text-decoration:none;
- color:black;
- display:inline;
- margin:0 3px;
- padding:2px 0px 2px 18px;
-}
-ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
- /*text-decoration:underline;*/
-}
-/***************************/
-ul#page-actions2 {
- float:left;
- margin:10px 10px 0 10px;
- padding:6px;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- border-radius:5px;
- -moz-border-radius:5px;
-}
-ul#user-actions2 {
- padding:5px;
- margin:0;
- display:inline;
- color:black;
- background-color:#ececec;
- list-style-type:none;
- border-radius:3px;
- -moz-border-radius:3px;
-}
-ul#page-actions2 li, ul#user-actions2 li {
- display:inline;
-}
-ul#page-actions2 a, ul#user-actions2 a {
- text-decoration:none;
- color:black;
- display:inline;
- margin:0 3px;
- padding:2px 0px 2px 18px;
-}
-ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
-ul#page-actions-more a:hover, ul#page-actions-more a:focus{
- color: #4e7bb4;
-}
-/****************************/
-.hidden {
- display:none;
-}
-
-a.action.index {
- background:transparent url(/media/default/img/wiki-tools-index.png) 0px 1px no-repeat;
-}
-a.action.recent {
- background:transparent url(/media/default/img/wiki-tools-recent.png) 0px 1px no-repeat;
-}
-a.logout {
- background:transparent url(/media/default/img/user-actions-logout.png) 0px 1px no-repeat;
-}
-
-a.info {
- background:transparent url(/media/default/img/user-info.png) 0px 1px no-repeat;
-}
-
-a.admin {
- background:transparent url(/media/default/img/user-actions-admin.png) 0px 1px no-repeat;
-}
-a.profile {
- background:transparent url(/media/default/img/user-actions-profile.png) 0px 1px no-repeat;
-}
-a.create, a.edit {
- background:transparent url(/media/default/img/page-tools-edit.png) 0px 1px no-repeat;
-}
-a.source, a.show {
- background:transparent url(/media/default/img/page-tools-source.png) 0px 1px no-repeat;
-}
-a.revisions {
- background:transparent url(/media/default/img/page-tools-revisions.png) 0px 1px no-repeat;
-}
-a.subscribe, a.unsubscribe {
- background:transparent url(/media/default/img/page-tools-subscribe.png) 0px 1px no-repeat;
-}
-a.backlink {
- background:transparent url(/media/default/img/page-tools-backlinks.png) 0px 1px no-repeat;
-}
-a.play {
- background:transparent url(/media/default/img/control_play.png) 0px 1px no-repeat;
-}
-.time {
- background:transparent url(/media/default/img/status_None.png) 0px 1px no-repeat;
- padding: 2px 0px 2px 18px;
- margin: 0px 3px;
-}
-.reconnect {
- background:transparent url(/media/default/img/reconnect.png) 0px 1px no-repeat;
- padding: 2px 0px 2px 18px;
- margin: 0px 3px;
-}
-a.play:hover {
- background:transparent url(/media/default/img/control_play_blue.png) 0px 1px no-repeat;
-}
-a.cancel {
- background:transparent url(/media/default/img/control_cancel.png) 0px 1px no-repeat;
-}
-a.cancel:hover {
- background:transparent url(/media/default/img/control_cancel_blue.png) 0px 1px no-repeat;
-}
-a.pause {
- background:transparent url(/media/default/img/control_pause.png) 0px 1px no-repeat;
-}
-a.pause:hover {
- background:transparent url(/media/default/img/control_pause_blue.png) 0px 1px no-repeat;
- font-weight: bold;
-}
-a.stop {
- background:transparent url(/media/default/img/control_stop.png) 0px 1px no-repeat;
-}
-a.stop:hover {
- background:transparent url(/media/default/img/control_stop_blue.png) 0px 1px no-repeat;
-}
-a.add {
- background:transparent url(/media/default/img/control_add.png) 0px 1px no-repeat;
-}
-a.add:hover {
- background:transparent url(/media/default/img/control_add_blue.png) 0px 1px no-repeat;
-}
-a.cog {
- background:transparent url(/media/default/img/cog.png) 0px 1px no-repeat;
-}
-#head-panel {
- background:#525252 url(/media/default/img/head_bg1.png) bottom left repeat-x;
-}
-#head-panel h1 {
- display:none;
- margin:0;
- text-decoration:none;
- padding-top:0.8em;
- padding-left:3.3em;
- font-size:2.6em;
- color:#eeeeec;
-}
-#head-panel #head-logo {
- float:left;
- margin:5px 0 -15px 5px;
- padding:0;
- overflow:visible;
-}
-#head-menu {
- background:transparent url(/media/default/img/tabs-border-bottom.png) 0 100% repeat-x;
- width:100%;
- float:left;
- margin:0;
- padding:0;
- padding-top:0.8em;
-}
-#head-menu ul {
- list-style:none;
- margin:0 1em 0 2em;
-}
-#head-menu ul li {
- float:left;
- margin:0;
- margin-left:0.3em;
- font-size:14px;
- margin-bottom:4px;
-}
-#head-menu ul li.selected, #head-menu ul li:hover {
- margin-bottom:0px;
-}
-#head-menu ul li a img {
- height:22px;
- width:22px;
- vertical-align:middle;
-}
-#head-menu ul li a, #head-menu ul li a:link {
- float:left;
- text-decoration:none;
- color:#555;
- background:#eaeaea url(/media/default/img/tab-background.png) 0 100% repeat-x;
- padding:3px 7px 3px 7px;
- border:2px solid #ccc;
- border-bottom:0px solid transparent;
- padding-bottom:3px;
- -moz-border-radius:5px;
- border-radius:5px;
-}
-#head-menu ul li a:hover, #head-menu ul li a:focus {
- color:#111;
- padding-bottom:7px;
- border-bottom:0px none transparent;
- outline:none;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
- -moz-border-radius-bottomright:0px;
- -moz-border-radius-bottomleft:0px;
-}
-#head-menu ul li a:focus {
- margin-bottom:-4px;
-}
-#head-menu ul li.selected a {
- color:#3566A5;
- background:#fff;
- padding-bottom:7px;
- border-bottom:0px none transparent;
- border-bottom-left-radius: 0px;
- border-bottom-right-radius: 0px;
- -moz-border-radius-bottomright:0px;
- -moz-border-radius-bottomleft:0px;
-}
-#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
- color:#111;
-}
-div#head-search-and-login {
- float:right;
- margin:0 1em 0 0;
- background-color:#222;
- padding:7px 7px 5px 5px;
- color:white;
- white-space: nowrap;
- border-bottom-left-radius: 6px;
- border-bottom-right-radius: 6px;
- -moz-border-radius-bottomright:6px;
- -moz-border-radius-bottomleft:6px;
-}
-div#head-search-and-login form {
- display:inline;
- padding:0 3px;
-}
-div#head-search-and-login form input {
- border:2px solid #888;
- background:#eee;
- font-size:14px;
- padding:2px;
- border-radius:3px;
- -moz-border-radius:3px;
-}
-div#head-search-and-login form input:focus {
- background:#fff;
-}
-#head-search {
- font-size:14px;
-}
-#head-username, #head-password {
- width:80px;
- font-size:14px;
-}
-#pageinfo {
- clear:both;
- color:#888;
- padding:0.6em 0;
- margin:0;
-}
-#foot {
- font-style:normal;
- color:#888;
- text-align:center;
-}
-#foot a {
- color:#aaf;
-}
-#foot img {
- vertical-align:middle;
-}
-div.toc {
- border:1px dotted #888;
- background:#f0f0f0;
- margin:1em 0 1em 1em;
- float:right;
- font-size:95%;
-}
-div.toc .tocheader {
- font-weight:bold;
- margin:0.5em 1em;
-}
-div.toc ol {
- margin:1em 0.5em 1em 1em;
- padding:0;
-}
-div.toc ol li {
- margin:0;
- padding:0;
- margin-left:1em;
-}
-div.toc ol ol {
- margin:0.5em 0.5em 0.5em 1em;
- padding:0;
-}
-div.recentchanges table {
- clear:both;
-}
-div#editor-help {
- font-size:90%;
- border:1px dotted #888;
- padding:0ex 1ex 1ex 1ex;
- background:#f7f6f2;
-}
-div#preview {
- margin-top:1em;
-}
-label.block {
- display:block;
- text-align:right;
- font-weight:bold;
-}
-label.simple {
- display:block;
- text-align:left;
- font-weight:normal;
-}
-label.block input.edit {
- width:50%;
-}
-/*fieldset {
- width:300px;
- text-align:center;
- padding:0.5em;
- margin:auto;
-}
-*/
-div.editor {
- margin:0 0 0 0;
-}
-table {
- margin:0.5em 0;
- border-collapse:collapse;
-}
-td {
- padding:0.25em;
- border:1pt solid #ADB9CC;
-}
-td p {
- margin:0;
- padding:0;
-}
-.u {
- text-decoration:underline;
-}
-.footnotes ul {
- padding:0 2em;
- margin:0 0 1em;
-}
-.footnotes li {
- list-style:none;
-}
-.userpref table, .userpref td {
- border:none;
-}
-#message {
- clear:both;
- padding:5px 10px;
- background-color:#eee;
- border-bottom:2px solid #ccc;
-}
-#message p {
- margin:5px 0;
- padding:0;
- font-weight:bold;
-}
-#message div.buttons {
- font-weight:normal;
-}
-.diff {
- width:99%;
-}
-.diff-title {
- background-color:#C0C0C0;
-}
-.searchresult dd span {
- font-weight:bold;
-}
-.boxtext {
- font-family:tahoma, arial, sans-serif;
- font-size:11px;
- color:#000;
- float:none;
- padding:3px 0 0 10px;
-}
-.statusbutton {
- width:32px;
- height:32px;
- float:left;
- margin-left:-32px;
- margin-right:5px;
- opacity:0;
- cursor:pointer
-}
-.dlsize {
- float:left;
- padding-right: 8px;
-}
-.dlspeed {
- float:left;
- padding-right: 8px;
-}
-.package {
- margin-bottom: 10px;
-}
-.packagename {
- font-weight: bold;
-}
-
-.child {
- margin-left: 20px;
-}
-.child_status {
- margin-right: 10px;
-}
-.child_secrow {
- font-size: 10px;
-}
-
-.header, .header th {
- text-align: left;
- font-weight: normal;
- background-color:#ececec;
- -moz-border-radius:5px;
- border-radius:5px;
-}
-.progress_bar {
- background: #0C0;
- height: 5px;
-
-}
-
-.queue {
- border: none
-}
-
-.queue tr td {
- border: none
-}
-
-.header, .header th{
- text-align: left;
- font-weight: normal;
-}
-
-
-.clearer
-{
- clear: both;
- height: 1px;
-}
-
-.left
-{
- float: left;
-}
-
-.right
-{
- float: right;
-}
-
-
-.setfield
-{
- display: table-cell;
-}
-
-ul.tabs li a
-{
- padding: 5px 16px 4px 15px;
- border: none;
- font-weight: bold;
-
- border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
-
-}
-
-
-#tabs span
-{
- display: none;
-}
-
-#tabs span.selected
-{
- display: inline;
-}
-
-#tabsback
-{
- background-color: #525252;
- margin: 2px 0 0;
- padding: 6px 4px 1px 4px;
-
- border-top-right-radius: 30px;
- border-top-left-radius: 3px;
- -moz-border-radius-topright: 30px;
- -moz-border-radius-topleft: 3px;
-}
-ul.tabs
-{
- list-style-type: none;
- margin:0;
- padding: 0 40px 0 0;
-}
-
-ul.tabs li
-{
- display: inline;
- margin-left: 8px;
-}
-
-
-ul.tabs li a
-{
- color: #42454a;
- background-color: #eaeaea;
- border: 1px none #c9c3ba;
- margin: 0;
- text-decoration: none;
-
- outline: 0;
-
- padding: 5px 16px 4px 15px;
- font-weight: bold;
-
- border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
-
-}
-
-ul.tabs li a.selected, ul.tabs li a:hover
-{
- color: #000;
- background-color: white;
-
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 0;
- -moz-border-radius-bottomright: 0;
- -moz-border-radius-bottomleft: 0;
-}
-
-ul.tabs li a:hover
-{
- background-color: #f1f4ee;
-}
-
-ul.tabs li a.selected
-{
- font-weight: bold;
- background-color: #525252;
- padding-bottom: 5px;
- color: white;
-}
-
-
-#tabs-body {
- position: relative;
- overflow: hidden;
-}
-
-
-span.tabContent
-{
- border: 2px solid #525252;
- margin: 0;
- padding: 0;
- padding-bottom: 10px;
-}
-
-#tabs-body > span {
- display: none;
-}
-
-#tabs-body > span.active {
- display: block;
-}
-
-.hide
-{
- display: none;
-}
-
-.settable
-{
- margin: 20px;
- border: none;
-}
-.settable td
-{
- border: none;
- margin: 0;
- padding: 5px;
-}
-
-.settable th{
- padding-bottom: 8px;
-}
-
-.settable.wide td , .settable.wide th {
- padding-left: 15px;
- padding-right: 15px;
-}
-
-
-/*settings navbar*/
-ul.nav {
- margin: -30px 0 0;
- padding: 0;
- list-style: none;
- position: absolute;
-}
-
-
-ul.nav li {
- position: relative;
- float: left;
- padding: 5px;
-}
-
-ul.nav > li a {
- background: white;
- -moz-border-radius: 4px 4px 4px 4px;
- border: 1px solid #C9C3BA;
- border-bottom: medium none;
- color: black;
-}
-
-ul.nav ul {
- position: absolute;
- top: 26px;
- left: 10px;
- margin: 0;
- padding: 0;
- list-style: none;
- border: 1px solid #AAA;
- background: #f1f1f1;
- -webkit-box-shadow: 1px 1px 5px #AAA;
- -moz-box-shadow: 1px 1px 5px #AAA;
- box-shadow: 1px 1px 5px #AAA;
- cursor: pointer;
-}
-
-ul.nav .open {
- display: block;
-}
-
-ul.nav .close {
- display: none;
-}
-
-ul.nav ul li {
- float: none;
- padding: 0;
-}
-
-ul.nav ul li a {
- width: 130px;
- background: #f1f1f1;
- padding: 3px;
- display: block;
- font-weight: normal;
-}
-
-ul.nav ul li a:hover {
- background: #CDCDCD;
-}
-
-ul.nav ul ul {
- left: 137px;
- top: 0;
-}
-
-.purr-wrapper{
- margin:10px;
-}
-
-/*Purr alert styles*/
-
-.purr-alert{
- margin-bottom:10px;
- padding:10px;
- background:#000;
- font-size:13px;
- font-weight:bold;
- color:#FFF;
- -moz-border-radius:5px;
- -webkit-border-radius:5px;
- /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
- width:300px;
-}
-.purr-alert.error{
- color:#F55;
- padding-left:30px;
- background:url(/media/default/img/error.png) no-repeat #000 7px 10px;
- width:280px;
-}
-.purr-alert.success{
- color:#5F5;
- padding-left:30px;
- background:url(/media/default/img/success.png) no-repeat #000 7px 10px;
- width:280px;
-}
-.purr-alert.notice{
- color:#99F;
- padding-left:30px;
- background:url(/media/default/img//notice.png) no-repeat #000 7px 10px;
- width:280px;
-}
-
-table.system {
- border: none;
- margin-left: 10px;
-}
-
-table.system td {
- border: none
-}
-
-table.system tr > td:first-child {
- font-weight: bold;
- padding-right: 10px;
-} \ No newline at end of file
diff --git a/module/web/media/default/css/log.css b/module/web/media/default/css/log.css
deleted file mode 100644
index 73786bfb4..000000000
--- a/module/web/media/default/css/log.css
+++ /dev/null
@@ -1,72 +0,0 @@
-
-html, body, #content
-{
- height: 100%;
-}
-#body-wrapper
-{
- height: 70%;
-}
-.logdiv
-{
- height: 90%;
- width: 100%;
- overflow: auto;
- border: 2px solid #CCC;
- outline: 1px solid #666;
- background-color: #FFE;
- margin-right: auto;
- margin-left: auto;
-}
-.logform
-{
- display: table;
- margin: 0 auto 0 auto;
- padding-top: 5px;
-}
-.logtable
-{
-
- margin: 0px;
-}
-.logtable td
-{
- border: none;
- white-space: nowrap;
-
-
- font-family: monospace;
- font-size: 16px;
- margin: 0px;
- padding: 0px 10px 0px 10px;
- line-height: 110%;
-}
-td.logline
-{
- background-color: #EEE;
- text-align:right;
- padding: 0px 5px 0px 5px;
-}
-td.loglevel
-{
- text-align:right;
-}
-.logperpage
-{
- float: right;
- padding-bottom: 8px;
-}
-.logpaginator
-{
- float: left;
- padding-top: 5px;
-}
-.logpaginator a
-{
- padding: 0px 8px 0px 8px;
-}
-.logwarn
-{
- text-align: center;
- color: red;
-} \ No newline at end of file
diff --git a/module/web/media/img/favicon.ico b/module/web/media/img/favicon.ico
deleted file mode 100644
index 58b1f4b89..000000000
--- a/module/web/media/img/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/module/web/media/js/MooDialog_static.js b/module/web/media/js/MooDialog_static.js
deleted file mode 100644
index d497d3d57..000000000
--- a/module/web/media/js/MooDialog_static.js
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
----
-
-name: Overlay
-
-authors:
- - David Walsh (http://davidwalsh.name)
-
-license:
- - MIT-style license
-
-requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween]
-
-provides:
- - Overlay
-...
-*/
-
-var Overlay = new Class({
-
- Implements: [Options, Events],
-
- options: {
- id: 'overlay',
- color: '#000',
- duration: 500,
- opacity: 0.5,
- zIndex: 5000/*,
- onClick: $empty,
- onClose: $empty,
- onHide: $empty,
- onOpen: $empty,
- onShow: $empty
- */
- },
-
- initialize: function(container, options){
- this.setOptions(options);
- this.container = document.id(container);
-
- this.bound = {
- 'window': {
- resize: this.resize.bind(this),
- scroll: this.scroll.bind(this)
- },
- overlayClick: this.overlayClick.bind(this),
- tweenStart: this.tweenStart.bind(this),
- tweenComplete: this.tweenComplete.bind(this)
- };
-
- this.build().attach();
- },
-
- build: function(){
- this.overlay = new Element('div', {
- id: this.options.id,
- opacity: 0,
- styles: {
- position: (Browser.ie6) ? 'absolute' : 'fixed',
- background: this.options.color,
- left: 0,
- top: 0,
- 'z-index': this.options.zIndex
- }
- }).inject(this.container);
- this.tween = new Fx.Tween(this.overlay, {
- duration: this.options.duration,
- link: 'cancel',
- property: 'opacity'
- });
- this.tween.set('opacity', 0)
- return this;
- }.protect(),
-
- attach: function(){
- window.addEvents(this.bound.window);
- this.overlay.addEvent('click', this.bound.overlayClick);
- this.tween.addEvents({
- onStart: this.bound.tweenStart,
- onComplete: this.bound.tweenComplete
- });
- return this;
- },
-
- detach: function(){
- var args = Array.prototype.slice.call(arguments);
- args.each(function(item){
- if(item == 'window') window.removeEvents(this.bound.window);
- if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick);
- }, this);
- return this;
- },
-
- overlayClick: function(){
- this.fireEvent('click');
- return this;
- },
-
- tweenStart: function(){
- this.overlay.setStyles({
- width: '100%',
- height: this.container.getScrollSize().y
- });
- return this;
- },
-
- tweenComplete: function(){
- this.fireEvent(this.overlay.get('opacity') == this.options.opacity ? 'show' : 'hide');
- return this;
- },
-
- open: function(){
- this.fireEvent('open');
- this.tween.set('display', 'block');
- this.tween.start(this.options.opacity);
- return this;
- },
-
- close: function(){
- this.fireEvent('close');
- this.tween.start(0).chain(function(){
- this.tween.set('display', 'none');
- }.bind(this));
- return this;
- },
-
- resize: function(){
- this.fireEvent('resize');
- this.overlay.setStyle('height', this.container.getScrollSize().y);
- return this;
- },
-
- scroll: function(){
- this.fireEvent('scroll');
- if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x);
- return this;
- }
-
-});
-/*
----
-name: MooDialog
-description: The base class of MooDialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Core/Class, Core/Element, Core/Element.Styles, Core/Element.Event]
-provides: [MooDialog, Element.MooDialog]
-...
-*/
-
-
-var MooDialog = new Class({
-
- Implements: [Options, Events],
-
- options: {
- 'class': 'MooDialog',
- title: null,
- scroll: true, // IE
- forceScroll: false,
- useEscKey: true,
- destroyOnHide: true,
- autoOpen: true,
- closeButton: true,
- onInitialize: function(){
- this.wrapper.setStyle('display', 'none');
- },
- onBeforeOpen: function(){
- this.wrapper.setStyle('display', 'block');
- this.fireEvent('show');
- },
- onBeforeClose: function(){
- this.wrapper.setStyle('display', 'none');
- this.fireEvent('hide');
- }/*,
- onOpen: function(){},
- onClose: function(){},
- onShow: function(){},
- onHide: function(){}*/
- },
-
- initialize: function(options){
- this.setOptions(options);
- this.options.inject = this.options.inject || document.body;
- options = this.options;
-
- var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
- this.content = new Element('div.content').inject(wrapper);
- wrapper.setStyle('display', 'none');
-
- if (options.title){
- this.title = new Element('div.title').set('text', options.title).inject(wrapper);
- wrapper.addClass('MooDialogTitle');
- }
-
- if (options.closeButton){
- this.closeButton = new Element('a.close', {
- events: {click: this.close.bind(this)}
- }).inject(wrapper);
- }
-
-
- /*<ie6>*/// IE 6 scroll
- if ((options.scroll && Browser.ie6) || options.forceScroll){
- wrapper.setStyle('position', 'absolute');
- var position = wrapper.getPosition(options.inject);
- window.addEvent('scroll', function(){
- var scroll = document.getScroll();
- wrapper.setPosition({
- x: position.x + scroll.x,
- y: position.y + scroll.y
- });
- });
- }
- /*</ie6>*/
-
- if (options.useEscKey){
- // Add event for the esc key
- document.addEvent('keydown', function(e){
- if (e.key == 'esc') this.close();
- }.bind(this));
- }
-
- this.addEvent('hide', function(){
- if (options.destroyOnHide) this.destroy();
- }.bind(this));
-
- this.fireEvent('initialize', wrapper);
- },
-
- setContent: function(){
- var content = Array.from(arguments);
- if (content.length == 1) content = content[0];
-
- this.content.empty();
-
- var type = typeOf(content);
- if (['string', 'number'].contains(type)) this.content.set('text', content);
- else this.content.adopt(content);
-
- return this;
- },
-
- open: function(){
- this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
- this.opened = true;
- return this;
- },
-
- close: function(){
- this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
- this.opened = false;
- return this;
- },
-
- destroy: function(){
- this.wrapper.destroy();
- },
-
- toElement: function(){
- return this.wrapper;
- }
-
-});
-
-
-Element.implement({
-
- MooDialog: function(options){
- this.store('MooDialog',
- new MooDialog(options).setContent(this).open()
- );
- return this;
- }
-
-});
-/*
----
-name: MooDialog.Fx
-description: Overwrite the default events so the Dialogs are using Fx on open and close
-authors: Arian Stolwijk
-license: MIT-style license
-requires: [Cores/Fx.Tween, Overlay]
-provides: MooDialog.Fx
-...
-*/
-
-
-MooDialog.implement('options', {
-
- duration: 400,
- closeOnOverlayClick: true,
-
- onInitialize: function(wrapper){
- this.fx = new Fx.Tween(wrapper, {
- property: 'opacity',
- duration: this.options.duration
- }).set(0);
- this.overlay = new Overlay(this.options.inject, {
- duration: this.options.duration
- });
- if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this));
- },
-
- onBeforeOpen: function(wrapper){
- this.overlay.open();
- wrapper.setStyle('display', 'block');
- this.fx.start(1).chain(function(){
- this.fireEvent('show');
- }.bind(this));
- },
-
- onBeforeClose: function(wrapper){
- this.overlay.close();
- this.fx.start(0).chain(function(){
- this.fireEvent('hide');
- wrapper.setStyle('display', 'none');
- }.bind(this));
- }
-
-});
-/*
----
-name: MooDialog.Confirm
-description: Creates an Confirm Dialog
-authors: Arian Stolwijk
-license: MIT-style license
-requires: MooDialog
-provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit]
-...
-*/
-
-
-MooDialog.Confirm = new Class({
-
- Extends: MooDialog,
-
- options: {
- okText: 'Ok',
- cancelText: 'Cancel',
- focus: true,
- textPClass: 'MooDialogConfirm'
- },
-
- initialize: function(msg, fn, fn1, options){
- this.parent(options);
- var emptyFn = function(){},
- self = this;
-
- var buttons = [
- {fn: fn || emptyFn, txt: this.options.okText},
- {fn: fn1 || emptyFn, txt: this.options.cancelText}
- ].map(function(button){
- return new Element('input[type=button]', {
- events: {
- click: function(){
- button.fn();
- self.close();
- }
- },
- value: button.txt
- });
- });
-
- this.setContent(
- new Element('p.' + this.options.textPClass, {text: msg}),
- new Element('div.buttons').adopt(buttons)
- );
- if (this.options.autoOpen) this.open();
-
- if(this.options.focus) this.addEvent('show', function(){
- buttons[1].focus();
- });
-
- }
-});
-
-
-Element.implement({
-
- confirmLinkClick: function(msg, options){
- this.addEvent('click', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- location.href = this.get('href');
- }.bind(this), null, options)
- });
- return this;
- },
-
- confirmFormSubmit: function(msg, options){
- this.addEvent('submit', function(e){
- e.stop();
- new MooDialog.Confirm(msg, function(){
- this.submit();
- }.bind(this), null, options)
- }.bind(this));
- return this;
- }
-
-});
diff --git a/module/web/media/js/MooDropMenu_static.js b/module/web/media/js/MooDropMenu_static.js
deleted file mode 100644
index b9cd8cc10..000000000
--- a/module/web/media/js/MooDropMenu_static.js
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
----
-description: This provides a simple Drop Down menu with infinit levels
-
-license: MIT-style
-
-authors:
-- Arian Stolwijk
-
-requires:
- - Core/Class.Extras
- - Core/Element.Event
- - Core/Selectors
-
-provides: [MooDropMenu, Element.MooDropMenu]
-
-...
-*/
-
-var MooDropMenu = new Class({
-
- Implements: [Options, Events],
-
- options: {
- onOpen: function(el){
- el.removeClass('close').addClass('open');
- },
- onClose: function(el){
- el.removeClass('open').addClass('close');
- },
- onInitialize: function(el){
- el.removeClass('open').addClass('close');
- },
- mouseoutDelay: 200,
- mouseoverDelay: 0,
- listSelector: 'ul',
- itemSelector: 'li'
- },
-
- initialize: function(menu, options, level){
- this.setOptions(options);
- options = this.options;
-
- var menu = this.menu = document.id(menu);
-
- menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
-
- this.fireEvent('initialize', el);
-
- var parent = el.getParent(options.itemSelector),
- timer;
-
- parent.addEvents({
-
- 'mouseenter': function(){
- parent.store('DropDownOpen', true);
-
- clearTimeout(timer);
- if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
- else this.fireEvent('open', el);
-
- }.bind(this),
-
- 'mouseleave': function(){
- parent.store('DropDownOpen', false);
-
- clearTimeout(timer);
- timer = (function(){
- if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
- }).delay(options.mouseoutDelay, this);
-
- }.bind(this)
- });
-
- }, this);
- },
-
- toElement: function(){
- return this.menu
- }
-
-});
-
-/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
-Element.implement({
- MooDropMenu: function(options){
- return this.store('MooDropMenu', new MooDropMenu(this, options));
- }
-});
diff --git a/module/web/media/js/admin.coffee b/module/web/media/js/admin.coffee
deleted file mode 100644
index 82b0dd3ec..000000000
--- a/module/web/media/js/admin.coffee
+++ /dev/null
@@ -1,58 +0,0 @@
-root = this
-
-window.addEvent "domready", ->
-
- root.passwordDialog = new MooDialog {destroyOnHide: false}
- root.passwordDialog.setContent $ 'password_box'
-
- $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
- $("login_password_button").addEvent "click", (e) ->
-
- newpw = $("login_new_password").get("value")
- newpw2 = $("login_new_password2").get("value")
-
- if newpw is newpw2
- form = $("password_form")
- form.set "send", {
- onSuccess: (data) ->
- root.notify.alert "Success", {
- 'className': 'success'
- }
- onFailure: (data) ->
- root.notify.alert "Error", {
- 'className': 'error'
- }
- }
-
- form.send()
-
- root.passwordDialog.close()
- else
- alert '{{_("Passwords did not match.")}}'
-
- e.stop()
-
- for item in $$(".change_password")
- id = item.get("id")
- user = id.split("|")[1]
- $("user_login").set("value", user)
- item.addEvent "click", (e) -> root.passwordDialog.open()
-
- $('quit-pyload').addEvent "click", (e) ->
- new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
- new Request.JSON({
- url: '/api/kill'
- method: 'get'
- }).send()
- , ->
- e.stop()
-
- $('restart-pyload').addEvent "click", (e) ->
- new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
- new Request.JSON({
- url: '/api/restart'
- method: 'get'
- onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
- }).send()
- , ->
- e.stop() \ No newline at end of file
diff --git a/module/web/media/js/admin.js b/module/web/media/js/admin.js
deleted file mode 100644
index d34d310a0..000000000
--- a/module/web/media/js/admin.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/base.coffee b/module/web/media/js/base.coffee
deleted file mode 100644
index 3b5d33e82..000000000
--- a/module/web/media/js/base.coffee
+++ /dev/null
@@ -1,173 +0,0 @@
-# External scope
-root = this
-
-# helper functions
-humanFileSize = (size) ->
- filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
- loga = Math.log(size) / Math.log(1024)
- i = Math.floor(loga)
- a = Math.pow(1024, i)
- if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
-
-parseUri = () ->
- oldString = $("add_links").value
- regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
- resu = oldString.match regxp
- return if resu == null
- res = "";
-
- for part in resu
- if part.indexOf(" ") != -1
- res = res + part.replace(" ", " \n")
- else if part.indexOf("\t") != -1
- res = res + part.replace("\t", " \n")
- else if part.indexOf("\r") != -1
- res = res + part.replace("\r", " \n")
- else if part.indexOf("\"") != -1
- res = res + part.replace("\"", " \n")
- else if part.indexOf("<") != -1
- res = res + part.replace("<", " \n")
- else if part.indexOf("'") != -1
- res = res + part.replace("'", " \n")
- else
- res = res + part.replace("\n", " \n")
-
- $("add_links").value = res;
-
-
-Array::remove = (from, to) ->
- rest = this.slice((to || from) + 1 || this.length)
- this.length = from < 0 ? this.length + from : from
- return [] if this.length == 0
- return this.push.apply(this, rest)
-
-
-document.addEvent "domready", ->
-
- # global notification
- root.notify = new Purr {
- 'mode': 'top'
- 'position': 'center'
- }
-
- root.captchaBox = new MooDialog {destroyOnHide: false}
- root.captchaBox.setContent $ 'cap_box'
-
- root.addBox = new MooDialog {destroyOnHide: false}
- root.addBox.setContent $ 'add_box'
-
- $('add_form').onsubmit = ->
- $('add_form').target = 'upload_target'
- if $('add_name').value is "" and $('add_file').value is ""
- alert '{{_("Please Enter a packagename.")}}'
- return false
- else
- root.addBox.close()
- return true
-
- $('add_reset').addEvent 'click', -> root.addBox.close()
-
- $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
- $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
- $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
- $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
-
-
- # captcha events
-
- $('cap_info').addEvent 'click', ->
- load_captcha "get", ""
- root.captchaBox.open()
- $('cap_reset').addEvent 'click', -> root.captchaBox.close()
- $('cap_form').addEvent 'submit', (e) ->
- submit_captcha()
- e.stop()
-
- $('cap_positional').addEvent 'click', on_captcha_click
-
- new Request.JSON({
- url: "/json/status"
- onSuccess: LoadJsonToContent
- secure: false
- async: true
- initialDelay: 0
- delay: 4000
- limit: 3000
- }).startTimer()
-
-LoadJsonToContent = (data) ->
- $("speed").set 'text', humanFileSize(data.speed)+"/s"
- $("aktiv").set 'text', data.active
- $("aktiv_from").set 'text', data.queue
- $("aktiv_total").set 'text', data.total
-
- if data.captcha
- if $("cap_info").getStyle("display") != "inline"
- $("cap_info").setStyle 'display', 'inline'
- root.notify.alert '{{_("New Captcha Request")}}', {
- 'className': 'notify'
- }
- else
- $("cap_info").setStyle 'display', 'none'
-
-
- if data.download
- $("time").set 'text', ' {{_("on")}}'
- $("time").setStyle 'background-color', "#8ffc25"
- else
- $("time").set 'text', ' {{_("off")}}'
- $("time").setStyle 'background-color', "#fc6e26"
-
- if data.reconnect
- $("reconnect").set 'text', ' {{_("on")}}'
- $("reconnect").setStyle 'background-color', "#8ffc25"
- else
- $("reconnect").set 'text', ' {{_("off")}}'
- $("reconnect").setStyle 'background-color', "#fc6e26"
-
- return null
-
-
-set_captcha = (data) ->
- $('cap_id').set 'value', data.id
- if (data.result_type is 'textual')
- $('cap_textual_img').set 'src', data.src
- $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
- $('cap_submit').setStyle 'display', 'inline'
- $('cap_textual').setStyle 'display', 'block'
- $('cap_positional').setStyle 'display', 'none'
-
- else if (data.result_type == 'positional')
- $('cap_positional_img').set('src', data.src)
- $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
- $('cap_submit').setStyle('display', 'none')
- $('cap_textual').setStyle('display', 'none')
-
-
-load_captcha = (method, post) ->
- new Request.JSON({
- url: "/json/set_captcha"
- onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
- secure: false
- async: true
- method: method
- }).send(post)
-
-clear_captcha = ->
- $('cap_textual').setStyle 'display', 'none'
- $('cap_textual_img').set 'src', ''
- $('cap_positional').setStyle 'display', 'none'
- $('cap_positional_img').set 'src', ''
- $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
-
-submit_captcha = ->
- load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') );
- $('cap_result').set('value', '')
- false
-
-on_captcha_click = (e) ->
- position = e.target.getPosition()
- x = e.page.x - position.x
- y = e.page.y - position.y
- $('cap_result').value = x + "," + y
- submit_captcha() \ No newline at end of file
diff --git a/module/web/media/js/base.js b/module/web/media/js/base.js
deleted file mode 100644
index c68b1047a..000000000
--- a/module/web/media/js/base.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/mootools-core-1.4.1.js b/module/web/media/js/mootools-core-1.4.1.js
deleted file mode 100644
index 835b4bbe2..000000000
--- a/module/web/media/js/mootools-core-1.4.1.js
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
----
-MooTools: the javascript framework
-
-web build:
- - http://mootools.net/core/76bf47062d6c1983d66ce47ad66aa0e0
-
-packager build:
- - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady Core/Swiff
-
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-(function(){this.MooTools={version:"1.4.1",build:"d1fb25710e3c5482a219ab9dc675a4e0ad2176b6"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family){return i.$family();
-}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if(i.callee){return"arguments";
-}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
-while(s){if(s===i){return true;}s=s.parent;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];
-}f.prototype.overloadSetter=function(s){var i=this;return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);
-}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;
-return function(u){var v,t;if(s||typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);
-}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;
-}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;};};Array.from=function(i){if(i==null){return[];
-}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);return isFinite(i)?i:null;
-};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;return this;
-}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
-}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
-};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
-u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
-});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
-}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,w,u){var t=(w!=Object),A=w.prototype;if(t){w=new a(s,w);
-}for(var x=0,v=u.length;x<v;x++){var B=u[x],z=w[B],y=A[B];if(z){z.protect();}if(t&&y){delete A[B];A[B]=y.protect();}}if(t){w.implement(A);}return d;};d("String",String,["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
-Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
-}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
-}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
-return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
-while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
-}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
-y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
-v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
-});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
-b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,e){var c=[];for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&d.call(e,this[b],b,this)){c.push(this[b]);
-}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);
-for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;
-}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);return this.map(function(c){return c[a].apply(c,b);
-});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];}return d;},link:function(c){var a={};for(var e=0,b=this.length;
-e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;},append:function(a){this.push.apply(this,a);
-return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
-},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
-},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
-for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
-}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
-}var a=this.map(function(c){if(c.length==1){c+=c;}return c.toInt(16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;}if(this.length==4&&this[3]==0&&!d){return"transparent";
-}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");}});String.implement({test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
-},contains:function(a,b){return(b)?(b+this+b).indexOf(b+a+b)>-1:String(this).indexOf(a)>-1;},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");
-},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
-});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
-});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
-},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
-return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
-}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
-return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
-}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
-};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
-b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
-},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
-g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
-if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
-},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
-for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
-}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
-}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
-}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
-}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
-}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
-break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var k=this.document;var i=k.window=this;
-var b=1;this.$uid=(i.ActiveXObject)?function(e){return(e.uid||(e.uid=[b++]))[0];}:function(e){return e.uid||(e.uid=b++);};$uid(i);$uid(k);var a=navigator.userAgent.toLowerCase(),c=navigator.platform.toLowerCase(),j=a.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0],f=j[1]=="ie"&&k.documentMode;
-var o=this.Browser={extend:Function.prototype.extend,name:(j[1]=="version")?j[3]:j[1],version:f||parseFloat((j[1]=="opera"&&j[4])?j[4]:j[2]),Platform:{name:a.match(/ip(?:ad|od|hone)/)?"ios":(a.match(/(?:webos|android)/)||c.match(/mac|win|linux/)||["other"])[0]},Features:{xpath:!!(k.evaluate),air:!!(i.runtime),query:!!(k.querySelector),json:!!(i.JSON)},Plugins:{}};
-o[o.name]=true;o[o.name+parseInt(o.version,10)]=true;o.Platform[o.Platform.name]=true;o.Request=(function(){var q=function(){return new XMLHttpRequest();
-};var p=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");};return Function.attempt(function(){q();
-return q;},function(){p();return p;},function(){e();return e;});})();o.Features.xhr=!!(o.Request);var h=(Function.attempt(function(){return navigator.plugins["Shockwave Flash"].description;
-},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");})||"0 r0").match(/\d+/g);o.Plugins.Flash={version:Number(h[0]||"0."+h[1])||0,build:Number(h[2])||0};
-o.exec=function(p){if(!p){return p;}if(i.execScript){i.execScript(p);}else{var e=k.createElement("script");e.setAttribute("type","text/javascript");e.text=p;
-k.head.appendChild(e);k.head.removeChild(e);}return p;};String.implement("stripScripts",function(p){var e="";var q=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(r,s){e+=s+"\n";
-return"";});if(p===true){o.exec(e);}else{if(typeOf(p)=="function"){p(e,q);}}return q;});o.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
-this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,p){i[e]=p;});this.Document=k.$constructor=new Type("Document",function(){});
-k.$family=Function.from("document").hide();Document.mirror(function(e,p){k[e]=p;});k.html=k.documentElement;if(!k.head){k.head=k.getElementsByTagName("head")[0];
-}if(k.execCommand){try{k.execCommand("BackgroundImageCache",false,true);}catch(g){}}if(this.attachEvent&&!this.addEventListener){var d=function(){this.detachEvent("onunload",d);
-k.head=k.html=k.window=null;};this.attachEvent("onunload",d);}var m=Array.from;try{m(k.html.childNodes);}catch(g){Array.from=function(p){if(typeof p!="string"&&Type.isEnumerable(p)&&typeOf(p)!="array"){var e=p.length,q=new Array(e);
-while(e--){q[e]=p[e];}return q;}return m(p);};var l=Array.prototype,n=l.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var p=l[e];
-Array[e]=function(q){return p.apply(Array.from(q),n.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
-}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
-var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
-this.key=b[d];if(i=="keydown"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
-}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
-this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
-if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
-while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
-this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
-this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
-},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
-}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
-})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
-}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
-g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
-}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
-};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
-break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
-}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
-return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
-}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
-return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
-for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
-return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
-return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
-this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
-},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
-}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
-},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
-}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
-if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
-(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
-var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
-return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
-}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
-o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
-};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
-function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
-if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
-}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
-}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
-}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
-break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
-case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
-};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
-};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
-k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
-};k.setDocument=function(x){var u=x.nodeType;if(u==9){}else{if(u){x=x.ownerDocument;}else{if(x.navigator){x=x.document;}else{return;}}}if(this.document===x){return;
-}this.document=x;var z=x.documentElement,v=this.getUIDXML(z),p=m[v],B;if(p){for(B in p){this[B]=p[B];}return;}p=m[v]={};p.root=z;p.isXMLDocument=this.isXML(x);
-p.brokenStarGEBTN=p.starSelectsClosedQSA=p.idGetsName=p.brokenMixedCaseQSA=p.brokenGEBCN=p.brokenCheckedQSA=p.brokenEmptyAttributeQSA=p.isHTMLDocument=p.nativeMatchesSelector=false;
-var n,o,y,r,s;var t,c="slick_uniqueid";var A=x.createElement("div");var q=x.body||x.getElementsByTagName("body")[0]||z;q.appendChild(A);try{A.innerHTML='<a id="'+c+'"></a>';
-p.isHTMLDocument=!!x.getElementById(c);}catch(w){}if(p.isHTMLDocument){A.style.display="none";A.appendChild(x.createComment(""));o=(A.getElementsByTagName("*").length>1);
-try{A.innerHTML="foo</foo>";t=A.getElementsByTagName("*");n=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");}catch(w){}p.brokenStarGEBTN=o||n;try{A.innerHTML='<a name="'+c+'"></a><b id="'+c+'"></b>';
-p.idGetsName=x.getElementById(c)===A.firstChild;}catch(w){}if(A.getElementsByClassName){try{A.innerHTML='<a class="f"></a><a class="b"></a>';A.getElementsByClassName("b").length;
-A.firstChild.className="b";r=(A.getElementsByClassName("b").length!=2);}catch(w){}try{A.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(A.getElementsByClassName("a").length!=2);
-}catch(w){}p.brokenGEBCN=r||y;}if(A.querySelectorAll){try{A.innerHTML="foo</foo>";t=A.querySelectorAll("*");p.starSelectsClosedQSA=(t&&!!t.length&&t[0].nodeName.charAt(0)=="/");
-}catch(w){}try{A.innerHTML='<a class="MiX"></a>';p.brokenMixedCaseQSA=!A.querySelectorAll(".MiX").length;}catch(w){}try{A.innerHTML='<select><option selected="selected">a</option></select>';
-p.brokenCheckedQSA=(A.querySelectorAll(":checked").length==0);}catch(w){}try{A.innerHTML='<a class=""></a>';p.brokenEmptyAttributeQSA=(A.querySelectorAll('[class*=""]').length!=0);
-}catch(w){}}try{A.innerHTML='<form action="s"><input id="action"/></form>';s=(A.firstChild.getAttribute("action")!="s");}catch(w){}p.nativeMatchesSelector=z.matchesSelector||z.mozMatchesSelector||z.webkitMatchesSelector;
-if(p.nativeMatchesSelector){try{p.nativeMatchesSelector.call(z,":slick");p.nativeMatchesSelector=null;}catch(w){}}}try{z.slick_expando=1;delete z.slick_expando;
-p.getUID=this.getUIDHTML;}catch(w){p.getUID=this.getUIDXML;}q.removeChild(A);A=t=q=null;p.getAttribute=(p.isHTMLDocument&&s)?function(E,C){var F=this.attributeGetters[C];
-if(F){return F.call(E);}var D=E.getAttributeNode(C);return(D)?D.nodeValue:null;}:function(D,C){var E=this.attributeGetters[C];return(E)?E.call(D):D.getAttribute(C);
-};p.hasAttribute=(z&&this.isNativeCode(z.hasAttribute))?function(D,C){return D.hasAttribute(C);}:function(D,C){D=D.getAttributeNode(C);return !!(D&&(D.specified||D.nodeValue));
-};p.contains=(z&&this.isNativeCode(z.contains))?function(C,D){return C.contains(D);}:(z&&z.compareDocumentPosition)?function(C,D){return C===D||!!(C.compareDocumentPosition(D)&16);
-}:function(C,D){if(D){do{if(D===C){return true;}}while((D=D.parentNode));}return false;};p.documentSorter=(z.compareDocumentPosition)?function(D,C){if(!D.compareDocumentPosition||!C.compareDocumentPosition){return 0;
-}return D.compareDocumentPosition(C)&4?-1:D===C?0:1;}:("sourceIndex" in z)?function(D,C){if(!D.sourceIndex||!C.sourceIndex){return 0;}return D.sourceIndex-C.sourceIndex;
-}:(x.createRange)?function(F,D){if(!F.ownerDocument||!D.ownerDocument){return 0;}var E=F.ownerDocument.createRange(),C=D.ownerDocument.createRange();E.setStart(F,0);
-E.setEnd(F,0);C.setStart(D,0);C.setEnd(D,0);return E.compareBoundaryPoints(Range.START_TO_END,C);}:null;z=null;for(B in p){this[B]=p[B];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};
-k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);
-if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);
-simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;}E=U.getElementsByTagName(v);if(s){return E[0]||null;
-}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;}A=U.getElementById(v);
-if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
-}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
-if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
-for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
-}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
-}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
-}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
-A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
-}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
-return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
-if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
-if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
-this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
-}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
-}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
-if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
-}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
-if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
-return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
-}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
-}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
-if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
-}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
-k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
-}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
-if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
-}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
-}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
-if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=o.getAttribute("class")||o.className;
-if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
-}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
-if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
-if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
-}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
-if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
-}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
-}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
-if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
-this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
-}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
-break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
-}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
-return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
-},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
-}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+c+1);
-},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
-while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
-}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
-}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
-},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"class":function(){return this.getAttribute("class")||this.className;
-},"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");
-},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;
-},type:function(){return this.getAttribute("type");},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;
-}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});e.version="1.1.6";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);
-};e.contains=function(c,n){k.setDocument(c);return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);
-return k.hasAttribute(n,c);};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
-return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
-};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
-return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
-var Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];
-b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;f<c;f++){a=d[f];if(g[a.key]!=null){continue;
-}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
-}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;
-}var b={};b[a]=function(){var h=[],e=arguments,j=true;for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");
-}return(j)?new Elements(h):h;};Elements.implement(b);});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$family":Function.from("element").hide()};
-Element.mirror(function(a,b){Element.Prototype[a]=b;});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
-}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
-b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
-for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
-}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
-b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
-c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
-for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
-b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var g=Array.prototype.splice,b={"0":0,"1":1,length:2};
-g.call(b,1,1);if(b[1]==1){Elements.implement("splice",function(){var h=this.length;var e=g.apply(this,arguments);while(h>=this.length){delete this[h--];
-}return e;}.protect());}Elements.implement(Array.prototype);Array.mirror(Elements);var f;try{var a=document.createElement("<input name=x>");f=(a.name=="x");
-}catch(c){}var d=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,h){if(h&&h.checked!=null){h.defaultChecked=h.checked;
-}if(f&&h){e="<"+e;if(h.name){e+=' name="'+d(h.name)+'"';}if(h.type){e+=' type="'+d(h.type)+'"';}e+=">";delete h.name;delete h.type;}return this.id(this.createElement(e)).set(h);
-}});})();Document.implement({newTextNode:function(a){return this.createTextNode(a);},getDocument:function(){return this;},getWindow:function(){return this.window;
-},id:(function(){var a={string:function(d,c,b){d=Slick.find(b,"#"+d.replace(/(\W)/g,"\\$1"));return(d)?a.element(d,c):null;},element:function(b,c){$uid(b);
-if(!c&&!b.$family&&!(/^(?:object|embed)$/i).test(b.tagName)){Object.append(b,Element.Prototype);}return b;},object:function(c,d,b){if(c.toElement){return a.element(c.toElement(b),d);
-}return null;}};a.textnode=a.whitespace=a.window=a.document=function(b){return b;};return function(c,e,d){if(c&&c.$family&&c.uid){return c;}var b=typeOf(c);
-return(a[b])?a[b](c,e,d||document):null;};})()});if(window.$==null){Window.implement("$",function(a,b){return document.id(a,b,this.document);});}Window.implement({getDocument:function(){return this.document;
-},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(a){return Slick.search(this,a,new Elements);},getElement:function(a){return document.id(Slick.find(this,a));
-}});var contains={contains:function(a){return Slick.contains(this,a);}};if(!document.contains){Document.implement(contains);}if(!document.createElement("div").contains){Element.implement(contains);
-}var injectCombinator=function(d,c){if(!d){return c;}d=Object.clone(Slick.parse(d));var b=d.expressions;for(var a=b.length;a--;){b[a][0].combinator=c;}return d;
-};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(a,b){Element.implement(b,function(c){return this.getElement(injectCombinator(c,a));
-});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(a,b){Element.implement(b,function(c){return this.getElements(injectCombinator(c,a));
-});});Element.implement({getFirst:function(a){return document.id(Slick.search(this,injectCombinator(a,">"))[0]);},getLast:function(a){return document.id(Slick.search(this,injectCombinator(a,">")).getLast());
-},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(a){return document.id(Slick.find(this,"#"+(""+a).replace(/(\W)/g,"\\$1")));
-},match:function(a){return !a||Slick.match(this,a);}});if(window.$$==null){Window.implement("$$",function(a){if(arguments.length==1){if(typeof a=="string"){return Slick.search(this.document,a,new Elements);
-}else{if(Type.isEnumerable(a)){return new Elements(a);}}}return new Elements(arguments);});}(function(){var b={before:function(n,m){var o=m.parentNode;
-if(o){o.insertBefore(n,m);}},after:function(n,m){var o=m.parentNode;if(o){o.insertBefore(n,m.nextSibling);}},bottom:function(n,m){m.appendChild(n);},top:function(n,m){m.insertBefore(n,m.firstChild);
-}};b.inside=b.bottom;var k={},d={};var i={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","readOnly","rowSpan","tabIndex","useMap"],function(m){i[m.toLowerCase()]=m;
-});Object.append(i,{html:"innerHTML",text:(function(){var m=document.createElement("div");return(m.textContent==null)?"innerText":"textContent";})()});
-Object.forEach(i,function(n,m){d[m]=function(o,p){o[n]=p;};k[m]=function(o){return o[n];};});var a=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
-var h={};Array.forEach(a,function(m){var n=m.toLowerCase();h[n]=m;d[n]=function(o,p){o[m]=!!p;};k[n]=function(o){return !!o[m];};});Object.append(d,{"class":function(m,n){("className" in m)?m.className=n:m.setAttribute("class",n);
-},"for":function(m,n){("htmlFor" in m)?m.htmlFor=n:m.setAttribute("for",n);},style:function(m,n){(m.style)?m.style.cssText=n:m.setAttribute("style",n);
-}});Element.implement({setProperty:function(n,o){var m=n.toLowerCase();if(o==null){if(!h[m]){this.removeAttribute(n);return this;}o=false;}var p=d[m];if(p){p(this,o);
-}else{this.setAttribute(n,o);}return this;},setProperties:function(m){for(var n in m){this.setProperty(n,m[n]);}return this;},getProperty:function(o){var n=k[o.toLowerCase()];
-if(n){return n(this);}var m=Slick.getAttribute(this,o);return(!m&&!Slick.hasAttribute(this,o))?null:m;},getProperties:function(){var m=Array.from(arguments);
-return m.map(this.getProperty,this).associate(m);},removeProperty:function(m){return this.setProperty(m,null);},removeProperties:function(){Array.each(arguments,this.removeProperty,this);
-return this;},set:function(o,n){var m=Element.Properties[o];(m&&m.set)?m.set.call(this,n):this.setProperty(o,n);}.overloadSetter(),get:function(n){var m=Element.Properties[n];
-return(m&&m.get)?m.get.apply(this):this.getProperty(n);}.overloadGetter(),erase:function(n){var m=Element.Properties[n];(m&&m.erase)?m.erase.apply(this):this.removeProperty(n);
-return this;},hasClass:function(m){return this.className.clean().contains(m," ");},addClass:function(m){if(!this.hasClass(m)){this.className=(this.className+" "+m).clean();
-}return this;},removeClass:function(m){this.className=this.className.replace(new RegExp("(^|\\s)"+m+"(?:\\s|$)"),"$1");return this;},toggleClass:function(m,n){if(n==null){n=!this.hasClass(m);
-}return(n)?this.addClass(m):this.removeClass(m);},adopt:function(){var p=this,m,r=Array.flatten(arguments),q=r.length;if(q>1){p=m=document.createDocumentFragment();
-}for(var o=0;o<q;o++){var n=document.id(r[o],true);if(n){p.appendChild(n);}}if(m){this.appendChild(m);}return this;},appendText:function(n,m){return this.grab(this.getDocument().newTextNode(n),m);
-},grab:function(n,m){b[m||"bottom"](document.id(n,true),this);return this;},inject:function(n,m){b[m||"bottom"](this,document.id(n,true));return this;},replaces:function(m){m=document.id(m,true);
-m.parentNode.replaceChild(this,m);return this;},wraps:function(n,m){n=document.id(n,true);return this.replaces(n).grab(n,m);},getSelected:function(){this.selectedIndex;
-return new Elements(Array.from(this.options).filter(function(m){return m.selected;}));},toQueryString:function(){var m=[];this.getElements("input, select, textarea").each(function(o){var n=o.type;
-if(!o.name||o.disabled||n=="submit"||n=="reset"||n=="file"||n=="image"){return;}var p=(o.get("tag")=="select")?o.getSelected().map(function(q){return document.id(q).get("value");
-}):((n=="radio"||n=="checkbox")&&!o.checked)?null:o.get("value");Array.from(p).each(function(q){if(typeof q!="undefined"){m.push(encodeURIComponent(o.name)+"="+encodeURIComponent(q));
-}});});return m.join("&");}});var j={},e={};var c=function(m){return(e[m]||(e[m]={}));};var g=function(n){var m=n.uid;if(n.removeEvents){n.removeEvents();
-}if(n.clearAttributes){n.clearAttributes();}if(m!=null){delete j[m];delete e[m];}return n;};var l={input:"checked",option:"selected",textarea:"value"};
-Element.implement({destroy:function(){var m=g(this).getElementsByTagName("*");Array.each(m,g);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
-return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(r,p){r=r!==false;var w=this.cloneNode(r),o=[w],q=[this],u;
-if(r){o.append(Array.from(w.getElementsByTagName("*")));q.append(Array.from(this.getElementsByTagName("*")));}for(u=o.length;u--;){var s=o[u],v=q[u];if(!p){s.removeAttribute("id");
-}if(s.clearAttributes){s.clearAttributes();s.mergeAttributes(v);s.removeAttribute("uid");if(s.options){var z=s.options,m=v.options;for(var t=z.length;t--;
-){z[t].selected=m[t].selected;}}}var n=l[v.tagName.toLowerCase()];if(n&&v[n]){s[n]=v[n];}}if(Browser.ie){var x=w.getElementsByTagName("object"),y=this.getElementsByTagName("object");
-for(u=x.length;u--;){x[u].outerHTML=y[u].outerHTML;}}return document.id(w);}});[Element,Window,Document].invoke("implement",{addListener:function(p,o){if(p=="unload"){var m=o,n=this;
-o=function(){n.removeListener("unload",o);m();};}else{j[$uid(this)]=this;}if(this.addEventListener){this.addEventListener(p,o,!!arguments[2]);}else{this.attachEvent("on"+p,o);
-}return this;},removeListener:function(n,m){if(this.removeEventListener){this.removeEventListener(n,m,!!arguments[2]);}else{this.detachEvent("on"+n,m);
-}return this;},retrieve:function(n,m){var p=c($uid(this)),o=p[n];if(m!=null&&o==null){o=p[n]=m;}return o!=null?o:null;},store:function(n,m){var o=c($uid(this));
-o[n]=m;return this;},eliminate:function(m){var n=c($uid(this));delete n[m];return this;}});if(window.attachEvent&&!window.addEventListener){window.addListener("unload",function(){Object.each(j,g);
-if(window.CollectGarbage){CollectGarbage();}});}Element.Properties={};Element.Properties.style={set:function(m){this.style.cssText=m;},get:function(){return this.style.cssText;
-},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();}};Element.Properties.html=(function(){var s=Function.attempt(function(){var u=document.createElement("table");
-u.innerHTML="<tr><td></td></tr>";});var t=document.createElement("div");var o={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
-o.thead=o.tfoot=o.tbody;t.innerHTML="<nav></nav>";var n=t.childNodes.length==1;if(!n){var q="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),p=document.createDocumentFragment(),m=q.length;
-while(m--){p.createElement(q[m]);}p.appendChild(t);}var r={set:function(v){if(typeOf(v)=="array"){v=v.join("");}var w=(!s&&o[this.get("tag")]);if(!w&&!n){w=[0,"",""];
-}if(w){var x=t;x.innerHTML=w[1]+v+w[2];for(var u=w[0];u--;){x=x.firstChild;}this.empty().adopt(x.childNodes);}else{this.innerHTML=v;}}};r.erase=r.set;return r;
-})();var f=document.createElement("form");f.innerHTML="<select><option>s</option></select>";if(f.firstChild.value!="s"){Element.Properties.value={set:function(r){var n=this.get("tag");
-if(n!="select"){return this.setProperty("value",r);}var o=this.getElements("option");for(var p=0;p<o.length;p++){var q=o[p],m=q.getAttributeNode("value"),s=(m&&m.specified)?q.value:q.get("text");
-if(s==r){return q.selected=true;}}},get:function(){var o=this,n=o.get("tag");if(n!="select"&&n!="option"){return this.getProperty("value");}if(n=="select"&&!(o=o.getSelected()[0])){return"";
-}var m=o.getAttributeNode("value");return(m&&m.specified)?o.value:o.get("text");}};}})();(function(){var f=document.html;Element.Properties.styles={set:function(i){this.setStyles(i);
-}};var h=(f.style.opacity!=null),a=(f.style.filter!=null),g=/alpha\(opacity=([\d.]+)\)/i;var b=function(j,i){j.store("$opacity",i);j.style.visibility=i>0?"visible":"hidden";
-};var d=(h?function(j,i){j.style.opacity=i;}:(a?function(j,i){if(!j.currentStyle||!j.currentStyle.hasLayout){j.style.zoom=1;}i=(i*100).limit(0,100).round();
-i=(i==100)?"":"alpha(opacity="+i+")";var k=j.style.filter||j.getComputedStyle("filter")||"";j.style.filter=g.test(k)?k.replace(g,i):k+i;}:b));var e=(h?function(j){var i=j.style.opacity||j.getComputedStyle("opacity");
-return(i=="")?1:i.toFloat();}:(a?function(j){var k=(j.style.filter||j.getComputedStyle("filter")),i;if(k){i=k.match(g);}return(i==null||k==null)?1:(i[1]/100);
-}:function(j){var i=j.retrieve("$opacity");if(i==null){i=(j.style.visibility=="hidden"?0:1);}return i;}));var c=(f.style.cssFloat==null)?"styleFloat":"cssFloat";
-Element.implement({getComputedStyle:function(k){if(this.currentStyle){return this.currentStyle[k.camelCase()];}var j=Element.getDocument(this).defaultView,i=j?j.getComputedStyle(this,null):null;
-return(i)?i.getPropertyValue((k==c)?"float":k.hyphenate()):null;},setStyle:function(j,i){if(j=="opacity"){d(this,parseFloat(i));return this;}j=(j=="float"?c:j).camelCase();
-if(typeOf(i)!="string"){var k=(Element.Styles[j]||"@").split(" ");i=Array.from(i).map(function(m,l){if(!k[l]){return"";}return(typeOf(m)=="number")?k[l].replace("@",Math.round(m)):m;
-}).join(" ");}else{if(i==String(Number(i))){i=Math.round(i);}}this.style[j]=i;return this;},getStyle:function(o){if(o=="opacity"){return e(this);}o=(o=="float"?c:o).camelCase();
-var i=this.style[o];if(!i||o=="zIndex"){i=[];for(var n in Element.ShortStyles){if(o!=n){continue;}for(var m in Element.ShortStyles[n]){i.push(this.getStyle(m));
-}return i.join(" ");}i=this.getComputedStyle(o);}if(i){i=String(i);var k=i.match(/rgba?\([\d\s,]+\)/);if(k){i=i.replace(k[0],k[0].rgbToHex());}}if(Browser.opera||(Browser.ie&&isNaN(parseFloat(i)))){if((/^(height|width)$/).test(o)){var j=(o=="width")?["left","right"]:["top","bottom"],l=0;
-j.each(function(p){l+=this.getStyle("border-"+p+"-width").toInt()+this.getStyle("padding-"+p).toInt();},this);return this["offset"+o.capitalize()]-l+"px";
-}if(Browser.opera&&String(i).indexOf("px")!=-1){return i;}if((/^border(.+)Width|margin|padding/).test(o)){return"0px";}}return i;},setStyles:function(j){for(var i in j){this.setStyle(i,j[i]);
-}return this;},getStyles:function(){var i={};Array.flatten(arguments).each(function(j){i[j]=this.getStyle(j);},this);return i;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
-Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(o){var n=Element.ShortStyles;
-var j=Element.Styles;["margin","padding"].each(function(p){var q=p+o;n[p][q]=j[q]="@px";});var m="border"+o;n.border[m]=j[m]="@px @ rgb(@, @, @)";var l=m+"Width",i=m+"Style",k=m+"Color";
-n[m]={};n.borderWidth[l]=n[m][l]=j[l]="@px";n.borderStyle[i]=n[m][i]=j[i]="@";n.borderColor[k]=n[m][k]=j[k]="rgb(@, @, @)";});})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);
-}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;
-}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);
-}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());
-if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");
-if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];
-if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;
-},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);
-}return this;}var c=this.retrieve("events");if(!c){return this;}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);
-},this);delete c[b];}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
-}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
-}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};
-var a=function(b){var c=b.relatedTarget;if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));
-};Element.Events={mouseenter:{base:"mouseover",condition:a},mouseleave:{base:"mouseout",condition:a},mousewheel:{base:(Browser.firefox)?"DOMMouseScroll":"mousewheel"}};
-if(!window.addEventListener){Element.NativeEvents.propertychange=2;Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";
-},condition:function(b){return !!(this.type!="radio"||this.checked);}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;
-var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover"},mouseleave:{base:"mouseout"},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
-var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
-n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
-if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
-}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
-}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
-}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
-});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
-}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(w&&w.condition){var l=q,m=w.condition;q=function(C,B){return l(C,B)&&m.call(C,B,v);
-};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);}}:function(B,C){if(!C&&B&&B.target){C=B.target;
-}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];
-if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);}delete p[u];q[m]=p;return j.call(this,r,w);}var o,v;
-if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);
-}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});})();(function(){var h=document.createElement("div"),e=document.createElement("div");
-h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);
-};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();
-}return{x:this.offsetWidth,y:this.offsetHeight};},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};
-},getScroll:function(){if(a(this)){return this.getWindow().getScroll();}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};
-while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}var n=(k(m,"position")=="static")?i:l;while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;
-}try{return m.offsetParent;}catch(n){}return null;},getOffsets:function(){if(this.getBoundingClientRect&&!Browser.Platform.ios){var r=this.getBoundingClientRect(),o=document.id(this.getDocument().documentElement),q=o.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
-return{x:r.left.toInt()+t.x+((s)?0:q.x)-o.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-o.clientTop};}var n=this,m={x:0,y:0};if(a(this)){return m;}while(n&&!a(n)){m.x+=n.offsetLeft;
-m.y+=n.offsetTop;if(Browser.firefox){if(!c(n)){m.x+=b(n);m.y+=g(n);}var p=n.parentNode;if(p&&k(p,"overflow")!="visible"){m.x+=b(p);m.y+=g(p);}}else{if(n!=this&&Browser.safari){m.x+=b(n);
-m.y+=g(n);}}n=n.offsetParent;}if(Browser.firefox&&!c(this)){m.x-=b(this);m.y-=g(this);}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();
-var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();
-}var m=this.getPosition(o),n=this.getSize();var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
-},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
-},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
-return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
-return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
-}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
-}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
-},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
-},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
-},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
-this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
-this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
-}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
-},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
-return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
-this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
-this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
-d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
-}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
-this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
-},resume:function(){if((this.frame<this.frames)&&!this.isRunning()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];
-return g&&g.contains(this);}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};var e={},c={};var a=function(){var h=Date.now();
-for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);
-}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(c,d,b){b=Array.from(b);
-if(b[1]==null){b[1]=b[0];b[0]=c.getStyle(d);}var a=b.map(this.parse);return{from:a[0],to:a[1]};},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);
-return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};
-}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
-});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
-});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var c={},b=new RegExp("^"+a.escapeRegExp()+"$");
-Array.each(document.styleSheets,function(f,e){var d=f.href;if(d&&d.contains("://")&&!d.contains(document.domain)){return;}var g=f.rules||f.cssRules;Array.each(g,function(k,h){if(!k.style){return;
-}var j=(k.selectorText)?k.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();}):null;if(!j||!b.test(j)){return;}Object.each(Element.Styles,function(l,i){if(!k.style[i]||Element.ShortStyles[i]){return;
-}l=String(k.style[i]);c[i]=((/^rgb/).test(l))?l.rgbToHex():l;});});});return Fx.CSS.Cache[a]=c;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
-}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
-});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
-},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
-b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
-}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
-}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
-this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(c){var d=this.get("tween"),f,e,a;
-if(c==null){c="toggle";}switch(c){case"in":f="start";e=1;break;case"out":f="start";e=0;break;case"show":f="set";e=1;break;case"hide":f="set";e=0;break;
-case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);f="start";e=b?0:1;this.store("fade:flag",!b);a=true;break;default:f="start";e=c;
-}if(!a){this.eliminate("fade:flag");}d[f]("opacity",e);if(f=="set"||e!=0){this.setStyle("visibility",e==0?"hidden":"visible");}else{d.chain(function(){this.element.setStyle("visibility","hidden");
-});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
-}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
-b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
-},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
-for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
-for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
-return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
-return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
-a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
-var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
-}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
-},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
-return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
-},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
-});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
-this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
-this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
-}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
-}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
-}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
-},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
-},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
-return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
-}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
-}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
-o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
-break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
-j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
-}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.contains("?")?"&":"?")+String.uniqueID();
-}if(j&&e=="get"){f+=(f.contains("?")?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
-}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
-}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
-}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
-}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
-if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
-if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
-return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
-this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
-Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
-c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
-c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
-}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
-}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
-},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
-}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
-}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
-};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
-return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
-}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
-Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
-case"null":return"null";}return null;};JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure||JSON.secure){if(JSON.parse){return JSON.parse(string);
-}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");}}return eval("("+string+")");
-};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
-},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
-}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
-this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
-}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
-}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
-return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
-Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
-};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
-k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
-if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
-c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
-}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
-}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document);(function(){var Swiff=this.Swiff=new Class({Implements:Options,options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"window",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object;
-},initialize:function(path,options){this.instance="Swiff_"+String.uniqueID();this.setOptions(options);options=this.options;var id=this.id=options.id||this.instance;
-var container=document.id(options.container);Swiff.CallBacks[this.instance]={};var params=options.params,vars=options.vars,callBacks=options.callBacks;
-var properties=Object.append({height:options.height,width:options.width},options.properties);var self=this;for(var callBack in callBacks){Swiff.CallBacks[this.instance][callBack]=(function(option){return function(){return option.apply(self.object,arguments);
-};})(callBacks[callBack]);vars[callBack]="Swiff.CallBacks."+this.instance+"."+callBack;}params.flashVars=Object.toQueryString(vars);if(Browser.ie){properties.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
-params.movie=path;}else{properties.type="application/x-shockwave-flash";}properties.data=path;var build='<object id="'+id+'"';for(var property in properties){build+=" "+property+'="'+properties[property]+'"';
-}build+=">";for(var param in params){if(params[param]){build+='<param name="'+param+'" value="'+params[param]+'" />';}}build+="</object>";this.object=((container)?container.empty():new Element("div")).set("html",build).firstChild;
-},replaces:function(element){element=document.id(element,true);element.parentNode.replaceChild(this.toElement(),element);return this;},inject:function(element){document.id(element,true).appendChild(this.toElement());
-return this;},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].append(arguments));}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");
-return eval(rs);};})(); \ No newline at end of file
diff --git a/module/web/media/js/mootools-more-1.4.0.1.js b/module/web/media/js/mootools-more-1.4.0.1.js
deleted file mode 100644
index f3f8e4ee1..000000000
--- a/module/web/media/js/mootools-more-1.4.0.1.js
+++ /dev/null
@@ -1,216 +0,0 @@
-// MooTools: the javascript framework.
-// Load this file's selection again by visiting: http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
-// Or build this file again with packager using: packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
-/*
----
-copyrights:
- - [MooTools](http://mootools.net)
-
-licenses:
- - [MIT License](http://mootools.net/license.txt)
-...
-*/
-MooTools.More={version:"1.4.0.1",build:"a4244edf2aa97ac8a196fc96082dd35af1abab87"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
-}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
-if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
-if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
-c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
-return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
-});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
-};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
-g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
-}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
-},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
-};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
-},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
-if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
-},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
-e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
-a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
-},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
-},setOffsetOption:function(f,d){var c={x:0,y:0},g=f.measure(function(){return document.id(this.getOffsetParent());}),e=g.getScroll();if(!g||g==f.getDocument().body){return;
-}c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};
-},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});},getPosition:function(e,d){var c={};
-d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();
-c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);
-}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;
-return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
-switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
-break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
-["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
-},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
-},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
-c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
-},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
-break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
-c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
-}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
-return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);var IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:(Browser.ie6||(Browser.firefox&&Browser.version<3&&Browser.Platform.mac))},property:"IframeShim",initialize:function(b,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var c=this.element.getStyle("zIndex").toInt();
-if(!c){c=1;var b=this.element.getStyle("position");if(b=="static"||!b){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",c);
-}c=((this.options.zIndex!=null||this.options.zIndex===0)&&c>this.options.zIndex)?this.options.zIndex:c-1;if(c<0){c=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:c,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
-var a=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",a);
-}else{a();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
-}var a=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){a.x=a.x-(this.options.margin*2);a.y=a.y-(this.options.margin*2);
-this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:a.x,height:a.y}).position({relativeTo:this.element,offset:this.options.offset});
-return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
-}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
-}});window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
-this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
-if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||this.target==document.body?"inside":"after";
-b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
-this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
-this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
-},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
-var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
-return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
-return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
-}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
-this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
-},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
-}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
-this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
-},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
-this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
-this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
-}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
-}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
-b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
-return this;}this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
-this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
-}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
-if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
-if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
-},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
-},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
-}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
-}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
-if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
-}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
-return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
-}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
-}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
-while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
-if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
-if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
-}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
-}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
-},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
-e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
-}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
-["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
-while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
-})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
-if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
-}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
-});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
-},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
-}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
-var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
-},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
-if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
-b.addEvent("onFormValidate",this.onFormValidate);this.element.validate();return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
-if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
-this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
-if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
-this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
-}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
-},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
-},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
-},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
-},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
-if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
-if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
-};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:!Browser.ie6,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
-},opacity:1,hideInputs:Browser.ie?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
-this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
-var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
-this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
-}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
-}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
-this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
-}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
-}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
-Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
-this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
-}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
-}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
-}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
-if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
-return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
-Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
-return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
-}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
-b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
-}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
-this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.ie)?"selectstart":"mousedown";if(Browser.ie&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
-Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
-this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
-return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
-}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
-}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
-}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
-if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
-}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
-}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
-this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
-}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
-}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
-}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
-}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
-if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
-a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
-this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
-b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
-}if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));
-}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();
-}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
-["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
-g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
-if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
-n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
-l+=((Browser.ie6||Browser.ie7)?0:k.top)+m.top;}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};},getDroppableCoordinates:function(c){var b=c.getCoordinates();
-if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
-var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
-}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
-}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
-this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{}},initialize:function(a,b){this.setOptions(b);
-this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
-}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
-var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
-},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
-},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
-return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
-},this));},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);
-}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
-});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
-}});}return c.inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
-if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
-this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
-},start:function(b,a){if(!this.idle||b.rightClick||["button","input","a","textarea"].contains(b.target.get("tag"))){return;}this.idle=false;this.element=a;
-this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
-this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
-}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
-},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);if(this.effect){var b=this.element.getStyles("width","height"),d=this.clone,c=d.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
-var a=function(){this.removeEvent("cancel",a);d.destroy();};this.effect.element=d;this.effect.start({top:c.top,left:c.left,width:b.width,height:b.height,opacity:0.25}).addEvent("cancel",a).chain(a);
-}else{this.clone.destroy();}this.reset();},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
-}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
-}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
-}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
-this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
-}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
-}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
-break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
-var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
-}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
-},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
-}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
-};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
-if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
-},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
-var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
-case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
-return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/module/web/media/js/package_ui.js b/module/web/media/js/package_ui.js
deleted file mode 100644
index 3ea965649..000000000
--- a/module/web/media/js/package_ui.js
+++ /dev/null
@@ -1,377 +0,0 @@
-var root = this;
-
-document.addEvent("domready", function() {
- root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
- root.load.set("opacity", 0);
-
-
- root.packageBox = new MooDialog({destroyOnHide: false});
- root.packageBox.setContent($('pack_box'));
-
- $('pack_reset').addEvent('click', function() {
- $('pack_form').reset();
- root.packageBox.close();
- });
-});
-
-function indicateLoad() {
- //$("load-indicator").reveal();
- root.load.start("opacity", 1)
-}
-
-function indicateFinish() {
- root.load.start("opacity", 0)
-}
-
-function indicateSuccess() {
- indicateFinish();
- root.notify.alert('{{_("Success")}}.', {
- 'className': 'success'
- });
-}
-
-function indicateFail() {
- indicateFinish();
- root.notify.alert('{{_("Failed")}}.', {
- 'className': 'error'
- });
-}
-
-var PackageUI = new Class({
- initialize: function(url, type) {
- this.url = url;
- this.type = type;
- this.packages = [];
- this.parsePackages();
-
- this.sorts = new Sortables($("package-list"), {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".package_drag",
- onComplete: this.saveSort.bind(this)
- });
-
- $("del_finished").addEvent("click", this.deleteFinished.bind(this));
- $("restart_failed").addEvent("click", this.restartFailed.bind(this));
-
- },
-
- parsePackages: function() {
- $("package-list").getChildren("li").each(function(ele) {
- var id = ele.getFirst().get("id").match(/[0-9]+/);
- this.packages.push(new Package(this, id, ele))
- }.bind(this))
- },
-
- loadPackages: function() {
- },
-
- deleteFinished: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/deleteFinished',
- onSuccess: function(data) {
- if (data.length > 0) {
- window.location.reload()
- } else {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- restartFailed: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/api/restartFailed',
- onSuccess: function(data) {
- this.packages.each(function(pack) {
- pack.close();
- });
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- startSort: function(ele, copy) {
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("pid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
-var Package = new Class({
- initialize: function(ui, id, ele, data) {
- this.ui = ui;
- this.id = id;
- this.linksLoaded = false;
-
- if (!ele) {
- this.createElement(data);
- } else {
- this.ele = ele;
- this.order = ele.getElements("div.order")[0].get("html");
- this.ele.store("order", this.order);
- this.ele.store("pid", this.id);
- this.parseElement();
- }
-
- var pname = this.ele.getElements(".packagename")[0];
- this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
- this.buttons.set("opacity", 0);
-
- pname.addEvent("mouseenter", function(e) {
- this.buttons.start("opacity", 1)
- }.bind(this));
-
- pname.addEvent("mouseleave", function(e) {
- this.buttons.start("opacity", 0)
- }.bind(this));
-
-
- },
-
- createElement: function() {
- alert("create")
- },
-
- parseElement: function() {
- var imgs = this.ele.getElements('img');
-
- this.name = this.ele.getElements('.name')[0];
- this.folder = this.ele.getElements('.folder')[0];
- this.password = this.ele.getElements('.password')[0];
-
- imgs[1].addEvent('click', this.deletePackage.bind(this));
- imgs[2].addEvent('click', this.restartPackage.bind(this));
- imgs[3].addEvent('click', this.editPackage.bind(this));
- imgs[4].addEvent('click', this.movePackage.bind(this));
-
- this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
-
- },
-
- loadLinks: function() {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/package/' + this.id,
- onSuccess: this.createLinks.bind(this),
- onFailure: indicateFail
- }).send();
- },
-
- createLinks: function(data) {
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.set("html", "");
- data.links.each(function(link) {
- link.id = link.fid;
- var li = new Element("li", {
- "style": {
- "margin-left": 0
- }
- });
-
- var html = "<span style='cursor: move' class='child_status sorthandle'><img src='/media/default/img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({"icon": link.icon});
- html += "<span style='font-size: 15px'>{name}</span><br /><div class='child_secrow'>".substitute({"name": link.name});
- html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({"statusmsg": link.statusmsg, "error":link.error});
- html += "<span class='child_status'>{format_size}</span>".substitute({"format_size": link.format_size});
- html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({"plugin": link.plugin});
- html += "<img title='{{_("Delete Link")}}' style='cursor: pointer;' width='10px' height='10px' src='/media/default/img/delete.png' />&nbsp;&nbsp;";
- html += "<img title='{{_("Restart Link")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='/media/default/img/arrow_refresh.png' /></div>";
-
- var div = new Element("div", {
- "id": "file_" + link.id,
- "class": "child",
- "html": html
- });
-
- li.store("order", link.order);
- li.store("lid", link.id);
-
- li.adopt(div);
- ul.adopt(li);
- });
- this.sorts = new Sortables(ul, {
- constrain: false,
- clone: true,
- revert: true,
- opacity: 0.4,
- handle: ".sorthandle",
- onComplete: this.saveSort.bind(this)
- });
- this.registerLinkEvents();
- this.linksLoaded = true;
- indicateFinish();
- this.toggle();
- },
-
- registerLinkEvents: function() {
- this.ele.getElements('.child').each(function(child) {
- var lid = child.get('id').match(/[0-9]+/);
- var imgs = child.getElements('.child_secrow img');
- imgs[0].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/deleteFiles/[' + this + "]",
- onSuccess: function() {
- $('file_' + this).nix()
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
-
- imgs[1].addEvent('click', function(e) {
- new Request({
- method: 'get',
- url: '/api/restartFile/' + this,
- onSuccess: function() {
- var ele = $('file_' + this);
- var imgs = ele.getElements("img");
- imgs[0].set("src", "/media/default/img/status_queue.png");
- var spans = ele.getElements(".child_status");
- spans[1].set("html", "queued");
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- }.bind(lid));
- });
- },
-
- toggle: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- } else {
- if (!this.linksLoaded) {
- this.loadLinks();
- } else {
- child.reveal();
- }
- }
- },
-
-
- deletePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/deletePackages/[' + this.id + "]",
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- //hide_pack();
- event.stop();
- },
-
- restartPackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/api/restartPackage/' + this.id,
- onSuccess: function() {
- this.close();
- indicateSuccess();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- close: function() {
- var child = this.ele.getElement('.children');
- if (child.getStyle('display') == "block") {
- child.dissolve();
- }
- var ul = $("sort_children_{id}".substitute({"id": this.id}));
- ul.erase("html");
- this.linksLoaded = false;
- },
-
- movePackage: function(event) {
- indicateLoad();
- new Request({
- method: 'get',
- url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
- onSuccess: function() {
- this.ele.nix();
- indicateFinish();
- }.bind(this),
- onFailure: indicateFail
- }).send();
- event.stop();
- },
-
- editPackage: function(event) {
- $("pack_form").removeEvents("submit");
- $("pack_form").addEvent("submit", this.savePackage.bind(this));
-
- $("pack_id").set("value", this.id);
- $("pack_name").set("value", this.name.get("text"));
- $("pack_folder").set("value", this.folder.get("text"));
- $("pack_pws").set("value", this.password.get("text"));
-
- root.packageBox.open();
- event.stop();
- },
-
- savePackage: function(event) {
- $("pack_form").send();
- this.name.set("text", $("pack_name").get("value"));
- this.folder.set("text", $("pack_folder").get("value"));
- this.password.set("text", $("pack_pws").get("value"));
- root.packageBox.close();
- event.stop();
- },
-
- saveSort: function(ele, copy) {
- var order = [];
- this.sorts.serialize(function(li, pos) {
- if (li == ele && ele.retrieve("order") != pos) {
- order.push(ele.retrieve("lid") + "|" + pos)
- }
- li.store("order", pos)
- });
- if (order.length > 0) {
- indicateLoad();
- new Request.JSON({
- method: 'get',
- url: '/json/link_order/' + order[0],
- onSuccess: indicateFinish,
- onFailure: indicateFail
- }).send();
- }
- }
-
-});
-
diff --git a/module/web/media/js/purr_static.js b/module/web/media/js/purr_static.js
deleted file mode 100644
index 7e0aee949..000000000
--- a/module/web/media/js/purr_static.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
----
-script: purr.js
-
-description: Class to create growl-style popup notifications.
-
-license: MIT-style
-
-authors: [atom smith]
-
-requires:
-- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
-
-provides: [Purr, Element.alert]
-...
-*/
-
-
-var Purr = new Class({
-
- 'options': {
- 'mode': 'top',
- 'position': 'left',
- 'elementAlertClass': 'purr-element-alert',
- 'elements': {
- 'wrapper': 'div',
- 'alert': 'div',
- 'buttonWrapper': 'div',
- 'button': 'button'
- },
- 'elementOptions': {
- 'wrapper': {
- 'styles': {
- 'position': 'fixed',
- 'z-index': '9999'
- },
- 'class': 'purr-wrapper'
- },
- 'alert': {
- 'class': 'purr-alert',
- 'styles': {
- 'opacity': '.85'
- }
- },
- 'buttonWrapper': {
- 'class': 'purr-button-wrapper'
- },
- 'button': {
- 'class': 'purr-button'
- }
- },
- 'alert': {
- 'buttons': [],
- 'clickDismiss': true,
- 'hoverWait': true,
- 'hideAfter': 5000,
- 'fx': {
- 'duration': 500
- },
- 'highlightRepeat': false,
- 'highlight': { // false to disable highlighting
- 'start': '#FF0',
- 'end': false
- }
- }
- },
-
- 'Implements': [Options, Events, Chain],
-
- 'initialize': function(options){
- this.setOptions(options);
- this.createWrapper();
- return this;
- },
-
- 'bindAlert': function(){
- return this.alert.bind(this);
- },
-
- 'createWrapper': function(){
- this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
- if(this.options.mode == 'top')
- {
- this.wrapper.setStyle('top', 0);
- }
- else
- {
- this.wrapper.setStyle('bottom', 0);
- }
- document.id(document.body).grab(this.wrapper);
- this.positionWrapper(this.options.position);
- },
-
- 'positionWrapper': function(position){
- if(typeOf(position) == 'object')
- {
-
- var wrapperCoords = this.getWrapperCoords();
-
- this.wrapper.setStyles({
- 'bottom': '',
- 'left': position.x,
- 'top': position.y - wrapperCoords.height,
- 'position': 'absolute'
- });
- }
- else if(position == 'left')
- {
- this.wrapper.setStyle('left', 0);
- }
- else if(position == 'right')
- {
- this.wrapper.setStyle('right', 0);
- }
- else
- {
- this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
- }
- return this;
- },
-
- 'getWrapperCoords': function(){
- this.wrapper.setStyle('visibility', 'hidden');
- var measurer = this.alert('need something in here to measure');
- var coords = this.wrapper.getCoordinates();
- measurer.destroy();
- this.wrapper.setStyle('visibility','');
- return coords;
- },
-
- 'alert': function(msg, options){
-
- options = Object.merge({}, this.options.alert, options || {});
-
- var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
-
- if(typeOf(msg) == 'string')
- {
- alert.set('html', msg);
- }
- else if(typeOf(msg) == 'element')
- {
- alert.grab(msg);
- }
- else if(typeOf(msg) == 'array')
- {
- var alerts = [];
- msg.each(function(m){
- alerts.push(this.alert(m, options));
- }, this);
- return alerts;
- }
-
- alert.store('options', options);
-
- if(options.buttons.length > 0)
- {
- options.clickDismiss = false;
- options.hideAfter = false;
- options.hoverWait = false;
- var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
- alert.grab(buttonWrapper);
- options.buttons.each(function(button){
- if(button.text !== undefined)
- {
- var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
- callbackButton.set('html', button.text);
- if(button.callback !== undefined)
- {
- callbackButton.addEvent('click', button.callback.pass(alert));
- }
- if(button.dismiss !== undefined && button.dismiss)
- {
- callbackButton.addEvent('click', this.dismiss.pass(alert, this));
- }
- buttonWrapper.grab(callbackButton);
- }
- }, this);
- }
- if(options.className !== undefined)
- {
- alert.addClass(options.className);
- }
-
- this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
-
- var fx = Object.merge(this.options.alert.fx, options.fx);
- var alertFx = new Fx.Morph(alert, fx);
- alert.store('fx', alertFx);
- this.fadeIn(alert);
-
- if(options.highlight)
- {
- alertFx.addEvent('complete', function(){
- alert.highlight(options.highlight.start, options.highlight.end);
- if(options.highlightRepeat)
- {
- alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
- }
- });
- }
- if(options.hideAfter)
- {
- this.dismiss(alert);
- }
-
- if(options.clickDismiss)
- {
- alert.addEvent('click', function(){
- this.holdUp = false;
- this.dismiss(alert, true);
- }.bind(this));
- }
-
- if(options.hoverWait)
- {
- alert.addEvents({
- 'mouseenter': function(){
- this.holdUp = true;
- }.bind(this),
- 'mouseleave': function(){
- this.holdUp = false;
- }.bind(this)
- });
- }
-
- return alert;
- },
-
- 'fadeIn': function(alert){
- var alertFx = alert.retrieve('fx');
- alertFx.set({
- 'opacity': 0
- });
- alertFx.start({
- 'opacity': [this.options.elementOptions.alert.styles.opacity, '.9'].pick()
- });
- },
-
- 'dismiss': function(alert, now){
- now = now || false;
- var options = alert.retrieve('options');
- if(now)
- {
- this.fadeOut(alert);
- }
- else
- {
- this.fadeOut.delay(options.hideAfter, this, alert);
- }
- },
-
- 'fadeOut': function(alert){
- if(this.holdUp)
- {
- this.dismiss.delay(100, this, [alert, true]);
- return null;
- }
- var alertFx = alert.retrieve('fx');
- if(!alertFx)
- {
- return null;
- }
- var to = {
- 'opacity': 0
- };
- if(this.options.mode == 'top')
- {
- to['margin-top'] = '-'+alert.offsetHeight+'px';
- }
- else
- {
- to['margin-bottom'] = '-'+alert.offsetHeight+'px';
- }
- alertFx.start(to);
- alertFx.addEvent('complete', function(){
- alert.destroy();
- });
- }
-});
-
-Element.implement({
-
- 'alert': function(msg, options){
- var alert = this.retrieve('alert');
- if(!alert)
- {
- options = options || {
- 'mode':'top'
- };
- alert = new Purr(options);
- this.store('alert', alert);
- }
-
- var coords = this.getCoordinates();
-
- alert.alert(msg, options);
-
- alert.wrapper.setStyles({
- 'bottom': '',
- 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
- 'top': coords.top - (alert.wrapper.getHeight()),
- 'position': 'absolute'
- });
-
- }
-
-}); \ No newline at end of file
diff --git a/module/web/media/js/settings.coffee b/module/web/media/js/settings.coffee
deleted file mode 100644
index 9205233e3..000000000
--- a/module/web/media/js/settings.coffee
+++ /dev/null
@@ -1,107 +0,0 @@
-root = this
-
-window.addEvent 'domready', ->
- root.accountDialog = new MooDialog {destroyOnHide: false}
- root.accountDialog.setContent $ 'account_box'
-
- new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
-
- $$('ul.nav').each (nav) ->
- new MooDropMenu nav, {
- onOpen: (el) -> el.fade 'in'
- onClose: (el) -> el.fade 'out'
- onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
- }
-
- new SettingsUI()
-
-
-class SettingsUI
- constructor: ->
- @menu = $$ "#general-menu li"
- @menu.append $$ "#plugin-menu li"
-
- @name = $ "tabsback"
- @general = $ "general_form_content"
- @plugin = $ "plugin_form_content"
-
- el.addEvent 'click', @menuClick.bind(this) for el in @menu
-
- $("general|submit").addEvent "click", @configSubmit.bind(this)
- $("plugin|submit").addEvent "click", @configSubmit.bind(this)
-
- $("account_add").addEvent "click", (e) ->
- root.accountDialog.open()
- e.stop()
-
- $("account_reset").addEvent "click", (e) ->
- root.accountDialog.close()
-
- $("account_add_button").addEvent "click", @addAccount.bind(this)
- $("account_submit").addEvent "click", @submitAccounts.bind(this)
-
-
- menuClick: (e) ->
- [category, section] = e.target.get("id").split("|")
- name = e.target.get "text"
-
-
- target = if category is "general" then @general else @plugin
- target.dissolve()
-
- new Request({
- "method" : "get"
- "url" : "/json/load_config/#{category}/#{section}"
- "onSuccess": (data) =>
- target.set "html", data
- target.reveal()
- this.name.set "text", name
- }).send()
-
-
- configSubmit: (e) ->
- category = e.target.get("id").split("|")[0];
- form = $("#{category}_form");
-
- form.set "send", {
- "method": "post"
- "url": "/json/save_config/#{category}"
- "onSuccess" : ->
- root.notify.alert '{{ _("Settings saved.")}}', {
- 'className': 'success'
- }
- "onFailure": ->
- root.notify.alert '{{ _("Error occured.")}}', {
- 'className': 'error'
- }
- }
- form.send()
- e.stop()
-
- addAccount: (e) ->
- form = $ "add_account_form"
- form.set "send", {
- "method": "post"
- "onSuccess" : -> window.location.reload()
- "onFailure": ->
- root.notify.alert '{{_("Error occured.")}}', {
- 'className': 'error'
- }
- }
-
- form.send()
- e.stop()
-
- submitAccounts: (e) ->
- form = $ "account_form"
- form.set "send", {
- "method": "post",
- "onSuccess" : -> window.location.reload()
- "onFailure": ->
- root.notify.alert('{{ _("Error occured.") }}', {
- 'className': 'error'
- });
- }
-
- form.send()
- e.stop() \ No newline at end of file
diff --git a/module/web/media/js/settings.js b/module/web/media/js/settings.js
deleted file mode 100644
index 9191fac72..000000000
--- a/module/web/media/js/settings.js
+++ /dev/null
@@ -1,3 +0,0 @@
-{% autoescape true %}
-var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
-{% endautoescape %} \ No newline at end of file
diff --git a/module/web/media/js/tinytab_static.js b/module/web/media/js/tinytab_static.js
deleted file mode 100644
index 6c38292f5..000000000
--- a/module/web/media/js/tinytab_static.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
----
-description: TinyTab - Tiny and simple tab handler for Mootools.
-
-license: MIT-style
-
-authors:
-- Danillo César de O. Melo
-
-requires:
-- core/1.2.4: '*'
-
-provides: TinyTab
-
-...
-*/
-(function($) {
- this.TinyTab = new Class({
- Implements: Events,
- initialize: function(tabs, contents, opt) {
- this.tabs = tabs;
- this.contents = contents;
- this.header = $("tabsback");
- this.headers = [];
- for(var i =0; i < this.tabs.length; i++){
- this.headers.push("");
- }
- if(!opt) opt = {};
- this.css = opt.selectedClass || 'selected';
- this.select(this.tabs[0]);
- tabs.each(function(el){
- el.addEvent('click',function(e){
- this.select(el);
- e.stop();
- }.bind(this));
- }.bind(this));
- },
-
- select: function(el) {
- this.tabs.removeClass(this.css);
- el.addClass(this.css);
- this.contents.setStyle('display','none');
- var index = this.tabs.indexOf(el);
- this.header.set("text", this.headers[index]);
- var content = this.contents[index];
- content.setStyle('display','block');
- this.fireEvent('change',[content,el]);
- }
- });
-})(document.id); \ No newline at end of file
diff --git a/module/web/middlewares.py b/module/web/middlewares.py
deleted file mode 100644
index e0e6c3102..000000000
--- a/module/web/middlewares.py
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import gzip
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-class StripPathMiddleware(object):
- def __init__(self, app):
- self.app = app
-
- def __call__(self, e, h):
- e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
- return self.app(e, h)
-
-
-class PrefixMiddleware(object):
- def __init__(self, app, prefix="/pyload"):
- self.app = app
- self.prefix = prefix
-
- def __call__(self, e, h):
- path = e["PATH_INFO"]
- if path.startswith(self.prefix):
- e['PATH_INFO'] = path.replace(self.prefix, "", 1)
- return self.app(e, h)
-
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-# WSGI middleware
-# Gzip-encodes the response.
-
-class GZipMiddleWare(object):
-
- def __init__(self, application, compress_level=6):
- self.application = application
- self.compress_level = int(compress_level)
-
- def __call__(self, environ, start_response):
- if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
- # nothing for us to do, so this middleware will
- # be a no-op:
- return self.application(environ, start_response)
- response = GzipResponse(start_response, self.compress_level)
- app_iter = self.application(environ,
- response.gzip_start_response)
- if app_iter is not None:
- response.finish_response(app_iter)
-
- return response.write()
-
-def header_value(headers, key):
- for header, value in headers:
- if key.lower() == header.lower():
- return value
-
-def update_header(headers, key, value):
- remove_header(headers, key)
- headers.append((key, value))
-
-def remove_header(headers, key):
- for header, value in headers:
- if key.lower() == header.lower():
- headers.remove((header, value))
- break
-
-class GzipResponse(object):
-
- def __init__(self, start_response, compress_level):
- self.start_response = start_response
- self.compress_level = compress_level
- self.buffer = StringIO()
- self.compressible = False
- self.content_length = None
- self.headers = ()
-
- def gzip_start_response(self, status, headers, exc_info=None):
- self.headers = headers
- ct = header_value(headers,'content-type')
- ce = header_value(headers,'content-encoding')
- cl = header_value(headers, 'content-length')
- if cl:
- cl = int(cl)
- else:
- cl = 201
- self.compressible = False
- if ct and (ct.startswith('text/') or ct.startswith('application/')) \
- and 'zip' not in ct and cl > 200:
- self.compressible = True
- if ce:
- self.compressible = False
- if self.compressible:
- headers.append(('content-encoding', 'gzip'))
- remove_header(headers, 'content-length')
- self.headers = headers
- self.status = status
- return self.buffer.write
-
- def write(self):
- out = self.buffer
- out.seek(0)
- s = out.getvalue()
- out.close()
- return [s]
-
- def finish_response(self, app_iter):
- if self.compressible:
- output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level,
- fileobj=self.buffer)
- else:
- output = self.buffer
- try:
- for s in app_iter:
- output.write(s)
- if self.compressible:
- output.close()
- finally:
- if hasattr(app_iter, 'close'):
- try:
- app_iter.close()
- except :
- pass
-
- content_length = self.buffer.tell()
- update_header(self.headers, "Content-Length" , str(content_length))
- self.start_response(self.status, self.headers) \ No newline at end of file
diff --git a/module/web/pyload_app.py b/module/web/pyload_app.py
deleted file mode 100644
index df4a4b3d4..000000000
--- a/module/web/pyload_app.py
+++ /dev/null
@@ -1,533 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from datetime import datetime
-from operator import itemgetter, attrgetter
-
-import time
-import os
-import sys
-from os import listdir
-from os.path import isdir, isfile, join, abspath
-from sys import getfilesystemencoding
-from urllib import unquote
-
-from bottle import route, static_file, request, response, redirect, HTTPError, error
-
-from webinterface import PYLOAD, PYLOAD_DIR, PROJECT_DIR, SETUP, env
-
-from utils import render_to_response, parse_permissions, parse_userdata, \
- login_required, get_permission, set_permission, permlist, toDict, set_session
-
-from filters import relpath, unquotepath
-
-from module.utils import formatSize, save_join, fs_encode, fs_decode
-
-# Helper
-
-def pre_processor():
- s = request.environ.get('beaker.session')
- user = parse_userdata(s)
- perms = parse_permissions(s)
- status = {}
- captcha = False
- update = False
- plugins = False
- if user["is_authenticated"]:
- status = PYLOAD.statusServer()
- info = PYLOAD.getInfoByPlugin("UpdateManager")
- captcha = PYLOAD.isCaptchaWaiting()
-
- # check if update check is available
- if info:
- if info["pyload"] == "True": update = True
- if info["plugins"] == "True": plugins = True
-
-
- return {"user": user,
- 'status': status,
- 'captcha': captcha,
- 'perms': perms,
- 'url': request.url,
- 'update': update,
- 'plugins': plugins}
-
-
-def base(messages):
- return render_to_response('base.html', {'messages': messages}, [pre_processor])
-
-
-## Views
-@error(500)
-def error500(error):
- print "An error occured while processing the request."
- if error.traceback:
- print error.traceback
-
- return base(["An Error occured, please enable debug mode to get more details.", error,
- error.traceback.replace("\n", "<br>") if error.traceback else "No Traceback"])
-
-# render js
-@route("/media/js/<path:re:.+\.js>")
-def js_dynamic(path):
- response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
- time.gmtime(time.time() + 60 * 60 * 24 * 2))
- response.headers['Cache-control'] = "public"
- response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
-
- try:
- # static files are not rendered
- if "static" not in path and "mootools" not in path:
- t = env.get_template("js/%s" % path)
- return t.render()
- else:
- return static_file(path, root=join(PROJECT_DIR, "media", "js"))
- except:
- return HTTPError(404, "Not Found")
-
-@route('/media/<path:path>')
-def server_static(path):
- response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
- time.gmtime(time.time() + 60 * 60 * 24 * 7))
- response.headers['Cache-control'] = "public"
- return static_file(path, root=join(PROJECT_DIR, "media"))
-
-@route('/favicon.ico')
-def favicon():
- return static_file("favicon.ico", root=join(PROJECT_DIR, "media", "img"))
-
-
-@route('/login', method="GET")
-def login():
- if not PYLOAD and SETUP:
- redirect("/setup")
- else:
- return render_to_response("login.html", proc=[pre_processor])
-
-
-@route('/nopermission')
-def nopermission():
- return base([_("You dont have permission to access this page.")])
-
-
-@route("/login", method="POST")
-def login_post():
- user = request.forms.get("username")
- password = request.forms.get("password")
-
- info = PYLOAD.checkAuth(user, password)
-
- if not info:
- return render_to_response("login.html", {"errors": True}, [pre_processor])
-
- set_session(request, info)
- return redirect("/")
-
-
-@route("/logout")
-def logout():
- s = request.environ.get('beaker.session')
- s.delete()
- return render_to_response("logout.html", proc=[pre_processor])
-
-
-@route("/")
-@route("/home")
-@login_required("LIST")
-def home():
- try:
- res = [toDict(x) for x in PYLOAD.statusDownloads()]
- except:
- s = request.environ.get('beaker.session')
- s.delete()
- return redirect("/login")
-
- for link in res:
- if link["status"] == 12:
- link["information"] = "%s kB @ %s kB/s" % (link["size"] - link["bleft"], link["speed"])
-
- return render_to_response("home.html", {"res": res}, [pre_processor])
-
-
-@route("/queue")
-@login_required("LIST")
-def queue():
- queue = PYLOAD.getQueue()
-
- queue.sort(key=attrgetter("order"))
-
- return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor])
-
-
-@route("/collector")
-@login_required('LIST')
-def collector():
- queue = PYLOAD.getCollector()
-
- queue.sort(key=attrgetter("order"))
-
- return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor])
-
-
-@route("/downloads")
-@login_required('DOWNLOAD')
-def downloads():
- root = PYLOAD.getConfigValue("general", "download_folder")
-
- if not isdir(root):
- return base([_('Download directory not found.')])
- data = {
- 'folder': [],
- 'files': []
- }
-
- items = listdir(fs_encode(root))
-
- for item in sorted([fs_decode(x) for x in items]):
- if isdir(save_join(root, item)):
- folder = {
- 'name': item,
- 'path': item,
- 'files': []
- }
- files = listdir(save_join(root, item))
- for file in sorted([fs_decode(x) for x in files]):
- try:
- if isfile(save_join(root, item, file)):
- folder['files'].append(file)
- except:
- pass
-
- data['folder'].append(folder)
- elif isfile(join(root, item)):
- data['files'].append(item)
-
- return render_to_response('downloads.html', {'files': data}, [pre_processor])
-
-
-@route("/downloads/get/<path:re:.+>")
-@login_required("DOWNLOAD")
-def get_download(path):
- path = unquote(path).decode("utf8")
- #@TODO some files can not be downloaded
-
- root = PYLOAD.getConfigValue("general", "download_folder")
-
- path = path.replace("..", "")
- try:
- return static_file(fs_encode(path), fs_encode(root))
-
- except Exception, e:
- print e
- return HTTPError(404, "File not Found.")
-
-
-
-@route("/settings")
-@login_required('SETTINGS')
-def config():
- conf = PYLOAD.getConfig()
- plugin = PYLOAD.getPluginConfig()
-
- conf_menu = []
- plugin_menu = []
-
- for entry in sorted(conf.keys()):
- conf_menu.append((entry, conf[entry].description))
-
- for entry in sorted(plugin.keys()):
- plugin_menu.append((entry, plugin[entry].description))
-
- accs = PYLOAD.getAccounts(False)
-
- for data in accs:
- if data.trafficleft == -1:
- data.trafficleft = _("unlimited")
- elif not data.trafficleft:
- data.trafficleft = _("not available")
- else:
- data.trafficleft = formatSize(data.trafficleft * 1024)
-
- if data.validuntil == -1:
- data.validuntil = _("unlimited")
- elif not data.validuntil :
- data.validuntil = _("not available")
- else:
- t = time.localtime(data.validuntil)
- data.validuntil = time.strftime("%d.%m.%Y", t)
-
- if "time" in data.options:
- try:
- data.options["time"] = data.options["time"][0]
- except:
- data.options["time"] = "0:00-0:00"
-
- if "limitDL" in data.options:
- data.options["limitdl"] = data.options["limitDL"][0]
- else:
- data.options["limitdl"] = "0"
-
- return render_to_response('settings.html',
- {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, 'types': PYLOAD.getAccountTypes()},
- [pre_processor])
-
-
-@route("/filechooser")
-@route("/pathchooser")
-@route("/filechooser/:file#.+#")
-@route("/pathchooser/:path#.+#")
-@login_required('STATUS')
-def path(file="", path=""):
- if file:
- type = "file"
- else:
- type = "folder"
-
- path = os.path.normpath(unquotepath(path))
-
- if os.path.isfile(path):
- oldfile = path
- path = os.path.dirname(path)
- else:
- oldfile = ''
-
- abs = False
-
- if os.path.isdir(path):
- if os.path.isabs(path):
- cwd = os.path.abspath(path)
- abs = True
- else:
- cwd = relpath(path)
- else:
- cwd = os.getcwd()
-
- try:
- cwd = cwd.encode("utf8")
- except:
- pass
-
- cwd = os.path.normpath(os.path.abspath(cwd))
- parentdir = os.path.dirname(cwd)
- if not abs:
- if os.path.abspath(cwd) == "/":
- cwd = relpath(cwd)
- else:
- cwd = relpath(cwd) + os.path.sep
- parentdir = relpath(parentdir) + os.path.sep
-
- if os.path.abspath(cwd) == "/":
- parentdir = ""
-
- try:
- folders = os.listdir(cwd)
- except:
- folders = []
-
- files = []
-
- for f in folders:
- try:
- f = f.decode(getfilesystemencoding())
- data = {'name': f, 'fullpath': join(cwd, f)}
- data['sort'] = data['fullpath'].lower()
- data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f))))
- data['ext'] = os.path.splitext(f)[1]
- except:
- continue
-
- if os.path.isdir(join(cwd, f)):
- data['type'] = 'dir'
- else:
- data['type'] = 'file'
-
- if os.path.isfile(join(cwd, f)):
- data['size'] = os.path.getsize(join(cwd, f))
-
- power = 0
- while (data['size'] / 1024) > 0.3:
- power += 1
- data['size'] /= 1024.
- units = ('', 'K', 'M', 'G', 'T')
- data['unit'] = units[power] + 'Byte'
- else:
- data['size'] = ''
-
- files.append(data)
-
- files = sorted(files, key=itemgetter('type', 'sort'))
-
- return render_to_response('pathchooser.html',
- {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile,
- 'absolute': abs}, [])
-
-
-@route("/logs")
-@route("/logs", method="POST")
-@route("/logs/:item")
-@route("/logs/:item", method="POST")
-@login_required('LOGS')
-def logs(item=-1):
- s = request.environ.get('beaker.session')
-
- perpage = s.get('perpage', 34)
- reversed = s.get('reversed', False)
-
- warning = ""
- conf = PYLOAD.getConfigValue("log","file_log")
- if not conf:
- warning = "Warning: File log is disabled, see settings page."
-
- perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all'))
- fro = None
-
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- try:
- fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
- except:
- pass
- try:
- perpage = int(request.forms['perpage'])
- s['perpage'] = perpage
-
- reversed = bool(request.forms.get('reversed', False))
- s['reversed'] = reversed
- except:
- pass
-
- s.save()
-
- try:
- item = int(item)
- except:
- pass
-
- log = PYLOAD.getLog()
- if not perpage:
- item = 0
-
- if item < 1 or type(item) is not int:
- item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1
-
- if type(fro) is datetime: # we will search for datetime
- item = -1
-
- data = []
- counter = 0
- perpagecheck = 0
- for l in log:
- counter += 1
-
- if counter >= item:
- try:
- date, time, level, message = l.decode("utf8", "ignore").split(" ", 3)
- dtime = datetime.strptime(date + ' ' + time, '%d.%m.%Y %H:%M:%S')
- except:
- dtime = None
- date = '?'
- time = ' '
- level = '?'
- message = l
- if item == -1 and dtime is not None and fro <= dtime:
- item = counter #found our datetime
- if item >= 0:
- data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message})
- perpagecheck += 1
- if fro is None and dtime is not None: #if fro not set set it to first showed line
- fro = dtime
- if perpagecheck >= perpage > 0:
- break
-
- if fro is None: #still not set, empty log?
- fro = datetime.now()
- if reversed:
- data.reverse()
- return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'),
- 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p),
- 'iprev': 1 if item - perpage < 1 else item - perpage,
- 'inext': (item + perpage) if item + perpage < len(log) else item},
- [pre_processor])
-
-
-@route("/admin")
-@route("/admin", method="POST")
-@login_required("ADMIN")
-def admin():
- # convert to dict
- user = dict([(name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()])
- perms = permlist()
-
- for data in user.itervalues():
- data["perms"] = {}
- get_permission(data["perms"], data["permission"])
- data["perms"]["admin"] = True if data["role"] is 0 else False
-
-
- s = request.environ.get('beaker.session')
- if request.environ.get('REQUEST_METHOD', "GET") == "POST":
- for name in user:
- if request.POST.get("%s|admin" % name, False):
- user[name]["role"] = 0
- user[name]["perms"]["admin"] = True
- elif name != s["name"]:
- user[name]["role"] = 1
- user[name]["perms"]["admin"] = False
-
- # set all perms to false
- for perm in perms:
- user[name]["perms"][perm] = False
-
-
- for perm in request.POST.getall("%s|perms" % name):
- user[name]["perms"][perm] = True
-
- user[name]["permission"] = set_permission(user[name]["perms"])
-
- PYLOAD.setUserPermission(name, user[name]["permission"], user[name]["role"])
-
- return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor])
-
-
-@route("/setup")
-def setup():
- if PYLOAD or not SETUP:
- return base([_("Run pyLoadCore.py -s to access the setup.")])
-
- return render_to_response('setup.html', {"user": False, "perms": False})
-
-
-@route("/info")
-def info():
- conf = PYLOAD.getConfigDict()
-
- if hasattr(os, "uname"):
- extra = os.uname()
- else:
- extra = tuple()
-
- data = {"python": sys.version,
- "os": " ".join((os.name, sys.platform) + extra),
- "version": PYLOAD.getServerVersion(),
- "folder": abspath(PYLOAD_DIR), "config": abspath(""),
- "download": abspath(conf["general"]["download_folder"]["value"]),
- "freespace": formatSize(PYLOAD.freeSpace()),
- "remote": conf["remote"]["port"]["value"],
- "webif": conf["webinterface"]["port"]["value"],
- "language": conf["general"]["language"]["value"]}
-
- return render_to_response("info.html", data, [pre_processor])
diff --git a/module/web/servers/lighttpd_default.conf b/module/web/servers/lighttpd_default.conf
deleted file mode 100644
index e56dda35f..000000000
--- a/module/web/servers/lighttpd_default.conf
+++ /dev/null
@@ -1,153 +0,0 @@
-# lighttpd configuration file
-#
-# use it as a base for lighttpd 1.0.0 and above
-#
-# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
-
-############ Options you really have to take care of ####################
-
-## modules to load
-# at least mod_access and mod_accesslog should be loaded
-# all other module should only be loaded if really neccesary
-# - saves some time
-# - saves memory
-server.modules = (
- "mod_rewrite",
- "mod_redirect",
- "mod_alias",
- "mod_access",
-# "mod_trigger_b4_dl",
-# "mod_auth",
-# "mod_status",
-# "mod_setenv",
- "mod_fastcgi",
-# "mod_proxy",
-# "mod_simple_vhost",
-# "mod_evhost",
-# "mod_userdir",
-# "mod_cgi",
-# "mod_compress",
-# "mod_ssi",
-# "mod_usertrack",
-# "mod_expire",
-# "mod_secdownload",
-# "mod_rrdtool",
-# "mod_accesslog"
- )
-
-## A static document-root. For virtual hosting take a look at the
-## mod_simple_vhost module.
-server.document-root = "%(path)"
-
-## where to send error-messages to
-server.errorlog = "%(path)/error.log"
-
-# files to check for if .../ is requested
-index-file.names = ( "index.php", "index.html",
- "index.htm", "default.htm" )
-
-## set the event-handler (read the performance section in the manual)
-# server.event-handler = "freebsd-kqueue" # needed on OS X
-
-# mimetype mapping
-mimetype.assign = (
- ".pdf" => "application/pdf",
- ".sig" => "application/pgp-signature",
- ".spl" => "application/futuresplash",
- ".class" => "application/octet-stream",
- ".ps" => "application/postscript",
- ".torrent" => "application/x-bittorrent",
- ".dvi" => "application/x-dvi",
- ".gz" => "application/x-gzip",
- ".pac" => "application/x-ns-proxy-autoconfig",
- ".swf" => "application/x-shockwave-flash",
- ".tar.gz" => "application/x-tgz",
- ".tgz" => "application/x-tgz",
- ".tar" => "application/x-tar",
- ".zip" => "application/zip",
- ".mp3" => "audio/mpeg",
- ".m3u" => "audio/x-mpegurl",
- ".wma" => "audio/x-ms-wma",
- ".wax" => "audio/x-ms-wax",
- ".ogg" => "application/ogg",
- ".wav" => "audio/x-wav",
- ".gif" => "image/gif",
- ".jar" => "application/x-java-archive",
- ".jpg" => "image/jpeg",
- ".jpeg" => "image/jpeg",
- ".png" => "image/png",
- ".xbm" => "image/x-xbitmap",
- ".xpm" => "image/x-xpixmap",
- ".xwd" => "image/x-xwindowdump",
- ".css" => "text/css",
- ".html" => "text/html",
- ".htm" => "text/html",
- ".js" => "text/javascript",
- ".asc" => "text/plain",
- ".c" => "text/plain",
- ".cpp" => "text/plain",
- ".log" => "text/plain",
- ".conf" => "text/plain",
- ".text" => "text/plain",
- ".txt" => "text/plain",
- ".dtd" => "text/xml",
- ".xml" => "text/xml",
- ".mpeg" => "video/mpeg",
- ".mpg" => "video/mpeg",
- ".mov" => "video/quicktime",
- ".qt" => "video/quicktime",
- ".avi" => "video/x-msvideo",
- ".asf" => "video/x-ms-asf",
- ".asx" => "video/x-ms-asf",
- ".wmv" => "video/x-ms-wmv",
- ".bz2" => "application/x-bzip",
- ".tbz" => "application/x-bzip-compressed-tar",
- ".tar.bz2" => "application/x-bzip-compressed-tar",
- # default mime type
- "" => "application/octet-stream",
- )
-
-# Use the "Content-Type" extended attribute to obtain mime type if possible
-#mimetype.use-xattr = "enable"
-
-#### accesslog module
-accesslog.filename = "%(path)/access.log"
-
-url.access-deny = ( "~", ".inc" )
-
-$HTTP["url"] =~ "\.pdf$" {
- server.range-requests = "disable"
-}
-static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
-
-server.pid-file = "%(path)/lighttpd.pid"
-
-server.bind = "%(host)"
-server.port = %(port)
-
-#server.document-root = "/home/user/public_html"
-fastcgi.server = (
- "/pyload.fcgi" => (
- "main" => (
- "host" => "127.0.0.1",
- "port" => 9295,
- "check-local" => "disable",
- "docroot" => "/",
- )
- ),
-)
-
-alias.url = (
- "/media/" => "%(media)/",
- "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/",
-)
-
-url.rewrite-once = (
- "^(/media.*)$" => "$1",
- "^(/admin/media.*)$" => "$1",
- "^/favicon\.ico$" => "/media/img/favicon.ico",
- "^(/pyload.fcgi.*)$" => "$1",
- "^(/.*)$" => "/pyload.fcgi$1",
-)
-
-%(ssl) \ No newline at end of file
diff --git a/module/web/templates/500.html b/module/web/templates/500.html
deleted file mode 100644
index e15090b66..000000000
--- a/module/web/templates/500.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
- <title>Server Error</title>
-</head>
-<body>
-<h1>Server Error occured. Please enable debug mode to get a more detailed report.</h1>
-</body>
-</html>
diff --git a/module/web/templates/default/admin.html b/module/web/templates/default/admin.html
deleted file mode 100644
index b049411fd..000000000
--- a/module/web/templates/default/admin.html
+++ /dev/null
@@ -1,98 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
- <script type="text/javascript" src="media/js/admin.js"></script>
-{% endblock %}
-
-
-{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Administrate") }}{% endblock %}
-
-{% block content %}
-
- <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
- <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
- <br>
- <br>
-
- {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br>
- {{ _("Important: Admin user have always all permissions!") }}
-
- <form action="" method="POST">
- <table class="settable wide">
- <thead style="font-size: 11px">
- <th>
- {{ _("Name") }}
- </th>
- <th>
- {{ _("Change Password") }}
- </th>
- <th>
- {{ _("Admin") }}
- </th>
- <th>
- {{ _("Permissions") }}
- </th>
- </thead>
-
- {% for name, data in users.iteritems() %}
- <tr>
- <td>{{ name }}</td>
- <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
- <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
- checked="True" {% endif %}"></td>
- <td>
- <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
- {% for perm in permlist %}
- {% if data.perms|getitem(perm) %}
- <option selected="selected">{{ perm }}</option>
- {% else %}
- <option>{{ perm }}</option>
- {% endif %}
- {% endfor %}
- </select>
- </td>
- </tr>
- {% endfor %}
-
-
- </table>
-
- <button class="styled_button" type="submit">{{ _("Submit") }}</button>
- </form>
-{% endblock %}
-{% block hidden %}
- <div id="password_box" class="window_box" style="z-index: 2">
- <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
- <h1>{{ _("Change Password") }}</h1>
-
- <p>{{ _("Enter your current and desired Password.") }}</p>
- <label for="user_login">{{ _("User") }}
- <span class="small">{{ _("Your username.") }}</span>
- </label>
- <input id="user_login" name="user_login" type="text" size="20"/>
-
- <label for="login_current_password">{{ _("Current password") }}
- <span class="small">{{ _("The password for this account.") }}</span>
- </label>
- <input id="login_current_password" name="login_current_password" type="password" size="20"/>
-
- <label for="login_new_password">{{ _("New password") }}
- <span class="small">{{ _("The new password.") }}</span>
- </label>
- <input id="login_new_password" name="login_new_password" type="password" size="20"/>
-
- <label for="login_new_password2">{{ _("New password (repeat)") }}
- <span class="small">{{ _("Please repeat the new password.") }}</span>
- </label>
- <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
-
-
- <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
- <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
- <div class="spacer"></div>
-
- </form>
-
- </div>
-{% endblock %}
diff --git a/module/web/templates/default/base.html b/module/web/templates/default/base.html
deleted file mode 100644
index 147c08a37..000000000
--- a/module/web/templates/default/base.html
+++ /dev/null
@@ -1,180 +0,0 @@
-<?xml version="1.0" ?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head>
-
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/default.css"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/window.css"/>
-<link rel="stylesheet" type="text/css" href="/media/default/css/MooDialog.css"/>
-
-<script type="text/javascript" src="/media/js/mootools-core-1.4.1.js"></script>
-<script type="text/javascript" src="/media/js/mootools-more-1.4.0.1.js"></script>
-<script type="text/javascript" src="/media/js/MooDialog_static.js"></script>
-<script type="text/javascript" src="/media/js/purr_static.js"></script>
-
-
-<script type="text/javascript" src="/media/js/base.js"></script>
-
-<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
-
-{% block head %}
-{% endblock %}
-</head>
-<body>
-<a class="anchor" name="top" id="top"></a>
-
-<div id="head-panel">
-
-
- <div id="head-search-and-login">
- {% block headpanel %}
-
- {% if user.is_authenticated %}
-
-
-{% if update %}
-<span>
-<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
-</span>
-{% endif %}
-
-
-{% if plugins %}
-<span>
-<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
-</span>
-{% endif %}
-
-<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
-<img src="/media/default/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
-<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
-</span>
-
- <img src="/media/default/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
- <ul id="user-actions">
- <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
- {% if user.is_admin %}
- <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
- {% endif %}
- <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
-
- </ul>
-{% else %}
- <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
-{% endif %}
-
- {% endblock %}
- </div>
-
- <a href="/"><img id="head-logo" src="/media/default/img/pyload-logo-edited3.5-new-font-small.png" alt="pyLoad" /></a>
-
- <div id="head-menu">
- <ul>
-
- {% macro selected(name, right=False) -%}
- {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
- {% if not name in url and right -%}class="right"{%- endif %}
- {%- endmacro %}
-
-
- {% block menu %}
- <li>
- <a href="/" title=""><img src="/media/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
- </li>
- <li {{ selected('queue') }}>
- <a href="/queue/" title=""><img src="/media/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
- </li>
- <li {{ selected('collector') }}>
- <a href="/collector/" title=""><img src="/media/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
- </li>
- <li {{ selected('downloads') }}>
- <a href="/downloads/" title=""><img src="/media/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
- </li>
-{# <li {{ selected('filemanager') }}>#}
-{# <a href="/filemanager/" title=""><img src="/media/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
-{# </li>#}
- <li {{ selected('logs', True) }}>
- <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
- </li>
- <li {{ selected('settings', True) }}>
- <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
- </li>
- {% endblock %}
-
- </ul>
- </div>
-
- <div style="clear:both;"></div>
-</div>
-
-{% if perms.STATUS %}
-<ul id="page-actions2">
- <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
- <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
- <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
- <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
-</ul>
-{% endif %}
-
-{% if perms.LIST %}
-<ul id="page-actions">
- <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
- <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
- <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
- <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
- <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
-</ul>
-{% endif %}
-
-{% block pageactions %}
-{% endblock %}
-<br/>
-
-<div id="body-wrapper" class="dokuwiki">
-
-<div id="content" lang="en" dir="ltr">
-
-<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
-
-{% block statusbar %}
-{% endblock %}
-
-
-<br/>
-
-<div class="level1" style="clear:both">
-</div>
-<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
-
-{% for message in messages %}
- <b><p>{{message}}</p></b>
-{% endfor %}
-
-<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
- <img src="/media/default/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
- {{_("loading")}}
-</div>
-
-{% block content %}
-{% endblock content %}
-
- <hr style="clear: both;" />
-
-<div id="foot">&copy; 2008-2011 pyLoad Team
-<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
-<!--<div class="breadcrumbs"></div>-->
-
-</div>
-</div>
-</div>
-
-<div style="display: none;">
- {% include "default/window.html" %}
- {% include "default/captcha.html" %}
- {% block hidden %}
- {% endblock %}
-</div>
-</body>
-</html>
diff --git a/module/web/templates/default/captcha.html b/module/web/templates/default/captcha.html
deleted file mode 100644
index 288375b76..000000000
--- a/module/web/templates/default/captcha.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!-- Captcha box -->
-<div id="cap_box" class="window_box">
-
- <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
-
- <h1>{{_("Captcha reading")}}</h1>
- <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
-
- <div id="cap_textual">
-
- <input id="cap_id" name="cap_id" type="hidden" value="" />
-
- <label>{{_("Captcha")}}
- <span class="small">{{_("The captcha.")}}</span>
- </label>
- <span class="cont">
- <img id="cap_textual_img" src="">
- </span>
-
- <label>{{_("Text")}}
- <span class="small">{{_("Input the text on the captcha.")}}</span>
- </label>
- <input id="cap_result" name="cap_result" type="text" size="20" />
-
- </div>
-
- <div id="cap_positional" style="text-align: center">
- <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
- </div>
-
- <div id="button_bar" style="text-align: center">
- <span>
- <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
- <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
- </span>
- </div>
-
- <div class="spacer"></div>
-
- </form>
-
-</div> \ No newline at end of file
diff --git a/module/web/templates/default/downloads.html b/module/web/templates/default/downloads.html
deleted file mode 100644
index 450b8a102..000000000
--- a/module/web/templates/default/downloads.html
+++ /dev/null
@@ -1,29 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}Downloads - {{super()}} {% endblock %}
-
-{% block subtitle %}
-{{_("Downloads")}}
-{% endblock %}
-
-{% block content %}
-
-<ul>
- {% for folder in files.folder %}
- <li>
- {{ folder.name }}
- <ul>
- {% for file in folder.files %}
- <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
- {% endfor %}
- </ul>
- </li>
- {% endfor %}
-
- {% for file in files.files %}
- <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
- {% endfor %}
-
-</ul>
-
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/filemanager.html b/module/web/templates/default/filemanager.html
deleted file mode 100644
index 97095c13e..000000000
--- a/module/web/templates/default/filemanager.html
+++ /dev/null
@@ -1,80 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
-
-<script type="text/javascript" src="/filemanager_ui.js"></script>
-
-<script type="text/javascript">
-
-document.addEvent("domready", function(){
- var fmUI = new FilemanagerUI("url",1);
-});
-</script>
-{% endblock %}
-
-{% block title %}Downloads - {{super()}} {% endblock %}
-
-
-{% block subtitle %}
-{{_("FileManager")}}
-{% endblock %}
-
-{% macro display_file(file) %}
- <li class="file">
- <input type="hidden" name="path" class="path" value="{{ file.path }}" />
- <input type="hidden" name="name" class="name" value="{{ file.name }}" />
- <span>
- <b>{{ file.name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- </span>
- </span>
- </li>
-{%- endmacro %}
-
-{% macro display_folder(fld, open = false) -%}
- <li class="folder">
- <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
- <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
- <span>
- <b>{{ fld.name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/add_folder.png" />
- </span>
- </span>
- {% if (fld.folders|length + fld.files|length) > 0 %}
- {% if open %}
- <ul>
- {% else %}
- <ul style="display:none">
- {% endif %}
- {% for child in fld.folders %}
- {{ display_folder(child) }}
- {% endfor %}
- {% for child in fld.files %}
- {{ display_file(child) }}
- {% endfor %}
- </ul>
- {% else %}
- <div style="display:none">{{ _("Folder is empty") }}</div>
- {% endif %}
- </li>
-{%- endmacro %}
-
-{% block content %}
-
-<div style="clear:both"><!-- --></div>
-
-<ul id="directories-list">
-{{ display_folder(root, true) }}
-</ul>
-
-{% include "default/rename_directory.html" %}
-
-{% endblock %}
diff --git a/module/web/templates/default/filemanager_ui.js b/module/web/templates/default/filemanager_ui.js
deleted file mode 100644
index ed64ab69d..000000000
--- a/module/web/templates/default/filemanager_ui.js
+++ /dev/null
@@ -1,291 +0,0 @@
-var load, rename_box, confirm_box;
-
-document.addEvent("domready", function() {
- load = new Fx.Tween($("load-indicator"), {link: "cancel"});
- load.set("opacity", 0);
-
- rename_box = new Fx.Tween($('rename_box'));
- confirm_box = new Fx.Tween($('confirm_box'));
- $('rename_reset').addEvent('click', function() {
- hide_rename_box()
- });
- $('delete_reset').addEvent('click', function() {
- hide_confirm_box()
- });
-
- /*$('filemanager_actions_list').getChildren("li").each(function(action) {
- var action_name = action.className;
- if(functions[action.className] != undefined)
- {
- action.addEvent('click', functions[action.className]);
- }
- });*/
-});
-
-function indicateLoad() {
- //$("load-indicator").reveal();
- load.start("opacity", 1)
-}
-
-function indicateFinish() {
- load.start("opacity", 0)
-}
-
-function indicateSuccess() {
- indicateFinish();
- notify.alert('{{_("Success")}}.', {
- 'className': 'success'
- });
-}
-
-function indicateFail() {
- indicateFinish();
- notify.alert('{{_("Failed")}}.', {
- 'className': 'error'
- });
-}
-
-function show_rename_box() {
- bg_show();
- $("rename_box").setStyle('display', 'block');
- rename_box.start('opacity', 1)
-}
-
-function hide_rename_box() {
- bg_hide();
- rename_box.start('opacity', 0).chain(function() {
- $('rename_box').setStyle('display', 'none');
- });
-}
-
-function show_confirm_box() {
- bg_show();
- $("confirm_box").setStyle('display', 'block');
- confirm_box.start('opacity', 1)
-}
-
-function hide_confirm_box() {
- bg_hide();
- confirm_box.start('opacity', 0).chain(function() {
- $('confirm_box').setStyle('display', 'none');
- });
-}
-
-var FilemanagerUI = new Class({
- initialize: function(url, type) {
- this.url = url;
- this.type = type;
- this.directories = [];
- this.files = [];
- this.parseChildren();
- },
-
- parseChildren: function() {
- $("directories-list").getChildren("li.folder").each(function(ele) {
- var path = ele.getElements("input.path")[0].get("value");
- var name = ele.getElements("input.name")[0].get("value");
- this.directories.push(new Item(this, path, name, ele))
- }.bind(this));
-
- $("directories-list").getChildren("li.file").each(function(ele) {
- var path = ele.getElements("input.path")[0].get("value");
- var name = ele.getElements("input.name")[0].get("value");
- this.files.push(new Item(this, path, name, ele))
- }.bind(this));
- }
-});
-
-var Item = new Class({
- initialize: function(ui, path, name, ele) {
- this.ui = ui;
- this.path = path;
- this.name = name;
- this.ele = ele;
- this.directories = [];
- this.files = [];
- this.actions = new Array();
- this.actions["delete"] = this.del;
- this.actions["rename"] = this.rename;
- this.actions["mkdir"] = this.mkdir;
- this.parseElement();
-
- var pname = this.ele.getElements("span")[0];
- this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
- this.buttons.set("opacity", 0);
-
- pname.addEvent("mouseenter", function(e) {
- this.buttons.start("opacity", 1)
- }.bind(this));
-
- pname.addEvent("mouseleave", function(e) {
- this.buttons.start("opacity", 0)
- }.bind(this));
-
- },
-
- parseElement: function() {
- this.ele.getChildren('span span.buttons img').each(function(img) {
- img.addEvent('click', this.actions[img.className].bind(this));
- }, this);
-
- //click on the directory name must open the directory itself
- this.ele.getElements('b')[0].addEvent('click', this.toggle.bind(this));
-
- //iterate over child directories
- var uls = this.ele.getElements('ul');
- if(uls.length > 0)
- {
- uls[0].getChildren("li.folder").each(function(fld) {
- var path = fld.getElements("input.path")[0].get("value");
- var name = fld.getElements("input.name")[0].get("value");
- this.directories.push(new Item(this, path, name, fld));
- }.bind(this));
- uls[0].getChildren("li.file").each(function(fld) {
- var path = fld.getElements("input.path")[0].get("value");
- var name = fld.getElements("input.name")[0].get("value");
- this.files.push(new Item(this, path, name, fld));
- }.bind(this));
- }
- },
-
- reorderElements: function() {
- //TODO sort the main ul again (to keep data ordered after renaming something)
- },
-
- del: function(event) {
- $("confirm_form").removeEvents("submit");
- $("confirm_form").addEvent("submit", this.deleteDirectory.bind(this));
-
- $$("#confirm_form p").set('html', '{{_(("Are you sure you want to delete the selected item?"))}}');
-
- show_confirm_box();
- event.stop();
- },
-
- deleteDirectory: function(event) {
- hide_confirm_box();
- new Request.JSON({
- method: 'POST',
- url: "/json/filemanager/delete",
- data: {"path": this.path, "name": this.name},
- onSuccess: function(data) {
- if(data.response == "success")
- {
- new Fx.Tween(this.ele).start('opacity', 0);
- var ul = this.ele.parentNode;
- this.ele.dispose();
- //if this was the only child, add a "empty folder" div
- if(!ul.getChildren('li')[0])
- {
- var div = new Element("div", { 'html': '{{ _("Folder is empty") }}' });
- div.replaces(ul);
- }
-
- indicateSuccess();
- } else
- {
- //error from json code...
- indicateFail();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send();
-
- event.stop();
- },
-
- rename: function(event) {
- $("rename_form").removeEvents("submit");
- $("rename_form").addEvent("submit", this.renameDirectory.bind(this));
-
- $("path").set("value", this.path);
- $("old_name").set("value", this.name);
- $("new_name").set("value", this.name);
-
- show_rename_box();
- event.stop();
- },
-
- renameDirectory: function(event) {
- hide_rename_box();
- new Request.JSON({
- method: 'POST',
- url: "/json/filemanager/rename",
- onSuccess: function(data) {
- if(data.response == "success")
- {
- this.name = $("new_name").get("value");
- this.ele.getElements("b")[0].set('html', $("new_name").get("value"));
- this.reorderElements();
- indicateSuccess();
- } else
- {
- //error from json code...
- indicateFail();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send($("rename_form").toQueryString());
-
- event.stop();
- },
-
- mkdir: function(event) {
- new Request.JSON({
- method: 'POST',
- url: "/json/filemanager/mkdir",
- data: {"path": this.path + "/" + this.name, "name": '{{_("New folder")}}'},
- onSuccess: function(data) {
- if(data.response == "success")
- {
- new Request.HTML({
- method: 'POST',
- url: "/filemanager/get_dir",
- data: {"path": data.path, "name": data.name},
- onSuccess: function(li) {
- //add node as first child of ul
- var ul = this.ele.getChildren('ul')[0];
- if(!ul)
- {
- //remove the "Folder Empty" div
- this.ele.getChildren('div').dispose();
-
- //create new ul to contain subfolder
- ul = new Element("ul");
- ul.inject(this.ele, 'bottom');
- }
- li[0].inject(ul, 'top');
-
- //add directory as a subdirectory of the current item
- this.directories.push(new Item(this.ui, data.path, data.name, ul.firstChild));
- }.bind(this),
- onFailure: indicateFail
- }).send();
- indicateSuccess();
- } else
- {
- //error from json code...
- indicateFail();
- }
- }.bind(this),
- onFailure: indicateFail
- }).send();
-
- event.stop();
- },
-
- toggle: function() {
- var child = this.ele.getElement('ul');
- if(child == null)
- child = this.ele.getElement('div');
-
- if(child != null)
- {
- if (child.getStyle('display') == "block") {
- child.dissolve();
- } else {
- child.reveal();
- }
- }
- }
-});
diff --git a/module/web/templates/default/folder.html b/module/web/templates/default/folder.html
deleted file mode 100644
index b385e80cb..000000000
--- a/module/web/templates/default/folder.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<li class="folder">
- <input type="hidden" name="path" class="path" value="{{ path }}" />
- <input type="hidden" name="name" class="name" value="{{ name }}" />
- <span>
- <b>{{ name }}</b>
- <span class="buttons" style="opacity:0">
- <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/media/default/img/add_folder.png" />
- </span>
- </span>
- <div style="display:none">{{ _("Folder is empty") }}</div>
-</li> \ No newline at end of file
diff --git a/module/web/templates/default/home.html b/module/web/templates/default/home.html
deleted file mode 100644
index 7359e326c..000000000
--- a/module/web/templates/default/home.html
+++ /dev/null
@@ -1,266 +0,0 @@
-{% extends 'default/base.html' %}
-{% block head %}
-
-<script type="text/javascript">
-
-var em;
-var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
-
-document.addEvent("domready", function(){
- em = new EntryManager();
-});
-
-var EntryManager = new Class({
- initialize: function(){
- this.json = new Request.JSON({
- url: "json/links",
- secure: false,
- async: true,
- onSuccess: this.update.bind(this),
- initialDelay: 0,
- delay: 2500,
- limit: 30000
- });
-
- this.ids = [{% for link in content %}
- {% if forloop.last %}
- {{ link.id }}
- {% else %}
- {{ link.id }},
- {% endif %}
- {% endfor %}];
-
- this.entries = [];
- this.container = $('LinksAktiv');
-
- this.parseFromContent();
-
- this.json.startTimer();
- },
- parseFromContent: function(){
- this.ids.each(function(id,index){
- var entry = new LinkEntry(id);
- entry.parse();
- this.entries.push(entry)
- }, this);
- },
- update: function(data){
-
- try{
- this.ids = this.entries.map(function(item){
- return item.fid
- });
-
- this.ids.filter(function(id){
- return !this.ids.contains(id)
- },data).each(function(id){
- var index = this.ids.indexOf(id);
- this.entries[index].remove();
- this.entries = this.entries.filter(function(item){return item.fid != this},id);
- this.ids = this.ids.erase(id)
- }, this);
-
- data.links.each(function(link, i){
- if (this.ids.contains(link.fid)){
-
- var index = this.ids.indexOf(link.fid);
- this.entries[index].update(link)
-
- }else{
- var entry = new LinkEntry(link.fid);
- entry.insert(link);
- this.entries.push(entry);
- this.ids.push(link.fid);
- this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
- entry.fade.start('opacity', 1);
- entry.fadeBar.start('opacity', 1);
-
- }
- }, this)
- }catch(e){
- //alert(e)
- }
- }
-});
-
-
-var LinkEntry = new Class({
- initialize: function(id){
- this.fid = id;
- this.id = id;
- },
- parse: function(){
- this.elements = {
- tr: $("link_{id}".substitute({id: this.id})),
- name: $("link_{id}_name".substitute({id: this.id})),
- status: $("link_{id}_status".substitute({id: this.id})),
- info: $("link_{id}_info".substitute({id: this.id})),
- bleft: $("link_{id}_bleft".substitute({id: this.id})),
- percent: $("link_{id}_percent".substitute({id: this.id})),
- remove: $("link_{id}_remove".substitute({id: this.id})),
- pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
- pgb: $("link_{id}_pgb".substitute({id: this.id}))
- };
- this.initEffects();
- },
- insert: function(item){
- try{
-
- this.elements = {
- tr: new Element('tr', {
- 'html': '',
- 'styles':{
- 'opacity': 0
- }
- }),
- name: new Element('td', {
- 'html': item.name
- }),
- status: new Element('td', {
- 'html': item.statusmsg
- }),
- info: new Element('td', {
- 'html': item.info
- }),
- bleft: new Element('td', {
- 'html': humanFileSize(item.size)
- }),
- percent: new Element('span', {
- 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
- }),
- remove: new Element('img',{
- 'src': 'media/default/img/control_cancel.png',
- 'styles':{
- 'vertical-align': 'middle',
- 'margin-right': '-20px',
- 'margin-left': '5px',
- 'margin-top': '-2px',
- 'cursor': 'pointer'
- }
- }),
- pgbTr: new Element('tr', {
- 'html':''
- }),
- pgb: new Element('div', {
- 'html': '&nbsp;',
- 'styles':{
- 'height': '4px',
- 'width': item.percent+'%',
- 'background-color': '#ddd'
- }
- })
- };
-
- this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
- this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
- this.initEffects();
- }catch(e){
- alert(e)
- }
- },
- initEffects: function(){
- if(!operafix)
- this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
- this.fade = new Fx.Tween(this.elements.tr);
- this.fadeBar = new Fx.Tween(this.elements.pgbTr);
-
- this.elements.remove.addEvent('click', function(){
- new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
- }.bind(this));
-
- },
- update: function(item){
- this.elements.name.set('text', item.name);
- this.elements.status.set('text', item.statusmsg);
- this.elements.info.set('text', item.info);
- this.elements.bleft.set('text', item.format_size);
- this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
- if(!operafix)
- {
- this.bar.start({
- 'width': item.percent,
- 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
- });
- }
- else
- {
- this.elements.pgb.set(
- 'styles', {
- 'height': '4px',
- 'width': item.percent+'%',
- 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
- });
- }
- },
- remove: function(){
- this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
- this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
-
- }
- });
-</script>
-
-{% endblock %}
-
-{% block subtitle %}
-{{_("Active Downloads")}}
-{% endblock %}
-
-{% block menu %}
-<li class="selected">
- <a href="/" title=""><img src="/media/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
-</li>
-<li>
- <a href="/queue/" title=""><img src="/media/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
-</li>
-<li>
- <a href="/collector/" title=""><img src="/media/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
-</li>
-<li>
- <a href="/downloads/" title=""><img src="/media/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
-</li>
-{#<li>#}
-{# <a href="/filemanager/" title=""><img src="/media/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
-{#</li>#}
-<li class="right">
- <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
-</li>
-<li class="right">
- <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><img src="/media/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
-</li>
-{% endblock %}
-
-{% block content %}
-<table width="100%" class="queue">
- <thead>
- <tr class="header">
- <th>{{_("Name")}}</th>
- <th>{{_("Status")}}</th>
- <th>{{_("Information")}}</th>
- <th>{{_("Size")}}</th>
- <th>{{_("Progress")}}</th>
- </tr>
- </thead>
- <tbody id="LinksAktiv">
-
- {% for link in content %}
- <tr id="link_{{ link.id }}">
- <td id="link_{{ link.id }}_name">{{ link.name }}</td>
- <td id="link_{{ link.id }}_status">{{ link.status }}</td>
- <td id="link_{{ link.id }}_info">{{ link.info }}</td>
- <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
- <td>
- <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
- <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="media/default/img/control_cancel.png"/>
- </td>
- </tr>
- <tr id="link_{{ link.id }}_pgb_tr">
- <td colspan="5">
- <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
- </td>
- </tr>
- {% endfor %}
-
- </tbody>
-</table>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/info.html b/module/web/templates/default/info.html
deleted file mode 100644
index 77ae57376..000000000
--- a/module/web/templates/default/info.html
+++ /dev/null
@@ -1,81 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
- <script type="text/javascript">
- window.addEvent("domready", function() {
- var ul = new Element('ul#twitter_update_list');
- var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.js][type=text/javascript]');
- var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
- $("twitter").adopt(ul, script1, script2);
- });
- </script>
-{% endblock %}
-
-{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Information") }}{% endblock %}
-
-{% block content %}
- <h3>{{ _("News") }}</h3>
- <div id="twitter"></div>
-
- <h3>{{ _("Support") }}</h3>
-
- <ul>
- <li style="font-weight:bold;">
- <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
- &nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="http://forum.pyload.org/" target="_blank">Forum</a>
- &nbsp;&nbsp;|&nbsp;&nbsp;
- <a href="http://pyload.org/irc/" target="_blank">Chat</a>
- </li>
- <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
- <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
- <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
-
- </ul>
-
- <h3>{{ _("System") }}</h3>
- <table class="system">
- <tr>
- <td>{{ _("Python:") }}</td>
- <td>{{ python }}</td>
- </tr>
- <tr>
- <td>{{ _("OS:") }}</td>
- <td>{{ os }}</td>
- </tr>
- <tr>
- <td>{{ _("pyLoad version:") }}</td>
- <td>{{ version }}</td>
- </tr>
- <tr>
- <td>{{ _("Installation Folder:") }}</td>
- <td>{{ folder }}</td>
- </tr>
- <tr>
- <td>{{ _("Config Folder:") }}</td>
- <td>{{ config }}</td>
- </tr>
- <tr>
- <td>{{ _("Download Folder:") }}</td>
- <td>{{ download }}</td>
- </tr>
- <tr>
- <td>{{ _("Free Space:") }}</td>
- <td>{{ freespace }}</td>
- </tr>
- <tr>
- <td>{{ _("Language:") }}</td>
- <td>{{ language }}</td>
- </tr>
- <tr>
- <td>{{ _("Webinterface Port:") }}</td>
- <td>{{ webif }}</td>
- </tr>
- <tr>
- <td>{{ _("Remote Interface Port:") }}</td>
- <td>{{ remote }}</td>
- </tr>
- </table>
-
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/login.html b/module/web/templates/default/login.html
deleted file mode 100644
index 9e91ad309..000000000
--- a/module/web/templates/default/login.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
-
-{% block content %}
-
-<div class="centeralign">
-<form action="" method="post" accept-charset="utf-8" id="login">
- <div class="no">
- <input type="hidden" name="do" value="login" />
- <fieldset>
- <legend>Login</legend>
- <label>
- <span>{{_("Username")}}</span>
- <input type="text" size="20" name="username" />
- </label>
- <br />
- <label>
- <span>{{_("Password")}}</span>
- <input type="password" size="20" name="password" />
- </label>
- <br />
- <input type="submit" value="Login" class="button" />
- </fieldset>
- </div>
-</form>
-
-{% if errors %}
-<p>{{_("Your username and password didn't match. Please try again.")}}</p>
- {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b>
-{% endif %}
-
-</div>
-<br>
-
-{% endblock %}
diff --git a/module/web/templates/default/logout.html b/module/web/templates/default/logout.html
deleted file mode 100644
index d3f07472b..000000000
--- a/module/web/templates/default/logout.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block head %}
-<meta http-equiv="refresh" content="3; url=/">
-{% endblock %}
-
-{% block content %}
-<p><b>{{_("You were successfully logged out.")}}</b></p>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/logs.html b/module/web/templates/default/logs.html
deleted file mode 100644
index d6288df0e..000000000
--- a/module/web/templates/default/logs.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
-{% block subtitle %}{{_("Logs")}}{% endblock %}
-{% block head %}
-<link rel="stylesheet" type="text/css" href="/media/default/css/log.css"/>
-{% endblock %}
-
-{% block content %}
-<div style="clear: both;"></div>
-
-<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
-<div class="logperpage">
- <form id="logform1" action="" method="POST">
- <label for="reversed">Reversed:</label>
- <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
- <label for="perpage">Lines per page:</label>
- <select name="perpage" onchange="this.form.submit();">
- {% for value in perpage_p %}
- <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
- {% endfor %}
- </select>
- </form>
-</div>
-<div class="logwarn">{{warning}}</div>
-<div style="clear: both;"></div>
-<div class="logdiv">
- <table class="logtable" cellpadding="0" cellspacing="0">
- {% for line in log %}
- <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
- {% endfor %}
- </table>
-</div>
-<div class="logform">
-<form id="logform2" action="" method="POST">
- <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
- <input type="submit" value="ok" />
-</form>
-</div>
-<div style="clear: both; height: 10px;">&nbsp; </div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/pathchooser.html b/module/web/templates/default/pathchooser.html
deleted file mode 100644
index d00637055..000000000
--- a/module/web/templates/default/pathchooser.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<html>
-<head>
- <script class="javascript">
- function chosen()
- {
- opener.ifield.value = document.forms[0].p.value;
- close();
- }
- function exit()
- {
- close();
- }
- function setInvalid() {
- document.forms[0].send.disabled = 'disabled';
- document.forms[0].p.style.color = '#FF0000';
- }
- function setValid() {
- document.forms[0].send.disabled = '';
- document.forms[0].p.style.color = '#000000';
- }
- function setFile(file)
- {
- document.forms[0].p.value = file;
- setValid();
-
- }
- </script>
- <link rel="stylesheet" type="text/css" href="/media/default/css/pathchooser.css"/>
-</head>
-<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
-<center>
- <div id="paths">
- <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
- <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
- <input type="submit" value="Ok" name="send">
- </form>
-
- {% if type == 'folder' %}
- <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
- {% else %}
- <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
- {% endif %}
- </div>
- <table border="0" cellspacing="0" cellpadding="3">
- <tr>
- <th>{{_("name")}}</th>
- <th>{{_("size")}}</th>
- <th>{{_("type")}}</th>
- <th>{{_("last modified")}}</th>
- </tr>
- {% if parentdir %}
- <tr>
- <td colspan="4">
- <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
- </td>
- </tr>
- {% endif %}
-{% for file in files %}
- <tr>
- {% if type == 'folder' %}
- <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
- {% else %}
- <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
- {% endif %}
- <td class="size">{{ file.size|float|filesizeformat }}</td>
- <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
- <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
- <tr>
-<!-- <tr>
- <td colspan="4">{{_("no content")}}</td>
- </tr> -->
-{% endfor %}
- </table>
- </center>
-</body>
-</html> \ No newline at end of file
diff --git a/module/web/templates/default/queue.html b/module/web/templates/default/queue.html
deleted file mode 100644
index 046abbe49..000000000
--- a/module/web/templates/default/queue.html
+++ /dev/null
@@ -1,104 +0,0 @@
-{% extends 'default/base.html' %}
-{% block head %}
-
-<script type="text/javascript" src="/media/js/package_ui.js"></script>
-
-<script type="text/javascript">
-
-document.addEvent("domready", function(){
- var pUI = new PackageUI("url", {{ target }});
-});
-</script>
-{% endblock %}
-
-{% if target %}
- {% set name = _("Queue") %}
-{% else %}
- {% set name = _("Collector") %}
-{% endif %}
-
-{% block title %}{{name}} - {{super()}} {% endblock %}
-{% block subtitle %}{{name}}{% endblock %}
-
-{% block pageactions %}
-<ul id="page-actions-more">
- <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
- <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
-</ul>
-{% endblock %}
-
-{% block content %}
-{% autoescape true %}
-
-<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
-{% for package in content %}
- <li>
-<div id="package_{{package.pid}}" class="package">
- <div class="order" style="display: none;">{{ package.order }}</div>
-
- <div class="packagename" style="cursor: pointer">
- <img class="package_drag" src="/media/default/img/folder.png" style="cursor: move; margin-bottom: -2px">
- <span class="name">{{package.name}}</span>
- &nbsp;&nbsp;
- <span class="buttons" style="opacity:0">
- <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/media/default/img/delete.png" />
- &nbsp;&nbsp;
- <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/arrow_refresh.png" />
- &nbsp;&nbsp;
- <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/pencil.png" />
- &nbsp;&nbsp;
- <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/media/default/img/package_go.png" />
- </span>
- </div>
- {% set progress = (package.linksdone * 100) / package.linkstotal %}
-
- <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
- <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
- <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
- {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
- <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
- {{ package.linksdone }} / {{ package.linkstotal }}</label>
- </div>
- <div style="clear: both; margin-bottom: -10px"></div>
-
- <div id="children_{{package.pid}}" style="display: none;" class="children">
- <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
- <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
- </ul>
- </div>
-</div>
- </li>
-{% endfor %}
-</ul>
-{% endautoescape %}
-{% endblock %}
-
-{% block hidden %}
-<div id="pack_box" class="window_box" style="z-index: 2">
- <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
- <h1>{{_("Edit Package")}}</h1>
- <p>{{_("Edit the package detais below.")}}</p>
- <input name="pack_id" id="pack_id" type="hidden" value=""/>
- <label for="pack_name">{{_("Name")}}
- <span class="small">{{_("The name of the package.")}}</span>
- </label>
- <input id="pack_name" name="pack_name" type="text" size="20" />
-
- <label for="pack_folder">{{_("Folder")}}
- <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
- </label>
- <input id="pack_folder" name="pack_folder" type="text" size="20" />
-
- <label for="pack_pws">{{_("Password")}}
- <span class="small">{{_("List of passwords used for unrar.")}}</span>
- </label>
- <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
-
- <button type="submit">{{_("Submit")}}</button>
- <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
- <div class="spacer"></div>
-
- </form>
-
-</div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/settings.html b/module/web/templates/default/settings.html
deleted file mode 100644
index a4443025a..000000000
--- a/module/web/templates/default/settings.html
+++ /dev/null
@@ -1,204 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Config") }}{% endblock %}
-
-{% block head %}
- <script type="text/javascript" src="/media/js/tinytab_static.js"></script>
- <script type="text/javascript" src="/media/js/MooDropMenu_static.js"></script>
- <script type="text/javascript" src="/media/js/settings.js"></script>
-
-{% endblock %}
-
-{% block content %}
-
- <ul id="toptabs" class="tabs">
- <li><a class="selected" href="#">{{ _("General") }}</a></li>
- <li><a href="#">{{ _("Plugins") }}</a></li>
- <li><a href="#">{{ _("Accounts") }}</a></li>
- </ul>
-
- <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
-
- </div>
-
- <span id="tabs-body">
- <!-- General -->
- <span id="general" class="active tabContent">
- <ul class="nav tabs">
- <li class>
- <a>Menu</a>
- <ul id="general-menu">
- {% for entry,name in conf.general %}
- <nobr>
- <li id="general|{{ entry }}">{{ name }}</li>
- </nobr>
- <br>
- {% endfor %}
- </ul>
- </li>
- </ul>
-
- <form id="general_form" action="" method="POST" autocomplete="off">
- <span id="general_form_content">
- <br>
- <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
- <br>
- </span>
-
- <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
- </form>
- </span>
-
- <!-- Plugins -->
- <span id="plugins" class="tabContent">
- <ul class="nav tabs">
- <li class>
- <a>Menu</a>
- <ul id="plugin-menu">
- {% for entry,name in conf.plugin %}
- <nobr>
- <li id="plugin|{{ entry }}">{{ name }}</li>
- </nobr>
- <br>
- {% endfor %}
- </ul>
- </li>
- </ul>
-
-
- <form id="plugin_form" action="" method="POST" autocomplete="off">
-
- <span id="plugin_form_content">
- <br>
- <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
- <br>
- </span>
- <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
- </form>
-
- </span>
-
- <!-- Accounts -->
- <span id="accounts" class="tabContent">
- <form id="account_form" action="/json/update_accounts" method="POST">
-
- <table class="settable wide">
-
- <thead>
- <tr>
- <th>{{ _("Plugin") }}</th>
- <th>{{ _("Name") }}</th>
- <th>{{ _("Password") }}</th>
- <th>{{ _("Status") }}</th>
- <th>{{ _("Premium") }}</th>
- <th>{{ _("Valid until") }}</th>
- <th>{{ _("Traffic left") }}</th>
- <th>{{ _("Time") }}</th>
- <th>{{ _("Max Parallel") }}</th>
- <th>{{ _("Delete?") }}</th>
- </tr>
- </thead>
-
-
- {% for account in conf.accs %}
- {% set plugin = account.type %}
- <tr>
- <td>
- <span style="padding:5px">{{ plugin }}</span>
- </td>
-
- <td><label for="{{plugin}}|password;{{account.login}}"
- style="color:#424242;">{{ account.login }}</label></td>
- <td>
- <input id="{{plugin}}|password;{{account.login}}"
- name="{{plugin}}|password;{{account.login}}"
- type="password" value="{{account.password}}" size="12"/>
- </td>
- <td>
- {% if account.valid %}
- <span style="font-weight: bold; color: #006400;">
- {{ _("valid") }}
- {% else %}
- <span style="font-weight: bold; color: #8b0000;">
- {{ _("not valid") }}
- {% endif %}
- </span>
- </td>
- <td>
- {% if account.premium %}
- <span style="font-weight: bold; color: #006400;">
- {{ _("yes") }}
- {% else %}
- <span style="font-weight: bold; color: #8b0000;">
- {{ _("no") }}
- {% endif %}
- </span>
- </td>
- <td>
- <span style="font-weight: bold;">
- {{ account.validuntil }}
- </span>
- </td>
- <td>
- <span style="font-weight: bold;">
- {{ account.trafficleft }}
- </span>
- </td>
- <td>
- <input id="{{plugin}}|time;{{account.login}}"
- name="{{plugin}}|time;{{account.login}}" type="text"
- size="7" value="{{account.time}}"/>
- </td>
- <td>
- <input id="{{plugin}}|limitdl;{{account.login}}"
- name="{{plugin}}|limitdl;{{account.login}}" type="text"
- size="2" value="{{account.limitdl}}"/>
- </td>
- <td>
- <input id="{{plugin}}|delete;{{account.login}}"
- name="{{plugin}}|delete;{{account.login}}" type="checkbox"
- value="True"/>
- </td>
- </tr>
- {% endfor %}
- </table>
-
- <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
- <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
- </form>
- </span>
- </span>
-{% endblock %}
-{% block hidden %}
-<div id="account_box" class="window_box" style="z-index: 2">
-<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
-<h1>{{_("Add Account")}}</h1>
-<p>{{_("Enter your account data to use premium features.")}}</p>
-<label for="account_login">{{_("Login")}}
-<span class="small">{{_("Your username.")}}</span>
-</label>
-<input id="account_login" name="account_login" type="text" size="20" />
-
-<label for="account_password">{{_("Password")}}
-<span class="small">{{_("The password for this account.")}}</span>
-</label>
-<input id="account_password" name="account_password" type="password" size="20" />
-
-<label for="account_type">{{_("Type")}}
-<span class="small">{{_("Choose the hoster for your account.")}}</span>
-</label>
- <select name=account_type id="account_type">
- {% for type in types|sort %}
- <option value="{{ type }}">{{ type }}</option>
- {% endfor %}
- </select>
-
-<button id="account_add_button" type="submit">{{_("Add")}}</button>
-<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
-<div class="spacer"></div>
-
-</form>
-
-</div>
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/setup.html b/module/web/templates/default/setup.html
deleted file mode 100644
index 39ef6f1e8..000000000
--- a/module/web/templates/default/setup.html
+++ /dev/null
@@ -1,13 +0,0 @@
-{% extends 'default/base.html' %}
-
-{% block title %}{{ _("Setup") }} - {{ super() }} {% endblock %}
-{% block subtitle %}{{ _("Setup") }}{% endblock %}
-{% block headpanel %}Welcome to pyLoad{% endblock %}
-{% block menu %}
- <li style="height: 25px"> <!-- Needed to get enough margin -->
- </li>
-{% endblock %}
-
-{% block content %}
- Comming Soon.
-{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/window.html b/module/web/templates/default/window.html
deleted file mode 100644
index a11323fe0..000000000
--- a/module/web/templates/default/window.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
-
-<div id="add_box" class="window_box">
-<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
-<h1>{{_("Add Package")}}</h1>
-<p>{{_("Paste your links or upload a container.")}}</p>
-<label for="add_name">{{_("Name")}}
-<span class="small">{{_("The name of the new package.")}}</span>
-</label>
-<input id="add_name" name="add_name" type="text" size="20" />
-
-<label for="add_links">{{_("Links")}}
-<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
-<span class="small"> {{ _("Filter urls") }}
-<img alt="URIParsing" Title="Parse Uri" src="/media/default/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
-</span>
-
-</label>
-<textarea rows="5" name="add_links" id="add_links"></textarea>
-
-<label for="add_password">{{_("Password")}}
- <span class="small">{{_("Password for RAR-Archive")}}</span>
-</label>
-<input id="add_password" name="add_password" type="text" size="20">
-
-<label>{{_("File")}}
-<span class="small">{{_("Upload a container.")}}</span>
-</label>
-<input type="file" name="add_file" id="add_file"/>
-
-<label for="add_dest">{{_("Destination")}}
-</label>
-<span class="cont">
- {{_("Queue")}}
- <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
- {{_("Collector")}}
- <input type="radio" name="add_dest" id="add_dest2" value="0"/>
-</span>
-
-<button type="submit">{{_("Add Package")}}</button>
-<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
-<div class="spacer"></div>
-
-</form>
-
-</div> \ No newline at end of file
diff --git a/module/web/utils.py b/module/web/utils.py
deleted file mode 100644
index a89c87558..000000000
--- a/module/web/utils.py
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this plrogram; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-from bottle import request, HTTPError, redirect, ServerAdapter
-
-from webinterface import env, TEMPLATE
-
-from module.Api import has_permission, PERMS, ROLE
-
-def render_to_response(name, args={}, proc=[]):
- for p in proc:
- args.update(p())
-
- t = env.get_template(TEMPLATE + "/" + name)
- return t.render(**args)
-
-
-def parse_permissions(session):
- perms = dict([(x, False) for x in dir(PERMS) if not x.startswith("_")])
- perms["ADMIN"] = False
- perms["is_admin"] = False
-
- if not session.get("authenticated", False):
- return perms
-
- if session.get("role") == ROLE.ADMIN:
- for k in perms.iterkeys():
- perms[k] = True
-
- elif session.get("perms"):
- p = session.get("perms")
- get_permission(perms, p)
-
- return perms
-
-
-def permlist():
- return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"]
-
-
-def get_permission(perms, p):
- """Returns a dict with permission key
-
- :param perms: dictionary
- :param p: bits
- """
- for name in permlist():
- perms[name] = has_permission(p, getattr(PERMS, name))
-
-
-def set_permission(perms):
- """generates permission bits from dictionary
-
- :param perms: dict
- """
- permission = 0
- for name in dir(PERMS):
- if name.startswith("_"): continue
-
- if name in perms and perms[name]:
- permission |= getattr(PERMS, name)
-
- return permission
-
-
-def set_session(request, info):
- s = request.environ.get('beaker.session')
- s["authenticated"] = True
- s["user_id"] = info["id"]
- s["name"] = info["name"]
- s["role"] = info["role"]
- s["perms"] = info["permission"]
- s["template"] = info["template"]
- s.save()
-
- return s
-
-
-def parse_userdata(session):
- return {"name": session.get("name", "Anonymous"),
- "is_admin": True if session.get("role", 1) == 0 else False,
- "is_authenticated": session.get("authenticated", False)}
-
-
-def login_required(perm=None):
- def _dec(func):
- def _view(*args, **kwargs):
- s = request.environ.get('beaker.session')
- if s.get("name", None) and s.get("authenticated", False):
- if perm:
- perms = parse_permissions(s)
- if perm not in perms or not perms[perm]:
- if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
- return HTTPError(403, "Forbidden")
- else:
- return redirect("/nopermission")
-
- return func(*args, **kwargs)
- else:
- if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
- return HTTPError(403, "Forbidden")
- else:
- return redirect("/login")
-
- return _view
-
- return _dec
-
-
-def toDict(obj):
- ret = {}
- for att in obj.__slots__:
- ret[att] = getattr(obj, att)
- return ret
-
-
-class CherryPyWSGI(ServerAdapter):
- def run(self, handler):
- from wsgiserver import CherryPyWSGIServer
-
- server = CherryPyWSGIServer((self.host, self.port), handler)
- server.start()
diff --git a/module/web/webinterface.py b/module/web/webinterface.py
deleted file mode 100644
index ec8b2e56c..000000000
--- a/module/web/webinterface.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: RaNaN
-"""
-
-import sys
-import module.common.pylgettext as gettext
-
-import os
-from os.path import join, abspath, dirname, exists
-from os import makedirs
-
-PROJECT_DIR = abspath(dirname(__file__))
-PYLOAD_DIR = abspath(join(PROJECT_DIR, "..", ".."))
-
-sys.path.append(PYLOAD_DIR)
-
-from module import InitHomeDir
-from module.utils import decode, formatSize
-
-import bottle
-from bottle import run, app
-
-from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache
-from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware
-
-SETUP = None
-PYLOAD = None
-
-from module.web import ServerThread
-
-if not ServerThread.core:
- if ServerThread.setup:
- SETUP = ServerThread.setup
- config = SETUP.config
- else:
- raise Exception("Could not access pyLoad Core")
-else:
- PYLOAD = ServerThread.core.api
- config = ServerThread.core.config
-
-from module.common.JsEngine import JsEngine
-
-JS = JsEngine()
-
-TEMPLATE = config.get('webinterface', 'template')
-DL_ROOT = config.get('general', 'download_folder')
-LOG_ROOT = config.get('log', 'log_folder')
-PREFIX = config.get('webinterface', 'prefix')
-
-if PREFIX:
- PREFIX = PREFIX.rstrip("/")
- if not PREFIX.startswith("/"):
- PREFIX = "/" + PREFIX
-
-DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv
-bottle.debug(DEBUG)
-
-cache = join("tmp", "jinja_cache")
-if not exists(cache):
- makedirs(cache)
-
-bcc = FileSystemBytecodeCache(cache, '%s.cache')
-loader = PrefixLoader({
- "default": FileSystemLoader(join(PROJECT_DIR, "templates", "default")),
- 'js': FileSystemLoader(join(PROJECT_DIR, 'media', 'js'))
-})
-
-env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False,
- bytecode_cache=bcc)
-
-from filters import quotepath, path_make_relative, path_make_absolute, truncate, date
-
-env.filters["quotepath"] = quotepath
-env.filters["truncate"] = truncate
-env.filters["date"] = date
-env.filters["path_make_relative"] = path_make_relative
-env.filters["path_make_absolute"] = path_make_absolute
-env.filters["decode"] = decode
-env.filters["type"] = lambda x: str(type(x))
-env.filters["formatsize"] = formatSize
-env.filters["getitem"] = lambda x, y: x.__getitem__(y)
-if PREFIX:
- env.filters["url"] = lambda x: x
-else:
- env.filters["url"] = lambda x: PREFIX + x if x.startswith("/") else x
-
-gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
-translation = gettext.translation("django", join(PYLOAD_DIR, "locale"),
- languages=[config.get("general", "language"), "en"],fallback=True)
-translation.install(True)
-env.install_gettext_translations(translation)
-
-from beaker.middleware import SessionMiddleware
-
-session_opts = {
- 'session.type': 'file',
- 'session.cookie_expires': False,
- 'session.data_dir': './tmp',
- 'session.auto': False
-}
-
-web = StripPathMiddleware(SessionMiddleware(app(), session_opts))
-web = GZipMiddleWare(web)
-
-if PREFIX:
- web = PrefixMiddleware(web, prefix=PREFIX)
-
-import pyload_app
-import json_app
-import cnl_app
-import api_app
-
-def run_simple(host="0.0.0.0", port="8000"):
- run(app=web, host=host, port=port, quiet=True)
-
-
-def run_lightweight(host="0.0.0.0", port="8000"):
- run(app=web, host=host, port=port, quiet=True, server="bjoern")
-
-
-def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""):
- from wsgiserver import CherryPyWSGIServer
-
- if cert and key:
- CherryPyWSGIServer.ssl_certificate = cert
- CherryPyWSGIServer.ssl_private_key = key
-
- CherryPyWSGIServer.numthreads = theads
-
- from utils import CherryPyWSGI
-
- run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True)
-
-
-def run_fcgi(host="0.0.0.0", port="8000"):
- from bottle import FlupFCGIServer
-
- run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True)
-
-
-if __name__ == "__main__":
- run(app=web, port=8001)
diff --git a/pavement.py b/pavement.py
index ac9a6fa1a..9b2dc98b3 100644
--- a/pavement.py
+++ b/pavement.py
@@ -1,12 +1,15 @@
# -*- coding: utf-8 -*-
-
from paver.easy import *
from paver.setuputils import setup
from paver.doctools import cog
+import os
import sys
+import shutil
import re
+from glob import glob
+from tempfile import mkdtemp
from urllib import urlretrieve
from subprocess import call, Popen, PIPE
from zipfile import ZipFile
@@ -15,7 +18,7 @@ PROJECT_DIR = path(__file__).dirname()
sys.path.append(PROJECT_DIR)
options = environment.options
-path('pyload').mkdir()
+path("pyload").mkdir()
extradeps = []
if sys.version_info <= (2, 5):
@@ -23,24 +26,24 @@ if sys.version_info <= (2, 5):
setup(
name="pyload",
- version="0.4.9",
+ version="0.4.10",
description='Fast, lightweight and full featured download manager.',
- long_description=open(PROJECT_DIR / "README").read(),
- keywords = ('pyload', 'download-manager', 'one-click-hoster', 'download'),
+ long_description=open(PROJECT_DIR / "README.md").read(),
+ keywords = ("pyload", "download-manager", "one-click-hoster", "download"),
url="http://pyload.org",
download_url='http://pyload.org/download',
license='GPL v3',
author="pyLoad Team",
author_email="support@pyload.org",
platforms = ('Any',),
- #package_dir={'pyload': 'src'},
- packages=['pyload'],
+ #package_dir={'pyload': "src"},
+ packages=["pyload"],
#package_data=find_package_data(),
#data_files=[],
include_package_data=True,
- exclude_package_data={'pyload': ['docs*', 'scripts*', 'tests*']}, #exluced from build but not from sdist
+ exclude_package_data={'pyload': ["docs*", "scripts*", "tests*"]}, #exluced from build but not from sdist
# 'bottle >= 0.10.0' not in list, because its small and contain little modifications
- install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker', 'BeautifulSoup>=3.2, <3.3'] + extradeps,
+ install_requires=['thrift >= 0.8.0', 'jinja2', 'pycurl', 'Beaker', 'BeautifulSoup >= 3.2, < 3.3'] + extradeps,
extras_require={
'SSL': ["pyOpenSSL"],
'DLC': ['pycrypto'],
@@ -91,15 +94,16 @@ options(
)
# xgettext args
-xargs = ["--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyLoad",
+xargs = ["--language=Python", "--add-comments=L10N",
+ "--from-code=utf-8", "--copyright-holder=pyLoad Team", "--package-name=pyLoad",
"--package-version=%s" % options.version, "--msgid-bugs-address='bugs@pyload.org'"]
@task
@needs('cog')
def html():
"""Build html documentation"""
- module = path("docs") / "module"
- module.rmtree()
+ module = path("docs") / "pyload"
+ pyload.rmtree()
call_task('paver.doctools.html')
@@ -136,7 +140,7 @@ def get_source(options):
file.chmod(0755)
(pyload / ".hgtags").remove()
- (pyload / ".hgignore").remove()
+ (pyload / ".gitignore").remove()
#(pyload / "docs").rmtree()
f = open(pyload / "__init__.py", "wb")
@@ -162,10 +166,10 @@ def thrift(options):
print "add import for TApplicationException manually as long it is not fixed"
- outdir = path("module") / "remote" / "thriftbackend"
+ outdir = path("pyload") / "remote" / "thriftbackend"
(outdir / "gen-py").rmtree()
- cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots,dynamic", outdir / "pyload.thrift"]
+ cmd = [options.thrift.path, "-strict", "-o", outdir, "--gen", "py:slots, dynamic", outdir / "pyload.thrift"]
if options.gen:
cmd.insert(len(cmd) - 1, "--gen")
@@ -180,14 +184,14 @@ def thrift(options):
(outdir / "gen-py").move(outdir / "thriftgen")
#create light ttypes
- from module.remote.socketbackend.create_ttypes import main
+ from pyload.remote.socketbackend.create_ttypes import main
main()
@task
def compile_js():
""" Compile .coffee files to javascript"""
- root = path("module") / "web" / "media" / "js"
+ root = path("pyload") / "web" / "media" / "js"
for f in root.glob("*.coffee"):
print "generate", f
coffee = Popen(["coffee", "-cbs"], stdin=open(f, "rb"), stdout=PIPE)
@@ -202,22 +206,21 @@ def compile_js():
@task
def generate_locale():
- """ Generates localisation files """
+ """ Generates localization files """
- EXCLUDE = ["BeautifulSoup.py", "module/gui", "module/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload",
+ EXCLUDE = ["BeautifulSoup.py", "pyload/cli", "web/locale", "web/ajax", "web/cnl", "web/pyload",
"setup.py"]
- makepot("core", path("module"), EXCLUDE, "./pyLoadCore.py\n")
+ makepot("core", path("pyload"), EXCLUDE, "./pyload.py\n")
- makepot("gui", path("module") / "gui", [], includes="./pyLoadGui.py\n")
- makepot("cli", path("module") / "cli", [], includes="./pyLoadCli.py\n")
- makepot("setup", "", [], includes="./module/setup.py\n")
+ makepot("cli", path("pyload") / "cli", [], includes="./pyload-cli.py\n")
+ makepot("setup", "", [], includes="./pyload/setup.py\n")
EXCLUDE = ["ServerThread.py", "web/media/default"]
# strings from js files
strings = set()
- for fi in path("module/web").walkfiles():
+ for fi in path("pyload/web").walkfiles():
if not fi.name.endswith(".js") and not fi.endswith(".coffee"): continue
with open(fi, "rb") as c:
content = c.read()
@@ -225,13 +228,13 @@ def generate_locale():
strings.update(re.findall(r"_\s*\(\s*\"([^\"]+)", content))
strings.update(re.findall(r"_\s*\(\s*\'([^\']+)", content))
- trans = path("module") / "web" / "translations.js"
+ trans = path("pyload") / "web" / "translations.js"
with open(trans, "wb") as js:
for s in strings:
js.write('_("%s")\n' % s)
- makepot("django", path("module/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"])
+ makepot("django", path("pyload/web"), EXCLUDE, "./%s\n" % trans.relpath(), [".py", ".html"], ["--language=Python"])
trans.remove()
@@ -241,6 +244,83 @@ def generate_locale():
@task
+@cmdopts([
+ ('key=', 'k', 'api key')
+])
+def upload_translations(options):
+ """ Uploads the locale files to translation server """
+ tmp = path(mkdtemp())
+
+ shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ content = open(config, 'rb').read()
+ content = content.format(key=options.key, tmp=tmp)
+ f = open(config, 'wb')
+ f.write(content)
+ f.close()
+
+ call(['crowdin-cli', '-c', config, 'upload', 'source'])
+
+ shutil.rmtree(tmp)
+
+ print "Translations uploaded"
+
+
+@task
+@cmdopts([
+ ('key=', 'k', 'api key')
+])
+def download_translations(options):
+ """ Downloads the translated files from translation server """
+ tmp = path(mkdtemp())
+
+ shutil.copy('locale/crowdin.yaml', tmp)
+ os.mkdir(tmp / 'pyLoad')
+ for f in glob('locale/*.pot'):
+ if os.path.isfile(f):
+ shutil.copy(f, tmp / 'pyLoad')
+
+ config = tmp / 'crowdin.yaml'
+ content = open(config, 'rb').read()
+ content = content.format(key=options.key, tmp=tmp)
+ f = open(config, 'wb')
+ f.write(content)
+ f.close()
+
+ call(['crowdin-cli', '-c', config, 'download'])
+
+ for language in (tmp / 'pyLoad').listdir():
+ if not language.isdir():
+ continue
+
+ target = path('locale') / language.basename()
+ print "Copy language %s" % target
+ if target.exists():
+ shutil.rmtree(target)
+
+ shutil.copytree(language, target)
+
+ shutil.rmtree(tmp)
+
+
+@task
+def compile_translations():
+ """ Compile PO files to MO """
+ for language in path('locale').listdir():
+ if not language.isdir():
+ continue
+
+ for f in glob(language / 'LC_MESSAGES' / '*.po'):
+ print "Compiling %s" % f
+ call(['msgfmt', '-o', f.replace('.po', '.mo'), f])
+
+
+@task
def tests():
call(["nosetests2"])
diff --git a/pyLoadCli.py b/pyLoadCli.py
deleted file mode 100755
index 079cee19c..000000000
--- a/pyLoadCli.py
+++ /dev/null
@@ -1,590 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-#Copyright (C) 2011 RaNaN
-#
-#This program is free software; you can redistribute it and/or modify
-#it under the terms of the GNU General Public License as published by
-#the Free Software Foundation; either version 3 of the License,
-#or (at your option) any later version.
-#
-#This program is distributed in the hope that it will be useful,
-#but WITHOUT ANY WARRANTY; without even the implied warranty of
-#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-#See the GNU General Public License for more details.
-#
-#You should have received a copy of the GNU General Public License
-# along with this program; if not, see <http://www.gnu.org/licenses/>.
-#
-###
-from __future__ import with_statement
-from getopt import GetoptError, getopt
-
-import module.common.pylgettext as gettext
-import os
-from os import _exit
-from os.path import join, exists, abspath, basename
-import sys
-from sys import exit
-from threading import Thread, Lock
-from time import sleep
-from traceback import print_exc
-
-import ConfigParser
-
-from codecs import getwriter
-
-if os.name == "nt":
- enc = "cp850"
-else:
- enc = "utf8"
-
-sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
-
-from module import InitHomeDir
-from module.cli.printer import *
-from module.cli import AddPackage, ManageFiles
-
-from module.Api import Destination
-from module.utils import formatSize, decode
-from module.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed
-from module.lib.Getch import Getch
-from module.lib.rename_process import renameProcess
-
-class Cli:
- def __init__(self, client, command):
- self.client = client
- self.command = command
-
- if not self.command:
- renameProcess('pyLoadCli')
- self.getch = Getch()
- self.input = ""
- self.inputline = 0
- self.lastLowestLine = 0
- self.menuline = 0
-
- self.lock = Lock()
-
- #processor funcions, these will be changed dynamically depending on control flow
- self.headerHandler = self #the download status
- self.bodyHandler = self #the menu section
- self.inputHandler = self
-
- os.system("clear")
- println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- println(2, "")
-
- self.thread = RefreshThread(self)
- self.thread.start()
-
- self.start()
- else:
- self.processCommand()
-
- def reset(self):
- """ reset to initial main menu """
- self.input = ""
- self.headerHandler = self.bodyHandler = self.inputHandler = self
-
- def start(self):
- """ main loop. handle input """
- while True:
- #inp = raw_input()
- inp = self.getch.impl()
- if ord(inp) == 3:
- os.system("clear")
- sys.exit() # ctrl + c
- elif ord(inp) == 13: #enter
- try:
- self.lock.acquire()
- self.inputHandler.onEnter(self.input)
-
- except Exception, e:
- println(2, red(e))
- finally:
- self.lock.release()
-
- elif ord(inp) == 127:
- self.input = self.input[:-1] #backspace
- try:
- self.lock.acquire()
- self.inputHandler.onBackSpace()
- finally:
- self.lock.release()
-
- elif ord(inp) == 27: #ugly symbol
- pass
- else:
- self.input += inp
- try:
- self.lock.acquire()
- self.inputHandler.onChar(inp)
- finally:
- self.lock.release()
-
- self.inputline = self.bodyHandler.renderBody(self.menuline)
- self.renderFooter(self.inputline)
-
-
- def refresh(self):
- """refresh screen"""
-
- println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- println(2, "")
-
- self.lock.acquire()
-
- self.menuline = self.headerHandler.renderHeader(3) + 1
- println(self.menuline - 1, "")
- self.inputline = self.bodyHandler.renderBody(self.menuline)
- self.renderFooter(self.inputline)
-
- self.lock.release()
-
-
- def setInput(self, string=""):
- self.input = string
-
- def setHandler(self, klass):
- #create new handler with reference to cli
- self.bodyHandler = self.inputHandler = klass(self)
- self.input = ""
-
- def renderHeader(self, line):
- """ prints download status """
- #print updated information
- # print "\033[J" #clear screen
- # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
- # self.println(2, "")
- # self.println(3, white(_("%s Downloads:") % (len(data))))
-
- data = self.client.statusDownloads()
- speed = 0
-
- println(line, white(_("%s Downloads:") % (len(data))))
- line += 1
-
- for download in data:
- if download.status == 12: # downloading
- percent = download.percent
- z = percent / 4
- speed += download.speed
- println(line, cyan(download.name))
- line += 1
- println(line,
- blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _(
- " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green(
- download.format_size) + _(" Finished in: ") + green(download.format_eta) + _(
- " ID: ") + green(download.fid))
- line += 1
- if download.status == 5:
- println(line, cyan(download.name))
- line += 1
- println(line, _("waiting: ") + green(download.format_wait))
- line += 1
-
- println(line, "")
- line += 1
- status = self.client.statusServer()
- if status.pause:
- paused = _("Status:") + " " + red(_("paused"))
- else:
- paused = _("Status:") + " " + red(_("running"))
-
- println(line,"%s %s: %s %s: %s %s: %s" % (
- paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red(
- status.queue), _("Total"), red(status.total)))
-
- return line + 1
-
- def renderBody(self, line):
- """ prints initial menu """
- println(line, white(_("Menu:")))
- println(line + 1, "")
- println(line + 2, mag("1.") + _(" Add Links"))
- println(line + 3, mag("2.") + _(" Manage Queue"))
- println(line + 4, mag("3.") + _(" Manage Collector"))
- println(line + 5, mag("4.") + _(" (Un)Pause Server"))
- println(line + 6, mag("5.") + _(" Kill Server"))
- println(line + 7, mag("6.") + _(" Quit"))
-
- return line + 8
-
- def renderFooter(self, line):
- """ prints out the input line with input """
- println(line, "")
- line += 1
-
- println(line, white(" Input: ") + decode(self.input))
-
- #clear old output
- if line < self.lastLowestLine:
- for i in range(line + 1, self.lastLowestLine + 1):
- println(i, "")
-
- self.lastLowestLine = line
-
- #set cursor to position
- print "\033[" + str(self.inputline) + ";0H"
-
- def onChar(self, char):
- """ default no special handling for single chars """
- if char == "1":
- self.setHandler(AddPackage)
- elif char == "2":
- self.setHandler(ManageFiles)
- elif char == "3":
- self.setHandler(ManageFiles)
- self.bodyHandler.target = Destination.Collector
- elif char == "4":
- self.client.togglePause()
- self.setInput()
- elif char == "5":
- self.client.kill()
- self.client.close()
- sys.exit()
- elif char == "6":
- os.system('clear')
- sys.exit()
-
- def onEnter(self, inp):
- pass
-
- def onBackSpace(self):
- pass
-
- def processCommand(self):
- command = self.command[0]
- args = []
- if len(self.command) > 1:
- args = self.command[1:]
-
- if command == "status":
- files = self.client.statusDownloads()
-
- if not files:
- print "No downloads running."
-
- for download in files:
- if download.status == 12: # downloading
- print print_status(download)
- print "\tDownloading: %s @ %s/s\t %s (%s%%)" % (
- download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft),
- download.percent)
- elif download.status == 5:
- print print_status(download)
- print "\tWaiting: %s" % download.format_wait
- else:
- print print_status(download)
-
- elif command == "queue":
- print_packages(self.client.getQueueData())
-
- elif command == "collector":
- print_packages(self.client.getCollectorData())
-
- elif command == "add":
- if len(args) < 2:
- print _("Please use this syntax: add <Package name> <link> <link2> ...")
- return
-
- self.client.addPackage(args[0], args[1:], Destination.Queue)
-
- elif command == "add_coll":
- if len(args) < 2:
- print _("Please use this syntax: add <Package name> <link> <link2> ...")
- return
-
- self.client.addPackage(args[0], args[1:], Destination.Collector)
-
- elif command == "del_file":
- self.client.deleteFiles([int(x) for x in args])
- print "Files deleted."
-
- elif command == "del_package":
- self.client.deletePackages([int(x) for x in args])
- print "Packages deleted."
-
- elif command == "move":
- for pid in args:
- pack = self.client.getPackageInfo(int(pid))
- self.client.movePackage((pack.dest + 1) % 2, pack.pid)
-
- elif command == "check":
- print _("Checking %d links:") % len(args)
- print
- rid = self.client.checkOnlineStatus(args).rid
- self.printOnlineCheck(self.client, rid)
-
-
- elif command == "check_container":
- path = args[0]
- if not exists(join(owd, path)):
- print _("File does not exists.")
- return
-
- f = open(join(owd, path), "rb")
- content = f.read()
- f.close()
-
- rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid
- self.printOnlineCheck(self.client, rid)
-
-
- elif command == "pause":
- self.client.pause()
-
- elif command == "unpause":
- self.client.unpause()
-
- elif command == "toggle":
- self.client.togglePause()
-
- elif command == "kill":
- self.client.kill()
- elif command == "restart_file":
- for x in args:
- self.client.restartFile(int(x))
- print "Files restarted."
- elif command == "restart_package":
- for pid in args:
- self.client.restartPackage(int(pid))
- print "Packages restarted."
-
- else:
- print_commands()
-
- def printOnlineCheck(self, client, rid):
- while True:
- sleep(1)
- result = client.pollResults(rid)
- for url, status in result.data.iteritems():
- if status.status == 2: check = "Online"
- elif status.status == 1: check = "Offline"
- else: check = "Unknown"
-
- print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check)
-
- if result.rid == -1: break
-
-
-class RefreshThread(Thread):
- def __init__(self, cli):
- Thread.__init__(self)
- self.setDaemon(True)
- self.cli = cli
-
- def run(self):
- while True:
- sleep(1)
- try:
- self.cli.refresh()
- except ConnectionClosed:
- os.system("clear")
- print _("pyLoad was terminated")
- _exit(0)
- except Exception, e:
- println(2, red(str(e)))
- self.cli.reset()
- print_exc()
-
-
-def print_help(config):
- print
- print "pyLoadCli Copyright (c) 2008-2011 the pyLoad Team"
- print
- print "Usage: [python] pyLoadCli.py [options] [command]"
- print
- print "<Commands>"
- print "See pyLoadCli.py -c for a complete listing."
- print
- print "<Options>"
- print " -i, --interactive", " Start in interactive mode"
- print
- print " -u, --username=", " " * 2, "Specify Username"
- print " --pw=<password>", " " * 2, "Password"
- print " -a, --address=", " " * 3, "Specify address (current=%s)" % config["addr"]
- print " -p, --port", " " * 7, "Specify port (current=%s)" % config["port"]
- print
- print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"]
- print " -h, --help", " " * 7, "Display this help screen"
- print " -c, --commands", " " * 3, "List all available commands"
- print
-
-
-def print_packages(data):
- for pack in data:
- print "Package %s (#%s):" % (pack.name, pack.pid)
- for download in pack.links:
- print "\t" + print_file(download)
- print
-
-
-def print_file(download):
- return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % {
- "id": download.fid,
- "name": download.name,
- "statusmsg": download.statusmsg,
- "plugin": download.plugin
- }
-
-
-def print_status(download):
- return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % {
- "id": download.fid,
- "name": download.name,
- "statusmsg": download.statusmsg,
- "size": download.format_size
- }
-
-
-def print_commands():
- commands = [("status", _("Prints server status")),
- ("queue", _("Prints downloads in queue")),
- ("collector", _("Prints downloads in collector")),
- ("add <name> <link1> <link2>...", _("Adds package to queue")),
- ("add_coll <name> <link1> <link2>...", _("Adds package to collector")),
- ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")),
- ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")),
- ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")),
- ("restart_file <fid> <fid2>...", _("Restart files")),
- ("restart_package <pid> <pid2>...", _("Restart packages")),
- ("check <container|url> ...", _("Check online status, works with local container")),
- ("check_container path", _("Checks online status of a container file")),
- ("pause", _("Pause the server")),
- ("unpause", _("continue downloads")),
- ("toggle", _("Toggle pause/unpause")),
- ("kill", _("kill server")), ]
-
- print _("List of commands:")
- print
- for c in commands:
- print "%-35s %s" % c
-
-
-def writeConfig(opts):
- try:
- with open(join(homedir, ".pyloadcli"), "w") as cfgfile:
- cfgfile.write("[cli]")
- for opt in opts:
- cfgfile.write("%s=%s\n" % (opt, opts[opt]))
- except:
- print _("Couldn't write user config file")
-
-
-def main():
- config = {"addr": "127.0.0.1", "port": "7227", "language": "en"}
- try:
- config["language"] = os.environ["LANG"][0:2]
- except:
- pass
-
- if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "":
- config["language"] = "en"
-
- configFile = ConfigParser.ConfigParser()
- configFile.read(join(homedir, ".pyloadcli"))
-
- if configFile.has_section("cli"):
- for opt in configFile.items("cli"):
- config[opt[0]] = opt[1]
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadCli", join(pypath, "locale"),
- languages=[config["language"],"en"],fallback=True)
- translation.install(unicode=True)
-
- interactive = False
- command = None
- username = ""
- password = ""
-
- shortOptions = 'iu:p:a:hcl:'
- longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="]
-
- try:
- opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions)
- for option, params in opts:
- if option in ("-i", "--interactive"):
- interactive = True
- elif option in ("-u", "--username"):
- username = params
- elif option in ("-a", "--address"):
- config["addr"] = params
- elif option in ("-p", "--port"):
- config["port"] = params
- elif option in ("-l", "--language"):
- config["language"] = params
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadCli", join(pypath, "locale"),
- languages=[config["language"],"en"],fallback=True)
- translation.install(unicode=True)
- elif option in ("-h", "--help"):
- print_help(config)
- exit()
- elif option in ("--pw"):
- password = params
- elif option in ("-c", "--comands"):
- print_commands()
- exit()
-
- except GetoptError:
- print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:])
- print_help(config)
- exit()
-
- if len(extraparams) >= 1:
- command = extraparams
-
- client = False
-
- if interactive:
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- pass
- except NoSSL:
- print _("You need py-openssl to connect to this pyLoad Core.")
- exit()
- except NoConnection:
- config["addr"] = False
- config["port"] = False
-
- if not client:
- if not config["addr"]: config["addr"] = raw_input(_("Address: "))
- if not config["port"]: config["port"] = raw_input(_("Port: "))
- if not username: username = raw_input(_("Username: "))
- if not password:
- from getpass import getpass
-
- password = getpass(_("Password: "))
-
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- print _("Login data is wrong.")
- except NoConnection:
- print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
- "port": config["port"]})
-
- else:
- try:
- client = ThriftClient(config["addr"], int(config["port"]), username, password)
- except WrongLogin:
- print _("Login data is wrong.")
- except NoConnection:
- print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
- "port": config["port"]})
- except NoSSL:
- print _("You need py-openssl to connect to this pyLoad core.")
-
- if interactive and command: print _("Interactive mode ignored since you passed some commands.")
-
- if client:
- writeConfig(config)
- cli = Cli(client, command)
-
-
-if __name__ == "__main__":
- main()
diff --git a/pyLoadCore.py b/pyLoadCore.py
deleted file mode 100755
index 35cac4682..000000000
--- a/pyLoadCore.py
+++ /dev/null
@@ -1,668 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: spoob
- @author: sebnapi
- @author: RaNaN
- @author: mkaay
- @version: v0.4.9
-"""
-CURRENT_VERSION = '0.4.9'
-
-import __builtin__
-
-from getopt import getopt, GetoptError
-import module.common.pylgettext as gettext
-from imp import find_module
-import logging
-import logging.handlers
-import os
-from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close
-from os.path import exists, join
-import signal
-import subprocess
-import sys
-from sys import argv, executable, exit
-from time import time, sleep
-from traceback import print_exc
-
-from module import InitHomeDir
-from module.plugins.AccountManager import AccountManager
-from module.CaptchaManager import CaptchaManager
-from module.ConfigParser import ConfigParser
-from module.plugins.PluginManager import PluginManager
-from module.PullEvents import PullManager
-from module.network.RequestFactory import RequestFactory
-from module.web.ServerThread import WebServer
-from module.Scheduler import Scheduler
-from module.common.JsEngine import JsEngine
-from module import remote
-from module.remote.RemoteManager import RemoteManager
-from module.database import DatabaseBackend, FileHandler
-
-from module.utils import freeSpace, formatSize, get_console_encoding
-
-from codecs import getwriter
-
-enc = get_console_encoding(sys.stdout.encoding)
-sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
-
-# TODO List
-# - configurable auth system ldap/mysql
-# - cron job like sheduler
-
-class Core(object):
- """pyLoad Core, one tool to rule them all... (the filehosters) :D"""
-
- def __init__(self):
- self.doDebug = False
- self.startedInGui = False
- self.running = False
- self.daemon = False
- self.remote = True
- self.arg_links = []
- self.pidfile = "pyload.pid"
- self.deleteLinks = False # will delete links on startup
-
- if len(argv) > 1:
- try:
- options, args = getopt(argv[1:], 'vchdusqp:',
- ["version", "clear", "clean", "help", "debug", "user",
- "setup", "configdir=", "changedir", "daemon",
- "quit", "status", "no-remote","pidfile="])
-
- for option, argument in options:
- if option in ("-v", "--version"):
- print "pyLoad", CURRENT_VERSION
- exit()
- elif option in ("-p", "--pidfile"):
- self.pidfile = argument
- elif option == "--daemon":
- self.daemon = True
- elif option in ("-c", "--clear"):
- self.deleteLinks = True
- elif option in ("-h", "--help"):
- self.print_help()
- exit()
- elif option in ("-d", "--debug"):
- self.doDebug = True
- elif option in ("-u", "--user"):
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.set_user()
- exit()
- elif option in ("-s", "--setup"):
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.start()
- exit()
- elif option == "--changedir":
- from module.setup import Setup
-
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- s.conf_path(True)
- exit()
- elif option in ("-q", "--quit"):
- self.quitInstance()
- exit()
- elif option == "--status":
- pid = self.isAlreadyRunning()
- if self.isAlreadyRunning():
- print pid
- exit(0)
- else:
- print "false"
- exit(1)
- elif option == "--clean":
- self.cleanTree()
- exit()
- elif option == "--no-remote":
- self.remote = False
-
- except GetoptError:
- print 'Unknown Argument(s) "%s"' % " ".join(argv[1:])
- self.print_help()
- exit()
-
- def print_help(self):
- print ""
- print "pyLoad v%s 2008-2011 the pyLoad Team" % CURRENT_VERSION
- print ""
- if sys.argv[0].endswith(".py"):
- print "Usage: python pyLoadCore.py [options]"
- else:
- print "Usage: pyLoadCore [options]"
- print ""
- print "<Options>"
- print " -v, --version", " " * 10, "Print version to terminal"
- print " -c, --clear", " " * 12, "Delete all saved packages/links"
- #print " -a, --add=<link/list>", " " * 2, "Add the specified links"
- print " -u, --user", " " * 13, "Manages users"
- print " -d, --debug", " " * 12, "Enable debug mode"
- print " -s, --setup", " " * 12, "Run Setup Assistent"
- print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory"
- print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
- print " --changedir", " " * 12, "Change config dir permanently"
- print " --daemon", " " * 15, "Daemonmize after start"
- print " --no-remote", " " * 12, "Disable remote access (saves RAM)"
- print " --status", " " * 15, "Display pid if running or False"
- print " --clean", " " * 16, "Remove .pyc/.pyo files"
- print " -q, --quit", " " * 13, "Quit running pyLoad instance"
- print " -h, --help", " " * 13, "Display this help screen"
- print ""
-
- def toggle_pause(self):
- if self.threadManager.pause:
- self.threadManager.pause = False
- return False
- elif not self.threadManager.pause:
- self.threadManager.pause = True
- return True
-
- def quit(self, a, b):
- self.shutdown()
- self.log.info(_("Received Quit signal"))
- _exit(1)
-
- def writePidFile(self):
- self.deletePidFile()
- pid = os.getpid()
- f = open(self.pidfile, "wb")
- f.write(str(pid))
- f.close()
-
- def deletePidFile(self):
- if self.checkPidFile():
- self.log.debug("Deleting old pidfile %s" % self.pidfile)
- os.remove(self.pidfile)
-
- def checkPidFile(self):
- """ return pid as int or 0"""
- if os.path.isfile(self.pidfile):
- f = open(self.pidfile, "rb")
- pid = f.read().strip()
- f.close()
- if pid:
- pid = int(pid)
- return pid
-
- return 0
-
- def isAlreadyRunning(self):
- pid = self.checkPidFile()
- if not pid or os.name == "nt": return False
- try:
- os.kill(pid, 0) # 0 - default signal (does nothing)
- except:
- return 0
-
- return pid
-
- def quitInstance(self):
- if os.name == "nt":
- print "Not supported on windows."
- return
-
- pid = self.isAlreadyRunning()
- if not pid:
- print "No pyLoad running."
- return
-
- try:
- os.kill(pid, 3) #SIGUIT
-
- t = time()
- print "waiting for pyLoad to quit"
-
- while exists(self.pidfile) and t + 10 > time():
- sleep(0.25)
-
- if not exists(self.pidfile):
- print "pyLoad successfully stopped"
- else:
- os.kill(pid, 9) #SIGKILL
- print "pyLoad did not respond"
- print "Kill signal was send to process with id %s" % pid
-
- except:
- print "Error quitting pyLoad"
-
-
- def cleanTree(self):
- for path, dirs, files in walk(self.path("")):
- for f in files:
- if not f.endswith(".pyo") and not f.endswith(".pyc"):
- continue
-
- if "_25" in f or "_26" in f or "_27" in f:
- continue
-
- print join(path, f)
- remove(join(path, f))
-
- def start(self, rpc=True, web=True):
- """ starts the fun :D """
-
- self.version = CURRENT_VERSION
-
- if not exists("pyload.conf"):
- from module.setup import Setup
-
- print "This is your first start, running configuration assistent now."
- self.config = ConfigParser()
- s = Setup(pypath, self.config)
- res = False
- try:
- res = s.start()
- except SystemExit:
- pass
- except KeyboardInterrupt:
- print "\nSetup interrupted"
- except:
- res = False
- print_exc()
- print "Setup failed"
- if not res:
- remove("pyload.conf")
-
- exit()
-
- try: signal.signal(signal.SIGQUIT, self.quit)
- except: pass
-
- self.config = ConfigParser()
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoad", self.path("locale"),
- languages=[self.config['general']['language'],"en"],fallback=True)
- translation.install(True)
-
- self.debug = self.doDebug or self.config['general']['debug_mode']
- self.remote &= self.config['remote']['activated']
-
- pid = self.isAlreadyRunning()
- if pid:
- print _("pyLoad already running with pid %s") % pid
- exit()
-
- if os.name != "nt" and self.config["general"]["renice"]:
- os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid()))
-
- if self.config["permission"]["change_group"]:
- if os.name != "nt":
- try:
- from grp import getgrnam
-
- group = getgrnam(self.config["permission"]["group"])
- os.setgid(group[2])
- except Exception, e:
- print _("Failed changing group: %s") % e
-
- if self.config["permission"]["change_user"]:
- if os.name != "nt":
- try:
- from pwd import getpwnam
-
- user = getpwnam(self.config["permission"]["user"])
- os.setuid(user[2])
- except Exception, e:
- print _("Failed changing user: %s") % e
-
- self.check_file(self.config['log']['log_folder'], _("folder for logs"), True)
-
- if self.debug:
- self.init_logger(logging.DEBUG) # logging level
- else:
- self.init_logger(logging.INFO) # logging level
-
- self.do_kill = False
- self.do_restart = False
- self.shuttedDown = False
-
- self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION)
- self.log.info(_("Using home directory: %s") % getcwd())
-
- self.writePidFile()
-
- #@TODO refractor
-
- remote.activated = self.remote
- self.log.debug("Remote activated: %s" % self.remote)
-
- self.check_install("Crypto", _("pycrypto to decode container files"))
- #img = self.check_install("Image", _("Python Image Libary (PIL) for captcha reading"))
- #self.check_install("pycurl", _("pycurl to download any files"), True, True)
- self.check_file("tmp", _("folder for temporary files"), True)
- #tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True
-
- self.captcha = True # checks seems to fail, althoug tesseract is available
-
- self.check_file(self.config['general']['download_folder'], _("folder for downloads"), True)
-
- if self.config['ssl']['activated']:
- self.check_install("OpenSSL", _("OpenSSL for secure connection"))
-
- self.setupDB()
- if self.config.oldRemoteData:
- self.log.info(_("Moving old user config to DB"))
- self.db.addUser(self.config.oldRemoteData["username"], self.config.oldRemoteData["password"])
-
- self.log.info(_("Please check your logindata with ./pyLoadCore.py -u"))
-
- if self.deleteLinks:
- self.log.info(_("All links removed"))
- self.db.purgeLinks()
-
- self.requestFactory = RequestFactory(self)
- __builtin__.pyreq = self.requestFactory
-
- self.lastClientConnected = 0
-
- # later imported because they would trigger api import, and remote value not set correctly
- from module import Api
- from module.HookManager import HookManager
- from module.ThreadManager import ThreadManager
-
- if Api.activated != self.remote:
- self.log.warning("Import error: API remote status not correct.")
-
- self.api = Api.Api(self)
-
- self.scheduler = Scheduler(self)
-
- #hell yeah, so many important managers :D
- self.pluginManager = PluginManager(self)
- self.pullManager = PullManager(self)
- self.accountManager = AccountManager(self)
- self.threadManager = ThreadManager(self)
- self.captchaManager = CaptchaManager(self)
- self.hookManager = HookManager(self)
- self.remoteManager = RemoteManager(self)
-
- self.js = JsEngine()
-
- self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
-
- if rpc:
- self.remoteManager.startBackends()
-
- if web:
- self.init_webserver()
-
- spaceLeft = freeSpace(self.config["general"]["download_folder"])
-
- self.log.info(_("Free space: %s") % formatSize(spaceLeft))
-
- self.config.save() #save so config files gets filled
-
- link_file = join(pypath, "links.txt")
-
- if exists(link_file):
- f = open(link_file, "rb")
- if f.read().strip():
- self.api.addPackage("links.txt", [link_file], 1)
- f.close()
-
- link_file = "links.txt"
- if exists(link_file):
- f = open(link_file, "rb")
- if f.read().strip():
- self.api.addPackage("links.txt", [link_file], 1)
- f.close()
-
- #self.scheduler.addJob(0, self.accountManager.getAccountInfos)
- self.log.info(_("Activating Accounts..."))
- self.accountManager.getAccountInfos()
-
- self.threadManager.pause = False
- self.running = True
-
- self.log.info(_("Activating Plugins..."))
- self.hookManager.coreReady()
-
- self.log.info(_("pyLoad is up and running"))
-
- #test api
-# from module.common.APIExerciser import startApiExerciser
-# startApiExerciser(self, 3)
-
- #some memory stats
-# from guppy import hpy
-# hp=hpy()
-# import objgraph
-# objgraph.show_most_common_types(limit=20)
-# import memdebug
-# memdebug.start(8002)
-
- locals().clear()
-
- while True:
- sleep(2)
- if self.do_restart:
- self.log.info(_("restarting pyLoad"))
- self.restart()
- if self.do_kill:
- self.shutdown()
- self.log.info(_("pyLoad quits"))
- self.removeLogger()
- _exit(0) #@TODO thrift blocks shutdown
-
- self.threadManager.work()
- self.scheduler.work()
-
- def setupDB(self):
- self.db = DatabaseBackend(self) # the backend
- self.db.setup()
-
- self.files = FileHandler(self)
- self.db.manager = self.files #ugly?
-
- def init_webserver(self):
- if self.config['webinterface']['activated']:
- self.webserver = WebServer(self)
- self.webserver.start()
-
- def init_logger(self, level):
- console = logging.StreamHandler(sys.stdout)
- frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S")
- console.setFormatter(frm)
- self.log = logging.getLogger("log") # settable in config
-
- if self.config['log']['file_log']:
- if self.config['log']['log_rotate']:
- file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'),
- maxBytes=self.config['log']['log_size'] * 1024,
- backupCount=int(self.config['log']['log_count']),
- encoding="utf8")
- else:
- file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8")
-
- file_handler.setFormatter(frm)
- self.log.addHandler(file_handler)
-
- self.log.addHandler(console) #if console logging
- self.log.setLevel(level)
-
- def removeLogger(self):
- for h in list(self.log.handlers):
- self.log.removeHandler(h)
- h.close()
-
- def check_install(self, check_name, legend, python=True, essential=False):
- """check wether needed tools are installed"""
- try:
- if python:
- find_module(check_name)
- else:
- pipe = subprocess.PIPE
- subprocess.Popen(check_name, stdout=pipe, stderr=pipe)
-
- return True
- except:
- if essential:
- self.log.info(_("Install %s") % legend)
- exit()
-
- return False
-
- def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False):
- """check wether needed files exists"""
- tmp_names = []
- if not type(check_names) == list:
- tmp_names.append(check_names)
- else:
- tmp_names.extend(check_names)
- file_created = True
- file_exists = True
- for tmp_name in tmp_names:
- if not exists(tmp_name):
- file_exists = False
- if empty:
- try:
- if folder:
- tmp_name = tmp_name.replace("/", sep)
- makedirs(tmp_name)
- else:
- open(tmp_name, "w")
- except:
- file_created = False
- else:
- file_created = False
-
- if not file_exists and not quiet:
- if file_created:
- #self.log.info( _("%s created") % description )
- pass
- else:
- if not empty:
- self.log.warning(
- _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name})
- else:
- print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}
- if essential:
- exit()
-
- def isClientConnected(self):
- return (self.lastClientConnected + 30) > time()
-
- def restart(self):
- self.shutdown()
- chdir(owd)
- # close some open fds
- for i in range(3,50):
- try:
- close(i)
- except :
- pass
-
- execl(executable, executable, *sys.argv)
- _exit(0)
-
- def shutdown(self):
- self.log.info(_("shutting down..."))
- try:
- if self.config['webinterface']['activated'] and hasattr(self, "webserver"):
- self.webserver.quit()
-
- for thread in self.threadManager.threads:
- thread.put("quit")
- pyfiles = self.files.cache.values()
-
- for pyfile in pyfiles:
- pyfile.abortDownload()
-
- self.hookManager.coreExiting()
-
- except:
- if self.debug:
- print_exc()
- self.log.info(_("error while shutting down"))
-
- finally:
- self.files.syncSave()
- self.shuttedDown = True
-
- self.deletePidFile()
-
-
- def path(self, *args):
- return join(pypath, *args)
-
-
-def deamon():
- try:
- pid = os.fork()
- if pid > 0:
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
- sys.exit(1)
-
- # decouple from parent environment
- os.setsid()
- os.umask(0)
-
- # do second fork
- try:
- pid = os.fork()
- if pid > 0:
- # exit from second parent, print eventual PID before
- print "Daemon PID %d" % pid
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
- sys.exit(1)
-
- # Iterate through and close some file descriptors.
- for fd in range(0, 3):
- try:
- os.close(fd)
- except OSError: # ERROR, fd wasn't open to begin with (ignored)
- pass
-
- os.open(os.devnull, os.O_RDWR) # standard input (0)
- os.dup2(0, 1) # standard output (1)
- os.dup2(0, 2)
-
- pyload_core = Core()
- pyload_core.start()
-
-
-def main():
- #change name to 'pyLoadCore'
- #from module.lib.rename_process import renameProcess
- #renameProcess('pyLoadCore')
- if "--daemon" in sys.argv:
- deamon()
- else:
- pyload_core = Core()
- try:
- pyload_core.start()
- except KeyboardInterrupt:
- pyload_core.shutdown()
- pyload_core.log.info(_("killed pyLoad from Terminal"))
- pyload_core.removeLogger()
- _exit(1)
-
-# And so it begins...
-if __name__ == "__main__":
- main()
-
diff --git a/pyLoadGui.py b/pyLoadGui.py
deleted file mode 100755
index 5f620e52a..000000000
--- a/pyLoadGui.py
+++ /dev/null
@@ -1,765 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License,
- or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>.
-
- @author: mkaay
-"""
-
-import sys
-
-from uuid import uuid4 as uuid # should be above PyQt imports
-from time import sleep, time
-
-from base64 import b64decode
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-import re
-import module.common.pylgettext as gettext
-import os
-from os.path import abspath
-from os.path import join
-from os.path import basename
-from os.path import commonprefix
-
-from module import InitHomeDir
-from module.gui.ConnectionManager import *
-from module.gui.Connector import Connector
-from module.gui.MainWindow import *
-from module.gui.Queue import *
-from module.gui.Overview import *
-from module.gui.Collector import *
-from module.gui.XMLParser import *
-from module.gui.CoreConfigParser import ConfigParser
-
-from module.lib.rename_process import renameProcess
-from module.utils import formatSize, formatSpeed
-
-try:
- import pynotify
-except ImportError:
- print "pynotify not installed, falling back to qt tray notification"
-
-class main(QObject):
- def __init__(self):
- """
- main setup
- """
- QObject.__init__(self)
- self.app = QApplication(sys.argv)
- self.path = pypath
- self.homedir = abspath("")
-
- self.configdir = ""
-
- self.init(True)
-
- def init(self, first=False):
- """
- set main things up
- """
- self.parser = XMLParser(join(self.configdir, "gui.xml"), join(self.path, "module", "config", "gui_default.xml"))
- lang = self.parser.xml.elementsByTagName("language").item(0).toElement().text()
- if not lang:
- parser = XMLParser(join(self.path, "module", "config", "gui_default.xml"))
- lang = parser.xml.elementsByTagName("language").item(0).toElement().text()
-
- gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
- translation = gettext.translation("pyLoadGui", join(pypath, "locale"), languages=[str(lang), "en"], fallback=True)
- try:
- translation.install(unicode=(True if sys.stdout.encoding.lower().startswith("utf") else False))
- except:
- translation.install(unicode=False)
-
-
- self.connector = Connector()
- self.mainWindow = MainWindow(self.connector)
- self.connWindow = ConnectionManager()
- self.mainloop = self.Loop(self)
- self.connectSignals()
-
- self.checkClipboard = False
- default = self.refreshConnections()
- self.connData = None
- self.captchaProcessing = False
- self.serverStatus = {"freespace":0}
-
- self.core = None # pyLoadCore if started
- self.connectionLost = False
-
- if True: # when used if first, minimizing not working correctly..
- self.tray = TrayIcon()
- self.tray.show()
- self.notification = Notification(self.tray)
- self.connect(self, SIGNAL("showMessage"), self.notification.showMessage)
- self.connect(self.tray.exitAction, SIGNAL("triggered()"), self.slotQuit)
- self.connect(self.tray.showAction, SIGNAL("toggled(bool)"), self.mainWindow.setVisible)
- self.connect(self.mainWindow, SIGNAL("hidden"), self.tray.mainWindowHidden)
-
- if not first:
- self.connWindow.show()
- else:
- self.connWindow.edit.setData(default)
- data = self.connWindow.edit.getData()
- self.slotConnect(data)
-
- def startMain(self):
- """
- start all refresh threads and show main window
- """
- if not self.connector.connectProxy():
- self.init()
- return
- self.connect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost)
- self.restoreMainWindow()
- self.mainWindow.show()
- self.initQueue()
- self.initPackageCollector()
- self.mainloop.start()
- self.clipboard = self.app.clipboard()
- self.connect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange)
- self.mainWindow.actions["clipboard"].setChecked(self.checkClipboard)
-
- self.mainWindow.tabs["settings"]["w"].setConnector(self.connector)
- self.mainWindow.tabs["settings"]["w"].loadConfig()
- self.tray.showAction.setDisabled(False)
-
- def stopMain(self):
- """
- stop all refresh threads and hide main window
- """
- self.tray.showAction.setDisabled(True)
- self.disconnect(self.clipboard, SIGNAL('dataChanged()'), self.slotClipboardChange)
- self.disconnect(self.connector, SIGNAL("connectionLost"), self.slotConnectionLost)
- self.mainloop.stop()
- self.mainWindow.saveWindow()
- self.mainWindow.hide()
- self.queue.stop()
-
- def connectSignals(self):
- """
- signal and slot stuff, yay!
- """
- self.connect(self.connector, SIGNAL("errorBox"), self.slotErrorBox)
- self.connect(self.connWindow, SIGNAL("saveConnection"), self.slotSaveConnection)
- self.connect(self.connWindow, SIGNAL("removeConnection"), self.slotRemoveConnection)
- self.connect(self.connWindow, SIGNAL("connect"), self.slotConnect)
- self.connect(self.mainWindow, SIGNAL("connector"), self.slotShowConnector)
- self.connect(self.mainWindow, SIGNAL("addPackage"), self.slotAddPackage)
- self.connect(self.mainWindow, SIGNAL("setDownloadStatus"), self.slotSetDownloadStatus)
- self.connect(self.mainWindow, SIGNAL("saveMainWindow"), self.slotSaveMainWindow)
- self.connect(self.mainWindow, SIGNAL("pushPackageToQueue"), self.slotPushPackageToQueue)
- self.connect(self.mainWindow, SIGNAL("restartDownload"), self.slotRestartDownload)
- self.connect(self.mainWindow, SIGNAL("removeDownload"), self.slotRemoveDownload)
- self.connect(self.mainWindow, SIGNAL("abortDownload"), self.slotAbortDownload)
- self.connect(self.mainWindow, SIGNAL("addContainer"), self.slotAddContainer)
- self.connect(self.mainWindow, SIGNAL("stopAllDownloads"), self.slotStopAllDownloads)
- self.connect(self.mainWindow, SIGNAL("setClipboardStatus"), self.slotSetClipboardStatus)
- self.connect(self.mainWindow, SIGNAL("changePackageName"), self.slotChangePackageName)
- self.connect(self.mainWindow, SIGNAL("pullOutPackage"), self.slotPullOutPackage)
- self.connect(self.mainWindow, SIGNAL("refreshStatus"), self.slotRefreshStatus)
- self.connect(self.mainWindow, SIGNAL("reloadAccounts"), self.slotReloadAccounts)
- self.connect(self.mainWindow, SIGNAL("Quit"), self.slotQuit)
-
- self.connect(self.mainWindow.mactions["exit"], SIGNAL("triggered()"), self.slotQuit)
- self.connect(self.mainWindow.captchaDock, SIGNAL("done"), self.slotCaptchaDone)
-
- def slotShowConnector(self):
- """
- emitted from main window (menu)
- hide the main window and show connection manager
- (to switch to other core)
- """
- self.quitInternal()
- self.stopMain()
- self.init()
-
- #def quit(self): #not used anymore?
- # """
- # quit gui
- # """
- # self.app.quit()
-
- def loop(self):
- """
- start application loop
- """
- sys.exit(self.app.exec_())
-
- def slotErrorBox(self, msg):
- """
- display a nice error box
- """
- msgb = QMessageBox(QMessageBox.Warning, "Error", msg)
- msgb.exec_()
-
- def initPackageCollector(self):
- """
- init the package collector view
- * columns
- * selection
- * refresh thread
- * drag'n'drop
- """
- view = self.mainWindow.tabs["collector"]["package_view"]
- view.setSelectionBehavior(QAbstractItemView.SelectRows)
- view.setSelectionMode(QAbstractItemView.ExtendedSelection)
- def dropEvent(klass, event):
- event.setDropAction(Qt.CopyAction)
- event.accept()
- view = event.source()
- if view == klass:
- items = view.selectedItems()
- for item in items:
- if not hasattr(item.parent(), "getPackData"):
- continue
- target = view.itemAt(event.pos())
- if not hasattr(target, "getPackData"):
- target = target.parent()
- klass.emit(SIGNAL("droppedToPack"), target.getPackData()["id"], item.getFileData()["id"])
- event.ignore()
- return
- items = view.selectedItems()
- for item in items:
- row = view.indexOfTopLevelItem(item)
- view.takeTopLevelItem(row)
- def dragEvent(klass, event):
- #view = event.source()
- #dragOkay = False
- #items = view.selectedItems()
- #for item in items:
- # if hasattr(item, "_data"):
- # if item._data["id"] == "fixed" or item.parent()._data["id"] == "fixed":
- # dragOkay = True
- # else:
- # dragOkay = True
- #if dragOkay:
- event.accept()
- #else:
- # event.ignore()
- view.dropEvent = dropEvent
- view.dragEnterEvent = dragEvent
- view.setDragEnabled(True)
- view.setDragDropMode(QAbstractItemView.DragDrop)
- view.setDropIndicatorShown(True)
- view.setDragDropOverwriteMode(True)
- view.connect(view, SIGNAL("droppedToPack"), self.slotAddFileToPackage)
- #self.packageCollector = PackageCollector(view, self.connector)
- self.packageCollector = view.model()
-
- def initQueue(self):
- """
- init the queue view
- * columns
- * progressbar
- """
- view = self.mainWindow.tabs["queue"]["view"]
- view.setSelectionBehavior(QAbstractItemView.SelectRows)
- view.setSelectionMode(QAbstractItemView.ExtendedSelection)
- self.queue = view.model()
- self.connect(self.queue, SIGNAL("updateCount"), self.slotUpdateCount)
- overview = self.mainWindow.tabs["overview"]["view"].model()
- overview.queue = self.queue
- self.connect(self.queue, SIGNAL("updateCount"), overview.queueChanged)
- self.queue.start()
-
- def slotUpdateCount(self, pc, fc):
- self.mainWindow.packageCount.setText("%i" % pc)
- self.mainWindow.fileCount.setText("%i" % fc)
-
- def refreshServerStatus(self):
- """
- refresh server status and overall speed in the status bar
- """
- s = self.connector.statusServer()
- if s.pause:
- self.mainWindow.status.setText(_("paused"))
- else:
- self.mainWindow.status.setText(_("running"))
- self.mainWindow.speed.setText(formatSpeed(s.speed))
- self.mainWindow.space.setText(formatSize(self.serverStatus["freespace"]))
- self.mainWindow.actions["toggle_status"].setChecked(not s.pause)
-
- def refreshLog(self):
- """
- update log window
- """
- offset = self.mainWindow.tabs["log"]["text"].logOffset
- lines = self.connector.getLog(offset)
- if not lines:
- return
- self.mainWindow.tabs["log"]["text"].logOffset += len(lines)
- for line in lines:
- self.mainWindow.tabs["log"]["text"].emit(SIGNAL("append(QString)"), line.strip("\n"))
- cursor = self.mainWindow.tabs["log"]["text"].textCursor()
- cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
- self.mainWindow.tabs["log"]["text"].setTextCursor(cursor)
-
- def getConnections(self):
- """
- parse all connections in the config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- ret = []
- for conn in connections:
- data = {}
- data["type"] = conn.attribute("type", "remote")
- data["default"] = conn.attribute("default", "False")
- data["id"] = conn.attribute("id", uuid().hex)
- if data["default"] == "True":
- data["default"] = True
- else:
- data["default"] = False
- subs = self.parser.parseNode(conn, "dict")
- if not subs.has_key("name"):
- data["name"] = _("Unnamed")
- else:
- data["name"] = subs["name"].text()
- if data["type"] == "remote":
- if not subs.has_key("server"):
- continue
- else:
- data["host"] = subs["server"].text()
- data["user"] = subs["server"].attribute("user", "admin")
- data["port"] = int(subs["server"].attribute("port", "7227"))
- data["password"] = subs["server"].attribute("password", "")
- ret.append(data)
- return ret
-
- def slotSaveConnection(self, data):
- """
- save connection to config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- connNode = self.parser.xml.createElement("connection")
- connNode.setAttribute("default", str(data["default"]))
- connNode.setAttribute("type", data["type"])
- connNode.setAttribute("id", data["id"])
- nameNode = self.parser.xml.createElement("name")
- nameText = self.parser.xml.createTextNode(data["name"])
- nameNode.appendChild(nameText)
- connNode.appendChild(nameNode)
- if data["type"] == "remote":
- serverNode = self.parser.xml.createElement("server")
- serverNode.setAttribute("user", data["user"])
- serverNode.setAttribute("port", data["port"])
- serverNode.setAttribute("password", data["password"])
- hostText = self.parser.xml.createTextNode(data["host"])
- serverNode.appendChild(hostText)
- connNode.appendChild(serverNode)
- found = False
- for c in connections:
- cid = c.attribute("id", "None")
- if str(cid) == str(data["id"]):
- found = c
- break
- if found:
- connectionsNode.replaceChild(connNode, found)
- else:
- connectionsNode.appendChild(connNode)
- self.parser.saveData()
- self.refreshConnections()
-
- def slotRemoveConnection(self, data):
- """
- remove connection from config file
- """
- connectionsNode = self.parser.xml.elementsByTagName("connections").item(0)
- if connectionsNode.isNull():
- raise Exception("null")
- connections = self.parser.parseNode(connectionsNode)
- found = False
- for c in connections:
- cid = c.attribute("id", "None")
- if str(cid) == str(data["id"]):
- found = c
- break
- if found:
- connectionsNode.removeChild(found)
- self.parser.saveData()
- self.refreshConnections()
-
- def slotConnect(self, data):
- """
- connect to a core
- if connection is local, parse the core config file for data
- if internal, start pyLoadCore
- set up connector, show main window
- """
- self.connWindow.hide()
- if data["type"] not in ("remote","internal"):
-
- coreparser = ConfigParser(self.configdir)
- if not coreparser.config:
- self.connector.setConnectionData("127.0.0.1", 7227, "anonymous", "anonymous", False)
- else:
- self.connector.setConnectionData("127.0.0.1", coreparser.get("remote","port"), "anonymous", "anonymous")
-
- elif data["type"] == "remote":
- self.connector.setConnectionData(data["host"], data["port"], data["user"], data["password"])
-
- elif data["type"] == "internal":
- from pyLoadCore import Core
- from module.ConfigParser import ConfigParser as CoreConfig
- import thread
-
- if not self.core:
-
- config = CoreConfig() #create so at least default config exists
- self.core = Core()
- self.core.startedInGui = True
- thread.start_new_thread(self.core.start, (False, False))
- while not self.core.running:
- sleep(0.5)
-
- self.connector.proxy = self.core.api
- self.connector.internal = True
-
- #self.connector.setConnectionData("127.0.0.1", config.get("remote","port"), "anonymous", "anonymous")
-
- self.startMain()
-# try:
-# host = data["host"]
-# except:
-# host = "127.0.0.1"
-
- def refreshConnections(self):
- """
- reload connetions and display them
- """
- self.parser.loadData()
- conns = self.getConnections()
- self.connWindow.emit(SIGNAL("setConnections"), conns)
- for conn in conns:
- if conn["default"]:
- return conn
- return None
-
- def slotSetDownloadStatus(self, status):
- """
- toolbar start/pause slot
- """
- if status:
- self.connector.unpauseServer()
- else:
- self.connector.pauseServer()
-
- def slotAddPackage(self, name, links, password=None):
- """
- emitted from main window
- add package to the collector
- """
- pack = self.connector.addPackage(name, links, Destination.Collector)
- if password:
- data = {"password": password}
- self.connector.setPackageData(pack, data)
-
- def slotAddFileToPackage(self, pid, fid): #TODO deprecated? gets called
- """
- emitted from collector view after a drop action
- """
- #self.connector.addFileToPackage(fid, pid)
- pass
-
- def slotAddContainer(self, path):
- """
- emitted from main window
- add container
- """
- filename = basename(path)
- #type = "".join(filename.split(".")[-1])
- fh = open(path, "r")
- content = fh.read()
- fh.close()
- self.connector.uploadContainer(filename, content)
-
- def slotSaveMainWindow(self, state, geo):
- """
- save the window geometry and toolbar/dock position to config file
- """
- mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0)
- if mainWindowNode.isNull():
- mainWindowNode = self.parser.xml.createElement("mainWindow")
- self.parser.root.appendChild(mainWindowNode)
- stateNode = mainWindowNode.toElement().elementsByTagName("state").item(0)
- geoNode = mainWindowNode.toElement().elementsByTagName("geometry").item(0)
- newStateNode = self.parser.xml.createTextNode(state)
- newGeoNode = self.parser.xml.createTextNode(geo)
-
- stateNode.removeChild(stateNode.firstChild())
- geoNode.removeChild(geoNode.firstChild())
- stateNode.appendChild(newStateNode)
- geoNode.appendChild(newGeoNode)
-
- self.parser.saveData()
-
- def restoreMainWindow(self):
- """
- load and restore main window geometry and toolbar/dock position from config
- """
- mainWindowNode = self.parser.xml.elementsByTagName("mainWindow").item(0)
- if mainWindowNode.isNull():
- return
- nodes = self.parser.parseNode(mainWindowNode, "dict")
-
- state = str(nodes["state"].text())
- geo = str(nodes["geometry"].text())
-
- self.mainWindow.restoreWindow(state, geo)
- self.mainWindow.captchaDock.hide()
-
- def slotPushPackageToQueue(self, id):
- """
- emitted from main window
- push the collector package to queue
- """
- self.connector.pushToQueue(id)
-
- def slotRestartDownload(self, id, isPack):
- """
- emitted from main window
- restart download
- """
- if isPack:
- self.connector.restartPackage(id)
- else:
- self.connector.restartFile(id)
-
- def slotRefreshStatus(self, id):
- """
- emitted from main window
- refresh download status
- """
- self.connector.recheckPackage(id)
-
- def slotRemoveDownload(self, id, isPack):
- """
- emitted from main window
- remove download
- """
- if isPack:
- self.connector.deletePackages([id])
- else:
- self.connector.deleteFiles([id])
-
- def slotAbortDownload(self, id, isPack):
- """
- emitted from main window
- remove download
- """
- if isPack:
- data = self.connector.getFileOrder(id) #less data to transmit
- self.connector.stopDownloads(data.values())
- else:
- self.connector.stopDownloads([id])
-
- def slotStopAllDownloads(self):
- """
- emitted from main window
- stop all running downloads
- """
- self.connector.stopAllDownloads()
-
- def slotClipboardChange(self):
- """
- called if clipboard changes
- """
- if self.checkClipboard:
- text = self.clipboard.text()
- pattern = re.compile(r"(http|https|ftp)://[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?/.*)?")
- matches = pattern.finditer(text)
-
- # thanks to: jmansour //#139
- links = [str(match.group(0)) for match in matches]
- if len(links) == 0:
- return
-
- filenames = [link.rpartition("/")[2] for link in links]
-
- packagename = commonprefix(filenames)
- if len(packagename) == 0:
- packagename = filenames[0]
-
- self.slotAddPackage(packagename, links)
-
- def slotSetClipboardStatus(self, status):
- """
- set clipboard checking
- """
- self.checkClipboard = status
-
- def slotChangePackageName(self, pid, name):
- """
- package name edit finished
- """
- self.connector.setPackageName(pid, str(name))
-
- def slotPullOutPackage(self, pid):
- """
- pull package out of the queue
- """
- self.connector.pullFromQueue(pid)
-
- def checkCaptcha(self):
- if self.connector.isCaptchaWaiting() and self.mainWindow.captchaDock.isFree():
- t = self.connector.getCaptchaTask(False)
- self.mainWindow.show()
- self.mainWindow.raise_()
- self.mainWindow.activateWindow()
- self.mainWindow.captchaDock.emit(SIGNAL("setTask"), t.tid, b64decode(t.data), t.type)
- elif not self.mainWindow.captchaDock.isFree():
- status = self.connector.getCaptchaTaskStatus(self.mainWindow.captchaDock.currentID)
- if not (status == "user" or status == "shared-user"):
- self.mainWindow.captchaDock.hide()
- self.mainWindow.captchaDock.processing = False
- self.mainWindow.captchaDock.currentID = None
-
- def slotCaptchaDone(self, cid, result):
- self.connector.setCaptchaResult(cid, str(result))
-
- def pullEvents(self):
- events = self.connector.getEvents(self.connector.connectionID)
- if not events:
- return
- for event in events:
- if event.eventname == "account":
- self.mainWindow.emit(SIGNAL("reloadAccounts"), False)
- elif event.eventname == "config":
- pass
- elif event.destination == Destination.Queue:
- self.queue.addEvent(event)
- try:
- if event.eventname == "update" and event.type == ElementType.File:
- info = self.connector.getFileData(event.id)
- if info.statusmsg == "finished":
- self.emit(SIGNAL("showMessage"), _("Finished downloading of '%s'") % info.name)
- elif info.statusmsg == "failed":
- self.emit(SIGNAL("showMessage"), _("Failed downloading '%s'!") % info.name)
- if event.event == "insert" and event.type == ElementType.File:
- info = self.connector.getLinkInfo(event[3])
- self.emit(SIGNAL("showMessage"), _("Added '%s' to queue") % info.name)
- except:
- print "can't send notification"
- elif event.destination == Destination.Collector:
- self.packageCollector.addEvent(event)
-
- def slotReloadAccounts(self, force=False):
- self.mainWindow.tabs["accounts"]["view"].model().reloadData(force)
-
- def slotQuit(self):
- self.tray.hide()
- self.quitInternal()
- self.app.quit()
-
- def quitInternal(self):
- if self.core:
- self.core.api.kill()
- for i in range(10):
- if self.core.shuttedDown:
- break
- sleep(0.5)
-
- def slotConnectionLost(self):
- if not self.connectionLost:
- self.connectionLost = True
- m = QMessageBox(QMessageBox.Critical, _("Connection lost"), _("Lost connection to the core!"), QMessageBox.Ok)
- m.exec_()
- self.slotQuit()
-
- class Loop():
- def __init__(self, parent):
- self.parent = parent
- self.timer = QTimer()
- self.timer.connect(self.timer, SIGNAL("timeout()"), self.update)
- self.lastSpaceCheck = 0
-
- def start(self):
- self.update()
- self.timer.start(1000)
-
- def update(self):
- """
- methods to call
- """
- self.parent.refreshServerStatus()
- if self.lastSpaceCheck + 5 < time():
- self.lastSpaceCheck = time()
- self.parent.serverStatus["freespace"] = self.parent.connector.freeSpace()
- self.parent.refreshLog()
- self.parent.checkCaptcha()
- self.parent.pullEvents()
-
- def stop(self):
- self.timer.stop()
-
-
-class TrayIcon(QSystemTrayIcon):
- def __init__(self):
- QSystemTrayIcon.__init__(self, QIcon(join(pypath, "icons", "logo-gui.png")))
- self.contextMenu = QMenu()
- self.showAction = QAction(_("Show"), self.contextMenu)
- self.showAction.setCheckable(True)
- self.showAction.setChecked(True)
- self.showAction.setDisabled(True)
- self.contextMenu.addAction(self.showAction)
- self.exitAction = QAction(QIcon(join(pypath, "icons", "close.png")), _("Exit"), self.contextMenu)
- self.contextMenu.addAction(self.exitAction)
- self.setContextMenu(self.contextMenu)
-
- self.connect(self, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.clicked)
-
- def mainWindowHidden(self):
- self.showAction.setChecked(False)
-
- def clicked(self, reason):
- if self.showAction.isEnabled():
- if reason == QSystemTrayIcon.Trigger:
- self.showAction.toggle()
-
-class Notification(QObject):
- def __init__(self, tray):
- QObject.__init__(self)
- self.tray = tray
- self.usePynotify = False
-
- try:
- self.usePynotify = pynotify.init("icon-summary-body")
- except:
- print "init error"
-
- def showMessage(self, body):
- if self.usePynotify:
- n = pynotify.Notification("pyload", body, join(pypath, "icons", "logo.png"))
- try:
- n.set_hint_string("x-canonical-append", "")
- except:
- pass
- n.show()
- else:
- self.tray.showMessage("pyload", body)
-
-if __name__ == "__main__":
- renameProcess('pyLoadGui')
- app = main()
- app.loop()
-
diff --git a/pyload-cli.py b/pyload-cli.py
new file mode 100644
index 000000000..0ef70368b
--- /dev/null
+++ b/pyload-cli.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from pyload.cli.Cli import main
+
+if __name__ == "__main__":
+ main()
diff --git a/pyload.py b/pyload.py
new file mode 100644
index 000000000..dc1957928
--- /dev/null
+++ b/pyload.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from pyload.Core import main
+
+if __name__ == "__main__":
+ main()
diff --git a/pyload/Core.py b/pyload/Core.py
new file mode 100644
index 000000000..902b6fdb3
--- /dev/null
+++ b/pyload/Core.py
@@ -0,0 +1,651 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: spoob
+ @author: sebnapi
+ @author: RaNaN
+ @author: mkaay
+ @version: v0.4.10
+"""
+CURRENT_VERSION = '0.4.10'
+
+import __builtin__
+
+from getopt import getopt, GetoptError
+import pyload.utils.pylgettext as gettext
+from imp import find_module
+import logging
+import logging.handlers
+import os
+from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close
+from os.path import exists, join
+import signal
+import subprocess
+import sys
+from sys import argv, executable, exit
+from time import time, sleep
+from traceback import print_exc
+
+from pyload import InitHomeDir
+from pyload.manager.AccountManager import AccountManager
+from pyload.manager.CaptchaManager import CaptchaManager
+from pyload.config.Parser import ConfigParser
+from pyload.manager.PluginManager import PluginManager
+from pyload.manager.event.PullEvents import PullManager
+from pyload.network.RequestFactory import RequestFactory
+from pyload.manager.thread.ServerThread import WebServer
+from pyload.manager.event.Scheduler import Scheduler
+from pyload.utils.JsEngine import JsEngine
+from pyload import remote
+from pyload.manager.RemoteManager import RemoteManager
+from pyload.database import DatabaseBackend, FileHandler
+
+from pyload.utils import freeSpace, formatSize, get_console_encoding
+
+from codecs import getwriter
+
+enc = get_console_encoding(sys.stdout.encoding)
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+# TODO List
+# - configurable auth system ldap/mysql
+# - cron job like sheduler
+
+class Core(object):
+ """pyLoad Core, one tool to rule them all... (the filehosters) :D"""
+
+ def __init__(self):
+ self.doDebug = False
+ self.running = False
+ self.daemon = False
+ self.remote = True
+ self.arg_links = []
+ self.pidfile = "pyload.pid"
+ self.deleteLinks = False # will delete links on startup
+
+ if len(argv) > 1:
+ try:
+ options, args = getopt(argv[1:], 'vchdusqp:',
+ ["version", "clear", "clean", "help", "debug", "user",
+ "setup", "configdir=", "changedir", "daemon",
+ "quit", "status", "no-remote","pidfile="])
+
+ for option, argument in options:
+ if option in ("-v", "--version"):
+ print "pyLoad", CURRENT_VERSION
+ exit()
+ elif option in ("-p", "--pidfile"):
+ self.pidfile = argument
+ elif option == "--daemon":
+ self.daemon = True
+ elif option in ("-c", "--clear"):
+ self.deleteLinks = True
+ elif option in ("-h", "--help"):
+ self.print_help()
+ exit()
+ elif option in ("-d", "--debug"):
+ self.doDebug = True
+ elif option in ("-u", "--user"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ s.set_user()
+ exit()
+ elif option in ("-s", "--setup"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ s.start()
+ exit()
+ elif option == "--changedir":
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ s.conf_path(True)
+ exit()
+ elif option in ("-q", "--quit"):
+ self.quitInstance()
+ exit()
+ elif option == "--status":
+ pid = self.isAlreadyRunning()
+ if self.isAlreadyRunning():
+ print pid
+ exit(0)
+ else:
+ print "false"
+ exit(1)
+ elif option == "--clean":
+ self.cleanTree()
+ exit()
+ elif option == "--no-remote":
+ self.remote = False
+
+ except GetoptError:
+ print 'Unknown Argument(s) "%s"' % " ".join(argv[1:])
+ self.print_help()
+ exit()
+
+ def print_help(self):
+ print
+ print "pyLoad v%s 2008-2014 the pyLoad Team" % CURRENT_VERSION
+ print
+ if sys.argv[0].endswith(".py"):
+ print "Usage: python pyload.py [options]"
+ else:
+ print "Usage: pyload [options]"
+ print
+ print "<Options>"
+ print " -v, --version", " " * 10, "Print version to terminal"
+ print " -c, --clear", " " * 12, "Delete all saved packages/links"
+ #print " -a, --add=<link/list>", " " * 2, "Add the specified links"
+ print " -u, --user", " " * 13, "Manages users"
+ print " -d, --debug", " " * 12, "Enable debug mode"
+ print " -s, --setup", " " * 12, "Run Setup Assistant"
+ print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory"
+ print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>"
+ print " --changedir", " " * 12, "Change config dir permanently"
+ print " --daemon", " " * 15, "Daemonmize after start"
+ print " --no-remote", " " * 12, "Disable remote access (saves RAM)"
+ print " --status", " " * 15, "Display pid if running or False"
+ print " --clean", " " * 16, "Remove .pyc/.pyo files"
+ print " -q, --quit", " " * 13, "Quit running pyLoad instance"
+ print " -h, --help", " " * 13, "Display this help screen"
+ print
+
+ def toggle_pause(self):
+ if self.threadManager.pause:
+ self.threadManager.pause = False
+ return False
+ elif not self.threadManager.pause:
+ self.threadManager.pause = True
+ return True
+
+ def quit(self, a, b):
+ self.shutdown()
+ self.log.info(_("Received Quit signal"))
+ _exit(1)
+
+ def writePidFile(self):
+ self.deletePidFile()
+ pid = os.getpid()
+ f = open(self.pidfile, "wb")
+ f.write(str(pid))
+ f.close()
+
+ def deletePidFile(self):
+ if self.checkPidFile():
+ self.log.debug("Deleting old pidfile %s" % self.pidfile)
+ os.remove(self.pidfile)
+
+ def checkPidFile(self):
+ """ return pid as int or 0"""
+ if os.path.isfile(self.pidfile):
+ f = open(self.pidfile, "rb")
+ pid = f.read().strip()
+ f.close()
+ if pid:
+ pid = int(pid)
+ return pid
+
+ return 0
+
+ def isAlreadyRunning(self):
+ pid = self.checkPidFile()
+ if not pid or os.name == "nt": return False
+ try:
+ os.kill(pid, 0) # 0 - default signal (does nothing)
+ except:
+ return 0
+
+ return pid
+
+ def quitInstance(self):
+ if os.name == "nt":
+ print "Not supported on windows."
+ return
+
+ pid = self.isAlreadyRunning()
+ if not pid:
+ print "No pyLoad running."
+ return
+
+ try:
+ os.kill(pid, 3) #SIGUIT
+
+ t = time()
+ print "waiting for pyLoad to quit"
+
+ while exists(self.pidfile) and t + 10 > time():
+ sleep(0.25)
+
+ if not exists(self.pidfile):
+ print "pyLoad successfully stopped"
+ else:
+ os.kill(pid, 9) #SIGKILL
+ print "pyLoad did not respond"
+ print "Kill signal was send to process with id %s" % pid
+
+ except:
+ print "Error quitting pyLoad"
+
+
+ def cleanTree(self):
+ for path, dirs, files in walk(self.path("")):
+ for f in files:
+ if not f.endswith(".pyo") and not f.endswith(".pyc"):
+ continue
+
+ if "_25" in f or "_26" in f or "_27" in f:
+ continue
+
+ print join(path, f)
+ remove(join(path, f))
+
+ def start(self, rpc=True, web=True):
+ """ starts the fun :D """
+
+ self.version = CURRENT_VERSION
+
+ if not exists("pyload.conf"):
+ from pyload.config.Setup import SetupAssistant as Setup
+
+ print "This is your first start, running configuration assistent now."
+ self.config = ConfigParser()
+ s = Setup(pypath, self.config)
+ res = False
+ try:
+ res = s.start()
+ except SystemExit:
+ pass
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ except:
+ res = False
+ print_exc()
+ print "Setup failed"
+ if not res:
+ remove("pyload.conf")
+
+ exit()
+
+ try: signal.signal(signal.SIGQUIT, self.quit)
+ except: pass
+
+ self.config = ConfigParser()
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("pyLoad", self.path("locale"),
+ languages=[self.config['general']['language'], "en"], fallback=True)
+ translation.install(True)
+
+ self.debug = self.doDebug or self.config['general']['debug_mode']
+ self.remote &= self.config['remote']['activated']
+
+ pid = self.isAlreadyRunning()
+ if pid:
+ print _("pyLoad already running with pid %s") % pid
+ exit()
+
+ if os.name != "nt" and self.config["general"]["renice"]:
+ os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid()))
+
+ if self.config["permission"]["change_group"]:
+ if os.name != "nt":
+ try:
+ from grp import getgrnam
+
+ group = getgrnam(self.config["permission"]["group"])
+ os.setgid(group[2])
+ except Exception, e:
+ print _("Failed changing group: %s") % e
+
+ if self.config["permission"]["change_user"]:
+ if os.name != "nt":
+ try:
+ from pwd import getpwnam
+
+ user = getpwnam(self.config["permission"]["user"])
+ os.setuid(user[2])
+ except Exception, e:
+ print _("Failed changing user: %s") % e
+
+ self.check_file(self.config['log']['log_folder'], _("folder for logs"), True)
+
+ if self.debug:
+ self.init_logger(logging.DEBUG) # logging level
+ else:
+ self.init_logger(logging.INFO) # logging level
+
+ self.do_kill = False
+ self.do_restart = False
+ self.shuttedDown = False
+
+ self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION)
+ self.log.info(_("Using home directory: %s") % getcwd())
+
+ self.writePidFile()
+
+ #@TODO refractor
+
+ remote.activated = self.remote
+ self.log.debug("Remote activated: %s" % self.remote)
+
+ self.check_install("Crypto", _("pycrypto to decode container files"))
+ #img = self.check_install("Image", _("Python Image Library (PIL) for captcha reading"))
+ #self.check_install("pycurl", _("pycurl to download any files"), True, True)
+ self.check_file("tmp", _("folder for temporary files"), True)
+ #tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True
+
+ self.captcha = True # checks seems to fail, although tesseract is available
+
+ self.check_file(self.config['general']['download_folder'], _("folder for downloads"), True)
+
+ if self.config['ssl']['activated']:
+ self.check_install("OpenSSL", _("OpenSSL for secure connection"))
+
+ self.setupDB()
+ if self.config.oldRemoteData:
+ self.log.info(_("Moving old user config to DB"))
+ self.db.addUser(self.config.oldRemoteData["username"], self.config.oldRemoteData["password"])
+
+ self.log.info(_("Please check your logindata with ./pyload.py -u"))
+
+ if self.deleteLinks:
+ self.log.info(_("All links removed"))
+ self.db.purgeLinks()
+
+ self.requestFactory = RequestFactory(self)
+ __builtin__.pyreq = self.requestFactory
+
+ self.lastClientConnected = 0
+
+ # later imported because they would trigger api import, and remote value not set correctly
+ from pyload import api
+ from pyload.manager.HookManager import HookManager
+ from pyload.manager.ThreadManager import ThreadManager
+
+ if api.activated != self.remote:
+ self.log.warning("Import error: API remote status not correct.")
+
+ self.api = api.Api(self)
+
+ self.scheduler = Scheduler(self)
+
+ #hell yeah, so many important managers :D
+ self.pluginManager = PluginManager(self)
+ self.pullManager = PullManager(self)
+ self.accountManager = AccountManager(self)
+ self.threadManager = ThreadManager(self)
+ self.captchaManager = CaptchaManager(self)
+ self.hookManager = HookManager(self)
+ self.remoteManager = RemoteManager(self)
+
+ self.js = JsEngine(self)
+
+ self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload())
+
+ if rpc:
+ self.remoteManager.startBackends()
+
+ if web:
+ self.init_webserver()
+
+ spaceLeft = freeSpace(self.config["general"]["download_folder"])
+
+ self.log.info(_("Free space: %s") % formatSize(spaceLeft))
+
+ self.config.save() #save so config files gets filled
+
+ link_file = join(pypath, "links.txt")
+
+ if exists(link_file):
+ f = open(link_file, "rb")
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+ f.close()
+
+ link_file = "links.txt"
+ if exists(link_file):
+ f = open(link_file, "rb")
+ if f.read().strip():
+ self.api.addPackage("links.txt", [link_file], 1)
+ f.close()
+
+ #self.scheduler.addJob(0, self.accountManager.getAccountInfos)
+ self.log.info(_("Activating Accounts..."))
+ self.accountManager.getAccountInfos()
+
+ self.threadManager.pause = False
+ self.running = True
+
+ self.log.info(_("Activating Plugins..."))
+ self.hookManager.coreReady()
+
+ self.log.info(_("pyLoad is up and running"))
+
+ locals().clear()
+
+ while True:
+ sleep(2)
+ if self.do_restart:
+ self.log.info(_("restarting pyLoad"))
+ self.restart()
+ if self.do_kill:
+ self.shutdown()
+ self.log.info(_("pyLoad quits"))
+ self.removeLogger()
+ _exit(0) #@TODO thrift blocks shutdown
+
+ self.threadManager.work()
+ self.scheduler.work()
+
+ def setupDB(self):
+ self.db = DatabaseBackend(self) # the backend
+ self.db.setup()
+
+ self.files = FileHandler(self)
+ self.db.manager = self.files #ugly?
+
+ def init_webserver(self):
+ if self.config['webinterface']['activated']:
+ self.webserver = WebServer(self)
+ self.webserver.start()
+
+ def init_logger(self, level):
+ console = logging.StreamHandler(sys.stdout)
+ frm = logging.Formatter("%(asctime)s %(levelname)-8s %(message)s", "%d.%m.%Y %H:%M:%S")
+ console.setFormatter(frm)
+ self.log = logging.getLogger("log") # settable in config
+
+ if self.config['log']['file_log']:
+ if self.config['log']['log_rotate']:
+ file_handler = logging.handlers.RotatingFileHandler(join(self.config['log']['log_folder'], 'log.txt'),
+ maxBytes=self.config['log']['log_size'] * 1024,
+ backupCount=int(self.config['log']['log_count']),
+ encoding="utf8")
+ else:
+ file_handler = logging.FileHandler(join(self.config['log']['log_folder'], 'log.txt'), encoding="utf8")
+
+ file_handler.setFormatter(frm)
+ self.log.addHandler(file_handler)
+
+ self.log.addHandler(console) #if console logging
+ self.log.setLevel(level)
+
+ def removeLogger(self):
+ for h in list(self.log.handlers):
+ self.log.removeHandler(h)
+ h.close()
+
+ def check_install(self, check_name, legend, python=True, essential=False):
+ """check wether needed tools are installed"""
+ try:
+ if python:
+ find_module(check_name)
+ else:
+ pipe = subprocess.PIPE
+ subprocess.Popen(check_name, stdout=pipe, stderr=pipe)
+
+ return True
+ except:
+ if essential:
+ self.log.info(_("Install %s") % legend)
+ exit()
+
+ return False
+
+ def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False):
+ """check wether needed files exists"""
+ tmp_names = []
+ if not type(check_names) == list:
+ tmp_names.append(check_names)
+ else:
+ tmp_names.extend(check_names)
+ file_created = True
+ file_exists = True
+ for tmp_name in tmp_names:
+ if not exists(tmp_name):
+ file_exists = False
+ if empty:
+ try:
+ if folder:
+ tmp_name = tmp_name.replace("/", sep)
+ makedirs(tmp_name)
+ else:
+ open(tmp_name, "w")
+ except:
+ file_created = False
+ else:
+ file_created = False
+
+ if not file_exists and not quiet:
+ if file_created:
+ #self.log.info( _("%s created") % description )
+ pass
+ else:
+ if not empty:
+ self.log.warning(
+ _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name})
+ else:
+ print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}
+ if essential:
+ exit()
+
+ def isClientConnected(self):
+ return (self.lastClientConnected + 30) > time()
+
+ def restart(self):
+ self.shutdown()
+ chdir(owd)
+ # close some open fds
+ for i in range(3, 50):
+ try:
+ close(i)
+ except :
+ pass
+
+ execl(executable, executable, *sys.argv)
+ _exit(0)
+
+ def shutdown(self):
+ self.log.info(_("shutting down..."))
+ try:
+ if self.config['webinterface']['activated'] and hasattr(self, "webserver"):
+ self.webserver.quit()
+
+ for thread in self.threadManager.threads:
+ thread.put("quit")
+ pyfiles = self.files.cache.values()
+
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ self.hookManager.coreExiting()
+
+ except:
+ if self.debug:
+ print_exc()
+ self.log.info(_("error while shutting down"))
+
+ finally:
+ self.files.syncSave()
+ self.shuttedDown = True
+
+ self.deletePidFile()
+
+
+ def path(self, *args):
+ return join(pypath, *args)
+
+
+def deamon():
+ try:
+ pid = os.fork()
+ if pid > 0:
+ sys.exit(0)
+ except OSError, e:
+ print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+ # decouple from parent environment
+ os.setsid()
+ os.umask(0)
+
+ # do second fork
+ try:
+ pid = os.fork()
+ if pid > 0:
+ # exit from second parent, print eventual PID before
+ print "Daemon PID %d" % pid
+ sys.exit(0)
+ except OSError, e:
+ print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
+ sys.exit(1)
+
+ # Iterate through and close some file descriptors.
+ for fd in range(0, 3):
+ try:
+ os.close(fd)
+ except OSError: # ERROR, fd wasn't open to begin with (ignored)
+ pass
+
+ os.open(os.devnull, os.O_RDWR) # standard input (0)
+ os.dup2(0, 1) # standard output (1)
+ os.dup2(0, 2)
+
+ pyload_core = Core()
+ pyload_core.start()
+
+
+def main():
+ if "--daemon" in sys.argv:
+ deamon()
+ else:
+ pyload_core = Core()
+ try:
+ pyload_core.start()
+ except KeyboardInterrupt:
+ pyload_core.shutdown()
+ pyload_core.log.info(_("killed pyLoad from Terminal"))
+ pyload_core.removeLogger()
+ _exit(1)
+
+# And so it begins...
+if __name__ == "__main__":
+ main()
diff --git a/pyload/InitHomeDir.py b/pyload/InitHomeDir.py
new file mode 100644
index 000000000..ca229fb1e
--- /dev/null
+++ b/pyload/InitHomeDir.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+
+ This modules inits working directories and global variables, pydir and homedir
+"""
+
+from os import makedirs, path, chdir
+from os.path import join
+import sys
+from sys import argv, platform
+
+import __builtin__
+
+__builtin__.owd = path.abspath("") # original working directory
+__builtin__.pypath = path.abspath(path.join(__file__, "..", ".."))
+
+sys.path.append(join(pypath, "pyload", "lib"))
+
+homedir = ""
+
+if platform == 'nt':
+ homedir = path.expanduser("~")
+ if homedir == "~":
+ import ctypes
+
+ CSIDL_APPDATA = 26
+ _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW
+ _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND,
+ ctypes.c_int,
+ ctypes.wintypes.HANDLE,
+ ctypes.wintypes.DWORD, ctypes.wintypes.LPCWSTR]
+
+ path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
+ result = _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf)
+ homedir = path_buf.value
+else:
+ homedir = path.expanduser("~")
+
+__builtin__.homedir = homedir
+
+args = " ".join(argv[1:])
+
+# dirty method to set configdir from commandline arguments
+if "--configdir=" in args:
+ for aa in argv:
+ if aa.startswith("--configdir="):
+ configdir = aa.replace("--configdir=", "", 1).strip()
+elif path.exists(path.join(pypath, "pyload", "config", "configdir")):
+ f = open(path.join(pypath, "pyload", "config", "configdir"), "rb")
+ c = f.read().strip()
+ f.close()
+ configdir = path.join(pypath, c)
+else:
+ if platform in ("posix", "linux2"):
+ configdir = path.join(homedir, ".pyload")
+ else:
+ configdir = path.join(homedir, "pyload")
+
+if not path.exists(configdir):
+ makedirs(configdir, 0700)
+
+__builtin__.configdir = configdir
+chdir(configdir)
+
+#print "Using %s as working directory." % configdir
diff --git a/pyload/__init__.py b/pyload/__init__.py
new file mode 100644
index 000000000..bd96630c3
--- /dev/null
+++ b/pyload/__init__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+__all__ = ["__status_code__", "__status__", "__version_info__", "__version__", "__author_name__", "__author_mail__", "__license__"]
+
+__status_code__ = 4
+__status__ = {1: "Planning",
+ 2: "Pre-Alpha",
+ 3: "Alpha",
+ 4: "Beta",
+ 5: "Production/Stable",
+ 6: "Mature",
+ 7: "Inactive"}[__status_code__] #: PyPI Development Status Classifiers
+
+__version_info__ = (0, 4, 10)
+__version__ = '.'.join(map(str(v), __version_info__))
+
+__author_name__ = "pyLoad Team"
+__author_mail__ = "admin@pyload.org"
+
+__license__ = "GNU Affero General Public License v3"
diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py
new file mode 100644
index 000000000..3da1e13e1
--- /dev/null
+++ b/pyload/api/__init__.py
@@ -0,0 +1,1030 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from base64 import standard_b64encode
+from os.path import join
+from time import time
+import re
+
+from pyload.datatypes.PyFile import PyFile
+from utils import freeSpace, compare_time
+from pyload.utils.packagetools import parseNames
+from network.RequestFactory import getURL
+from remote import activated
+
+if activated:
+ try:
+ from remote.thriftbackend.thriftgen.pyload.ttypes import *
+ from remote.thriftbackend.thriftgen.pyload.Pyload import Iface
+ BaseObject = TBase
+ except ImportError:
+ print "Thrift not imported"
+ from remote.socketbackend.ttypes import *
+else:
+ from remote.socketbackend.ttypes import *
+
+# contains function names mapped to their permissions
+# unlisted functions are for admins only
+permMap = {}
+
+# decorator only called on init, never initialized, so has no effect on runtime
+def permission(bits):
+ class _Dec(object):
+ def __new__(cls, func, *args, **kwargs):
+ permMap[func.__name__] = bits
+ return func
+
+ return _Dec
+
+
+urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&]*)", re.IGNORECASE)
+
+class PERMS:
+ ALL = 0 # requires no permission, but login
+ ADD = 1 # can add packages
+ DELETE = 2 # can delete packages
+ STATUS = 4 # see and change server status
+ LIST = 16 # see queue and collector
+ MODIFY = 32 # moddify some attribute of downloads
+ DOWNLOAD = 64 # can download from webinterface
+ SETTINGS = 128 # can access settings
+ ACCOUNTS = 256 # can access accounts
+ LOGS = 512 # can see server logs
+
+class ROLE:
+ ADMIN = 0 #admin has all permissions implicit
+ USER = 1
+
+def has_permission(userperms, perms):
+ # bytewise or perms before if needed
+ return perms == (userperms & perms)
+
+
+class Api(Iface):
+ """
+ **pyLoads API**
+
+ This is accessible either internal via core.api or via thrift backend.
+
+ see Thrift specification file remote/thriftbackend/pyload.thrift\
+ for information about data structures and what methods are usuable with rpc.
+
+ Most methods requires specific permissions, please look at the source code if you need to know.\
+ These can be configured via webinterface.
+ Admin user have all permissions, and are the only ones who can access the methods with no specific permission.
+ """
+
+ EXTERNAL = Iface # let the json api know which methods are external
+
+ def __init__(self, core):
+ self.core = core
+
+ def _convertPyFile(self, p):
+ f = FileData(p["id"], p["url"], p["name"], p["plugin"], p["size"],
+ p["format_size"], p["status"], p["statusmsg"],
+ p["package"], p["error"], p["order"])
+ return f
+
+ def _convertConfigFormat(self, c):
+ sections = {}
+ for sectionName, sub in c.iteritems():
+ section = ConfigSection(sectionName, sub["desc"])
+ items = []
+ for key, data in sub.iteritems():
+ if key in ("desc", "outline"):
+ continue
+ item = ConfigItem()
+ item.name = key
+ item.description = data["desc"]
+ item.value = str(data["value"]) if not isinstance(data["value"], basestring) else data["value"]
+ item.type = data["type"]
+ items.append(item)
+ section.items = items
+ sections[sectionName] = section
+ if "outline" in sub:
+ section.outline = sub["outline"]
+ return sections
+
+ @permission(PERMS.SETTINGS)
+ def getConfigValue(self, category, option, section="core"):
+ """Retrieve config value.
+
+ :param category: name of category, or plugin
+ :param option: config option
+ :param section: 'plugin' or 'core'
+ :return: config value as string
+ """
+ if section == "core":
+ value = self.core.config[category][option]
+ else:
+ value = self.core.config.getPlugin(category, option)
+
+ return str(value) if not isinstance(value, basestring) else value
+
+ @permission(PERMS.SETTINGS)
+ def setConfigValue(self, category, option, value, section="core"):
+ """Set new config value.
+
+ :param category:
+ :param option:
+ :param value: new config value
+ :param section: 'plugin' or 'core
+ """
+ self.core.hookManager.dispatchEvent("configChanged", category, option, value, section)
+
+ if section == "core":
+ self.core.config[category][option] = value
+
+ if option in ("limit_speed", "max_speed"): #not so nice to update the limit
+ self.core.requestFactory.updateBucket()
+
+ elif section == "plugin":
+ self.core.config.setPlugin(category, option, value)
+
+ @permission(PERMS.SETTINGS)
+ def getConfig(self):
+ """Retrieves complete config of core.
+
+ :return: list of `ConfigSection`
+ """
+ return self._convertConfigFormat(self.core.config.config)
+
+ def getConfigDict(self):
+ """Retrieves complete config in dict format, not for RPC.
+
+ :return: dict
+ """
+ return self.core.config.config
+
+ @permission(PERMS.SETTINGS)
+ def getPluginConfig(self):
+ """Retrieves complete config for all plugins.
+
+ :return: list of `ConfigSection`
+ """
+ return self._convertConfigFormat(self.core.config.plugin)
+
+ def getPluginConfigDict(self):
+ """Plugin config as dict, not for RPC.
+
+ :return: dict
+ """
+ return self.core.config.plugin
+
+
+ @permission(PERMS.STATUS)
+ def pauseServer(self):
+ """Pause server: Tt wont start any new downloads, but nothing gets aborted."""
+ self.core.threadManager.pause = True
+
+ @permission(PERMS.STATUS)
+ def unpauseServer(self):
+ """Unpause server: New Downloads will be started."""
+ self.core.threadManager.pause = False
+
+ @permission(PERMS.STATUS)
+ def togglePause(self):
+ """Toggle pause state.
+
+ :return: new pause state
+ """
+ self.core.threadManager.pause ^= True
+ return self.core.threadManager.pause
+
+ @permission(PERMS.STATUS)
+ def toggleReconnect(self):
+ """Toggle reconnect activation.
+
+ :return: new reconnect state
+ """
+ self.core.config["reconnect"]["activated"] ^= True
+ return self.core.config["reconnect"]["activated"]
+
+ @permission(PERMS.LIST)
+ def statusServer(self):
+ """Some general information about the current status of pyLoad.
+
+ :return: `ServerStatus`
+ """
+ serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()),
+ self.core.files.getQueueCount(), self.core.files.getFileCount(), 0,
+ not self.core.threadManager.pause and self.isTimeDownload(),
+ self.core.config['reconnect']['activated'] and self.isTimeReconnect())
+
+ for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]:
+ serverStatus.speed += pyfile.getSpeed() #bytes/s
+
+ return serverStatus
+
+ @permission(PERMS.STATUS)
+ def freeSpace(self):
+ """Available free space at download directory in bytes"""
+ return freeSpace(self.core.config["general"]["download_folder"])
+
+ @permission(PERMS.ALL)
+ def getServerVersion(self):
+ """pyLoad Core version """
+ return self.core.version
+
+ def kill(self):
+ """Clean way to quit pyLoad"""
+ self.core.do_kill = True
+
+ def restart(self):
+ """Restart pyload core"""
+ self.core.do_restart = True
+
+ @permission(PERMS.LOGS)
+ def getLog(self, offset=0):
+ """Returns most recent log entries.
+
+ :param offset: line offset
+ :return: List of log entries
+ """
+ filename = join(self.core.config['log']['log_folder'], 'log.txt')
+ try:
+ fh = open(filename, "r")
+ lines = fh.readlines()
+ fh.close()
+ if offset >= len(lines):
+ return []
+ return lines[offset:]
+ except:
+ return ['No log available']
+
+ @permission(PERMS.STATUS)
+ def isTimeDownload(self):
+ """Checks if pyload will start new downloads according to time in config.
+
+ :return: bool
+ """
+ start = self.core.config['downloadTime']['start'].split(":")
+ end = self.core.config['downloadTime']['end'].split(":")
+ return compare_time(start, end)
+
+ @permission(PERMS.STATUS)
+ def isTimeReconnect(self):
+ """Checks if pyload will try to make a reconnect
+
+ :return: bool
+ """
+ start = self.core.config['reconnect']['startTime'].split(":")
+ end = self.core.config['reconnect']['endTime'].split(":")
+ return compare_time(start, end) and self.core.config["reconnect"]["activated"]
+
+ @permission(PERMS.LIST)
+ def statusDownloads(self):
+ """ Status off all currently running downloads.
+
+ :return: list of `DownloadStatus`
+ """
+ data = []
+ for pyfile in self.core.threadManager.getActiveFiles():
+ if not isinstance(pyfile, PyFile):
+ continue
+
+ data.append(DownloadInfo(
+ pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(),
+ pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(),
+ pyfile.status, pyfile.getStatusName(), pyfile.formatWait(),
+ pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname))
+
+ return data
+
+ @permission(PERMS.ADD)
+ def addPackage(self, name, links, dest=Destination.Queue):
+ """Adds a package, with links to desired destination.
+
+ :param name: name of the new package
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: package id of the new package
+ """
+ if self.core.config['general']['folder_per_package']:
+ folder = name
+ else:
+ folder = ""
+
+ folder = folder.replace("http://", "").replace(":", "").replace("/", "_").replace("\\", "_")
+
+ pid = self.core.files.addPackage(name, folder, dest)
+
+ self.core.files.addLinks(links, pid)
+
+ self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)})
+
+ self.core.files.save()
+
+ return pid
+
+ @permission(PERMS.ADD)
+ def parseURLs(self, html=None, url=None):
+ """Parses html content or any arbitaty text for links and returns result of `checkURLs`
+
+ :param html: html source
+ :return:
+ """
+ urls = []
+
+ if html:
+ urls += [x[0] for x in urlmatcher.findall(html)]
+
+ if url:
+ page = getURL(url)
+ urls += [x[0] for x in urlmatcher.findall(page)]
+
+ # remove duplicates
+ return self.checkURLs(set(urls))
+
+
+ @permission(PERMS.ADD)
+ def checkURLs(self, urls):
+ """ Gets urls and returns pluginname mapped to list of matches urls.
+
+ :param urls:
+ :return: {plugin: urls}
+ """
+ data = self.core.pluginManager.parseUrls(urls)
+ plugins = {}
+
+ for url, plugin in data:
+ if plugin in plugins:
+ plugins[plugin].append(url)
+ else:
+ plugins[plugin] = [url]
+
+ return plugins
+
+ @permission(PERMS.ADD)
+ def checkOnlineStatus(self, urls):
+ """ initiates online status check
+
+ :param urls:
+ :return: initial set of data as `OnlineCheck` instance containing the result id
+ """
+ data = self.core.pluginManager.parseUrls(urls)
+
+ rid = self.core.threadManager.createResultThread(data, False)
+
+ tmp = [(url, (url, OnlineStatus(url, pluginname, "unknown", 3, 0))) for url, pluginname in data]
+ data = parseNames(tmp)
+ result = {}
+
+ for k, v in data.iteritems():
+ for url, status in v:
+ status.packagename = k
+ result[url] = status
+
+ return OnlineCheck(rid, result)
+
+ @permission(PERMS.ADD)
+ def checkOnlineStatusContainer(self, urls, container, data):
+ """ checks online status of urls and a submited container file
+
+ :param urls: list of urls
+ :param container: container file name
+ :param data: file content
+ :return: online check
+ """
+ th = open(join(self.core.config["general"]["download_folder"], "tmp_" + container), "wb")
+ th.write(str(data))
+ th.close()
+
+ return self.checkOnlineStatus(urls + [th.name])
+
+ @permission(PERMS.ADD)
+ def pollResults(self, rid):
+ """ Polls the result available for ResultID
+
+ :param rid: `ResultID`
+ :return: `OnlineCheck`, if rid is -1 then no more data available
+ """
+ result = self.core.threadManager.getInfoResult(rid)
+
+ if "ALL_INFO_FETCHED" in result:
+ del result["ALL_INFO_FETCHED"]
+ return OnlineCheck(-1, result)
+ else:
+ return OnlineCheck(rid, result)
+
+
+ @permission(PERMS.ADD)
+ def generatePackages(self, links):
+ """ Parses links, generates packages names from urls
+
+ :param links: list of urls
+ :return: package names mapped to urls
+ """
+ result = parseNames((x, x) for x in links)
+ return result
+
+ @permission(PERMS.ADD)
+ def generateAndAddPackages(self, links, dest=Destination.Queue):
+ """Generates and add packages
+
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: list of package ids
+ """
+ return [self.addPackage(name, urls, dest) for name, urls
+ in self.generatePackages(links).iteritems()]
+
+ @permission(PERMS.ADD)
+ def checkAndAddPackages(self, links, dest=Destination.Queue):
+ """Checks online status, retrieves names, and will add packages.\
+ Because of this packages are not added immediatly, only for internal use.
+
+ :param links: list of urls
+ :param dest: `Destination`
+ :return: None
+ """
+ data = self.core.pluginManager.parseUrls(links)
+ self.core.threadManager.createResultThread(data, True)
+
+
+ @permission(PERMS.LIST)
+ def getPackageData(self, pid):
+ """Returns complete information about package, and included files.
+
+ :param pid: package id
+ :return: `PackageData` with .links attribute
+ """
+ data = self.core.files.getPackageData(int(pid))
+
+ if not data:
+ raise PackageDoesNotExists(pid)
+
+ pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
+ data["queue"], data["order"],
+ links=[self._convertPyFile(x) for x in data["links"].itervalues()])
+
+ return pdata
+
+ @permission(PERMS.LIST)
+ def getPackageInfo(self, pid):
+ """Returns information about package, without detailed information about containing files
+
+ :param pid: package id
+ :return: `PackageData` with .fid attribute
+ """
+ data = self.core.files.getPackageData(int(pid))
+
+ if not data:
+ raise PackageDoesNotExists(pid)
+
+ pdata = PackageData(data["id"], data["name"], data["folder"], data["site"], data["password"],
+ data["queue"], data["order"],
+ fids=[int(x) for x in data["links"]])
+
+ return pdata
+
+ @permission(PERMS.LIST)
+ def getFileData(self, fid):
+ """Get complete information about a specific file.
+
+ :param fid: file id
+ :return: `FileData`
+ """
+ info = self.core.files.getFileData(int(fid))
+ if not info:
+ raise FileDoesNotExists(fid)
+
+ fdata = self._convertPyFile(info.values()[0])
+ return fdata
+
+ @permission(PERMS.DELETE)
+ def deleteFiles(self, fids):
+ """Deletes several file entries from pyload.
+
+ :param fids: list of file ids
+ """
+ for id in fids:
+ self.core.files.deleteLink(int(id))
+
+ self.core.files.save()
+
+ @permission(PERMS.DELETE)
+ def deletePackages(self, pids):
+ """Deletes packages and containing links.
+
+ :param pids: list of package ids
+ """
+ for id in pids:
+ self.core.files.deletePackage(int(id))
+
+ self.core.files.save()
+
+ @permission(PERMS.LIST)
+ def getQueue(self):
+ """Returns info about queue and packages, **not** about files, see `getQueueData` \
+ or `getPackageData` instead.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ pack["linkstotal"])
+ for pack in self.core.files.getInfoData(Destination.Queue).itervalues()]
+
+ @permission(PERMS.LIST)
+ def getQueueData(self):
+ """Return complete data about everything in queue, this is very expensive use it sparely.\
+ See `getQueue` for alternative.
+
+ :return: list of `PackageData`
+ """
+ return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
+ for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()]
+
+ @permission(PERMS.LIST)
+ def getCollector(self):
+ """same as `getQueue` for collector.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ pack["linkstotal"])
+ for pack in self.core.files.getInfoData(Destination.Collector).itervalues()]
+
+ @permission(PERMS.LIST)
+ def getCollectorData(self):
+ """same as `getQueueData` for collector.
+
+ :return: list of `PackageInfo`
+ """
+ return [PackageData(pack["id"], pack["name"], pack["folder"], pack["site"],
+ pack["password"], pack["queue"], pack["order"],
+ pack["linksdone"], pack["sizedone"], pack["sizetotal"],
+ links=[self._convertPyFile(x) for x in pack["links"].itervalues()])
+ for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()]
+
+
+ @permission(PERMS.ADD)
+ def addFiles(self, pid, links):
+ """Adds files to specific package.
+
+ :param pid: package id
+ :param links: list of urls
+ """
+ self.core.files.addLinks(links, int(pid))
+
+ self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid})
+ self.core.files.save()
+
+ @permission(PERMS.MODIFY)
+ def pushToQueue(self, pid):
+ """Moves package from Collector to Queue.
+
+ :param pid: package id
+ """
+ self.core.files.setPackageLocation(pid, Destination.Queue)
+
+ @permission(PERMS.MODIFY)
+ def pullFromQueue(self, pid):
+ """Moves package from Queue to Collector.
+
+ :param pid: package id
+ """
+ self.core.files.setPackageLocation(pid, Destination.Collector)
+
+ @permission(PERMS.MODIFY)
+ def restartPackage(self, pid):
+ """Restarts a package, resets every containing files.
+
+ :param pid: package id
+ """
+ self.core.files.restartPackage(int(pid))
+
+ @permission(PERMS.MODIFY)
+ def restartFile(self, fid):
+ """Resets file status, so it will be downloaded again.
+
+ :param fid: file id
+ """
+ self.core.files.restartFile(int(fid))
+
+ @permission(PERMS.MODIFY)
+ def recheckPackage(self, pid):
+ """Proofes online status of all files in a package, also a default action when package is added.
+
+ :param pid:
+ :return:
+ """
+ self.core.files.reCheckPackage(int(pid))
+
+ @permission(PERMS.MODIFY)
+ def stopAllDownloads(self):
+ """Aborts all running downloads."""
+
+ pyfiles = self.core.files.cache.values()
+ for pyfile in pyfiles:
+ pyfile.abortDownload()
+
+ @permission(PERMS.MODIFY)
+ def stopDownloads(self, fids):
+ """Aborts specific downloads.
+
+ :param fids: list of file ids
+ :return:
+ """
+ pyfiles = self.core.files.cache.values()
+
+ for pyfile in pyfiles:
+ if pyfile.id in fids:
+ pyfile.abortDownload()
+
+ @permission(PERMS.MODIFY)
+ def setPackageName(self, pid, name):
+ """Renames a package.
+
+ :param pid: package id
+ :param name: new package name
+ """
+ pack = self.core.files.getPackage(pid)
+ pack.name = name
+ pack.sync()
+
+ @permission(PERMS.MODIFY)
+ def movePackage(self, destination, pid):
+ """Set a new package location.
+
+ :param destination: `Destination`
+ :param pid: package id
+ """
+ if destination not in (0, 1): return
+ self.core.files.setPackageLocation(pid, destination)
+
+ @permission(PERMS.MODIFY)
+ def moveFiles(self, fids, pid):
+ """Move multiple files to another package
+
+ :param fids: list of file ids
+ :param pid: destination package
+ :return:
+ """
+ #TODO: implement
+ pass
+
+
+ @permission(PERMS.ADD)
+ def uploadContainer(self, filename, data):
+ """Uploads and adds a container file to pyLoad.
+
+ :param filename: filename, extension is important so it can correctly decrypted
+ :param data: file content
+ """
+ th = open(join(self.core.config["general"]["download_folder"], "tmp_" + filename), "wb")
+ th.write(str(data))
+ th.close()
+
+ self.addPackage(th.name, [th.name], Destination.Queue)
+
+ @permission(PERMS.MODIFY)
+ def orderPackage(self, pid, position):
+ """Gives a package a new position.
+
+ :param pid: package id
+ :param position:
+ """
+ self.core.files.reorderPackage(pid, position)
+
+ @permission(PERMS.MODIFY)
+ def orderFile(self, fid, position):
+ """Gives a new position to a file within its package.
+
+ :param fid: file id
+ :param position:
+ """
+ self.core.files.reorderFile(fid, position)
+
+ @permission(PERMS.MODIFY)
+ def setPackageData(self, pid, data):
+ """Allows to modify several package attributes.
+
+ :param pid: package id
+ :param data: dict that maps attribute to desired value
+ """
+ p = self.core.files.getPackage(pid)
+ if not p: raise PackageDoesNotExists(pid)
+
+ for key, value in data.iteritems():
+ if key == "id": continue
+ setattr(p, key, value)
+
+ p.sync()
+ self.core.files.save()
+
+ @permission(PERMS.DELETE)
+ def deleteFinished(self):
+ """Deletes all finished files and completly finished packages.
+
+ :return: list of deleted package ids
+ """
+ return self.core.files.deleteFinishedLinks()
+
+ @permission(PERMS.MODIFY)
+ def restartFailed(self):
+ """Restarts all failed failes."""
+ self.core.files.restartFailed()
+
+ @permission(PERMS.LIST)
+ def getPackageOrder(self, destination):
+ """Returns information about package order.
+
+ :param destination: `Destination`
+ :return: dict mapping order to package id
+ """
+
+ packs = self.core.files.getInfoData(destination)
+ order = {}
+
+ for pid in packs:
+ pack = self.core.files.getPackageData(int(pid))
+ while pack["order"] in order.keys(): #just in case
+ pack["order"] += 1
+ order[pack["order"]] = pack["id"]
+ return order
+
+ @permission(PERMS.LIST)
+ def getFileOrder(self, pid):
+ """Information about file order within package.
+
+ :param pid:
+ :return: dict mapping order to file id
+ """
+ rawData = self.core.files.getPackageData(int(pid))
+ order = {}
+ for id, pyfile in rawData["links"].iteritems():
+ while pyfile["order"] in order.keys(): #just in case
+ pyfile["order"] += 1
+ order[pyfile["order"]] = pyfile["id"]
+ return order
+
+
+ @permission(PERMS.STATUS)
+ def isCaptchaWaiting(self):
+ """Indicates wether a captcha task is available
+
+ :return: bool
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTask()
+ return not task is None
+
+ @permission(PERMS.STATUS)
+ def getCaptchaTask(self, exclusive=False):
+ """Returns a captcha task
+
+ :param exclusive: unused
+ :return: `CaptchaTask`
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTask()
+ if task:
+ task.setWatingForUser(exclusive=exclusive)
+ data, type, result = task.getCaptcha()
+ t = CaptchaTask(int(task.id), standard_b64encode(data), type, result)
+ return t
+ else:
+ return CaptchaTask(-1)
+
+ @permission(PERMS.STATUS)
+ def getCaptchaTaskStatus(self, tid):
+ """Get information about captcha task
+
+ :param tid: task id
+ :return: string
+ """
+ self.core.lastClientConnected = time()
+ t = self.core.captchaManager.getTaskByID(tid)
+ return t.getStatus() if t else ""
+
+ @permission(PERMS.STATUS)
+ def setCaptchaResult(self, tid, result):
+ """Set result for a captcha task
+
+ :param tid: task id
+ :param result: captcha result
+ """
+ self.core.lastClientConnected = time()
+ task = self.core.captchaManager.getTaskByID(tid)
+ if task:
+ task.setResult(result)
+ self.core.captchaManager.removeTask(task)
+
+
+ @permission(PERMS.STATUS)
+ def getEvents(self, uuid):
+ """Lists occured events, may be affected to changes in future.
+
+ :param uuid:
+ :return: list of `Events`
+ """
+ events = self.core.pullManager.getEvents(uuid)
+ newEvents = []
+
+ def convDest(d):
+ return Destination.Queue if d == "queue" else Destination.Collector
+
+ for e in events:
+ event = EventInfo()
+ event.eventname = e[0]
+ if e[0] in ("update", "remove", "insert"):
+ event.id = e[3]
+ event.type = ElementType.Package if e[2] == "pack" else ElementType.File
+ event.destination = convDest(e[1])
+ elif e[0] == "order":
+ if e[1]:
+ event.id = e[1]
+ event.type = ElementType.Package if e[2] == "pack" else ElementType.File
+ event.destination = convDest(e[3])
+ elif e[0] == "reload":
+ event.destination = convDest(e[1])
+ newEvents.append(event)
+ return newEvents
+
+ @permission(PERMS.ACCOUNTS)
+ def getAccounts(self, refresh):
+ """Get information about all entered accounts.
+
+ :param refresh: reload account info
+ :return: list of `AccountInfo`
+ """
+ accs = self.core.accountManager.getAccountInfos(False, refresh)
+ accounts = []
+ for group in accs.values():
+ accounts.extend([AccountInfo(acc["validuntil"], acc["login"], acc["options"], acc["valid"],
+ acc["trafficleft"], acc["maxtraffic"], acc["premium"], acc["type"])
+ for acc in group])
+ return accounts
+
+ @permission(PERMS.ALL)
+ def getAccountTypes(self):
+ """All available account types.
+
+ :return: list
+ """
+ return self.core.accountManager.accounts.keys()
+
+ @permission(PERMS.ACCOUNTS)
+ def updateAccount(self, plugin, account, password=None, options={}):
+ """Changes pw/options for specific account."""
+ self.core.accountManager.updateAccount(plugin, account, password, options)
+
+ @permission(PERMS.ACCOUNTS)
+ def removeAccount(self, plugin, account):
+ """Remove account from pyload.
+
+ :param plugin: pluginname
+ :param account: accountname
+ """
+ self.core.accountManager.removeAccount(plugin, account)
+
+ @permission(PERMS.ALL)
+ def login(self, username, password, remoteip=None):
+ """Login into pyLoad, this **must** be called when using rpc before any methods can be used.
+
+ :param username:
+ :param password:
+ :param remoteip: Omit this argument, its only used internal
+ :return: bool indicating login was successful
+ """
+ return True if self.checkAuth(username, password, remoteip) else False
+
+ def checkAuth(self, username, password, remoteip=None):
+ """Check authentication and returns details
+
+ :param username:
+ :param password:
+ :param remoteip:
+ :return: dict with info, empty when login is incorrect
+ """
+ if self.core.config["remote"]["nolocalauth"] and remoteip == "127.0.0.1":
+ return "local"
+ else:
+ return self.core.db.checkAuth(username, password)
+
+ def isAuthorized(self, func, userdata):
+ """checks if the user is authorized for specific method
+
+ :param func: function name
+ :param userdata: dictionary of user data
+ :return: boolean
+ """
+ if userdata == "local" or userdata["role"] == ROLE.ADMIN:
+ return True
+ elif func in permMap and has_permission(userdata["permission"], permMap[func]):
+ return True
+ else:
+ return False
+
+
+ @permission(PERMS.ALL)
+ def getUserData(self, username, password):
+ """similar to `checkAuth` but returns UserData thrift type """
+ user = self.checkAuth(username, password)
+ if user:
+ return UserData(user["name"], user["email"], user["role"], user["permission"], user["template"])
+ else:
+ return UserData()
+
+
+ def getAllUserData(self):
+ """returns all known user and info"""
+ res = {}
+ for user, data in self.core.db.getAllUserData().iteritems():
+ res[user] = UserData(user, data["email"], data["role"], data["permission"], data["template"])
+
+ return res
+
+ @permission(PERMS.STATUS)
+ def getServices(self):
+ """ A dict of available services, these can be defined by hook plugins.
+
+ :return: dict with this style: {"plugin": {"method": "description"}}
+ """
+ data = {}
+ for plugin, funcs in self.core.hookManager.methods.iteritems():
+ data[plugin] = funcs
+
+ return data
+
+ @permission(PERMS.STATUS)
+ def hasService(self, plugin, func):
+ """Checks wether a service is available.
+
+ :param plugin:
+ :param func:
+ :return: bool
+ """
+ cont = self.core.hookManager.methods
+ return plugin in cont and func in cont[plugin]
+
+ @permission(PERMS.STATUS)
+ def call(self, info):
+ """Calls a service (a method in hook plugin).
+
+ :param info: `ServiceCall`
+ :return: result
+ :raises: ServiceDoesNotExists, when its not available
+ :raises: ServiceException, when a exception was raised
+ """
+ plugin = info.plugin
+ func = info.func
+ args = info.arguments
+ parse = info.parseArguments
+
+ if not self.hasService(plugin, func):
+ raise ServiceDoesNotExists(plugin, func)
+
+ try:
+ ret = self.core.hookManager.callRPC(plugin, func, args, parse)
+ return str(ret)
+ except Exception, e:
+ raise ServiceException(e.message)
+
+ @permission(PERMS.STATUS)
+ def getAllInfo(self):
+ """Returns all information stored by hook plugins. Values are always strings
+
+ :return: {"plugin": {"name": value}}
+ """
+ return self.core.hookManager.getAllInfo()
+
+ @permission(PERMS.STATUS)
+ def getInfoByPlugin(self, plugin):
+ """Returns information stored by a specific plugin.
+
+ :param plugin: pluginname
+ :return: dict of attr names mapped to value {"name": value}
+ """
+ return self.core.hookManager.getInfo(plugin)
+
+ def changePassword(self, user, oldpw, newpw):
+ """ changes password for specific user """
+ return self.core.db.changePassword(user, oldpw, newpw)
+
+ def setUserPermission(self, user, permission, role):
+ self.core.db.setPermission(user, permission)
+ self.core.db.setRole(user, role)
diff --git a/pyload/cli/AddPackage.py b/pyload/cli/AddPackage.py
new file mode 100644
index 000000000..cc0bf2f7c
--- /dev/null
+++ b/pyload/cli/AddPackage.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2011-2014 RaNaN
+#
+#This program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 3 of the License,
+#or (at your option) any later version.
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+###
+
+from pyload.cli.Handler import Handler
+from pyload.utils.printer import *
+
+class AddPackage(Handler):
+ """ let the user add packages """
+
+ def init(self):
+ self.name = ""
+ self.urls = []
+
+ def onEnter(self, inp):
+ if inp == "0":
+ self.cli.reset()
+
+ if not self.name:
+ self.name = inp
+ self.setInput()
+ elif inp == "END":
+ #add package
+ self.client.addPackage(self.name, self.urls, 1)
+ self.cli.reset()
+ else:
+ if inp.strip():
+ self.urls.append(inp)
+ self.setInput()
+
+ def renderBody(self, line):
+ println(line, white(_("Add Package:")))
+ println(line + 1, "")
+ line += 2
+
+ if not self.name:
+ println(line, _("Enter a name for the new package"))
+ println(line + 1, "")
+ line += 2
+ else:
+ println(line, _("Package: %s") % self.name)
+ println(line + 1, _("Parse the links you want to add."))
+ println(line + 2, _("Type %s when done.") % mag("END"))
+ println(line + 3, _("Links added: ") + mag(len(self.urls)))
+ line += 4
+
+ println(line, "")
+ println(line + 1, mag("0.") + _(" back to main menu"))
+
+ return line + 2
diff --git a/pyload/cli/Cli.py b/pyload/cli/Cli.py
new file mode 100644
index 000000000..f05e98b1a
--- /dev/null
+++ b/pyload/cli/Cli.py
@@ -0,0 +1,585 @@
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2008-2014 RaNaN
+#
+#This program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 3 of the License,
+#or (at your option) any later version.
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+###
+from __future__ import with_statement
+from getopt import GetoptError, getopt
+
+import pyload.utils.pylgettext as gettext
+import os
+from os import _exit
+from os.path import join, exists, abspath, basename
+import sys
+from sys import exit
+from threading import Thread, Lock
+from time import sleep
+from traceback import print_exc
+
+from pyload.config.Parser import ConfigParser
+
+from codecs import getwriter
+
+if os.name == "nt":
+ enc = "cp850"
+else:
+ enc = "utf8"
+
+sys.stdout = getwriter(enc)(sys.stdout, errors="replace")
+
+from pyload import InitHomeDir
+from pyload.cli.printer import *
+from pyload.cli import AddPackage, ManageFiles
+
+from pyload.api import Destination
+from pyload.utils import formatSize, decode
+from pyload.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed
+from Getch import Getch
+from rename_process import renameProcess
+
+class Cli:
+ def __init__(self, client, command):
+ self.client = client
+ self.command = command
+
+ if not self.command:
+ renameProcess('pyload-cli')
+ self.getch = Getch()
+ self.input = ""
+ self.inputline = 0
+ self.lastLowestLine = 0
+ self.menuline = 0
+
+ self.lock = Lock()
+
+ #processor funcions, these will be changed dynamically depending on control flow
+ self.headerHandler = self #the download status
+ self.bodyHandler = self #the menu section
+ self.inputHandler = self
+
+ os.system("clear")
+ println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ println(2, "")
+
+ self.thread = RefreshThread(self)
+ self.thread.start()
+
+ self.start()
+ else:
+ self.processCommand()
+
+ def reset(self):
+ """ reset to initial main menu """
+ self.input = ""
+ self.headerHandler = self.bodyHandler = self.inputHandler = self
+
+ def start(self):
+ """ main loop. handle input """
+ while True:
+ #inp = raw_input()
+ inp = self.getch.impl()
+ if ord(inp) == 3:
+ os.system("clear")
+ sys.exit() # ctrl + c
+ elif ord(inp) == 13: #enter
+ try:
+ self.lock.acquire()
+ self.inputHandler.onEnter(self.input)
+
+ except Exception, e:
+ println(2, red(e))
+ finally:
+ self.lock.release()
+
+ elif ord(inp) == 127:
+ self.input = self.input[:-1] #backspace
+ try:
+ self.lock.acquire()
+ self.inputHandler.onBackSpace()
+ finally:
+ self.lock.release()
+
+ elif ord(inp) == 27: #ugly symbol
+ pass
+ else:
+ self.input += inp
+ try:
+ self.lock.acquire()
+ self.inputHandler.onChar(inp)
+ finally:
+ self.lock.release()
+
+ self.inputline = self.bodyHandler.renderBody(self.menuline)
+ self.renderFooter(self.inputline)
+
+
+ def refresh(self):
+ """refresh screen"""
+
+ println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ println(2, "")
+
+ self.lock.acquire()
+
+ self.menuline = self.headerHandler.renderHeader(3) + 1
+ println(self.menuline - 1, "")
+ self.inputline = self.bodyHandler.renderBody(self.menuline)
+ self.renderFooter(self.inputline)
+
+ self.lock.release()
+
+
+ def setInput(self, string=""):
+ self.input = string
+
+ def setHandler(self, klass):
+ #create new handler with reference to cli
+ self.bodyHandler = self.inputHandler = klass(self)
+ self.input = ""
+
+ def renderHeader(self, line):
+ """ prints download status """
+ #print updated information
+ # print "\033[J" #clear screen
+ # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface")))
+ # self.println(2, "")
+ # self.println(3, white(_("%s Downloads:") % (len(data))))
+
+ data = self.client.statusDownloads()
+ speed = 0
+
+ println(line, white(_("%s Downloads:") % (len(data))))
+ line += 1
+
+ for download in data:
+ if download.status == 12: # downloading
+ percent = download.percent
+ z = percent / 4
+ speed += download.speed
+ println(line, cyan(download.name))
+ line += 1
+ println(line,
+ blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _(
+ " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green(
+ download.format_size) + _(" Finished in: ") + green(download.format_eta) + _(
+ " ID: ") + green(download.fid))
+ line += 1
+ if download.status == 5:
+ println(line, cyan(download.name))
+ line += 1
+ println(line, _("waiting: ") + green(download.format_wait))
+ line += 1
+
+ println(line, "")
+ line += 1
+ status = self.client.statusServer()
+ if status.pause:
+ paused = _("Status:") + " " + red(_("paused"))
+ else:
+ paused = _("Status:") + " " + red(_("running"))
+
+ println(line,"%s %s: %s %s: %s %s: %s" % (
+ paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red(
+ status.queue), _("Total"), red(status.total)))
+
+ return line + 1
+
+ def renderBody(self, line):
+ """ prints initial menu """
+ println(line, white(_("Menu:")))
+ println(line + 1, "")
+ println(line + 2, mag("1.") + _(" Add Links"))
+ println(line + 3, mag("2.") + _(" Manage Queue"))
+ println(line + 4, mag("3.") + _(" Manage Collector"))
+ println(line + 5, mag("4.") + _(" (Un)Pause Server"))
+ println(line + 6, mag("5.") + _(" Kill Server"))
+ println(line + 7, mag("6.") + _(" Quit"))
+
+ return line + 8
+
+ def renderFooter(self, line):
+ """ prints out the input line with input """
+ println(line, "")
+ line += 1
+
+ println(line, white(" Input: ") + decode(self.input))
+
+ #clear old output
+ if line < self.lastLowestLine:
+ for i in range(line + 1, self.lastLowestLine + 1):
+ println(i, "")
+
+ self.lastLowestLine = line
+
+ #set cursor to position
+ print "\033[" + str(self.inputline) + ";0H"
+
+ def onChar(self, char):
+ """ default no special handling for single chars """
+ if char == "1":
+ self.setHandler(AddPackage)
+ elif char == "2":
+ self.setHandler(ManageFiles)
+ elif char == "3":
+ self.setHandler(ManageFiles)
+ self.bodyHandler.target = Destination.Collector
+ elif char == "4":
+ self.client.togglePause()
+ self.setInput()
+ elif char == "5":
+ self.client.kill()
+ self.client.close()
+ sys.exit()
+ elif char == "6":
+ os.system('clear')
+ sys.exit()
+
+ def onEnter(self, inp):
+ pass
+
+ def onBackSpace(self):
+ pass
+
+ def processCommand(self):
+ command = self.command[0]
+ args = []
+ if len(self.command) > 1:
+ args = self.command[1:]
+
+ if command == "status":
+ files = self.client.statusDownloads()
+
+ if not files:
+ print "No downloads running."
+
+ for download in files:
+ if download.status == 12: # downloading
+ print print_status(download)
+ print "\tDownloading: %s @ %s/s\t %s (%s%%)" % (
+ download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft),
+ download.percent)
+ elif download.status == 5:
+ print print_status(download)
+ print "\tWaiting: %s" % download.format_wait
+ else:
+ print print_status(download)
+
+ elif command == "queue":
+ print_packages(self.client.getQueueData())
+
+ elif command == "collector":
+ print_packages(self.client.getCollectorData())
+
+ elif command == "add":
+ if len(args) < 2:
+ print _("Please use this syntax: add <Package name> <link> <link2> ...")
+ return
+
+ self.client.addPackage(args[0], args[1:], Destination.Queue)
+
+ elif command == "add_coll":
+ if len(args) < 2:
+ print _("Please use this syntax: add <Package name> <link> <link2> ...")
+ return
+
+ self.client.addPackage(args[0], args[1:], Destination.Collector)
+
+ elif command == "del_file":
+ self.client.deleteFiles([int(x) for x in args])
+ print "Files deleted."
+
+ elif command == "del_package":
+ self.client.deletePackages([int(x) for x in args])
+ print "Packages deleted."
+
+ elif command == "move":
+ for pid in args:
+ pack = self.client.getPackageInfo(int(pid))
+ self.client.movePackage((pack.dest + 1) % 2, pack.pid)
+
+ elif command == "check":
+ print _("Checking %d links:") % len(args)
+ print
+ rid = self.client.checkOnlineStatus(args).rid
+ self.printOnlineCheck(self.client, rid)
+
+
+ elif command == "check_container":
+ path = args[0]
+ if not exists(join(owd, path)):
+ print _("File does not exists.")
+ return
+
+ f = open(join(owd, path), "rb")
+ content = f.read()
+ f.close()
+
+ rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid
+ self.printOnlineCheck(self.client, rid)
+
+
+ elif command == "pause":
+ self.client.pause()
+
+ elif command == "unpause":
+ self.client.unpause()
+
+ elif command == "toggle":
+ self.client.togglePause()
+
+ elif command == "kill":
+ self.client.kill()
+ elif command == "restart_file":
+ for x in args:
+ self.client.restartFile(int(x))
+ print "Files restarted."
+ elif command == "restart_package":
+ for pid in args:
+ self.client.restartPackage(int(pid))
+ print "Packages restarted."
+
+ else:
+ print_commands()
+
+ def printOnlineCheck(self, client, rid):
+ while True:
+ sleep(1)
+ result = client.pollResults(rid)
+ for url, status in result.data.iteritems():
+ if status.status == 2: check = "Online"
+ elif status.status == 1: check = "Offline"
+ else: check = "Unknown"
+
+ print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check)
+
+ if result.rid == -1: break
+
+
+class RefreshThread(Thread):
+ def __init__(self, cli):
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.cli = cli
+
+ def run(self):
+ while True:
+ sleep(1)
+ try:
+ self.cli.refresh()
+ except ConnectionClosed:
+ os.system("clear")
+ print _("pyLoad was terminated")
+ _exit(0)
+ except Exception, e:
+ println(2, red(str(e)))
+ self.cli.reset()
+ print_exc()
+
+
+def print_help(config):
+ print
+ print "pyLoad CLI Copyright (c) 2008-2014 the pyLoad Team"
+ print
+ print "Usage: [python] pyload-cli.py [options] [command]"
+ print
+ print "<Commands>"
+ print "See pyload-cli.py -c for a complete listing."
+ print
+ print "<Options>"
+ print " -i, --interactive", " Start in interactive mode"
+ print
+ print " -u, --username=", " " * 2, "Specify Username"
+ print " --pw=<password>", " " * 2, "Password"
+ print " -a, --address=", " " * 3, "Specify address (current=%s)" % config["addr"]
+ print " -p, --port", " " * 7, "Specify port (current=%s)" % config["port"]
+ print
+ print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config["language"]
+ print " -h, --help", " " * 7, "Display this help screen"
+ print " -c, --commands", " " * 3, "List all available commands"
+ print
+
+
+def print_packages(data):
+ for pack in data:
+ print "Package %s (#%s):" % (pack.name, pack.pid)
+ for download in pack.links:
+ print "\t" + print_file(download)
+ print
+
+
+def print_file(download):
+ return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % {
+ "id": download.fid,
+ "name": download.name,
+ "statusmsg": download.statusmsg,
+ "plugin": download.plugin
+ }
+
+
+def print_status(download):
+ return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % {
+ "id": download.fid,
+ "name": download.name,
+ "statusmsg": download.statusmsg,
+ "size": download.format_size
+ }
+
+
+def print_commands():
+ commands = [("status", _("Prints server status")),
+ ("queue", _("Prints downloads in queue")),
+ ("collector", _("Prints downloads in collector")),
+ ("add <name> <link1> <link2>...", _("Adds package to queue")),
+ ("add_coll <name> <link1> <link2>...", _("Adds package to collector")),
+ ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")),
+ ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")),
+ ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")),
+ ("restart_file <fid> <fid2>...", _("Restart files")),
+ ("restart_package <pid> <pid2>...", _("Restart packages")),
+ ("check <container|url> ...", _("Check online status, works with local container")),
+ ("check_container path", _("Checks online status of a container file")),
+ ("pause", _("Pause the server")),
+ ("unpause", _("continue downloads")),
+ ("toggle", _("Toggle pause/unpause")),
+ ("kill", _("kill server")), ]
+
+ print _("List of commands:")
+ print
+ for c in commands:
+ print "%-35s %s" % c
+
+
+def writeConfig(opts):
+ try:
+ with open(join(homedir, ".pyload-cli"), "w") as cfgfile:
+ cfgfile.write("[cli]")
+ for opt in opts:
+ cfgfile.write("%s=%s\n" % (opt, opts[opt]))
+ except:
+ print _("Couldn't write user config file")
+
+
+def main():
+ config = {"addr": "127.0.0.1", "port": "7227", "language": "en"}
+ try:
+ config["language"] = os.environ["LANG"][0:2]
+ except:
+ pass
+
+ if (not exists(join(pypath, "locale", config["language"]))) or config["language"] == "":
+ config["language"] = "en"
+
+ configFile = ConfigParser.ConfigParser()
+ configFile.read(join(homedir, ".pyload-cli"))
+
+ if configFile.has_section("cli"):
+ for opt in configFile.items("cli"):
+ config[opt[0]] = opt[1]
+
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("Cli", join(pypath, "locale"),
+ languages=[config["language"], "en"], fallback=True)
+ translation.install(unicode=True)
+
+ interactive = False
+ command = None
+ username = ""
+ password = ""
+
+ shortOptions = 'iu:p:a:hcl:'
+ longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="]
+
+ try:
+ opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions)
+ for option, params in opts:
+ if option in ("-i", "--interactive"):
+ interactive = True
+ elif option in ("-u", "--username"):
+ username = params
+ elif option in ("-a", "--address"):
+ config["addr"] = params
+ elif option in ("-p", "--port"):
+ config["port"] = params
+ elif option in ("-l", "--language"):
+ config["language"] = params
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("Cli", join(pypath, "locale"),
+ languages=[config["language"], "en"], fallback=True)
+ translation.install(unicode=True)
+ elif option in ("-h", "--help"):
+ print_help(config)
+ exit()
+ elif option in ("--pw"):
+ password = params
+ elif option in ("-c", "--comands"):
+ print_commands()
+ exit()
+
+ except GetoptError:
+ print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:])
+ print_help(config)
+ exit()
+
+ if len(extraparams) >= 1:
+ command = extraparams
+
+ client = False
+
+ if interactive:
+ try:
+ client = ThriftClient(config["addr"], int(config["port"]), username, password)
+ except WrongLogin:
+ pass
+ except NoSSL:
+ print _("You need py-openssl to connect to this pyLoad Core.")
+ exit()
+ except NoConnection:
+ config["addr"] = False
+ config["port"] = False
+
+ if not client:
+ if not config["addr"]: config["addr"] = raw_input(_("Address: "))
+ if not config["port"]: config["port"] = raw_input(_("Port: "))
+ if not username: username = raw_input(_("Username: "))
+ if not password:
+ from getpass import getpass
+
+ password = getpass(_("Password: "))
+
+ try:
+ client = ThriftClient(config["addr"], int(config["port"]), username, password)
+ except WrongLogin:
+ print _("Login data is wrong.")
+ except NoConnection:
+ print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
+ "port": config["port"]})
+
+ else:
+ try:
+ client = ThriftClient(config["addr"], int(config["port"]), username, password)
+ except WrongLogin:
+ print _("Login data is wrong.")
+ except NoConnection:
+ print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config["addr"],
+ "port": config["port"]})
+ except NoSSL:
+ print _("You need py-openssl to connect to this pyLoad core.")
+
+ if interactive and command: print _("Interactive mode ignored since you passed some commands.")
+
+ if client:
+ writeConfig(config)
+ cli = Cli(client, command)
diff --git a/pyload/cli/Handler.py b/pyload/cli/Handler.py
new file mode 100644
index 000000000..37b0d7b99
--- /dev/null
+++ b/pyload/cli/Handler.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2011-2014 RaNaN
+#
+#This program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 3 of the License,
+#or (at your option) any later version.
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+###
+class Handler:
+ def __init__(self, cli):
+ self.cli = cli
+ self.init()
+
+ client = property(lambda self: self.cli.client)
+ input = property(lambda self: self.cli.input)
+
+ def init(self):
+ pass
+
+ def onChar(self, char):
+ pass
+
+ def onBackSpace(self):
+ pass
+
+ def onEnter(self, inp):
+ pass
+
+ def setInput(self, inp=""):
+ self.cli.setInput(inp)
+
+ def backspace(self):
+ self.cli.setInput(self.input[:-1])
+
+ def renderBody(self, line):
+ """ gets the line where to render output and should return the line number below its content """
+ return line + 1
diff --git a/pyload/cli/ManageFiles.py b/pyload/cli/ManageFiles.py
new file mode 100644
index 000000000..335ea1ec1
--- /dev/null
+++ b/pyload/cli/ManageFiles.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+#
+#Copyright (C) 2011-2014 RaNaN
+#
+#This program is free software; you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation; either version 3 of the License,
+#or (at your option) any later version.
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+###
+
+from itertools import islice
+from time import time
+
+from pyload.cli.Handler import Handler
+from pyload.utils.printer import *
+
+from pyload.api import Destination, PackageData
+
+class ManageFiles(Handler):
+ """ possibility to manage queue/collector """
+
+ def init(self):
+ self.target = Destination.Queue
+ self.pos = 0 #position in queue
+ self.package = -1 #choosen package
+ self.mode = "" # move/delete/restart
+
+ self.cache = None
+ self.links = None
+ self.time = 0
+
+ def onChar(self, char):
+ if char in ("m", "d", "r"):
+ self.mode = char
+ self.setInput()
+ elif char == "p":
+ self.pos = max(0, self.pos - 5)
+ self.backspace()
+ elif char == "n":
+ self.pos += 5
+ self.backspace()
+
+ def onBackSpace(self):
+ if not self.input and self.mode:
+ self.mode = ""
+ if not self.input and self.package > -1:
+ self.package = -1
+
+ def onEnter(self, input):
+ if input == "0":
+ self.cli.reset()
+ elif self.package < 0 and self.mode:
+ #mode select
+ packs = self.parseInput(input)
+ if self.mode == "m":
+ [self.client.movePackage((self.target + 1) % 2, x) for x in packs]
+ elif self.mode == "d":
+ self.client.deletePackages(packs)
+ elif self.mode == "r":
+ [self.client.restartPackage(x) for x in packs]
+
+ elif self.mode:
+ #edit links
+ links = self.parseInput(input, False)
+
+ if self.mode == "d":
+ self.client.deleteFiles(links)
+ elif self.mode == "r":
+ map(self.client.restartFile, links)
+
+ else:
+ #look into package
+ try:
+ self.package = int(input)
+ except:
+ pass
+
+ self.cache = None
+ self.links = None
+ self.pos = 0
+ self.mode = ""
+ self.setInput()
+
+
+ def renderBody(self, line):
+ if self.package < 0:
+ println(line, white(_("Manage Packages:")))
+ else:
+ println(line, white((_("Manage Links:"))))
+ line += 1
+
+ if self.mode:
+ if self.mode == "m":
+ println(line, _("What do you want to move?"))
+ elif self.mode == "d":
+ println(line, _("What do you want to delete?"))
+ elif self.mode == "r":
+ println(line, _("What do you want to restart?"))
+
+ println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1, 2, 3 or 1-3.")
+ line += 2
+ else:
+ println(line, _("Choose what yout want to do or enter package number."))
+ println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % (
+ _("delete"), _("move"), _("restart")))
+ line += 2
+
+ if self.package < 0:
+ #print package info
+ pack = self.getPackages()
+ i = 0
+ for value in islice(pack, self.pos, self.pos + 5):
+ try:
+ println(line, mag(str(value.pid)) + ": " + value.name)
+ line += 1
+ i += 1
+ except Exception, e:
+ pass
+ for x in range(5 - i):
+ println(line, "")
+ line += 1
+ else:
+ #print links info
+ pack = self.getLinks()
+ i = 0
+ for value in islice(pack.links, self.pos, self.pos + 5):
+ try:
+ println(line, mag(value.fid) + ": %s | %s | %s" % (
+ value.name, value.statusmsg, value.plugin))
+ line += 1
+ i += 1
+ except Exception, e:
+ pass
+ for x in range(5 - i):
+ println(line, "")
+ line += 1
+
+ println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next"))
+ println(line + 1, mag("0.") + _(" back to main menu"))
+
+ return line + 2
+
+
+ def getPackages(self):
+ if self.cache and self.time + 2 < time():
+ return self.cache
+
+ if self.target == Destination.Queue:
+ data = self.client.getQueue()
+ else:
+ data = self.client.getCollector()
+
+
+ self.cache = data
+ self.time = time()
+
+ return data
+
+ def getLinks(self):
+ if self.links and self.time + 1 < time():
+ return self.links
+
+ try:
+ data = self.client.getPackageData(self.package)
+ except:
+ data = PackageData(links=[])
+
+ self.links = data
+ self.time = time()
+
+ return data
+
+ def parseInput(self, inp, package=True):
+ inp = inp.strip()
+ if "-" in inp:
+ l, n, h = inp.partition("-")
+ l = int(l)
+ h = int(h)
+ r = range(l, h + 1)
+
+ ret = []
+ if package:
+ for p in self.cache:
+ if p.pid in r:
+ ret.append(p.pid)
+ else:
+ for l in self.links.links:
+ if l.lid in r:
+ ret.append(l.lid)
+
+ return ret
+
+ else:
+ return [int(x) for x in inp.split(",")]
diff --git a/pyload/cli/__init__.py b/pyload/cli/__init__.py
new file mode 100644
index 000000000..a64fc0c0c
--- /dev/null
+++ b/pyload/cli/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from pyload.cli.AddPackage import AddPackage
+from pyload.cli.ManageFiles import ManageFiles
diff --git a/pyload/config/Parser.py b/pyload/config/Parser.py
new file mode 100644
index 000000000..64ce6b10e
--- /dev/null
+++ b/pyload/config/Parser.py
@@ -0,0 +1,373 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+from time import sleep
+from os.path import exists, join
+from shutil import copy
+
+from traceback import print_exc
+from utils import chmod
+
+# ignore these plugin configs, mainly because plugins were wiped out
+IGNORE = (
+ "FreakshareNet", "SpeedManager", "ArchiveTo", "ShareCx", ('hooks', 'UnRar'),
+ 'EasyShareCom', 'FlyshareCz'
+ )
+
+CONF_VERSION = 1
+
+class ConfigParser:
+ """
+ holds and manage the configuration
+
+ current dict layout:
+
+ {
+
+ section: {
+ option: {
+ value:
+ type:
+ desc:
+ }
+ desc:
+
+ }
+
+ """
+
+
+ def __init__(self):
+ """Constructor"""
+ self.config = {} # the config values
+ self.plugin = {} # the config for plugins
+ self.oldRemoteData = {}
+
+ self.pluginCB = None # callback when plugin config value is changed
+
+ self.checkVersion()
+
+ self.readConfig()
+
+ self.deleteOldPlugins()
+
+
+ def checkVersion(self, n=0):
+ """determines if config need to be copied"""
+ try:
+ if not exists("pyload.conf"):
+ copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+
+ if not exists("plugin.conf"):
+ f = open("plugin.conf", "wb")
+ f.write("version: " + str(CONF_VERSION))
+ f.close()
+
+ f = open("pyload.conf", "rb")
+ v = f.readline()
+ f.close()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf")
+ print "Old version of config was replaced"
+
+ f = open("plugin.conf", "rb")
+ v = f.readline()
+ f.close()
+ v = v[v.find(":") + 1:].strip()
+
+ if not v or int(v) < CONF_VERSION:
+ f = open("plugin.conf", "wb")
+ f.write("version: " + str(CONF_VERSION))
+ f.close()
+ print "Old version of plugin-config replaced"
+ except:
+ if n < 3:
+ sleep(0.3)
+ self.checkVersion(n + 1)
+ else:
+ raise
+
+ def readConfig(self):
+ """reads the config file"""
+
+ self.config = self.parseConfig(join(pypath, "pyload", "config", "default.conf"))
+ self.plugin = self.parseConfig("plugin.conf")
+
+ try:
+ homeconf = self.parseConfig("pyload.conf")
+ if "username" in homeconf["remote"]:
+ if "password" in homeconf["remote"]:
+ self.oldRemoteData = {"username": homeconf["remote"]["username"]["value"],
+ "password": homeconf["remote"]["username"]["value"]}
+ del homeconf["remote"]["password"]
+ del homeconf["remote"]["username"]
+ self.updateValues(homeconf, self.config)
+
+ except Exception, e:
+ print "Config Warning"
+ print_exc()
+
+
+ def parseConfig(self, config):
+ """parses a given configfile"""
+
+ f = open(config)
+
+ config = f.read()
+
+ config = config.splitlines()[1:]
+
+ conf = {}
+
+ section, option, value, typ, desc = "", "", "", "", ""
+
+ listmode = False
+
+ for line in config:
+ comment = line.rfind("#")
+ if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace():
+ line = line.rpartition("#") # removes comments
+ if line[1]:
+ line = line[0]
+ else:
+ line = line[2]
+
+ line = line.strip()
+
+ try:
+ if line == "":
+ continue
+ elif line.endswith(":"):
+ section, none, desc = line[:-1].partition('-')
+ section = section.strip()
+ desc = desc.replace('"', "").strip()
+ conf[section] = {"desc": desc}
+ else:
+ if listmode:
+ if line.endswith("]"):
+ listmode = False
+ line = line.replace("]", "")
+
+ value += [self.cast(typ, x.strip()) for x in line.split(",") if x]
+
+ if not listmode:
+ conf[section][option] = {"desc": desc,
+ "type": typ,
+ "value": value}
+
+
+ else:
+ content, none, value = line.partition("=")
+
+ content, none, desc = content.partition(":")
+
+ desc = desc.replace('"', "").strip()
+
+ typ, none, option = content.strip().rpartition(" ")
+
+ value = value.strip()
+
+ if value.startswith("["):
+ if value.endswith("]"):
+ listmode = False
+ value = value[:-1]
+ else:
+ listmode = True
+
+ value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x]
+ else:
+ value = self.cast(typ, value)
+
+ if not listmode:
+ conf[section][option] = {"desc": desc,
+ "type": typ,
+ "value": value}
+
+ except Exception, e:
+ print "Config Warning"
+ print_exc()
+
+ f.close()
+ return conf
+
+
+ def updateValues(self, config, dest):
+ """sets the config values from a parsed config file to values in destination"""
+
+ for section in config.iterkeys():
+ if section in dest:
+ for option in config[section].iterkeys():
+ if option in ("desc", "outline"): continue
+
+ if option in dest[section]:
+ dest[section][option]["value"] = config[section][option]["value"]
+
+ #else:
+ # dest[section][option] = config[section][option]
+
+
+ #else:
+ # dest[section] = config[section]
+
+ def saveConfig(self, config, filename):
+ """saves config to filename"""
+ with open(filename, "wb") as f:
+ chmod(filename, 0600)
+ f.write("version: %i \n" % CONF_VERSION)
+ for section in config.iterkeys():
+ f.write('\n%s - "%s":\n' % (section, config[section]["desc"]))
+
+ for option, data in config[section].iteritems():
+ if option in ("desc", "outline"): continue
+
+ if isinstance(data["value"], list):
+ value = "[ \n"
+ for x in data["value"]:
+ value += "\t\t" + str(x) + ",\n"
+ value += "\t\t]\n"
+ else:
+ if type(data["value"]) in (str, unicode):
+ value = data["value"] + "\n"
+ else:
+ value = str(data["value"]) + "\n"
+ try:
+ f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value))
+ except UnicodeEncodeError:
+ f.write('\t%s %s : "%s" = %s' % (data["type"], option, data["desc"], value.encode("utf8")))
+
+ def cast(self, typ, value):
+ """cast value to given format"""
+ if type(value) not in (str, unicode):
+ return value
+
+ elif typ == "int":
+ return int(value)
+ elif typ == "bool":
+ return True if value.lower() in ("1", "true", "on", "an", "yes") else False
+ elif typ == "time":
+ if not value: value = "0:00"
+ if not ":" in value: value += ":00"
+ return value
+ elif typ in ("str", "file", "folder"):
+ try:
+ return value.encode("utf8")
+ except:
+ return value
+ else:
+ return value
+
+
+ def save(self):
+ """saves the configs to disk"""
+
+ self.saveConfig(self.config, "pyload.conf")
+ self.saveConfig(self.plugin, "plugin.conf")
+
+
+ def __getitem__(self, section):
+ """provides dictonary like access: c['section']['option']"""
+ return Section(self, section)
+
+
+ def get(self, section, option):
+ """get value"""
+ val = self.config[section][option]["value"]
+ try:
+ if type(val) in (str, unicode):
+ return val.decode("utf8")
+ else:
+ return val
+ except:
+ return val
+
+ def set(self, section, option, value):
+ """set value"""
+
+ value = self.cast(self.config[section][option]["type"], value)
+
+ self.config[section][option]["value"] = value
+ self.save()
+
+ def getPlugin(self, plugin, option):
+ """gets a value for a plugin"""
+ val = self.plugin[plugin][option]["value"]
+ try:
+ if type(val) in (str, unicode):
+ return val.decode("utf8")
+ else:
+ return val
+ except:
+ return val
+
+ def setPlugin(self, plugin, option, value):
+ """sets a value for a plugin"""
+
+ value = self.cast(self.plugin[plugin][option]["type"], value)
+
+ if self.pluginCB: self.pluginCB(plugin, option, value)
+
+ self.plugin[plugin][option]["value"] = value
+ self.save()
+
+ def getMetaData(self, section, option):
+ """ get all config data for an option """
+ return self.config[section][option]
+
+ def addPluginConfig(self, name, config, outline=""):
+ """adds config options with tuples (name, type, desc, default)"""
+ if name not in self.plugin:
+ conf = {"desc": name,
+ "outline": outline}
+ self.plugin[name] = conf
+ else:
+ conf = self.plugin[name]
+ conf["outline"] = outline
+
+ for item in config:
+ if item[0] in conf:
+ conf[item[0]]["type"] = item[1]
+ conf[item[0]]["desc"] = item[2]
+ else:
+ conf[item[0]] = {
+ "desc": item[2],
+ "type": item[1],
+ "value": self.cast(item[1], item[3])
+ }
+
+ values = [x[0] for x in config] + ["desc", "outline"]
+ #delete old values
+ for item in conf.keys():
+ if item not in values:
+ del conf[item]
+
+ def deleteConfig(self, name):
+ """Removes a plugin config"""
+ if name in self.plugin:
+ del self.plugin[name]
+
+
+ def deleteOldPlugins(self):
+ """ remove old plugins from config """
+
+ for name in IGNORE:
+ if name in self.plugin:
+ del self.plugin[name]
+
+
+class Section:
+ """provides dictionary like access for configparser"""
+
+ def __init__(self, parser, section):
+ """Constructor"""
+ self.parser = parser
+ self.section = section
+
+ def __getitem__(self, item):
+ """getitem"""
+ return self.parser.get(self.section, item)
+
+ def __setitem__(self, item, value):
+ """setitem"""
+ self.parser.set(self.section, item, value)
diff --git a/pyload/config/Setup.py b/pyload/config/Setup.py
new file mode 100644
index 000000000..697b92fbb
--- /dev/null
+++ b/pyload/config/Setup.py
@@ -0,0 +1,538 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+import pyload.utils.pylgettext as gettext
+
+from getpass import getpass
+from os import makedirs
+from os.path import abspath, dirname, exists, join
+from subprocess import PIPE, call
+
+from pyload.utils import get_console_encoding, versiontuple
+
+
+class SetupAssistant:
+ """ pyLoads initial setup configuration assistant """
+
+ def __init__(self, path, config):
+ self.path = path
+ self.config = config
+ self.stdin_encoding = get_console_encoding(sys.stdin.encoding)
+
+
+ def start(self):
+ langs = self.config.getMetaData("general", "language")["type"].split(";")
+ lang = self.ask(u"Choose setup language", "en", langs)
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("setup", join(self.path, "locale"), languages=[lang, "en"], fallback=True)
+ translation.install(True)
+
+ #Input shorthand for yes
+ self.yes = _("y")
+ #Input shorthand for no
+ self.no = _("n")
+
+ # print
+ # print _("Would you like to configure pyLoad via Webinterface?")
+ # print _("You need a Browser and a connection to this PC for it.")
+ # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True)
+ # if viaweb:
+ # try:
+ # from pyload.manager.thread import ServerThread
+ # ServerThread.setup = self
+ # import pyload.webui as webinterface
+ # webinterface.run_simple()
+ # return False
+ # except Exception, e:
+ # print "Setup failed with this error: ", e
+ # print "Falling back to commandline setup."
+
+ print
+ print
+ print _("Welcome to the pyLoad Configuration Assistant.")
+ print _("It will check your system and make a basic setup in order to run pyLoad.")
+ print
+ print _("The value in brackets [] always is the default value,")
+ print _("in case you don't want to change it or you are unsure what to choose, just hit enter.")
+ print _(
+ "Don't forget: You can always rerun this assistant with --setup or -s parameter, when you start pyload.py .")
+ print _("If you have any problems with this assistant hit STRG-C,")
+ print _("to abort and don't let him start with pyload.py automatically anymore.")
+ print
+ print
+ raw_input(_("When you are ready for system check, hit enter."))
+ print
+ print
+
+ basic, ssl, captcha, web, js = self.system_check()
+ print
+ print
+
+ if not basic:
+ print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.")
+ print _("Please correct this and re-run pyLoad.")
+ print
+ print _("Setup will now close.")
+ print
+ print
+ raw_input(_("Press Enter to exit."))
+ return False
+
+ raw_input(_("System check finished, hit enter to see your status report."))
+ print
+ print
+ print _("## Status ##")
+ print
+
+ avail = []
+ if self.check_module("Crypto"):
+ avail.append(_("container decrypting"))
+ if ssl:
+ avail.append(_("ssl connection"))
+ if captcha:
+ avail.append(_("automatic captcha decryption"))
+ if web:
+ avail.append(_("webinterface"))
+ if js:
+ avail.append(_("extended Click'N'Load"))
+
+ string = ""
+
+ for av in avail:
+ string += ", " + av
+
+ print _("AVAILABLE FEATURES:") + string[1:]
+ print
+
+ if len(avail) < 5:
+ print _("MISSING FEATURES: ")
+
+ if not self.check_module("Crypto"):
+ print _("- no py-crypto available")
+ print _("You need this if you want to decrypt container files.")
+ print
+
+ if not ssl:
+ print _("- no SSL available")
+ print _("This is needed if you want to establish a secure connection to core or webinterface.")
+ print _("If you only want to access locally to pyLoad ssl is not usefull.")
+ print
+
+ if not captcha:
+ print _("- no Captcha Recognition available")
+ print _("Only needed for some hosters and as freeuser.")
+ print
+
+ if not js:
+ print _("- no JavaScript engine found")
+ print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino")
+ print
+
+ print
+ print _("You can abort the setup now and fix some dependicies if you want.")
+
+ print
+ con = self.ask(_("Continue with setup?"), self.yes, bool=True)
+
+ if not con:
+ return False
+
+ print
+ print
+ print _("CURRENT CONFIG PATH: %s") % abspath("")
+ print
+ print _("NOTE: If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.")
+ path = self.ask(_("Do you want to change the config path?"), self.no, bool=True)
+ if path:
+ print
+ self.conf_path()
+ #calls exit when changed
+
+ print
+ print _("Do you want to configure login data and basic settings?")
+ print _("This is recommend for first run.")
+ con = self.ask(_("Make basic setup?"), self.yes, bool=True)
+
+ if con:
+ print
+ print
+ self.conf_basic()
+
+ if ssl:
+ print
+ print _("Do you want to configure ssl?")
+ ssl = self.ask(_("Configure ssl?"), self.no, bool=True)
+ if ssl:
+ print
+ print
+ self.conf_ssl()
+
+ if web:
+ print
+ print _("Do you want to configure webinterface?")
+ web = self.ask(_("Configure webinterface?"), self.yes, bool=True)
+ if web:
+ print
+ print
+ self.conf_web()
+
+ print
+ print
+ print _("Setup finished successfully!")
+ print
+ print
+ raw_input(_("Hit enter to exit and restart pyLoad."))
+ return True
+
+
+ def system_check(self):
+ """ make a systemcheck and return the results"""
+
+ print _("## System Information ##")
+ print
+ print _("Platform: %s") % sys.platform
+ print _("Operating System: %s") % os.name
+ print _("Python: %s") % sys.version.replace("\n", "")
+ print
+ print
+
+ print _("## System Check ##")
+ print
+
+ if sys.version_info[:2] > (2, 7):
+ print _("Your python version is to new, Please use Python 2.6/2.7")
+ python = False
+ elif sys.version_info[:2] < (2, 5):
+ print _("Your python version is to old, Please use at least Python 2.5")
+ python = False
+ else:
+ print _("Python Version: OK")
+ python = True
+
+ curl = self.check_module("pycurl")
+ self.print_dep("pycurl", curl)
+
+ sqlite = self.check_module("sqlite3")
+ self.print_dep("sqlite3", sqlite)
+
+ basic = python and curl and sqlite
+
+ print
+
+ crypto = self.check_module("Crypto")
+ self.print_dep("pycrypto", crypto)
+
+ ssl = self.check_module("OpenSSL")
+ self.print_dep("py-OpenSSL", ssl)
+
+ print
+
+ pil = self.check_module("PIL.Image")
+ self.print_dep("PIL/Pillow", pil)
+
+ if os.name == "nt":
+ tesser = self.check_prog([join(pypath, "tesseract", "tesseract.exe"), "-v"])
+ else:
+ tesser = self.check_prog(["tesseract", "-v"])
+
+ self.print_dep("tesseract", tesser)
+
+ captcha = pil and tesser
+
+ print
+
+ try:
+ import jinja2
+
+ v = jinja2.__version__
+ if v and versiontuple(v) < (2, 5, 0):
+ jinja = False
+ else:
+ jinja = True
+ except:
+ jinja = False
+
+ jinja = self.print_dep("jinja2", jinja)
+
+ beaker = self.check_module("beaker")
+ self.print_dep("beaker", beaker)
+
+ bjoern = self.check_module("bjoern")
+ self.print_dep("bjoern", bjoern)
+
+ web = sqlite and beaker
+
+ from pyload.utils.JsEngine import JsEngine
+ js = True if JsEngine.find() else False
+ self.print_dep(_("JS engine"), js)
+
+ if not jinja:
+ print
+ print
+ print _("WARNING: Your installed jinja2 version %s seems too old.") % jinja2.__version__
+ print _("You can safely continue but if the webinterface is not working,")
+ print _("please upgrade or uninstall it, because pyLoad self-includes jinja2 libary.")
+
+ return basic, ssl, captcha, web, js
+
+
+ def conf_basic(self):
+ print _("## Basic Setup ##")
+
+ print
+ print _("The following logindata is valid for CLI and webinterface.")
+
+ from pyload.database import DatabaseBackend
+
+ db = DatabaseBackend(None)
+ db.setup()
+ print _("NOTE: Consider a password of 10 or more symbols if you expect to access from outside your local network (ex. internet).")
+ print
+ username = self.ask(_("Username"), "User")
+ password = self.ask("", "", password=True)
+ db.addUser(username, password)
+ db.shutdown()
+
+ print
+ print _("External clients (GUI, CLI or other) need remote access to work over the network.")
+ print _("However, if you only want to use the webinterface you may disable it to save ram.")
+ self.config["remote"]["activated"] = self.ask(_("Enable remote access"), self.no, bool=True)
+
+ print
+ langs = self.config.getMetaData("general", "language")
+ self.config["general"]["language"] = self.ask(_("Choose pyLoad language"), "en", langs["type"].split(";"))
+
+ print
+ self.config["general"]["download_folder"] = self.ask(_("Download folder"), "Downloads")
+ print
+ self.config["download"]["max_downloads"] = self.ask(_("Max parallel downloads"), "3")
+ print
+ reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True)
+ self.config["reconnect"]["activated"] = reconnect
+ if reconnect:
+ self.config["reconnect"]["method"] = self.ask(_("Reconnect script location"), "./reconnect.sh")
+
+
+ def conf_web(self):
+ print _("## Webinterface Setup ##")
+
+ print
+ self.config["webinterface"]["activated"] = self.ask(_("Activate webinterface?"), self.yes, bool=True)
+ print
+ print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.")
+ self.config["webinterface"]["host"] = self.ask(_("Address"), "0.0.0.0")
+ self.config["webinterface"]["port"] = self.ask(_("Port"), "8000")
+ print
+ print _("pyLoad offers several server backends, now following a short explanation.")
+ print "- builtin:", _("Default server; best choice if you plan to use pyLoad just for you.")
+ print "- threaded:", _("Support SSL connection and can serve simultaneously more client flawlessly.")
+ print "- fastcgi:", _(
+ "Can be used by apache, lighttpd, etc.; needs to be properly configured before.")
+ if os.name != "nt":
+ print "- lightweight:", _("Very fast alternative to builtin; requires libev and bjoern packages.")
+
+ print
+ print _("NOTE: In some rare cases the builtin server is not working, if you notice problems with the webinterface")
+ print _("come back here and change the builtin server to the threaded one here.")
+
+ if os.name == "nt":
+ servers = ["builtin", "threaded", "fastcgi"]
+ default = "threaded"
+ else:
+ servers = ["builtin", "threaded", "fastcgi", "lightweight"]
+ default = "lightweight" if self.check_module("bjoern") else "builtin"
+
+ self.config["webinterface"]["server"] = self.ask(_("Server"), default, servers)
+
+
+ def conf_ssl(self):
+ print _("## SSL Setup ##")
+ print
+ print _("Execute these commands from pyLoad config folder to make ssl certificates:")
+ print
+ print "openssl genrsa -out ssl.key 1024"
+ print "openssl req -new -key ssl.key -out ssl.csr"
+ print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt "
+ print
+ print _("If you're done and everything went fine, you can activate ssl now.")
+
+ self.config["ssl"]["activated"] = self.ask(_("Activate SSL?"), self.yes, bool=True)
+
+
+ def set_user(self):
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("setup", join(self.path, "locale"),
+ languages=[self.config["general"]["language"], "en"], fallback=True)
+ translation.install(True)
+
+ from pyload.database import DatabaseBackend
+
+ db = DatabaseBackend(None)
+ db.setup()
+
+ noaction = True
+ try:
+ while True:
+ print _("Select action")
+ print _("1 - Create/Edit user")
+ print _("2 - List users")
+ print _("3 - Remove user")
+ print _("4 - Quit")
+ action = raw_input("[1]/2/3/4: ")
+ if not action in ("1", "2", "3", "4"):
+ continue
+ elif action == "1":
+ print
+ username = self.ask(_("Username"), "User")
+ password = self.ask("", "", password=True)
+ db.addUser(username, password)
+ noaction = False
+ elif action == "2":
+ print
+ print _("Users")
+ print "-----"
+ users = db.listUsers()
+ noaction = False
+ for user in users:
+ print user
+ print "-----"
+ print
+ elif action == "3":
+ print
+ username = self.ask(_("Username"), "")
+ if username:
+ db.removeUser(username)
+ noaction = False
+ elif action == "4":
+ break
+ finally:
+ if not noaction:
+ db.shutdown()
+
+
+ def conf_path(self, trans=False):
+ if trans:
+ gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+ translation = gettext.translation("setup", join(self.path, "locale"),
+ languages=[self.config["general"]["language"], "en"], fallback=True)
+ translation.install(True)
+
+ print _("Setting new config path, current configuration will not be transfered!")
+ path = self.ask(_("CONFIG PATH"), abspath(""))
+ try:
+ path = join(pypath, path)
+ if not exists(path):
+ makedirs(path)
+ f = open(join(pypath, "pyload", "config", "configdir"), "wb")
+ f.write(path)
+ f.close()
+ print
+ print
+ print _("pyLoad config path changed, setup will now close!")
+ print
+ print
+ raw_input(_("Press Enter to exit."))
+ sys.exit()
+ except Exception, e:
+ print _("Setting config path failed: %s") % str(e)
+
+
+ def print_dep(self, name, value):
+ """Print Status of dependency"""
+ if value:
+ print _("%s: OK") % name
+ else:
+ print _("%s: MISSING") % name
+
+
+ def check_module(self, module):
+ try:
+ __import__(module)
+ return True
+ except:
+ return False
+
+
+ def check_prog(self, command):
+ pipe = PIPE
+ try:
+ call(command, stdout=pipe, stderr=pipe)
+ return True
+ except:
+ return False
+
+
+ def ask(self, qst, default, answers=[], bool=False, password=False):
+ """produce one line to asking for input"""
+ if answers:
+ info = "("
+
+ for i, answer in enumerate(answers):
+ info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer)
+
+ info += ")"
+ elif bool:
+ if default == self.yes:
+ info = "([%s]/%s)" % (self.yes, self.no)
+ else:
+ info = "(%s/[%s])" % (self.yes, self.no)
+ else:
+ info = "[%s]" % default
+
+ if password:
+ p1 = True
+ p2 = False
+ pwlen = 8
+ while p1 != p2:
+ # getpass(_("Password: ")) will crash on systems with broken locales (Win, NAS)
+ sys.stdout.write(_("Password: "))
+ p1 = getpass("")
+
+ if len(p1) < pwlen:
+ print _("Password too short! Use at least %s symbols." % pwlen)
+ continue
+ elif not p1.isalnum():
+ print _("Password must be alphanumeric.")
+ continue
+
+ sys.stdout.write(_("Password (again): "))
+ p2 = getpass("")
+
+ if p1 == p2:
+ return p1
+ else:
+ print _("Passwords did not match.")
+
+ while True:
+ try:
+ input = raw_input(qst + " %s: " % info)
+ except KeyboardInterrupt:
+ print "\nSetup interrupted"
+ sys.exit()
+
+ input = input.decode(self.stdin_encoding)
+
+ if input.strip() == "":
+ input = default
+
+ if bool:
+ # yes, true, t are inputs for booleans with value true
+ if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]:
+ return True
+ # no, false, f are inputs for booleans with value false
+ elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]:
+ return False
+ else:
+ print _("Invalid Input")
+ continue
+
+ if not answers:
+ return input
+
+ else:
+ if input in answers:
+ return input
+ else:
+ print _("Invalid Input")
diff --git a/pyload/config/default.conf b/pyload/config/default.conf
new file mode 100644
index 000000000..3a513f122
--- /dev/null
+++ b/pyload/config/default.conf
@@ -0,0 +1,64 @@
+version: 1
+
+remote - "Remote":
+ int port : "Port" = 7227
+ ip listenaddr : "Address" = 0.0.0.0
+ bool nolocalauth : "No authentication on local connections" = True
+ bool activated : "Activated" = True
+ssl - "SSL":
+ bool activated : "Activated"= False
+ file cert : "SSL Certificate" = ssl.crt
+ file key : "SSL Key" = ssl.key
+webinterface - "Web UI":
+ bool activated : "Activated" = True
+ builtin;threaded;fastcgi;lightweight server : "Server" = builtin
+ bool https : "Use HTTPS" = False
+ ip host : "IP" = 0.0.0.0
+ int port : "Port" = 8001
+ default;dark;flat theme : "Theme" = flat
+ str prefix: "Path Prefix" =
+log - "Log":
+ bool file_log : "File Log" = True
+ folder log_folder : "Folder" = Logs
+ int log_count : "Count" = 5
+ int log_size : "Size in kb" = 100
+ bool log_rotate : "Log Rotate" = True
+general - "General":
+ en;de;fr;it;es;nl;sv;ru;pl;cs;sr;pt_BR language : "Language" = en
+ folder download_folder : "Download Folder" = Downloads
+ bool debug_mode : "Debug Mode" = False
+ int min_free_space : "Min Free Space (MB)" = 200
+ bool folder_per_package : "Create folder for each package" = True
+ int renice : "CPU Priority" = 0
+download - "Download":
+ int chunks : "Max connections for one download" = 3
+ int max_downloads : "Max Parallel Downloads" = 3
+ int max_speed : "Max Download Speed in kb/s" = -1
+ bool limit_speed : "Limit Download Speed" = False
+ str interface : "Download interface to bind (ip or Name)" = None
+ bool ipv6: "Allow IPv6" = False
+ bool skip_existing : "Skip already existing files" = False
+permission - "Permissions":
+ bool change_user : "Change user of running process" = False
+ str user : "Username" = user
+ str folder : "Folder Permission mode" = 0755
+ bool change_file : "Change file mode of downloads" = False
+ str file : "Filemode for Downloads" = 0644
+ bool change_group : "Change group of running process" = False
+ str group : "Groupname" = users
+ bool change_dl : "Change Group and User of Downloads" = False
+reconnect - "Reconnect":
+ bool activated : "Use Reconnect" = False
+ str method : "Method" = None
+ time startTime : "Start" = 0:00
+ time endTime : "End" = 0:00
+downloadTime - "Download Time":
+ time start : "Start" = 0:00
+ time end : "End" = 0:00
+proxy - "Proxy":
+ str address : "Address" = "localhost"
+ int port : "Port" = 7070
+ http;socks4;socks5 type : "Protocol" = http
+ str username : "Username" = None
+ password password : "Password" = None
+ bool proxy : "Use Proxy" = False
diff --git a/pyload/database/DatabaseBackend.py b/pyload/database/DatabaseBackend.py
new file mode 100644
index 000000000..9ebe31701
--- /dev/null
+++ b/pyload/database/DatabaseBackend.py
@@ -0,0 +1,305 @@
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+ @author: mkaay
+"""
+from threading import Thread
+from threading import Event
+from os import remove
+from os.path import exists
+from shutil import move
+
+from Queue import Queue
+from traceback import print_exc
+
+from pyload.utils import chmod
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except:
+ import sqlite3
+
+DB_VERSION = 4
+
+class style:
+ db = None
+
+ @classmethod
+ def setDB(cls, db):
+ cls.db = db
+
+ @classmethod
+ def inner(cls, f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return f(cls.db, *args, **kwargs)
+ return x
+
+ @classmethod
+ def queue(cls, f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return cls.db.queue(f, *args, **kwargs)
+ return x
+
+ @classmethod
+ def async(cls, f):
+ @staticmethod
+ def x(*args, **kwargs):
+ if cls.db:
+ return cls.db.async(f, *args, **kwargs)
+ return x
+
+class DatabaseJob:
+ def __init__(self, f, *args, **kwargs):
+ self.done = Event()
+
+ self.f = f
+ self.args = args
+ self.kwargs = kwargs
+
+ self.result = None
+ self.exception = False
+
+# import inspect
+# self.frame = inspect.currentframe()
+
+ def __repr__(self):
+ from os.path import basename
+ frame = self.frame.f_back
+ output = ""
+ for i in range(5):
+ output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name)
+ frame = frame.f_back
+ del frame
+ del self.frame
+
+ return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result)
+
+ def processJob(self):
+ try:
+ self.result = self.f(*self.args, **self.kwargs)
+ except Exception, e:
+ print_exc()
+ try:
+ print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e
+ except:
+ pass
+
+ self.exception = e
+ finally:
+ self.done.set()
+
+ def wait(self):
+ self.done.wait()
+
+class DatabaseBackend(Thread):
+ subs = []
+ def __init__(self, core):
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+
+ self.jobs = Queue()
+
+ self.setuplock = Event()
+
+ style.setDB(self)
+
+ def setup(self):
+ self.start()
+ self.setuplock.wait()
+
+ def run(self):
+ """main loop, which executes commands"""
+ convert = self._checkVersion() #returns None or current version
+
+ self.conn = sqlite3.connect("files.db")
+ chmod("files.db", 0600)
+
+ self.c = self.conn.cursor() #compatibility
+
+ if convert is not None:
+ self._convertDB(convert)
+
+ self._createTables()
+ self._migrateUser()
+
+ self.conn.commit()
+
+ self.setuplock.set()
+
+ while True:
+ j = self.jobs.get()
+ if j == "quit":
+ self.c.close()
+ self.conn.close()
+ break
+ j.processJob()
+
+ @style.queue
+ def shutdown(self):
+ self.conn.commit()
+ self.jobs.put("quit")
+
+ def _checkVersion(self):
+ """ check db version and delete it if needed"""
+ if not exists("files.version"):
+ f = open("files.version", "wb")
+ f.write(str(DB_VERSION))
+ f.close()
+ return
+
+ f = open("files.version", "rb")
+ v = int(f.read().strip())
+ f.close()
+ if v < DB_VERSION:
+ if v < 2:
+ try:
+ self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version."))
+ except:
+ print "Filedatabase was deleted due to incompatible version."
+ remove("files.version")
+ move("files.db", "files.backup.db")
+ f = open("files.version", "wb")
+ f.write(str(DB_VERSION))
+ f.close()
+ return v
+
+ def _convertDB(self, v):
+ try:
+ getattr(self, "_convertV%i" % v)()
+ except:
+ try:
+ self.core.log.error(_("Filedatabase could NOT be converted."))
+ except:
+ print "Filedatabase could NOT be converted."
+
+ #convert scripts start-----------------------------------------------------
+
+ def _convertV2(self):
+ self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
+ try:
+ self.manager.core.log.info(_("Database was converted from v2 to v3."))
+ except:
+ print "Database was converted from v2 to v3."
+ self._convertV3()
+
+ def _convertV3(self):
+ self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
+ try:
+ self.manager.core.log.info(_("Database was converted from v3 to v4."))
+ except:
+ print "Database was converted from v3 to v4."
+
+ #convert scripts end-------------------------------------------------------
+
+ def _createTables(self):
+ """create tables for database"""
+
+ self.c.execute('CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)')
+ self.c.execute('CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))')
+ self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)')
+ self.c.execute('CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")')
+ self.c.execute('CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)')
+
+ self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \
+ SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\
+ FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\
+ (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \
+ FROM packages p JOIN links l ON p.id = l.package AND l.status in (0, 4, 13) GROUP BY p.id) s ON s.id = p.id \
+ GROUP BY p.id')
+
+ #try to lower ids
+ self.c.execute('SELECT max(id) FROM LINKS')
+ fid = self.c.fetchone()[0]
+ if fid:
+ fid = int(fid)
+ else:
+ fid = 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links"))
+
+
+ self.c.execute('SELECT max(id) FROM packages')
+ pid = self.c.fetchone()[0]
+ if pid:
+ pid = int(pid)
+ else:
+ pid = 0
+ self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages"))
+
+ self.c.execute('VACUUM')
+
+
+ def _migrateUser(self):
+ if exists("pyload.db"):
+ try:
+ self.core.log.info(_("Converting old Django DB"))
+ except:
+ print "Converting old Django DB"
+ conn = sqlite3.connect('pyload.db')
+ c = conn.cursor()
+ c.execute("SELECT username, password, email from auth_user WHERE is_superuser")
+ users = []
+ for r in c:
+ pw = r[1].split("$")
+ users.append((r[0], pw[1] + pw[2], r[2]))
+ c.close()
+ conn.close()
+
+ self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users)
+ move("pyload.db", "pyload.old.db")
+
+ def createCursor(self):
+ return self.conn.cursor()
+
+ @style.async
+ def commit(self):
+ self.conn.commit()
+
+ @style.queue
+ def syncSave(self):
+ self.conn.commit()
+
+ @style.async
+ def rollback(self):
+ self.conn.rollback()
+
+ def async(self, f, *args, **kwargs):
+ args = (self,) + args
+ job = DatabaseJob(f, *args, **kwargs)
+ self.jobs.put(job)
+
+ def queue(self, f, *args, **kwargs):
+ args = (self,) + args
+ job = DatabaseJob(f, *args, **kwargs)
+ self.jobs.put(job)
+ job.wait()
+ return job.result
+
+ @classmethod
+ def registerSub(cls, klass):
+ cls.subs.append(klass)
+
+ @classmethod
+ def unregisterSub(cls, klass):
+ cls.subs.remove(klass)
+
+ def __getattr__(self, attr):
+ for sub in DatabaseBackend.subs:
+ if hasattr(sub, attr):
+ return getattr(sub, attr)
diff --git a/pyload/database/FileDatabase.py b/pyload/database/FileDatabase.py
new file mode 100644
index 000000000..54e5450e8
--- /dev/null
+++ b/pyload/database/FileDatabase.py
@@ -0,0 +1,891 @@
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+ @author: mkaay
+"""
+
+
+from threading import RLock
+from time import time
+
+from pyload.utils import formatSize, lock
+from pyload.manager.event.PullEvents import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent
+from pyload.datatypes.PyPackage import PyPackage
+from pyload.datatypes.PyFile import PyFile
+from pyload.database import style, DatabaseBackend
+
+try:
+ from pysqlite2 import dbapi2 as sqlite3
+except:
+ import sqlite3
+
+
+class FileHandler:
+ """Handles all request made to obtain information,
+ modify status or other request for links or packages"""
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+
+ # translations
+ self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"), _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"), _("downloading"), _("processing"), _("unknown")]
+
+ self.cache = {} #holds instances for files
+ self.packageCache = {} # same for packages
+ #@TODO: purge the cache
+
+ self.jobCache = {}
+
+ self.lock = RLock() #@TODO should be a Lock w/o R
+ #self.lock._Verbose__verbose = True
+
+ self.filecount = -1 # if an invalid value is set get current value from db
+ self.queuecount = -1 #number of package to be loaded
+ self.unchanged = False #determines if any changes was made since last call
+
+ self.db = self.core.db
+
+ def change(func):
+ def new(*args):
+ args[0].unchanged = False
+ args[0].filecount = -1
+ args[0].queuecount = -1
+ args[0].jobCache = {}
+ return func(*args)
+ return new
+
+ #--------------------------------------------------------------------------
+ def save(self):
+ """saves all data to backend"""
+ self.db.commit()
+
+ #--------------------------------------------------------------------------
+ def syncSave(self):
+ """saves all data to backend and waits until all data are written"""
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ pyfile.sync()
+
+ pypacks = self.packageCache.values()
+ for pypack in pypacks:
+ pypack.sync()
+
+ self.db.syncSave()
+
+ @lock
+ def getCompleteData(self, queue=1):
+ """gets a complete data representation"""
+
+ data = self.db.getAllLinks(queue)
+ packs = self.db.getAllPackages(queue)
+
+ data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()])
+
+ for x in self.packageCache.itervalues():
+ if x.queue != queue or x.id not in packs: continue
+ packs[x.id].update(x.toDict()[x.id])
+
+ for key, value in data.iteritems():
+ if value["package"] in packs:
+ packs[value["package"]]["links"][key] = value
+
+ return packs
+
+ @lock
+ def getInfoData(self, queue=1):
+ """gets a data representation without links"""
+
+ packs = self.db.getAllPackages(queue)
+ for x in self.packageCache.itervalues():
+ if x.queue != queue or x.id not in packs: continue
+ packs[x.id].update(x.toDict()[x.id])
+
+ return packs
+
+ @lock
+ @change
+ def addLinks(self, urls, package):
+ """adds links"""
+
+ self.core.hookManager.dispatchEvent("linksAdded", urls, package)
+
+ data = self.core.pluginManager.parseUrls(urls)
+
+ self.db.addLinks(data, package)
+ self.core.threadManager.createInfoThread(data, package)
+
+ #@TODO change from reloadAll event to package update event
+ self.core.pullManager.addEvent(ReloadAllEvent("collector"))
+
+ #--------------------------------------------------------------------------
+ @lock
+ @change
+ def addPackage(self, name, folder, queue=0):
+ """adds a package, default to link collector"""
+ lastID = self.db.addPackage(name, folder, queue)
+ p = self.db.getPackage(lastID)
+ e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue")
+ self.core.pullManager.addEvent(e)
+ return lastID
+
+ #--------------------------------------------------------------------------
+ @lock
+ @change
+ def deletePackage(self, id):
+ """delete package and all contained links"""
+
+ p = self.getPackage(id)
+ if not p:
+ if id in self.packageCache: del self.packageCache[id]
+ return
+
+ oldorder = p.order
+ queue = p.queue
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+
+ pyfiles = self.cache.values()
+
+ for pyfile in pyfiles:
+ if pyfile.packageid == id:
+ pyfile.abortDownload()
+ pyfile.release()
+
+ self.db.deletePackage(p)
+ self.core.pullManager.addEvent(e)
+ self.core.hookManager.dispatchEvent("packageDeleted", id)
+
+ if id in self.packageCache:
+ del self.packageCache[id]
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue == queue and pack.order > oldorder:
+ pack.order -= 1
+ pack.notifyChange()
+
+ #--------------------------------------------------------------------------
+ @lock
+ @change
+ def deleteLink(self, id):
+ """deletes links"""
+
+ f = self.getFile(id)
+ if not f:
+ return None
+
+ pid = f.packageid
+ e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue")
+
+ oldorder = f.order
+
+ if id in self.core.threadManager.processingIds():
+ self.cache[id].abortDownload()
+
+ if id in self.cache:
+ del self.cache[id]
+
+ self.db.deleteLink(f)
+
+ self.core.pullManager.addEvent(e)
+
+ p = self.getPackage(pid)
+ if not len(p.getChildren()):
+ p.delete()
+
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid == pid and pyfile.order > oldorder:
+ pyfile.order -= 1
+ pyfile.notifyChange()
+
+ #--------------------------------------------------------------------------
+ def releaseLink(self, id):
+ """removes pyfile from cache"""
+ if id in self.cache:
+ del self.cache[id]
+
+ #--------------------------------------------------------------------------
+ def releasePackage(self, id):
+ """removes package from cache"""
+ if id in self.packageCache:
+ del self.packageCache[id]
+
+ #--------------------------------------------------------------------------
+ def updateLink(self, pyfile):
+ """updates link"""
+ self.db.updateLink(pyfile)
+
+ e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ #--------------------------------------------------------------------------
+ def updatePackage(self, pypack):
+ """updates a package"""
+ self.db.updatePackage(pypack)
+
+ e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ #--------------------------------------------------------------------------
+ def getPackage(self, id):
+ """return package instance"""
+
+ if id in self.packageCache:
+ return self.packageCache[id]
+ else:
+ return self.db.getPackage(id)
+
+ #--------------------------------------------------------------------------
+ def getPackageData(self, id):
+ """returns dict with package information"""
+ pack = self.getPackage(id)
+
+ if not pack:
+ return None
+
+ pack = pack.toDict()[id]
+
+ data = self.db.getPackageData(id)
+
+ tmplist = []
+
+ cache = self.cache.values()
+ for x in cache:
+ if int(x.toDbDict()[x.id]["package"]) == int(id):
+ tmplist.append((x.id, x.toDbDict()[x.id]))
+ data.update(tmplist)
+
+ pack["links"] = data
+
+ return pack
+
+ #--------------------------------------------------------------------------
+ def getFileData(self, id):
+ """returns dict with file information"""
+ if id in self.cache:
+ return self.cache[id].toDbDict()
+
+ return self.db.getLinkData(id)
+
+ #--------------------------------------------------------------------------
+ def getFile(self, id):
+ """returns pyfile instance"""
+ if id in self.cache:
+ return self.cache[id]
+ else:
+ return self.db.getFile(id)
+
+ #--------------------------------------------------------------------------
+ @lock
+ def getJob(self, occ):
+ """get suitable job"""
+
+ #@TODO clean mess
+ #@TODO improve selection of valid jobs
+
+ if occ in self.jobCache:
+ if self.jobCache[occ]:
+ id = self.jobCache[occ].pop()
+ if id == "empty":
+ pyfile = None
+ self.jobCache[occ].append("empty")
+ else:
+ pyfile = self.getFile(id)
+ else:
+ jobs = self.db.getJob(occ)
+ jobs.reverse()
+ if not jobs:
+ self.jobCache[occ].append("empty")
+ pyfile = None
+ else:
+ self.jobCache[occ].extend(jobs)
+ pyfile = self.getFile(self.jobCache[occ].pop())
+
+ else:
+ self.jobCache = {} #better not caching to much
+ jobs = self.db.getJob(occ)
+ jobs.reverse()
+ self.jobCache[occ] = jobs
+
+ if not jobs:
+ self.jobCache[occ].append("empty")
+ pyfile = None
+ else:
+ pyfile = self.getFile(self.jobCache[occ].pop())
+
+ #@TODO: maybe the new job has to be approved...
+
+
+ #pyfile = self.getFile(self.jobCache[occ].pop())
+ return pyfile
+
+ @lock
+ def getDecryptJob(self):
+ """return job for decrypting"""
+ if "decrypt" in self.jobCache:
+ return None
+
+ plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys()
+ plugins = str(tuple(plugins))
+
+ jobs = self.db.getPluginJob(plugins)
+ if jobs:
+ return self.getFile(jobs[0])
+ else:
+ self.jobCache["decrypt"] = "empty"
+ return None
+
+ def getFileCount(self):
+ """returns number of files"""
+
+ if self.filecount == -1:
+ self.filecount = self.db.filecount(1)
+
+ return self.filecount
+
+ def getQueueCount(self, force=False):
+ """number of files that have to be processed"""
+ if self.queuecount == -1 or force:
+ self.queuecount = self.db.queuecount(1)
+
+ return self.queuecount
+
+ def checkAllLinksFinished(self):
+ """checks if all files are finished and dispatch event"""
+
+ if not self.getQueueCount(True):
+ self.core.hookManager.dispatchEvent("allDownloadsFinished")
+ self.core.log.debug("All downloads finished")
+ return True
+
+ return False
+
+ def checkAllLinksProcessed(self, fid):
+ """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting"""
+
+ # reset count so statistic will update (this is called when dl was processed)
+ self.resetCount()
+
+ if not self.db.processcount(1, fid):
+ self.core.hookManager.dispatchEvent("allDownloadsProcessed")
+ self.core.log.debug("All downloads processed")
+ return True
+
+ return False
+
+ def resetCount(self):
+ self.queuecount = -1
+
+ @lock
+ @change
+ def restartPackage(self, id):
+ """restart package"""
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid == id:
+ self.restartFile(pyfile.id)
+
+ self.db.restartPackage(id)
+
+ if id in self.packageCache:
+ self.packageCache[id].setFinished = False
+
+ e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ @lock
+ @change
+ def restartFile(self, id):
+ """ restart file"""
+ if id in self.cache:
+ self.cache[id].status = 3
+ self.cache[id].name = self.cache[id].url
+ self.cache[id].error = ""
+ self.cache[id].abortDownload()
+
+
+ self.db.restartFile(id)
+
+ e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ @lock
+ @change
+ def setPackageLocation(self, id, queue):
+ """push package to queue"""
+
+ p = self.db.getPackage(id)
+ oldorder = p.order
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ self.db.clearPackageOrder(p)
+
+ p = self.db.getPackage(id)
+
+ p.queue = queue
+ self.db.updatePackage(p)
+
+ self.db.reorderPackage(p, -1, True)
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue != queue and pack.order > oldorder:
+ pack.order -= 1
+ pack.notifyChange()
+
+ self.db.commit()
+ self.releasePackage(id)
+ p = self.getPackage(id)
+
+ e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ @lock
+ @change
+ def reorderPackage(self, id, position):
+ p = self.getPackage(id)
+
+ e = RemoveEvent("pack", id, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+ self.db.reorderPackage(p, position)
+
+ packs = self.packageCache.values()
+ for pack in packs:
+ if pack.queue != p.queue or pack.order < 0 or pack == p: continue
+ if p.order > position:
+ if pack.order >= position and pack.order < p.order:
+ pack.order += 1
+ pack.notifyChange()
+ elif p.order < position:
+ if pack.order <= position and pack.order > p.order:
+ pack.order -= 1
+ pack.notifyChange()
+
+ p.order = position
+ self.db.commit()
+
+ e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ @lock
+ @change
+ def reorderFile(self, id, position):
+ f = self.getFileData(id)
+ f = f[id]
+
+ e = RemoveEvent("file", id, "collector" if not self.getPackage(f["package"]).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ self.db.reorderLink(f, position)
+
+ pyfiles = self.cache.values()
+ for pyfile in pyfiles:
+ if pyfile.packageid != f["package"] or pyfile.order < 0: continue
+ if f["order"] > position:
+ if pyfile.order >= position and pyfile.order < f["order"]:
+ pyfile.order += 1
+ pyfile.notifyChange()
+ elif f["order"] < position:
+ if pyfile.order <= position and pyfile.order > f["order"]:
+ pyfile.order -= 1
+ pyfile.notifyChange()
+
+ if id in self.cache:
+ self.cache[id].order = position
+
+ self.db.commit()
+
+ e = InsertEvent("file", id, position, "collector" if not self.getPackage(f["package"]).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ @change
+ def updateFileInfo(self, data, pid):
+ """ updates file info (name, size, status, url)"""
+ ids = self.db.updateLinkInfo(data)
+ e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue")
+ self.core.pullManager.addEvent(e)
+
+ def checkPackageFinished(self, pyfile):
+ """ checks if package is finished and calls hookmanager """
+
+ ids = self.db.getUnfinished(pyfile.packageid)
+ if not ids or (pyfile.id in ids and len(ids) == 1):
+ if not pyfile.package().setFinished:
+ self.core.log.info(_("Package finished: %s") % pyfile.package().name)
+ self.core.hookManager.packageFinished(pyfile.package())
+ pyfile.package().setFinished = True
+
+
+ def reCheckPackage(self, pid):
+ """ recheck links in package """
+ data = self.db.getPackageData(pid)
+
+ urls = []
+
+ for pyfile in data.itervalues():
+ if pyfile["status"] not in (0, 12, 13):
+ urls.append((pyfile["url"], pyfile["plugin"]))
+
+ self.core.threadManager.createInfoThread(urls, pid)
+
+ @lock
+ @change
+ def deleteFinishedLinks(self):
+ """ deletes finished links and packages, return deleted packages """
+
+ old_packs = self.getInfoData(0)
+ old_packs.update(self.getInfoData(1))
+
+ self.db.deleteFinished()
+
+ new_packs = self.db.getAllPackages(0)
+ new_packs.update(self.db.getAllPackages(1))
+ #get new packages only from db
+
+ deleted = []
+ for id in old_packs.iterkeys():
+ if id not in new_packs:
+ deleted.append(id)
+ self.deletePackage(int(id))
+
+ return deleted
+
+ @lock
+ @change
+ def restartFailed(self):
+ """ restart all failed links """
+ self.db.restartFailed()
+
+class FileMethods:
+ @style.queue
+ def filecount(self, queue):
+ """returns number of files in queue"""
+ self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?", (queue,))
+ return self.c.fetchone()[0]
+
+ @style.queue
+ def queuecount(self, queue):
+ """ number of files in queue not finished yet"""
+ self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0, 4)", (queue,))
+ return self.c.fetchone()[0]
+
+ @style.queue
+ def processcount(self, queue, fid):
+ """ number of files which have to be proccessed """
+ self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2, 3, 5, 7, 12) AND l.id != ?", (queue, str(fid)))
+ return self.c.fetchone()[0]
+
+ @style.inner
+ def _nextPackageOrder(self, queue=0):
+ self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,))
+ max = self.c.fetchone()[0]
+ if max is not None:
+ return max + 1
+ else:
+ return 0
+
+ @style.inner
+ def _nextFileOrder(self, package):
+ self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,))
+ max = self.c.fetchone()[0]
+ if max is not None:
+ return max + 1
+ else:
+ return 0
+
+ @style.queue
+ def addLink(self, url, name, plugin, package):
+ order = self._nextFileOrder(package)
+ self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', (url, name, plugin, package, order))
+ return self.c.lastrowid
+
+ @style.queue
+ def addLinks(self, links, package):
+ """ links is a list of tupels (url, plugin)"""
+ order = self._nextFileOrder(package)
+ orders = [order + x for x in range(len(links))]
+ links = [(x[0], x[0], x[1], package, o) for x, o in zip(links, orders)]
+ self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links)
+
+ @style.queue
+ def addPackage(self, name, folder, queue):
+ order = self._nextPackageOrder(queue)
+ self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)', (name, folder, queue, order))
+ return self.c.lastrowid
+
+ @style.queue
+ def deletePackage(self, p):
+
+ self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),))
+ self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),))
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?', (p.order, p.queue))
+
+ @style.queue
+ def deleteLink(self, f):
+
+ self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),))
+ self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?', (f.order, str(f.packageid)))
+
+
+ @style.queue
+ def getAllLinks(self, q):
+ """return information about all links in queue q
+
+ q0 queue
+ q1 collector
+
+ format:
+
+ {
+ id: {'name': name, ... 'package': id }, ...
+ }
+
+ """
+ self.c.execute('SELECT l.id, l.url, l.name, l.size, l.status, l.error, l.plugin, l.package, l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder', (q,))
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': r[6],
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+ @style.queue
+ def getAllPackages(self, q):
+ """return information about packages in queue q
+ (only useful in get all data)
+
+ q0 queue
+ q1 collector
+
+ format:
+
+ {
+ id: {'name': name ... 'links': {}}, ...
+ }
+ """
+ self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \
+ FROM packages p JOIN pstats s ON p.id = s.id \
+ WHERE p.queue=? ORDER BY p.packageorder', str(q))
+
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'name': r[1],
+ 'folder': r[2],
+ 'site': r[3],
+ 'password': r[4],
+ 'queue': r[5],
+ 'order': r[6],
+ 'sizetotal': int(r[7]),
+ 'sizedone': r[8] if r[8] else 0, #these can be None
+ 'linksdone': r[9] if r[9] else 0,
+ 'linkstotal': r[10],
+ 'links': {}
+ }
+
+ return data
+
+ @style.queue
+ def getLinkData(self, id):
+ """get link information as dict"""
+ self.c.execute('SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?', (str(id),))
+ data = {}
+ r = self.c.fetchone()
+ if not r:
+ return None
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': r[6],
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+ @style.queue
+ def getPackageData(self, id):
+ """get data about links for a package"""
+ self.c.execute('SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE package=? ORDER BY linkorder', (str(id),))
+
+ data = {}
+ for r in self.c:
+ data[r[0]] = {
+ 'id': r[0],
+ 'url': r[1],
+ 'name': r[2],
+ 'size': r[3],
+ 'format_size': formatSize(r[3]),
+ 'status': r[4],
+ 'statusmsg': self.manager.statusMsg[r[4]],
+ 'error': r[5],
+ 'plugin': r[6],
+ 'package': r[7],
+ 'order': r[8],
+ }
+
+ return data
+
+
+ @style.async
+ def updateLink(self, f):
+ self.c.execute('UPDATE links SET url=?, name=?, size=?, status=?, error=?, package=? WHERE id=?', (f.url, f.name, f.size, f.status, f.error, str(f.packageid), str(f.id)))
+
+ @style.queue
+ def updatePackage(self, p):
+ self.c.execute('UPDATE packages SET name=?, folder=?, site=?, password=?, queue=? WHERE id=?', (p.name, p.folder, p.site, p.password, p.queue, str(p.id)))
+
+ @style.queue
+ def updateLinkInfo(self, data):
+ """ data is list of tupels (name, size, status, url) """
+ self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1, 2, 3, 14)', data)
+ ids = []
+ self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data]))
+ for r in self.c:
+ ids.append(int(r[0]))
+ return ids
+
+ @style.queue
+ def reorderPackage(self, p, position, noMove=False):
+ if position == -1:
+ position = self._nextPackageOrder(p.queue)
+ if not noMove:
+ if p.order > position:
+ self.c.execute('UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
+ elif p.order < position:
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0', (position, p.order, p.queue))
+
+ self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id)))
+
+ @style.queue
+ def reorderLink(self, f, position):
+ """ reorder link with f as dict for pyfile """
+ if f["order"] > position:
+ self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?', (position, f["order"], f["package"]))
+ elif f["order"] < position:
+ self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?', (position, f["order"], f["package"]))
+
+ self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f["id"]))
+
+ @style.queue
+ def clearPackageOrder(self, p):
+ self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id)))
+ self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?', (p.order, p.queue, str(p.id)))
+
+ @style.async
+ def restartFile(self, id):
+ self.c.execute('UPDATE links SET status=3, error="" WHERE id=?', (str(id),))
+
+ @style.async
+ def restartPackage(self, id):
+ self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),))
+
+ @style.queue
+ def getPackage(self, id):
+ """return package instance from id"""
+ self.c.execute("SELECT name, folder, site, password, queue, packageorder FROM packages WHERE id=?", (str(id),))
+ r = self.c.fetchone()
+ if not r: return None
+ return PyPackage(self.manager, id, * r)
+
+ #--------------------------------------------------------------------------
+ @style.queue
+ def getFile(self, id):
+ """return link instance from id"""
+ self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?", (str(id),))
+ r = self.c.fetchone()
+ if not r: return None
+ return PyFile(self.manager, id, * r)
+
+
+ @style.queue
+ def getJob(self, occ):
+ """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
+
+ #@TODO improve this hardcoded method
+ pre = "('DLC', 'LinkList', 'SerienjunkiesOrg', 'CCF', 'RSDF')" #plugins which are processed in collector
+
+ cmd = "("
+ for i, item in enumerate(occ):
+ if i: cmd += ", "
+ cmd += "'%s'" % item
+
+ cmd += ")"
+
+ cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre)
+
+ self.c.execute(cmd) # very bad!
+
+ return [x[0] for x in self.c]
+
+ @style.queue
+ def getPluginJob(self, plugins):
+ """returns pyfile ids with suited plugins"""
+ cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins
+
+ self.c.execute(cmd) # very bad!
+
+ return [x[0] for x in self.c]
+
+ @style.queue
+ def getUnfinished(self, pid):
+ """return list of max length 3 ids with pyfiles in package not finished or processed"""
+
+ self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),))
+ return [r[0] for r in self.c]
+
+ @style.queue
+ def deleteFinished(self):
+ self.c.execute("DELETE FROM links WHERE status IN (0, 4)")
+ self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)")
+
+ @style.queue
+ def restartFailed(self):
+ self.c.execute("UPDATE links SET status=3, error='' WHERE status IN (6, 8, 9)")
+
+ @style.queue
+ def findDuplicates(self, id, folder, filename):
+ """ checks if filename exists with different id and same package """
+ self.c.execute("SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?", (folder, id, filename))
+ return self.c.fetchone()
+
+ @style.queue
+ def purgeLinks(self):
+ self.c.execute("DELETE FROM links;")
+ self.c.execute("DELETE FROM packages;")
+
+DatabaseBackend.registerSub(FileMethods)
diff --git a/pyload/database/StorageDatabase.py b/pyload/database/StorageDatabase.py
new file mode 100644
index 000000000..c2473e7b7
--- /dev/null
+++ b/pyload/database/StorageDatabase.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay
+"""
+
+from pyload.database import style
+from pyload.database import DatabaseBackend
+
+class StorageMethods:
+ @style.queue
+ def setStorage(db, identifier, key, value):
+ db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key))
+ if db.c.fetchone() is not None:
+ db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key))
+ else:
+ db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value))
+
+ @style.queue
+ def getStorage(db, identifier, key=None):
+ if key is not None:
+ db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key))
+ row = db.c.fetchone()
+ if row is not None:
+ return row[0]
+ else:
+ db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier,))
+ d = {}
+ for row in db.c:
+ d[row[0]] = row[1]
+ return d
+
+ @style.queue
+ def delStorage(db, identifier, key):
+ db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key))
+
+DatabaseBackend.registerSub(StorageMethods)
diff --git a/pyload/database/UserDatabase.py b/pyload/database/UserDatabase.py
new file mode 100644
index 000000000..59b0f6dbf
--- /dev/null
+++ b/pyload/database/UserDatabase.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay
+"""
+
+from hashlib import sha1
+import random
+
+from DatabaseBackend import DatabaseBackend
+from DatabaseBackend import style
+
+class UserMethods:
+ @style.queue
+ def checkAuth(db, user, password):
+ c = db.c
+ c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user,))
+ r = c.fetchone()
+ if not r:
+ return {}
+
+ salt = r[2][:5]
+ pw = r[2][5:]
+ h = sha1(salt + password)
+ if h.hexdigest() == pw:
+ return {"id": r[0], "name": r[1], "role": r[3],
+ "permission": r[4], "template": r[5], "email": r[6]}
+ else:
+ return {}
+
+ @style.queue
+ def addUser(db, user, password):
+ salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
+ h = sha1(salt + password)
+ password = salt + h.hexdigest()
+
+ c = db.c
+ c.execute('SELECT name FROM users WHERE name=?', (user,))
+ if c.fetchone() is not None:
+ c.execute('UPDATE users SET password=? WHERE name=?', (password, user))
+ else:
+ c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password))
+
+
+ @style.queue
+ def changePassword(db, user, oldpw, newpw):
+ db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user,))
+ r = db.c.fetchone()
+ if not r:
+ return False
+
+ salt = r[2][:5]
+ pw = r[2][5:]
+ h = sha1(salt + oldpw)
+ if h.hexdigest() == pw:
+ salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for i in range(0, 5)])
+ h = sha1(salt + newpw)
+ password = salt + h.hexdigest()
+
+ db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user))
+ return True
+
+ return False
+
+
+ @style.async
+ def setPermission(db, user, perms):
+ db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user))
+
+ @style.async
+ def setRole(db, user, role):
+ db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
+
+
+ @style.queue
+ def listUsers(db):
+ db.c.execute('SELECT name FROM users')
+ users = []
+ for row in db.c:
+ users.append(row[0])
+ return users
+
+ @style.queue
+ def getAllUserData(db):
+ db.c.execute("SELECT name, permission, role, template, email FROM users")
+ user = {}
+ for r in db.c:
+ user[r[0]] = {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]}
+
+ return user
+
+ @style.queue
+ def removeUser(db, user):
+ db.c.execute('DELETE FROM users WHERE name=?', (user,))
+
+DatabaseBackend.registerSub(UserMethods)
diff --git a/pyload/database/__init__.py b/pyload/database/__init__.py
new file mode 100644
index 000000000..5f287a47f
--- /dev/null
+++ b/pyload/database/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from DatabaseBackend import DatabaseBackend
+from DatabaseBackend import style
+
+from FileDatabase import FileHandler
+from UserDatabase import UserMethods
+from StorageDatabase import StorageMethods
diff --git a/pyload/datatypes/PyFile.py b/pyload/datatypes/PyFile.py
new file mode 100644
index 000000000..be3129681
--- /dev/null
+++ b/pyload/datatypes/PyFile.py
@@ -0,0 +1,284 @@
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+ @author: mkaay
+"""
+
+from pyload.manager.event.PullEvents import UpdateEvent
+from pyload.utils import formatSize, lock
+
+from time import sleep, time
+
+from threading import RLock
+
+statusMap = {
+ "finished": 0,
+ "offline": 1,
+ "online": 2,
+ "queued": 3,
+ "skipped": 4,
+ "waiting": 5,
+ "temp. offline": 6,
+ "starting": 7,
+ "failed": 8,
+ "aborted": 9,
+ "decrypting": 10,
+ "custom": 11,
+ "downloading": 12,
+ "processing": 13,
+ "unknown": 14,
+}
+
+
+def setSize(self, value):
+ self._size = int(value)
+
+class PyFile(object):
+ """
+ Represents a file object at runtime
+ """
+ __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "pluginname", "packageid",
+ "error", "order", "lock", "plugin", "waitUntil", "active", "abort", "statusname",
+ "reconnected", "progress", "maxprogress", "pluginmodule", "pluginclass")
+
+ def __init__(self, manager, id, url, name, size, status, error, pluginname, package, order):
+ self.m = manager
+
+ self.id = int(id)
+ self.url = url
+ self.name = name
+ self.size = size
+ self.status = status
+ self.pluginname = pluginname
+ self.packageid = package #should not be used, use package() instead
+ self.error = error
+ self.order = order
+ # database information ends here
+
+ self.lock = RLock()
+
+ self.plugin = None
+ #self.download = None
+
+ self.waitUntil = 0 # time() + time to wait
+
+ # status attributes
+ self.active = False #obsolete?
+ self.abort = False
+ self.reconnected = False
+
+ self.statusname = None
+
+ self.progress = 0
+ self.maxprogress = 100
+
+ self.m.cache[int(id)] = self
+
+
+ # will convert all sizes to ints
+ size = property(lambda self: self._size, setSize)
+
+ def __repr__(self):
+ return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname)
+
+ @lock
+ def initPlugin(self):
+ """ inits plugin instance """
+ if not self.plugin:
+ self.pluginmodule = self.m.core.pluginManager.getPlugin(self.pluginname)
+ self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.pluginname))
+ self.plugin = self.pluginclass(self)
+
+ @lock
+ def hasPlugin(self):
+ """Thread safe way to determine this file has initialized plugin attribute
+
+ :return:
+ """
+ return hasattr(self, "plugin") and self.plugin
+
+ def package(self):
+ """ return package instance"""
+ return self.m.getPackage(self.packageid)
+
+ def setStatus(self, status):
+ self.status = statusMap[status]
+ self.sync() #@TODO needed aslong no better job approving exists
+
+ def setCustomStatus(self, msg, status="processing"):
+ self.statusname = msg
+ self.setStatus(status)
+
+ def getStatusName(self):
+ if self.status not in (13, 14) or not self.statusname:
+ return self.m.statusMsg[self.status]
+ else:
+ return self.statusname
+
+ def hasStatus(self, status):
+ return statusMap[status] == self.status
+
+ def sync(self):
+ """sync PyFile instance with database"""
+ self.m.updateLink(self)
+
+ @lock
+ def release(self):
+ """sync and remove from cache"""
+ # file has valid package
+ if self.packageid > 0:
+ self.sync()
+
+ if hasattr(self, "plugin") and self.plugin:
+ self.plugin.clean()
+ del self.plugin
+
+ self.m.releaseLink(self.id)
+
+ def delete(self):
+ """delete pyfile from database"""
+ self.m.deleteLink(self.id)
+
+ def toDict(self):
+ """return dict with all information for interface"""
+ return self.toDbDict()
+
+ def toDbDict(self):
+ """return data as dict for databse
+
+ format:
+
+ {
+ id: {'url': url, 'name': name ... }
+ }
+
+ """
+ return {
+ self.id: {
+ 'id': self.id,
+ 'url': self.url,
+ 'name': self.name,
+ 'plugin': self.pluginname,
+ 'size': self.getSize(),
+ 'format_size': self.formatSize(),
+ 'status': self.status,
+ 'statusmsg': self.getStatusName(),
+ 'package': self.packageid,
+ 'error': self.error,
+ 'order': self.order
+ }
+ }
+
+ def abortDownload(self):
+ """abort pyfile if possible"""
+ while self.id in self.m.core.threadManager.processingIds():
+ self.abort = True
+ if self.plugin and self.plugin.req:
+ self.plugin.req.abortDownloads()
+ sleep(0.1)
+
+ self.abort = False
+ if self.hasPlugin() and self.plugin.req:
+ self.plugin.req.abortDownloads()
+
+ self.release()
+
+ def finishIfDone(self):
+ """set status to finish and release file if every thread is finished with it"""
+
+ if self.id in self.m.core.threadManager.processingIds():
+ return False
+
+ self.setStatus("finished")
+ self.release()
+ self.m.checkAllLinksFinished()
+ return True
+
+ def checkIfProcessed(self):
+ self.m.checkAllLinksProcessed(self.id)
+
+ def formatWait(self):
+ """ formats and return wait time in humanreadable format """
+ seconds = self.waitUntil - time()
+
+ if seconds < 0: return "00:00:00"
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+ def formatSize(self):
+ """ formats size to readable format """
+ return formatSize(self.getSize())
+
+ def formatETA(self):
+ """ formats eta to readable format """
+ seconds = self.getETA()
+
+ if seconds < 0: return "00:00:00"
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+ def getSpeed(self):
+ """ calculates speed """
+ try:
+ return self.plugin.req.speed
+ except:
+ return 0
+
+ def getETA(self):
+ """ gets established time of arrival"""
+ try:
+ return self.getBytesLeft() / self.getSpeed()
+ except:
+ return 0
+
+ def getBytesLeft(self):
+ """ gets bytes left """
+ try:
+ return self.getSize() - self.plugin.req.arrived
+ except:
+ return 0
+
+ def getPercent(self):
+ """ get % of download """
+ if self.status == 12:
+ try:
+ return self.plugin.req.percent
+ except:
+ return 0
+ else:
+ return self.progress
+
+ def getSize(self):
+ """ get size of download """
+ try:
+ if self.plugin.req.size:
+ return self.plugin.req.size
+ else:
+ return self.size
+ except:
+ return self.size
+
+ def notifyChange(self):
+ e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue")
+ self.m.core.pullManager.addEvent(e)
+
+ def setProgress(self, value):
+ if not value == self.progress:
+ self.progress = value
+ self.notifyChange()
diff --git a/pyload/datatypes/PyPackage.py b/pyload/datatypes/PyPackage.py
new file mode 100644
index 000000000..c8d3e6096
--- /dev/null
+++ b/pyload/datatypes/PyPackage.py
@@ -0,0 +1,79 @@
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+ @author: mkaay
+"""
+
+from pyload.manager.event.PullEvents import UpdateEvent
+from pyload.utils import safe_filename
+
+class PyPackage:
+ """
+ Represents a package object at runtime
+ """
+ def __init__(self, manager, id, name, folder, site, password, queue, order):
+ self.m = manager
+ self.m.packageCache[int(id)] = self
+
+ self.id = int(id)
+ self.name = name
+ self._folder = folder
+ self.site = site
+ self.password = password
+ self.queue = queue
+ self.order = order
+ self.setFinished = False
+
+ @property
+ def folder(self):
+ return safe_filename(self._folder)
+
+ def toDict(self):
+ """ Returns a dictionary representation of the data.
+
+ :return: dict: {id: { attr: value }}
+ """
+ return {
+ self.id: {
+ 'id': self.id,
+ 'name': self.name,
+ 'folder': self.folder,
+ 'site': self.site,
+ 'password': self.password,
+ 'queue': self.queue,
+ 'order': self.order,
+ 'links': {}
+ }
+ }
+
+ def getChildren(self):
+ """get information about contained links"""
+ return self.m.getPackageData(self.id)["links"]
+
+ def sync(self):
+ """sync with db"""
+ self.m.updatePackage(self)
+
+ def release(self):
+ """sync and delete from cache"""
+ self.sync()
+ self.m.releasePackage(self.id)
+
+ def delete(self):
+ self.m.deletePackage(self.id)
+
+ def notifyChange(self):
+ e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue")
+ self.m.core.pullManager.addEvent(e)
diff --git a/module/__init__.py b/pyload/datatypes/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/__init__.py
+++ b/pyload/datatypes/__init__.py
diff --git a/pyload/lib/BeautifulSoup.py b/pyload/lib/BeautifulSoup.py
new file mode 100644
index 000000000..7278215ca
--- /dev/null
+++ b/pyload/lib/BeautifulSoup.py
@@ -0,0 +1,2017 @@
+"""Beautiful Soup
+Elixir and Tonic
+"The Screen-Scraper's Friend"
+http://www.crummy.com/software/BeautifulSoup/
+
+Beautiful Soup parses a (possibly invalid) XML or HTML document into a
+tree representation. It provides methods and Pythonic idioms that make
+it easy to navigate, search, and modify the tree.
+
+A well-formed XML/HTML document yields a well-formed data
+structure. An ill-formed XML/HTML document yields a correspondingly
+ill-formed data structure. If your document is only locally
+well-formed, you can use this library to find and process the
+well-formed part of it.
+
+Beautiful Soup works with Python 2.2 and up. It has no external
+dependencies, but you'll have more success at converting data to UTF-8
+if you also install these three packages:
+
+* chardet, for auto-detecting character encodings
+ http://chardet.feedparser.org/
+* cjkcodecs and iconv_codec, which add more encodings to the ones supported
+ by stock Python.
+ http://cjkpython.i18n.org/
+
+Beautiful Soup defines classes for two main parsing strategies:
+
+ * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific
+ language that kind of looks like XML.
+
+ * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid
+ or invalid. This class has web browser-like heuristics for
+ obtaining a sensible parse tree in the face of common HTML errors.
+
+Beautiful Soup also defines a class (UnicodeDammit) for autodetecting
+the encoding of an HTML or XML document, and converting it to
+Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser.
+
+For more than you ever wanted to know about Beautiful Soup, see the
+documentation:
+http://www.crummy.com/software/BeautifulSoup/documentation.html
+
+Here, have some legalese:
+
+Copyright (c) 2004-2010, Leonard Richardson
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of the the Beautiful Soup Consortium and All
+ Night Kosher Bakery nor the names of its contributors may be
+ used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT.
+
+"""
+from __future__ import generators
+
+__author__ = "Leonard Richardson (leonardr@segfault.org)"
+__version__ = "3.2.1"
+__copyright__ = "Copyright (c) 2004-2012 Leonard Richardson"
+__license__ = "New-style BSD"
+
+from sgmllib import SGMLParser, SGMLParseError
+import codecs
+import markupbase
+import types
+import re
+import sgmllib
+try:
+ from htmlentitydefs import name2codepoint
+except ImportError:
+ name2codepoint = {}
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+#These hacks make Beautiful Soup able to parse XML with namespaces
+sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
+markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match
+
+DEFAULT_OUTPUT_ENCODING = "utf-8"
+
+def _match_css_class(str):
+ """Build a RE to match the given CSS class."""
+ return re.compile(r"(^|.*\s)%s($|\s)" % str)
+
+# First, the classes that represent markup elements.
+
+class PageElement(object):
+ """Contains the navigational information for some part of the page
+ (either a tag or a piece of text)"""
+
+ def _invert(h):
+ "Cheap function to invert a hash."
+ i = {}
+ for k,v in h.items():
+ i[v] = k
+ return i
+
+ XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'",
+ "quot" : '"',
+ "amp" : "&",
+ "lt" : "<",
+ "gt" : ">" }
+
+ XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS)
+
+ def setup(self, parent=None, previous=None):
+ """Sets up the initial relations between this element and
+ other elements."""
+ self.parent = parent
+ self.previous = previous
+ self.next = None
+ self.previousSibling = None
+ self.nextSibling = None
+ if self.parent and self.parent.contents:
+ self.previousSibling = self.parent.contents[-1]
+ self.previousSibling.nextSibling = self
+
+ def replaceWith(self, replaceWith):
+ oldParent = self.parent
+ myIndex = self.parent.index(self)
+ if hasattr(replaceWith, "parent")\
+ and replaceWith.parent is self.parent:
+ # We're replacing this element with one of its siblings.
+ index = replaceWith.parent.index(replaceWith)
+ if index and index < myIndex:
+ # Furthermore, it comes before this element. That
+ # means that when we extract it, the index of this
+ # element will change.
+ myIndex = myIndex - 1
+ self.extract()
+ oldParent.insert(myIndex, replaceWith)
+
+ def replaceWithChildren(self):
+ myParent = self.parent
+ myIndex = self.parent.index(self)
+ self.extract()
+ reversedChildren = list(self.contents)
+ reversedChildren.reverse()
+ for child in reversedChildren:
+ myParent.insert(myIndex, child)
+
+ def extract(self):
+ """Destructively rips this element out of the tree."""
+ if self.parent:
+ try:
+ del self.parent.contents[self.parent.index(self)]
+ except ValueError:
+ pass
+
+ #Find the two elements that would be next to each other if
+ #this element (and any children) hadn't been parsed. Connect
+ #the two.
+ lastChild = self._lastRecursiveChild()
+ nextElement = lastChild.next
+
+ if self.previous:
+ self.previous.next = nextElement
+ if nextElement:
+ nextElement.previous = self.previous
+ self.previous = None
+ lastChild.next = None
+
+ self.parent = None
+ if self.previousSibling:
+ self.previousSibling.nextSibling = self.nextSibling
+ if self.nextSibling:
+ self.nextSibling.previousSibling = self.previousSibling
+ self.previousSibling = self.nextSibling = None
+ return self
+
+ def _lastRecursiveChild(self):
+ "Finds the last element beneath this object to be parsed."
+ lastChild = self
+ while hasattr(lastChild, 'contents') and lastChild.contents:
+ lastChild = lastChild.contents[-1]
+ return lastChild
+
+ def insert(self, position, newChild):
+ if isinstance(newChild, basestring) \
+ and not isinstance(newChild, NavigableString):
+ newChild = NavigableString(newChild)
+
+ position = min(position, len(self.contents))
+ if hasattr(newChild, 'parent') and newChild.parent is not None:
+ # We're 'inserting' an element that's already one
+ # of this object's children.
+ if newChild.parent is self:
+ index = self.index(newChild)
+ if index > position:
+ # Furthermore we're moving it further down the
+ # list of this object's children. That means that
+ # when we extract this element, our target index
+ # will jump down one.
+ position = position - 1
+ newChild.extract()
+
+ newChild.parent = self
+ previousChild = None
+ if position == 0:
+ newChild.previousSibling = None
+ newChild.previous = self
+ else:
+ previousChild = self.contents[position-1]
+ newChild.previousSibling = previousChild
+ newChild.previousSibling.nextSibling = newChild
+ newChild.previous = previousChild._lastRecursiveChild()
+ if newChild.previous:
+ newChild.previous.next = newChild
+
+ newChildsLastElement = newChild._lastRecursiveChild()
+
+ if position >= len(self.contents):
+ newChild.nextSibling = None
+
+ parent = self
+ parentsNextSibling = None
+ while not parentsNextSibling:
+ parentsNextSibling = parent.nextSibling
+ parent = parent.parent
+ if not parent: # This is the last element in the document.
+ break
+ if parentsNextSibling:
+ newChildsLastElement.next = parentsNextSibling
+ else:
+ newChildsLastElement.next = None
+ else:
+ nextChild = self.contents[position]
+ newChild.nextSibling = nextChild
+ if newChild.nextSibling:
+ newChild.nextSibling.previousSibling = newChild
+ newChildsLastElement.next = nextChild
+
+ if newChildsLastElement.next:
+ newChildsLastElement.next.previous = newChildsLastElement
+ self.contents.insert(position, newChild)
+
+ def append(self, tag):
+ """Appends the given tag to the contents of this tag."""
+ self.insert(len(self.contents), tag)
+
+ def findNext(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the first item that matches the given criteria and
+ appears after this Tag in the document."""
+ return self._findOne(self.findAllNext, name, attrs, text, **kwargs)
+
+ def findAllNext(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns all items that match the given criteria and appear
+ after this Tag in the document."""
+ return self._findAll(name, attrs, text, limit, self.nextGenerator,
+ **kwargs)
+
+ def findNextSibling(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the closest sibling to this Tag that matches the
+ given criteria and appears after this Tag in the document."""
+ return self._findOne(self.findNextSiblings, name, attrs, text,
+ **kwargs)
+
+ def findNextSiblings(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns the siblings of this Tag that match the given
+ criteria and appear after this Tag in the document."""
+ return self._findAll(name, attrs, text, limit,
+ self.nextSiblingGenerator, **kwargs)
+ fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x
+
+ def findPrevious(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the first item that matches the given criteria and
+ appears before this Tag in the document."""
+ return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs)
+
+ def findAllPrevious(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns all items that match the given criteria and appear
+ before this Tag in the document."""
+ return self._findAll(name, attrs, text, limit, self.previousGenerator,
+ **kwargs)
+ fetchPrevious = findAllPrevious # Compatibility with pre-3.x
+
+ def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the closest sibling to this Tag that matches the
+ given criteria and appears before this Tag in the document."""
+ return self._findOne(self.findPreviousSiblings, name, attrs, text,
+ **kwargs)
+
+ def findPreviousSiblings(self, name=None, attrs={}, text=None,
+ limit=None, **kwargs):
+ """Returns the siblings of this Tag that match the given
+ criteria and appear before this Tag in the document."""
+ return self._findAll(name, attrs, text, limit,
+ self.previousSiblingGenerator, **kwargs)
+ fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x
+
+ def findParent(self, name=None, attrs={}, **kwargs):
+ """Returns the closest parent of this Tag that matches the given
+ criteria."""
+ # NOTE: We can't use _findOne because findParents takes a different
+ # set of arguments.
+ r = None
+ l = self.findParents(name, attrs, 1)
+ if l:
+ r = l[0]
+ return r
+
+ def findParents(self, name=None, attrs={}, limit=None, **kwargs):
+ """Returns the parents of this Tag that match the given
+ criteria."""
+
+ return self._findAll(name, attrs, None, limit, self.parentGenerator,
+ **kwargs)
+ fetchParents = findParents # Compatibility with pre-3.x
+
+ #These methods do the real heavy lifting.
+
+ def _findOne(self, method, name, attrs, text, **kwargs):
+ r = None
+ l = method(name, attrs, text, 1, **kwargs)
+ if l:
+ r = l[0]
+ return r
+
+ def _findAll(self, name, attrs, text, limit, generator, **kwargs):
+ "Iterates over a generator looking for things that match."
+
+ if isinstance(name, SoupStrainer):
+ strainer = name
+ # (Possibly) special case some findAll*(...) searches
+ elif text is None and not limit and not attrs and not kwargs:
+ # findAll*(True)
+ if name is True:
+ return [element for element in generator()
+ if isinstance(element, Tag)]
+ # findAll*('tag-name')
+ elif isinstance(name, basestring):
+ return [element for element in generator()
+ if isinstance(element, Tag) and
+ element.name == name]
+ else:
+ strainer = SoupStrainer(name, attrs, text, **kwargs)
+ # Build a SoupStrainer
+ else:
+ strainer = SoupStrainer(name, attrs, text, **kwargs)
+ results = ResultSet(strainer)
+ g = generator()
+ while True:
+ try:
+ i = g.next()
+ except StopIteration:
+ break
+ if i:
+ found = strainer.search(i)
+ if found:
+ results.append(found)
+ if limit and len(results) >= limit:
+ break
+ return results
+
+ #These Generators can be used to navigate starting from both
+ #NavigableStrings and Tags.
+ def nextGenerator(self):
+ i = self
+ while i is not None:
+ i = i.next
+ yield i
+
+ def nextSiblingGenerator(self):
+ i = self
+ while i is not None:
+ i = i.nextSibling
+ yield i
+
+ def previousGenerator(self):
+ i = self
+ while i is not None:
+ i = i.previous
+ yield i
+
+ def previousSiblingGenerator(self):
+ i = self
+ while i is not None:
+ i = i.previousSibling
+ yield i
+
+ def parentGenerator(self):
+ i = self
+ while i is not None:
+ i = i.parent
+ yield i
+
+ # Utility methods
+ def substituteEncoding(self, str, encoding=None):
+ encoding = encoding or "utf-8"
+ return str.replace("%SOUP-ENCODING%", encoding)
+
+ def toEncoding(self, s, encoding=None):
+ """Encodes an object to a string in some encoding, or to Unicode.
+ ."""
+ if isinstance(s, unicode):
+ if encoding:
+ s = s.encode(encoding)
+ elif isinstance(s, str):
+ if encoding:
+ s = s.encode(encoding)
+ else:
+ s = unicode(s)
+ else:
+ if encoding:
+ s = self.toEncoding(str(s), encoding)
+ else:
+ s = unicode(s)
+ return s
+
+ BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|"
+ + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)"
+ + ")")
+
+ def _sub_entity(self, x):
+ """Used with a regular expression to substitute the
+ appropriate XML entity for an XML special character."""
+ return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";"
+
+
+class NavigableString(unicode, PageElement):
+
+ def __new__(cls, value):
+ """Create a new NavigableString.
+
+ When unpickling a NavigableString, this method is called with
+ the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be
+ passed in to the superclass's __new__ or the superclass won't know
+ how to handle non-ASCII characters.
+ """
+ if isinstance(value, unicode):
+ return unicode.__new__(cls, value)
+ return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING)
+
+ def __getnewargs__(self):
+ return (NavigableString.__str__(self),)
+
+ def __getattr__(self, attr):
+ """text.string gives you text. This is for backwards
+ compatibility for Navigable*String, but for CData* it lets you
+ get the string without the CData wrapper."""
+ if attr == 'string':
+ return self
+ else:
+ raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)
+
+ def __unicode__(self):
+ return str(self).decode(DEFAULT_OUTPUT_ENCODING)
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ # Substitute outgoing XML entities.
+ data = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, self)
+ if encoding:
+ return data.encode(encoding)
+ else:
+ return data
+
+class CData(NavigableString):
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<![CDATA[%s]]>" % NavigableString.__str__(self, encoding)
+
+class ProcessingInstruction(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ output = self
+ if "%SOUP-ENCODING%" in output:
+ output = self.substituteEncoding(output, encoding)
+ return "<?%s?>" % self.toEncoding(output, encoding)
+
+class Comment(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<!--%s-->" % NavigableString.__str__(self, encoding)
+
+class Declaration(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<!%s>" % NavigableString.__str__(self, encoding)
+
+class Tag(PageElement):
+
+ """Represents a found HTML tag with its attributes and contents."""
+
+ def _convertEntities(self, match):
+ """Used in a call to re.sub to replace HTML, XML, and numeric
+ entities with the appropriate Unicode characters. If HTML
+ entities are being converted, any unrecognized entities are
+ escaped."""
+ x = match.group(1)
+ if self.convertHTMLEntities and x in name2codepoint:
+ return unichr(name2codepoint[x])
+ elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS:
+ if self.convertXMLEntities:
+ return self.XML_ENTITIES_TO_SPECIAL_CHARS[x]
+ else:
+ return u'&%s;' % x
+ elif len(x) > 0 and x[0] == '#':
+ # Handle numeric entities
+ if len(x) > 1 and x[1] == 'x':
+ return unichr(int(x[2:], 16))
+ else:
+ return unichr(int(x[1:]))
+
+ elif self.escapeUnrecognizedEntities:
+ return u'&amp;%s;' % x
+ else:
+ return u'&%s;' % x
+
+ def __init__(self, parser, name, attrs=None, parent=None,
+ previous=None):
+ "Basic constructor."
+
+ # We don't actually store the parser object: that lets extracted
+ # chunks be garbage-collected
+ self.parserClass = parser.__class__
+ self.isSelfClosing = parser.isSelfClosingTag(name)
+ self.name = name
+ if attrs is None:
+ attrs = []
+ elif isinstance(attrs, dict):
+ attrs = attrs.items()
+ self.attrs = attrs
+ self.contents = []
+ self.setup(parent, previous)
+ self.hidden = False
+ self.containsSubstitutions = False
+ self.convertHTMLEntities = parser.convertHTMLEntities
+ self.convertXMLEntities = parser.convertXMLEntities
+ self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities
+
+ # Convert any HTML, XML, or numeric entities in the attribute values.
+ convert = lambda(k, val): (k,
+ re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);",
+ self._convertEntities,
+ val))
+ self.attrs = map(convert, self.attrs)
+
+ def getString(self):
+ if (len(self.contents) == 1
+ and isinstance(self.contents[0], NavigableString)):
+ return self.contents[0]
+
+ def setString(self, string):
+ """Replace the contents of the tag with a string"""
+ self.clear()
+ self.append(string)
+
+ string = property(getString, setString)
+
+ def getText(self, separator=u""):
+ if not len(self.contents):
+ return u""
+ stopNode = self._lastRecursiveChild().next
+ strings = []
+ current = self.contents[0]
+ while current is not stopNode:
+ if isinstance(current, NavigableString):
+ strings.append(current.strip())
+ current = current.next
+ return separator.join(strings)
+
+ text = property(getText)
+
+ def get(self, key, default=None):
+ """Returns the value of the 'key' attribute for the tag, or
+ the value given for 'default' if it doesn't have that
+ attribute."""
+ return self._getAttrMap().get(key, default)
+
+ def clear(self):
+ """Extract all children."""
+ for child in self.contents[:]:
+ child.extract()
+
+ def index(self, element):
+ for i, child in enumerate(self.contents):
+ if child is element:
+ return i
+ raise ValueError("Tag.index: element not in tag")
+
+ def has_key(self, key):
+ return self._getAttrMap().has_key(key)
+
+ def __getitem__(self, key):
+ """tag[key] returns the value of the 'key' attribute for the tag,
+ and throws an exception if it's not there."""
+ return self._getAttrMap()[key]
+
+ def __iter__(self):
+ "Iterating over a tag iterates over its contents."
+ return iter(self.contents)
+
+ def __len__(self):
+ "The length of a tag is the length of its list of contents."
+ return len(self.contents)
+
+ def __contains__(self, x):
+ return x in self.contents
+
+ def __nonzero__(self):
+ "A tag is non-None even if it has no contents."
+ return True
+
+ def __setitem__(self, key, value):
+ """Setting tag[key] sets the value of the 'key' attribute for the
+ tag."""
+ self._getAttrMap()
+ self.attrMap[key] = value
+ found = False
+ for i in range(0, len(self.attrs)):
+ if self.attrs[i][0] == key:
+ self.attrs[i] = (key, value)
+ found = True
+ if not found:
+ self.attrs.append((key, value))
+ self._getAttrMap()[key] = value
+
+ def __delitem__(self, key):
+ "Deleting tag[key] deletes all 'key' attributes for the tag."
+ for item in self.attrs:
+ if item[0] == key:
+ self.attrs.remove(item)
+ #We don't break because bad HTML can define the same
+ #attribute multiple times.
+ self._getAttrMap()
+ if self.attrMap.has_key(key):
+ del self.attrMap[key]
+
+ def __call__(self, *args, **kwargs):
+ """Calling a tag like a function is the same as calling its
+ findAll() method. Eg. tag('a') returns a list of all the A tags
+ found within this tag."""
+ return apply(self.findAll, args, kwargs)
+
+ def __getattr__(self, tag):
+ #print "Getattr %s.%s" % (self.__class__, tag)
+ if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3:
+ return self.find(tag[:-3])
+ elif tag.find('__') != 0:
+ return self.find(tag)
+ raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag)
+
+ def __eq__(self, other):
+ """Returns true iff this tag has the same name, the same attributes,
+ and the same contents (recursively) as the given tag.
+
+ NOTE: right now this will return false if two tags have the
+ same attributes in a different order. Should this be fixed?"""
+ if other is self:
+ return True
+ if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other):
+ return False
+ for i in range(0, len(self.contents)):
+ if self.contents[i] != other.contents[i]:
+ return False
+ return True
+
+ def __ne__(self, other):
+ """Returns true iff this tag is not identical to the other tag,
+ as defined in __eq__."""
+ return not self == other
+
+ def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ """Renders this tag as a string."""
+ return self.__str__(encoding)
+
+ def __unicode__(self):
+ return self.__str__(None)
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING,
+ prettyPrint=False, indentLevel=0):
+ """Returns a string or Unicode representation of this tag and
+ its contents. To get Unicode, pass None for encoding.
+
+ NOTE: since Python's HTML parser consumes whitespace, this
+ method is not certain to reproduce the whitespace present in
+ the original string."""
+
+ encodedName = self.toEncoding(self.name, encoding)
+
+ attrs = []
+ if self.attrs:
+ for key, val in self.attrs:
+ fmt = '%s="%s"'
+ if isinstance(val, basestring):
+ if self.containsSubstitutions and '%SOUP-ENCODING%' in val:
+ val = self.substituteEncoding(val, encoding)
+
+ # The attribute value either:
+ #
+ # * Contains no embedded double quotes or single quotes.
+ # No problem: we enclose it in double quotes.
+ # * Contains embedded single quotes. No problem:
+ # double quotes work here too.
+ # * Contains embedded double quotes. No problem:
+ # we enclose it in single quotes.
+ # * Embeds both single _and_ double quotes. This
+ # can't happen naturally, but it can happen if
+ # you modify an attribute value after parsing
+ # the document. Now we have a bit of a
+ # problem. We solve it by enclosing the
+ # attribute in single quotes, and escaping any
+ # embedded single quotes to XML entities.
+ if '"' in val:
+ fmt = "%s='%s'"
+ if "'" in val:
+ # TODO: replace with apos when
+ # appropriate.
+ val = val.replace("'", "&squot;")
+
+ # Now we're okay w/r/t quotes. But the attribute
+ # value might also contain angle brackets, or
+ # ampersands that aren't part of entities. We need
+ # to escape those to XML entities too.
+ val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val)
+
+ attrs.append(fmt % (self.toEncoding(key, encoding),
+ self.toEncoding(val, encoding)))
+ close = ''
+ closeTag = ''
+ if self.isSelfClosing:
+ close = ' /'
+ else:
+ closeTag = '</%s>' % encodedName
+
+ indentTag, indentContents = 0, 0
+ if prettyPrint:
+ indentTag = indentLevel
+ space = (' ' * (indentTag-1))
+ indentContents = indentTag + 1
+ contents = self.renderContents(encoding, prettyPrint, indentContents)
+ if self.hidden:
+ s = contents
+ else:
+ s = []
+ attributeString = ''
+ if attrs:
+ attributeString = ' ' + ' '.join(attrs)
+ if prettyPrint:
+ s.append(space)
+ s.append('<%s%s%s>' % (encodedName, attributeString, close))
+ if prettyPrint:
+ s.append("\n")
+ s.append(contents)
+ if prettyPrint and contents and contents[-1] != "\n":
+ s.append("\n")
+ if prettyPrint and closeTag:
+ s.append(space)
+ s.append(closeTag)
+ if prettyPrint and closeTag and self.nextSibling:
+ s.append("\n")
+ s = ''.join(s)
+ return s
+
+ def decompose(self):
+ """Recursively destroys the contents of this tree."""
+ self.extract()
+ if len(self.contents) == 0:
+ return
+ current = self.contents[0]
+ while current is not None:
+ next = current.next
+ if isinstance(current, Tag):
+ del current.contents[:]
+ current.parent = None
+ current.previous = None
+ current.previousSibling = None
+ current.next = None
+ current.nextSibling = None
+ current = next
+
+ def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return self.__str__(encoding, True)
+
+ def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING,
+ prettyPrint=False, indentLevel=0):
+ """Renders the contents of this tag as a string in the given
+ encoding. If encoding is None, returns a Unicode string.."""
+ s=[]
+ for c in self:
+ text = None
+ if isinstance(c, NavigableString):
+ text = c.__str__(encoding)
+ elif isinstance(c, Tag):
+ s.append(c.__str__(encoding, prettyPrint, indentLevel))
+ if text and prettyPrint:
+ text = text.strip()
+ if text:
+ if prettyPrint:
+ s.append(" " * (indentLevel-1))
+ s.append(text)
+ if prettyPrint:
+ s.append("\n")
+ return ''.join(s)
+
+ #Soup methods
+
+ def find(self, name=None, attrs={}, recursive=True, text=None,
+ **kwargs):
+ """Return only the first child of this Tag matching the given
+ criteria."""
+ r = None
+ l = self.findAll(name, attrs, recursive, text, 1, **kwargs)
+ if l:
+ r = l[0]
+ return r
+ findChild = find
+
+ def findAll(self, name=None, attrs={}, recursive=True, text=None,
+ limit=None, **kwargs):
+ """Extracts a list of Tag objects that match the given
+ criteria. You can specify the name of the Tag and any
+ attributes you want the Tag to have.
+
+ The value of a key-value pair in the 'attrs' map can be a
+ string, a list of strings, a regular expression object, or a
+ callable that takes a string and returns whether or not the
+ string matches for some custom definition of 'matches'. The
+ same is true of the tag name."""
+ generator = self.recursiveChildGenerator
+ if not recursive:
+ generator = self.childGenerator
+ return self._findAll(name, attrs, text, limit, generator, **kwargs)
+ findChildren = findAll
+
+ # Pre-3.x compatibility methods
+ first = find
+ fetch = findAll
+
+ def fetchText(self, text=None, recursive=True, limit=None):
+ return self.findAll(text=text, recursive=recursive, limit=limit)
+
+ def firstText(self, text=None, recursive=True):
+ return self.find(text=text, recursive=recursive)
+
+ #Private methods
+
+ def _getAttrMap(self):
+ """Initializes a map representation of this tag's attributes,
+ if not already initialized."""
+ if not getattr(self, 'attrMap'):
+ self.attrMap = {}
+ for (key, value) in self.attrs:
+ self.attrMap[key] = value
+ return self.attrMap
+
+ #Generator methods
+ def childGenerator(self):
+ # Just use the iterator from the contents
+ return iter(self.contents)
+
+ def recursiveChildGenerator(self):
+ if not len(self.contents):
+ raise StopIteration
+ stopNode = self._lastRecursiveChild().next
+ current = self.contents[0]
+ while current is not stopNode:
+ yield current
+ current = current.next
+
+
+# Next, a couple classes to represent queries and their results.
+class SoupStrainer:
+ """Encapsulates a number of ways of matching a markup element (tag or
+ text)."""
+
+ def __init__(self, name=None, attrs={}, text=None, **kwargs):
+ self.name = name
+ if isinstance(attrs, basestring):
+ kwargs['class'] = _match_css_class(attrs)
+ attrs = None
+ if kwargs:
+ if attrs:
+ attrs = attrs.copy()
+ attrs.update(kwargs)
+ else:
+ attrs = kwargs
+ self.attrs = attrs
+ self.text = text
+
+ def __str__(self):
+ if self.text:
+ return self.text
+ else:
+ return "%s|%s" % (self.name, self.attrs)
+
+ def searchTag(self, markupName=None, markupAttrs={}):
+ found = None
+ markup = None
+ if isinstance(markupName, Tag):
+ markup = markupName
+ markupAttrs = markup
+ callFunctionWithTagData = callable(self.name) \
+ and not isinstance(markupName, Tag)
+
+ if (not self.name) \
+ or callFunctionWithTagData \
+ or (markup and self._matches(markup, self.name)) \
+ or (not markup and self._matches(markupName, self.name)):
+ if callFunctionWithTagData:
+ match = self.name(markupName, markupAttrs)
+ else:
+ match = True
+ markupAttrMap = None
+ for attr, matchAgainst in self.attrs.items():
+ if not markupAttrMap:
+ if hasattr(markupAttrs, 'get'):
+ markupAttrMap = markupAttrs
+ else:
+ markupAttrMap = {}
+ for k,v in markupAttrs:
+ markupAttrMap[k] = v
+ attrValue = markupAttrMap.get(attr)
+ if not self._matches(attrValue, matchAgainst):
+ match = False
+ break
+ if match:
+ if markup:
+ found = markup
+ else:
+ found = markupName
+ return found
+
+ def search(self, markup):
+ #print 'looking for %s in %s' % (self, markup)
+ found = None
+ # If given a list of items, scan it for a text element that
+ # matches.
+ if hasattr(markup, "__iter__") \
+ and not isinstance(markup, Tag):
+ for element in markup:
+ if isinstance(element, NavigableString) \
+ and self.search(element):
+ found = element
+ break
+ # If it's a Tag, make sure its name or attributes match.
+ # Don't bother with Tags if we're searching for text.
+ elif isinstance(markup, Tag):
+ if not self.text:
+ found = self.searchTag(markup)
+ # If it's text, make sure the text matches.
+ elif isinstance(markup, NavigableString) or \
+ isinstance(markup, basestring):
+ if self._matches(markup, self.text):
+ found = markup
+ else:
+ raise Exception, "I don't know how to match against a %s" \
+ % markup.__class__
+ return found
+
+ def _matches(self, markup, matchAgainst):
+ #print "Matching %s against %s" % (markup, matchAgainst)
+ result = False
+ if matchAgainst is True:
+ result = markup is not None
+ elif callable(matchAgainst):
+ result = matchAgainst(markup)
+ else:
+ #Custom match methods take the tag as an argument, but all
+ #other ways of matching match the tag name as a string.
+ if isinstance(markup, Tag):
+ markup = markup.name
+ if markup and not isinstance(markup, basestring):
+ markup = unicode(markup)
+ #Now we know that chunk is either a string, or None.
+ if hasattr(matchAgainst, 'match'):
+ # It's a regexp object.
+ result = markup and matchAgainst.search(markup)
+ elif hasattr(matchAgainst, '__iter__'): # list-like
+ result = markup in matchAgainst
+ elif hasattr(matchAgainst, 'items'):
+ result = markup.has_key(matchAgainst)
+ elif matchAgainst and isinstance(markup, basestring):
+ if isinstance(markup, unicode):
+ matchAgainst = unicode(matchAgainst)
+ else:
+ matchAgainst = str(matchAgainst)
+
+ if not result:
+ result = matchAgainst == markup
+ return result
+
+class ResultSet(list):
+ """A ResultSet is just a list that keeps track of the SoupStrainer
+ that created it."""
+ def __init__(self, source):
+ list.__init__([])
+ self.source = source
+
+# Now, some helper functions.
+
+def buildTagMap(default, *args):
+ """Turns a list of maps, lists, or scalars into a single map.
+ Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and
+ NESTING_RESET_TAGS maps out of lists and partial maps."""
+ built = {}
+ for portion in args:
+ if hasattr(portion, 'items'):
+ #It's a map. Merge it.
+ for k,v in portion.items():
+ built[k] = v
+ elif hasattr(portion, '__iter__'): # is a list
+ #It's a list. Map each item to the default.
+ for k in portion:
+ built[k] = default
+ else:
+ #It's a scalar. Map it to the default.
+ built[portion] = default
+ return built
+
+# Now, the parser classes.
+
+class BeautifulStoneSoup(Tag, SGMLParser):
+
+ """This class contains the basic parser and search code. It defines
+ a parser that knows nothing about tag behavior except for the
+ following:
+
+ You can't close a tag without closing all the tags it encloses.
+ That is, "<foo><bar></foo>" actually means
+ "<foo><bar></bar></foo>".
+
+ [Another possible explanation is "<foo><bar /></foo>", but since
+ this class defines no SELF_CLOSING_TAGS, it will never use that
+ explanation.]
+
+ This class is useful for parsing XML or made-up markup languages,
+ or when BeautifulSoup makes an assumption counter to what you were
+ expecting."""
+
+ SELF_CLOSING_TAGS = {}
+ NESTABLE_TAGS = {}
+ RESET_NESTING_TAGS = {}
+ QUOTE_TAGS = {}
+ PRESERVE_WHITESPACE_TAGS = []
+
+ MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'),
+ lambda x: x.group(1) + ' />'),
+ (re.compile('<!\s+([^<>]*)>'),
+ lambda x: '<!' + x.group(1) + '>')
+ ]
+
+ ROOT_TAG_NAME = u'[document]'
+
+ HTML_ENTITIES = "html"
+ XML_ENTITIES = "xml"
+ XHTML_ENTITIES = "xhtml"
+ # TODO: This only exists for backwards-compatibility
+ ALL_ENTITIES = XHTML_ENTITIES
+
+ # Used when determining whether a text node is all whitespace and
+ # can be replaced with a single space. A text node that contains
+ # fancy Unicode spaces (usually non-breaking) should be left
+ # alone.
+ STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, }
+
+ def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None,
+ markupMassage=True, smartQuotesTo=XML_ENTITIES,
+ convertEntities=None, selfClosingTags=None, isHTML=False):
+ """The Soup object is initialized as the 'root tag', and the
+ provided markup (which can be a string or a file-like object)
+ is fed into the underlying parser.
+
+ sgmllib will process most bad HTML, and the BeautifulSoup
+ class has some tricks for dealing with some HTML that kills
+ sgmllib, but Beautiful Soup can nonetheless choke or lose data
+ if your data uses self-closing tags or declarations
+ incorrectly.
+
+ By default, Beautiful Soup uses regexes to sanitize input,
+ avoiding the vast majority of these problems. If the problems
+ don't apply to you, pass in False for markupMassage, and
+ you'll get better performance.
+
+ The default parser massage techniques fix the two most common
+ instances of invalid HTML that choke sgmllib:
+
+ <br/> (No space between name of closing tag and tag close)
+ <! --Comment--> (Extraneous whitespace in declaration)
+
+ You can pass in a custom list of (RE object, replace method)
+ tuples to get Beautiful Soup to scrub your input the way you
+ want."""
+
+ self.parseOnlyThese = parseOnlyThese
+ self.fromEncoding = fromEncoding
+ self.smartQuotesTo = smartQuotesTo
+ self.convertEntities = convertEntities
+ # Set the rules for how we'll deal with the entities we
+ # encounter
+ if self.convertEntities:
+ # It doesn't make sense to convert encoded characters to
+ # entities even while you're converting entities to Unicode.
+ # Just convert it all to Unicode.
+ self.smartQuotesTo = None
+ if convertEntities == self.HTML_ENTITIES:
+ self.convertXMLEntities = False
+ self.convertHTMLEntities = True
+ self.escapeUnrecognizedEntities = True
+ elif convertEntities == self.XHTML_ENTITIES:
+ self.convertXMLEntities = True
+ self.convertHTMLEntities = True
+ self.escapeUnrecognizedEntities = False
+ elif convertEntities == self.XML_ENTITIES:
+ self.convertXMLEntities = True
+ self.convertHTMLEntities = False
+ self.escapeUnrecognizedEntities = False
+ else:
+ self.convertXMLEntities = False
+ self.convertHTMLEntities = False
+ self.escapeUnrecognizedEntities = False
+
+ self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags)
+ SGMLParser.__init__(self)
+
+ if hasattr(markup, 'read'): # It's a file-type object.
+ markup = markup.read()
+ self.markup = markup
+ self.markupMassage = markupMassage
+ try:
+ self._feed(isHTML=isHTML)
+ except StopParsing:
+ pass
+ self.markup = None # The markup can now be GCed
+
+ def convert_charref(self, name):
+ """This method fixes a bug in Python's SGMLParser."""
+ try:
+ n = int(name)
+ except ValueError:
+ return
+ if not 0 <= n <= 127 : # ASCII ends at 127, not 255
+ return
+ return self.convert_codepoint(n)
+
+ def _feed(self, inDocumentEncoding=None, isHTML=False):
+ # Convert the document to Unicode.
+ markup = self.markup
+ if isinstance(markup, unicode):
+ if not hasattr(self, 'originalEncoding'):
+ self.originalEncoding = None
+ else:
+ dammit = UnicodeDammit\
+ (markup, [self.fromEncoding, inDocumentEncoding],
+ smartQuotesTo=self.smartQuotesTo, isHTML=isHTML)
+ markup = dammit.unicode
+ self.originalEncoding = dammit.originalEncoding
+ self.declaredHTMLEncoding = dammit.declaredHTMLEncoding
+ if markup:
+ if self.markupMassage:
+ if not hasattr(self.markupMassage, "__iter__"):
+ self.markupMassage = self.MARKUP_MASSAGE
+ for fix, m in self.markupMassage:
+ markup = fix.sub(m, markup)
+ # TODO: We get rid of markupMassage so that the
+ # soup object can be deepcopied later on. Some
+ # Python installations can't copy regexes. If anyone
+ # was relying on the existence of markupMassage, this
+ # might cause problems.
+ del(self.markupMassage)
+ self.reset()
+
+ SGMLParser.feed(self, markup)
+ # Close out any unfinished strings and close all the open tags.
+ self.endData()
+ while self.currentTag.name != self.ROOT_TAG_NAME:
+ self.popTag()
+
+ def __getattr__(self, methodName):
+ """This method routes method call requests to either the SGMLParser
+ superclass or the Tag superclass, depending on the method name."""
+ #print "__getattr__ called on %s.%s" % (self.__class__, methodName)
+
+ if methodName.startswith('start_') or methodName.startswith('end_') \
+ or methodName.startswith('do_'):
+ return SGMLParser.__getattr__(self, methodName)
+ elif not methodName.startswith('__'):
+ return Tag.__getattr__(self, methodName)
+ else:
+ raise AttributeError
+
+ def isSelfClosingTag(self, name):
+ """Returns true iff the given string is the name of a
+ self-closing tag according to this parser."""
+ return self.SELF_CLOSING_TAGS.has_key(name) \
+ or self.instanceSelfClosingTags.has_key(name)
+
+ def reset(self):
+ Tag.__init__(self, self, self.ROOT_TAG_NAME)
+ self.hidden = 1
+ SGMLParser.reset(self)
+ self.currentData = []
+ self.currentTag = None
+ self.tagStack = []
+ self.quoteStack = []
+ self.pushTag(self)
+
+ def popTag(self):
+ tag = self.tagStack.pop()
+
+ #print "Pop", tag.name
+ if self.tagStack:
+ self.currentTag = self.tagStack[-1]
+ return self.currentTag
+
+ def pushTag(self, tag):
+ #print "Push", tag.name
+ if self.currentTag:
+ self.currentTag.contents.append(tag)
+ self.tagStack.append(tag)
+ self.currentTag = self.tagStack[-1]
+
+ def endData(self, containerClass=NavigableString):
+ if self.currentData:
+ currentData = u''.join(self.currentData)
+ if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and
+ not set([tag.name for tag in self.tagStack]).intersection(
+ self.PRESERVE_WHITESPACE_TAGS)):
+ if '\n' in currentData:
+ currentData = '\n'
+ else:
+ currentData = ' '
+ self.currentData = []
+ if self.parseOnlyThese and len(self.tagStack) <= 1 and \
+ (not self.parseOnlyThese.text or \
+ not self.parseOnlyThese.search(currentData)):
+ return
+ o = containerClass(currentData)
+ o.setup(self.currentTag, self.previous)
+ if self.previous:
+ self.previous.next = o
+ self.previous = o
+ self.currentTag.contents.append(o)
+
+
+ def _popToTag(self, name, inclusivePop=True):
+ """Pops the tag stack up to and including the most recent
+ instance of the given tag. If inclusivePop is false, pops the tag
+ stack up to but *not* including the most recent instqance of
+ the given tag."""
+ #print "Popping to %s" % name
+ if name == self.ROOT_TAG_NAME:
+ return
+
+ numPops = 0
+ mostRecentTag = None
+ for i in range(len(self.tagStack)-1, 0, -1):
+ if name == self.tagStack[i].name:
+ numPops = len(self.tagStack)-i
+ break
+ if not inclusivePop:
+ numPops = numPops - 1
+
+ for i in range(0, numPops):
+ mostRecentTag = self.popTag()
+ return mostRecentTag
+
+ def _smartPop(self, name):
+
+ """We need to pop up to the previous tag of this type, unless
+ one of this tag's nesting reset triggers comes between this
+ tag and the previous tag of this type, OR unless this tag is a
+ generic nesting trigger and another generic nesting trigger
+ comes between this tag and the previous tag of this type.
+
+ Examples:
+ <p>Foo<b>Bar *<p>* should pop to 'p', not 'b'.
+ <p>Foo<table>Bar *<p>* should pop to 'table', not 'p'.
+ <p>Foo<table><tr>Bar *<p>* should pop to 'tr', not 'p'.
+
+ <li><ul><li> *<li>* should pop to 'ul', not the first 'li'.
+ <tr><table><tr> *<tr>* should pop to 'table', not the first 'tr'
+ <td><tr><td> *<td>* should pop to 'tr', not the first 'td'
+ """
+
+ nestingResetTriggers = self.NESTABLE_TAGS.get(name)
+ isNestable = nestingResetTriggers != None
+ isResetNesting = self.RESET_NESTING_TAGS.has_key(name)
+ popTo = None
+ inclusive = True
+ for i in range(len(self.tagStack)-1, 0, -1):
+ p = self.tagStack[i]
+ if (not p or p.name == name) and not isNestable:
+ #Non-nestable tags get popped to the top or to their
+ #last occurance.
+ popTo = name
+ break
+ if (nestingResetTriggers is not None
+ and p.name in nestingResetTriggers) \
+ or (nestingResetTriggers is None and isResetNesting
+ and self.RESET_NESTING_TAGS.has_key(p.name)):
+
+ #If we encounter one of the nesting reset triggers
+ #peculiar to this tag, or we encounter another tag
+ #that causes nesting to reset, pop up to but not
+ #including that tag.
+ popTo = p.name
+ inclusive = False
+ break
+ p = p.parent
+ if popTo:
+ self._popToTag(popTo, inclusive)
+
+ def unknown_starttag(self, name, attrs, selfClosing=0):
+ #print "Start tag %s: %s" % (name, attrs)
+ if self.quoteStack:
+ #This is not a real tag.
+ #print "<%s> is not real!" % name
+ attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs])
+ self.handle_data('<%s%s>' % (name, attrs))
+ return
+ self.endData()
+
+ if not self.isSelfClosingTag(name) and not selfClosing:
+ self._smartPop(name)
+
+ if self.parseOnlyThese and len(self.tagStack) <= 1 \
+ and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)):
+ return
+
+ tag = Tag(self, name, attrs, self.currentTag, self.previous)
+ if self.previous:
+ self.previous.next = tag
+ self.previous = tag
+ self.pushTag(tag)
+ if selfClosing or self.isSelfClosingTag(name):
+ self.popTag()
+ if name in self.QUOTE_TAGS:
+ #print "Beginning quote (%s)" % name
+ self.quoteStack.append(name)
+ self.literal = 1
+ return tag
+
+ def unknown_endtag(self, name):
+ #print "End tag %s" % name
+ if self.quoteStack and self.quoteStack[-1] != name:
+ #This is not a real end tag.
+ #print "</%s> is not real!" % name
+ self.handle_data('</%s>' % name)
+ return
+ self.endData()
+ self._popToTag(name)
+ if self.quoteStack and self.quoteStack[-1] == name:
+ self.quoteStack.pop()
+ self.literal = (len(self.quoteStack) > 0)
+
+ def handle_data(self, data):
+ self.currentData.append(data)
+
+ def _toStringSubclass(self, text, subclass):
+ """Adds a certain piece of text to the tree as a NavigableString
+ subclass."""
+ self.endData()
+ self.handle_data(text)
+ self.endData(subclass)
+
+ def handle_pi(self, text):
+ """Handle a processing instruction as a ProcessingInstruction
+ object, possibly one with a %SOUP-ENCODING% slot into which an
+ encoding will be plugged later."""
+ if text[:3] == "xml":
+ text = u"xml version='1.0' encoding='%SOUP-ENCODING%'"
+ self._toStringSubclass(text, ProcessingInstruction)
+
+ def handle_comment(self, text):
+ "Handle comments as Comment objects."
+ self._toStringSubclass(text, Comment)
+
+ def handle_charref(self, ref):
+ "Handle character references as data."
+ if self.convertEntities:
+ data = unichr(int(ref))
+ else:
+ data = '&#%s;' % ref
+ self.handle_data(data)
+
+ def handle_entityref(self, ref):
+ """Handle entity references as data, possibly converting known
+ HTML and/or XML entity references to the corresponding Unicode
+ characters."""
+ data = None
+ if self.convertHTMLEntities:
+ try:
+ data = unichr(name2codepoint[ref])
+ except KeyError:
+ pass
+
+ if not data and self.convertXMLEntities:
+ data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref)
+
+ if not data and self.convertHTMLEntities and \
+ not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref):
+ # TODO: We've got a problem here. We're told this is
+ # an entity reference, but it's not an XML entity
+ # reference or an HTML entity reference. Nonetheless,
+ # the logical thing to do is to pass it through as an
+ # unrecognized entity reference.
+ #
+ # Except: when the input is "&carol;" this function
+ # will be called with input "carol". When the input is
+ # "AT&T", this function will be called with input
+ # "T". We have no way of knowing whether a semicolon
+ # was present originally, so we don't know whether
+ # this is an unknown entity or just a misplaced
+ # ampersand.
+ #
+ # The more common case is a misplaced ampersand, so I
+ # escape the ampersand and omit the trailing semicolon.
+ data = "&amp;%s" % ref
+ if not data:
+ # This case is different from the one above, because we
+ # haven't already gone through a supposedly comprehensive
+ # mapping of entities to Unicode characters. We might not
+ # have gone through any mapping at all. So the chances are
+ # very high that this is a real entity, and not a
+ # misplaced ampersand.
+ data = "&%s;" % ref
+ self.handle_data(data)
+
+ def handle_decl(self, data):
+ "Handle DOCTYPEs and the like as Declaration objects."
+ self._toStringSubclass(data, Declaration)
+
+ def parse_declaration(self, i):
+ """Treat a bogus SGML declaration as raw data. Treat a CDATA
+ declaration as a CData object."""
+ j = None
+ if self.rawdata[i:i+9] == '<![CDATA[':
+ k = self.rawdata.find(']]>', i)
+ if k == -1:
+ k = len(self.rawdata)
+ data = self.rawdata[i+9:k]
+ j = k+3
+ self._toStringSubclass(data, CData)
+ else:
+ try:
+ j = SGMLParser.parse_declaration(self, i)
+ except SGMLParseError:
+ toHandle = self.rawdata[i:]
+ self.handle_data(toHandle)
+ j = i + len(toHandle)
+ return j
+
+class BeautifulSoup(BeautifulStoneSoup):
+
+ """This parser knows the following facts about HTML:
+
+ * Some tags have no closing tag and should be interpreted as being
+ closed as soon as they are encountered.
+
+ * The text inside some tags (ie. 'script') may contain tags which
+ are not really part of the document and which should be parsed
+ as text, not tags. If you want to parse the text as tags, you can
+ always fetch it and parse it explicitly.
+
+ * Tag nesting rules:
+
+ Most tags can't be nested at all. For instance, the occurance of
+ a <p> tag should implicitly close the previous <p> tag.
+
+ <p>Para1<p>Para2
+ should be transformed into:
+ <p>Para1</p><p>Para2
+
+ Some tags can be nested arbitrarily. For instance, the occurance
+ of a <blockquote> tag should _not_ implicitly close the previous
+ <blockquote> tag.
+
+ Alice said: <blockquote>Bob said: <blockquote>Blah
+ should NOT be transformed into:
+ Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah
+
+ Some tags can be nested, but the nesting is reset by the
+ interposition of other tags. For instance, a <tr> tag should
+ implicitly close the previous <tr> tag within the same <table>,
+ but not close a <tr> tag in another table.
+
+ <table><tr>Blah<tr>Blah
+ should be transformed into:
+ <table><tr>Blah</tr><tr>Blah
+ but,
+ <tr>Blah<table><tr>Blah
+ should NOT be transformed into
+ <tr>Blah<table></tr><tr>Blah
+
+ Differing assumptions about tag nesting rules are a major source
+ of problems with the BeautifulSoup class. If BeautifulSoup is not
+ treating as nestable a tag your page author treats as nestable,
+ try ICantBelieveItsBeautifulSoup, MinimalSoup, or
+ BeautifulStoneSoup before writing your own subclass."""
+
+ def __init__(self, *args, **kwargs):
+ if not kwargs.has_key('smartQuotesTo'):
+ kwargs['smartQuotesTo'] = self.HTML_ENTITIES
+ kwargs['isHTML'] = True
+ BeautifulStoneSoup.__init__(self, *args, **kwargs)
+
+ SELF_CLOSING_TAGS = buildTagMap(None,
+ ('br' , 'hr', 'input', 'img', 'meta',
+ 'spacer', 'link', 'frame', 'base', 'col'))
+
+ PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea'])
+
+ QUOTE_TAGS = {'script' : None, 'textarea' : None}
+
+ #According to the HTML standard, each of these inline tags can
+ #contain another tag of the same type. Furthermore, it's common
+ #to actually use these tags this way.
+ NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup',
+ 'center')
+
+ #According to the HTML standard, these block tags can contain
+ #another tag of the same type. Furthermore, it's common
+ #to actually use these tags this way.
+ NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del')
+
+ #Lists can contain other lists, but there are restrictions.
+ NESTABLE_LIST_TAGS = { 'ol' : [],
+ 'ul' : [],
+ 'li' : ['ul', 'ol'],
+ 'dl' : [],
+ 'dd' : ['dl'],
+ 'dt' : ['dl'] }
+
+ #Tables can contain other tables, but there are restrictions.
+ NESTABLE_TABLE_TAGS = {'table' : [],
+ 'tr' : ['table', 'tbody', 'tfoot', 'thead'],
+ 'td' : ['tr'],
+ 'th' : ['tr'],
+ 'thead' : ['table'],
+ 'tbody' : ['table'],
+ 'tfoot' : ['table'],
+ }
+
+ NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre')
+
+ #If one of these tags is encountered, all tags up to the next tag of
+ #this type are popped.
+ RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript',
+ NON_NESTABLE_BLOCK_TAGS,
+ NESTABLE_LIST_TAGS,
+ NESTABLE_TABLE_TAGS)
+
+ NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS,
+ NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS)
+
+ # Used to detect the charset in a META tag; see start_meta
+ CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M)
+
+ def start_meta(self, attrs):
+ """Beautiful Soup can detect a charset included in a META tag,
+ try to convert the document to that charset, and re-parse the
+ document from the beginning."""
+ httpEquiv = None
+ contentType = None
+ contentTypeIndex = None
+ tagNeedsEncodingSubstitution = False
+
+ for i in range(0, len(attrs)):
+ key, value = attrs[i]
+ key = key.lower()
+ if key == 'http-equiv':
+ httpEquiv = value
+ elif key == 'content':
+ contentType = value
+ contentTypeIndex = i
+
+ if httpEquiv and contentType: # It's an interesting meta tag.
+ match = self.CHARSET_RE.search(contentType)
+ if match:
+ if (self.declaredHTMLEncoding is not None or
+ self.originalEncoding == self.fromEncoding):
+ # An HTML encoding was sniffed while converting
+ # the document to Unicode, or an HTML encoding was
+ # sniffed during a previous pass through the
+ # document, or an encoding was specified
+ # explicitly and it worked. Rewrite the meta tag.
+ def rewrite(match):
+ return match.group(1) + "%SOUP-ENCODING%"
+ newAttr = self.CHARSET_RE.sub(rewrite, contentType)
+ attrs[contentTypeIndex] = (attrs[contentTypeIndex][0],
+ newAttr)
+ tagNeedsEncodingSubstitution = True
+ else:
+ # This is our first pass through the document.
+ # Go through it again with the encoding information.
+ newCharset = match.group(3)
+ if newCharset and newCharset != self.originalEncoding:
+ self.declaredHTMLEncoding = newCharset
+ self._feed(self.declaredHTMLEncoding)
+ raise StopParsing
+ pass
+ tag = self.unknown_starttag("meta", attrs)
+ if tag and tagNeedsEncodingSubstitution:
+ tag.containsSubstitutions = True
+
+class StopParsing(Exception):
+ pass
+
+class ICantBelieveItsBeautifulSoup(BeautifulSoup):
+
+ """The BeautifulSoup class is oriented towards skipping over
+ common HTML errors like unclosed tags. However, sometimes it makes
+ errors of its own. For instance, consider this fragment:
+
+ <b>Foo<b>Bar</b></b>
+
+ This is perfectly valid (if bizarre) HTML. However, the
+ BeautifulSoup class will implicitly close the first b tag when it
+ encounters the second 'b'. It will think the author wrote
+ "<b>Foo<b>Bar", and didn't close the first 'b' tag, because
+ there's no real-world reason to bold something that's already
+ bold. When it encounters '</b></b>' it will close two more 'b'
+ tags, for a grand total of three tags closed instead of two. This
+ can throw off the rest of your document structure. The same is
+ true of a number of other tags, listed below.
+
+ It's much more common for someone to forget to close a 'b' tag
+ than to actually use nested 'b' tags, and the BeautifulSoup class
+ handles the common case. This class handles the not-co-common
+ case: where you can't believe someone wrote what they did, but
+ it's valid HTML and BeautifulSoup screwed up by assuming it
+ wouldn't be."""
+
+ I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \
+ ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong',
+ 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b',
+ 'big')
+
+ I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript',)
+
+ NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS,
+ I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS,
+ I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS)
+
+class MinimalSoup(BeautifulSoup):
+ """The MinimalSoup class is for parsing HTML that contains
+ pathologically bad markup. It makes no assumptions about tag
+ nesting, but it does know which tags are self-closing, that
+ <script> tags contain Javascript and should not be parsed, that
+ META tags may contain encoding information, and so on.
+
+ This also makes it better for subclassing than BeautifulStoneSoup
+ or BeautifulSoup."""
+
+ RESET_NESTING_TAGS = buildTagMap('noscript')
+ NESTABLE_TAGS = {}
+
+class BeautifulSOAP(BeautifulStoneSoup):
+ """This class will push a tag with only a single string child into
+ the tag's parent as an attribute. The attribute's name is the tag
+ name, and the value is the string child. An example should give
+ the flavor of the change:
+
+ <foo><bar>baz</bar></foo>
+ =>
+ <foo bar="baz"><bar>baz</bar></foo>
+
+ You can then access fooTag['bar'] instead of fooTag.barTag.string.
+
+ This is, of course, useful for scraping structures that tend to
+ use subelements instead of attributes, such as SOAP messages. Note
+ that it modifies its input, so don't print the modified version
+ out.
+
+ I'm not sure how many people really want to use this class; let me
+ know if you do. Mainly I like the name."""
+
+ def popTag(self):
+ if len(self.tagStack) > 1:
+ tag = self.tagStack[-1]
+ parent = self.tagStack[-2]
+ parent._getAttrMap()
+ if (isinstance(tag, Tag) and len(tag.contents) == 1 and
+ isinstance(tag.contents[0], NavigableString) and
+ not parent.attrMap.has_key(tag.name)):
+ parent[tag.name] = tag.contents[0]
+ BeautifulStoneSoup.popTag(self)
+
+#Enterprise class names! It has come to our attention that some people
+#think the names of the Beautiful Soup parser classes are too silly
+#and "unprofessional" for use in enterprise screen-scraping. We feel
+#your pain! For such-minded folk, the Beautiful Soup Consortium And
+#All-Night Kosher Bakery recommends renaming this file to
+#"RobustParser.py" (or, in cases of extreme enterprisiness,
+#"RobustParserBeanInterface.class") and using the following
+#enterprise-friendly class aliases:
+class RobustXMLParser(BeautifulStoneSoup):
+ pass
+class RobustHTMLParser(BeautifulSoup):
+ pass
+class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup):
+ pass
+class RobustInsanelyWackAssHTMLParser(MinimalSoup):
+ pass
+class SimplifyingSOAPParser(BeautifulSOAP):
+ pass
+
+######################################################
+#
+# Bonus library: Unicode, Dammit
+#
+# This class forces XML data into a standard format (usually to UTF-8
+# or Unicode). It is heavily based on code from Mark Pilgrim's
+# Universal Feed Parser. It does not rewrite the XML or HTML to
+# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi
+# (XML) and BeautifulSoup.start_meta (HTML).
+
+# Autodetects character encodings.
+# Download from http://chardet.feedparser.org/
+try:
+ import chardet
+# import chardet.constants
+# chardet.constants._debug = 1
+except ImportError:
+ chardet = None
+
+# cjkcodecs and iconv_codec make Python know about more character encodings.
+# Both are available from http://cjkpython.i18n.org/
+# They're built in if you use Python 2.4.
+try:
+ import cjkcodecs.aliases
+except ImportError:
+ pass
+try:
+ import iconv_codec
+except ImportError:
+ pass
+
+class UnicodeDammit:
+ """A class for detecting the encoding of a *ML document and
+ converting it to a Unicode string. If the source encoding is
+ windows-1252, can replace MS smart quotes with their HTML or XML
+ equivalents."""
+
+ # This dictionary maps commonly seen values for "charset" in HTML
+ # meta tags to the corresponding Python codec names. It only covers
+ # values that aren't in Python's aliases and can't be determined
+ # by the heuristics in find_codec.
+ CHARSET_ALIASES = { "macintosh" : "mac-roman",
+ "x-sjis" : "shift-jis" }
+
+ def __init__(self, markup, overrideEncodings=[],
+ smartQuotesTo='xml', isHTML=False):
+ self.declaredHTMLEncoding = None
+ self.markup, documentEncoding, sniffedEncoding = \
+ self._detectEncoding(markup, isHTML)
+ self.smartQuotesTo = smartQuotesTo
+ self.triedEncodings = []
+ if markup == '' or isinstance(markup, unicode):
+ self.originalEncoding = None
+ self.unicode = unicode(markup)
+ return
+
+ u = None
+ for proposedEncoding in overrideEncodings:
+ u = self._convertFrom(proposedEncoding)
+ if u: break
+ if not u:
+ for proposedEncoding in (documentEncoding, sniffedEncoding):
+ u = self._convertFrom(proposedEncoding)
+ if u: break
+
+ # If no luck and we have auto-detection library, try that:
+ if not u and chardet and not isinstance(self.markup, unicode):
+ u = self._convertFrom(chardet.detect(self.markup)['encoding'])
+
+ # As a last resort, try utf-8 and windows-1252:
+ if not u:
+ for proposed_encoding in ("utf-8", "windows-1252"):
+ u = self._convertFrom(proposed_encoding)
+ if u: break
+
+ self.unicode = u
+ if not u: self.originalEncoding = None
+
+ def _subMSChar(self, orig):
+ """Changes a MS smart quote character to an XML or HTML
+ entity."""
+ sub = self.MS_CHARS.get(orig)
+ if isinstance(sub, tuple):
+ if self.smartQuotesTo == 'xml':
+ sub = '&#x%s;' % sub[1]
+ else:
+ sub = '&%s;' % sub[0]
+ return sub
+
+ def _convertFrom(self, proposed):
+ proposed = self.find_codec(proposed)
+ if not proposed or proposed in self.triedEncodings:
+ return None
+ self.triedEncodings.append(proposed)
+ markup = self.markup
+
+ # Convert smart quotes to HTML if coming from an encoding
+ # that might have them.
+ if self.smartQuotesTo and proposed.lower() in("windows-1252",
+ "iso-8859-1",
+ "iso-8859-2"):
+ markup = re.compile("([\x80-\x9f])").sub \
+ (lambda(x): self._subMSChar(x.group(1)),
+ markup)
+
+ try:
+ # print "Trying to convert document to %s" % proposed
+ u = self._toUnicode(markup, proposed)
+ self.markup = u
+ self.originalEncoding = proposed
+ except Exception, e:
+ # print "That didn't work!"
+ # print e
+ return None
+ #print "Correct encoding: %s" % proposed
+ return self.markup
+
+ def _toUnicode(self, data, encoding):
+ '''Given a string and its encoding, decodes the string into Unicode.
+ %encoding is a string recognized by encodings.aliases'''
+
+ # strip Byte Order Mark (if present)
+ if (len(data) >= 4) and (data[:2] == '\xfe\xff') \
+ and (data[2:4] != '\x00\x00'):
+ encoding = 'utf-16be'
+ data = data[2:]
+ elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \
+ and (data[2:4] != '\x00\x00'):
+ encoding = 'utf-16le'
+ data = data[2:]
+ elif data[:3] == '\xef\xbb\xbf':
+ encoding = 'utf-8'
+ data = data[3:]
+ elif data[:4] == '\x00\x00\xfe\xff':
+ encoding = 'utf-32be'
+ data = data[4:]
+ elif data[:4] == '\xff\xfe\x00\x00':
+ encoding = 'utf-32le'
+ data = data[4:]
+ newdata = unicode(data, encoding)
+ return newdata
+
+ def _detectEncoding(self, xml_data, isHTML=False):
+ """Given a document, tries to detect its XML encoding."""
+ xml_encoding = sniffed_xml_encoding = None
+ try:
+ if xml_data[:4] == '\x4c\x6f\xa7\x94':
+ # EBCDIC
+ xml_data = self._ebcdic_to_ascii(xml_data)
+ elif xml_data[:4] == '\x00\x3c\x00\x3f':
+ # UTF-16BE
+ sniffed_xml_encoding = 'utf-16be'
+ xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
+ elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \
+ and (xml_data[2:4] != '\x00\x00'):
+ # UTF-16BE with BOM
+ sniffed_xml_encoding = 'utf-16be'
+ xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
+ elif xml_data[:4] == '\x3c\x00\x3f\x00':
+ # UTF-16LE
+ sniffed_xml_encoding = 'utf-16le'
+ xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
+ elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \
+ (xml_data[2:4] != '\x00\x00'):
+ # UTF-16LE with BOM
+ sniffed_xml_encoding = 'utf-16le'
+ xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
+ elif xml_data[:4] == '\x00\x00\x00\x3c':
+ # UTF-32BE
+ sniffed_xml_encoding = 'utf-32be'
+ xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
+ elif xml_data[:4] == '\x3c\x00\x00\x00':
+ # UTF-32LE
+ sniffed_xml_encoding = 'utf-32le'
+ xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
+ elif xml_data[:4] == '\x00\x00\xfe\xff':
+ # UTF-32BE with BOM
+ sniffed_xml_encoding = 'utf-32be'
+ xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
+ elif xml_data[:4] == '\xff\xfe\x00\x00':
+ # UTF-32LE with BOM
+ sniffed_xml_encoding = 'utf-32le'
+ xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
+ elif xml_data[:3] == '\xef\xbb\xbf':
+ # UTF-8 with BOM
+ sniffed_xml_encoding = 'utf-8'
+ xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
+ else:
+ sniffed_xml_encoding = 'ascii'
+ pass
+ except:
+ xml_encoding_match = None
+ xml_encoding_match = re.compile(
+ '^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data)
+ if not xml_encoding_match and isHTML:
+ regexp = re.compile('<\s*meta[^>]+charset=([^>]*?)[;\'">]', re.I)
+ xml_encoding_match = regexp.search(xml_data)
+ if xml_encoding_match is not None:
+ xml_encoding = xml_encoding_match.groups()[0].lower()
+ if isHTML:
+ self.declaredHTMLEncoding = xml_encoding
+ if sniffed_xml_encoding and \
+ (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode',
+ 'iso-10646-ucs-4', 'ucs-4', 'csucs4',
+ 'utf-16', 'utf-32', 'utf_16', 'utf_32',
+ 'utf16', 'u16')):
+ xml_encoding = sniffed_xml_encoding
+ return xml_data, xml_encoding, sniffed_xml_encoding
+
+
+ def find_codec(self, charset):
+ return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \
+ or (charset and self._codec(charset.replace("-", ""))) \
+ or (charset and self._codec(charset.replace("-", "_"))) \
+ or charset
+
+ def _codec(self, charset):
+ if not charset: return charset
+ codec = None
+ try:
+ codecs.lookup(charset)
+ codec = charset
+ except (LookupError, ValueError):
+ pass
+ return codec
+
+ EBCDIC_TO_ASCII_MAP = None
+ def _ebcdic_to_ascii(self, s):
+ c = self.__class__
+ if not c.EBCDIC_TO_ASCII_MAP:
+ emap = (0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15,
+ 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31,
+ 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7,
+ 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26,
+ 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33,
+ 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94,
+ 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63,
+ 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34,
+ 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200,
+ 201,202,106,107,108,109,110,111,112,113,114,203,204,205,
+ 206,207,208,209,126,115,116,117,118,119,120,121,122,210,
+ 211,212,213,214,215,216,217,218,219,220,221,222,223,224,
+ 225,226,227,228,229,230,231,123,65,66,67,68,69,70,71,72,
+ 73,232,233,234,235,236,237,125,74,75,76,77,78,79,80,81,
+ 82,238,239,240,241,242,243,92,159,83,84,85,86,87,88,89,
+ 90,244,245,246,247,248,249,48,49,50,51,52,53,54,55,56,57,
+ 250,251,252,253,254,255)
+ import string
+ c.EBCDIC_TO_ASCII_MAP = string.maketrans( \
+ ''.join(map(chr, range(256))), ''.join(map(chr, emap)))
+ return s.translate(c.EBCDIC_TO_ASCII_MAP)
+
+ MS_CHARS = { '\x80' : ('euro', '20AC'),
+ '\x81' : ' ',
+ '\x82' : ('sbquo', '201A'),
+ '\x83' : ('fnof', '192'),
+ '\x84' : ('bdquo', '201E'),
+ '\x85' : ('hellip', '2026'),
+ '\x86' : ('dagger', '2020'),
+ '\x87' : ('Dagger', '2021'),
+ '\x88' : ('circ', '2C6'),
+ '\x89' : ('permil', '2030'),
+ '\x8A' : ('Scaron', '160'),
+ '\x8B' : ('lsaquo', '2039'),
+ '\x8C' : ('OElig', '152'),
+ '\x8D' : '?',
+ '\x8E' : ('#x17D', '17D'),
+ '\x8F' : '?',
+ '\x90' : '?',
+ '\x91' : ('lsquo', '2018'),
+ '\x92' : ('rsquo', '2019'),
+ '\x93' : ('ldquo', '201C'),
+ '\x94' : ('rdquo', '201D'),
+ '\x95' : ('bull', '2022'),
+ '\x96' : ('ndash', '2013'),
+ '\x97' : ('mdash', '2014'),
+ '\x98' : ('tilde', '2DC'),
+ '\x99' : ('trade', '2122'),
+ '\x9a' : ('scaron', '161'),
+ '\x9b' : ('rsaquo', '203A'),
+ '\x9c' : ('oelig', '153'),
+ '\x9d' : '?',
+ '\x9e' : ('#x17E', '17E'),
+ '\x9f' : ('Yuml', ''),}
+
+#######################################################################
+
+
+#By default, act as an HTML pretty-printer.
+if __name__ == '__main__':
+ import sys
+ soup = BeautifulSoup(sys.stdin)
+ print soup.prettify()
diff --git a/module/lib/Getch.py b/pyload/lib/Getch.py
index 22b7ea7f8..22b7ea7f8 100644
--- a/module/lib/Getch.py
+++ b/pyload/lib/Getch.py
diff --git a/module/lib/MultipartPostHandler.py b/pyload/lib/MultipartPostHandler.py
index 94aee0193..94aee0193 100644
--- a/module/lib/MultipartPostHandler.py
+++ b/pyload/lib/MultipartPostHandler.py
diff --git a/module/lib/SafeEval.py b/pyload/lib/SafeEval.py
index 8fc57f261..8fc57f261 100644
--- a/module/lib/SafeEval.py
+++ b/pyload/lib/SafeEval.py
diff --git a/module/lib/__init__.py b/pyload/lib/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/lib/__init__.py
+++ b/pyload/lib/__init__.py
diff --git a/pyload/lib/beaker/__init__.py b/pyload/lib/beaker/__init__.py
new file mode 100644
index 000000000..d07785c52
--- /dev/null
+++ b/pyload/lib/beaker/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.6.4'
diff --git a/pyload/lib/beaker/cache.py b/pyload/lib/beaker/cache.py
new file mode 100644
index 000000000..0ae96e020
--- /dev/null
+++ b/pyload/lib/beaker/cache.py
@@ -0,0 +1,589 @@
+"""This package contains the "front end" classes and functions
+for Beaker caching.
+
+Included are the :class:`.Cache` and :class:`.CacheManager` classes,
+as well as the function decorators :func:`.region_decorate`,
+:func:`.region_invalidate`.
+
+"""
+import warnings
+
+import beaker.container as container
+import beaker.util as util
+from beaker.crypto.util import sha1
+from beaker.exceptions import BeakerException, InvalidCacheBackendError
+from beaker.synchronization import _threading
+
+import beaker.ext.memcached as memcached
+import beaker.ext.database as database
+import beaker.ext.sqla as sqla
+import beaker.ext.google as google
+
+# Initialize the cache region dict
+cache_regions = {}
+"""Dictionary of 'region' arguments.
+
+A "region" is a string name that refers to a series of cache
+configuration arguments. An application may have multiple
+"regions" - one which stores things in a memory cache, one
+which writes data to files, etc.
+
+The dictionary stores string key names mapped to dictionaries
+of configuration arguments. Example::
+
+ from beaker.cache import cache_regions
+ cache_regions.update({
+ 'short_term':{
+ 'expire':'60',
+ 'type':'memory'
+ },
+ 'long_term':{
+ 'expire':'1800',
+ 'type':'dbm',
+ 'data_dir':'/tmp',
+ }
+ })
+"""
+
+
+cache_managers = {}
+
+
+class _backends(object):
+ initialized = False
+
+ def __init__(self, clsmap):
+ self._clsmap = clsmap
+ self._mutex = _threading.Lock()
+
+ def __getitem__(self, key):
+ try:
+ return self._clsmap[key]
+ except KeyError, e:
+ if not self.initialized:
+ self._mutex.acquire()
+ try:
+ if not self.initialized:
+ self._init()
+ self.initialized = True
+
+ return self._clsmap[key]
+ finally:
+ self._mutex.release()
+
+ raise e
+
+ def _init(self):
+ try:
+ import pkg_resources
+
+ # Load up the additional entry point defined backends
+ for entry_point in pkg_resources.iter_entry_points('beaker.backends'):
+ try:
+ namespace_manager = entry_point.load()
+ name = entry_point.name
+ if name in self._clsmap:
+ raise BeakerException("NamespaceManager name conflict,'%s' "
+ "already loaded" % name)
+ self._clsmap[name] = namespace_manager
+ except (InvalidCacheBackendError, SyntaxError):
+ # Ignore invalid backends
+ pass
+ except:
+ import sys
+ from pkg_resources import DistributionNotFound
+ # Warn when there's a problem loading a NamespaceManager
+ if not isinstance(sys.exc_info()[1], DistributionNotFound):
+ import traceback
+ from StringIO import StringIO
+ tb = StringIO()
+ traceback.print_exc(file=tb)
+ warnings.warn(
+ "Unable to load NamespaceManager "
+ "entry point: '%s': %s" % (
+ entry_point,
+ tb.getvalue()),
+ RuntimeWarning, 2)
+ except ImportError:
+ pass
+
+# Initialize the basic available backends
+clsmap = _backends({
+ 'memory': container.MemoryNamespaceManager,
+ 'dbm': container.DBMNamespaceManager,
+ 'file': container.FileNamespaceManager,
+ 'ext:memcached': memcached.MemcachedNamespaceManager,
+ 'ext:database': database.DatabaseNamespaceManager,
+ 'ext:sqla': sqla.SqlaNamespaceManager,
+ 'ext:google': google.GoogleNamespaceManager,
+ })
+
+
+def cache_region(region, *args):
+ """Decorate a function such that its return result is cached,
+ using a "region" to indicate the cache arguments.
+
+ Example::
+
+ from beaker.cache import cache_regions, cache_region
+
+ # configure regions
+ cache_regions.update({
+ 'short_term':{
+ 'expire':'60',
+ 'type':'memory'
+ }
+ })
+
+ @cache_region('short_term', 'load_things')
+ def load(search_term, limit, offset):
+ '''Load from a database given a search term, limit, offset.'''
+ return database.query(search_term)[offset:offset + limit]
+
+ The decorator can also be used with object methods. The ``self``
+ argument is not part of the cache key. This is based on the
+ actual string name ``self`` being in the first argument
+ position (new in 1.6)::
+
+ class MyThing(object):
+ @cache_region('short_term', 'load_things')
+ def load(self, search_term, limit, offset):
+ '''Load from a database given a search term, limit, offset.'''
+ return database.query(search_term)[offset:offset + limit]
+
+ Classmethods work as well - use ``cls`` as the name of the class argument,
+ and place the decorator around the function underneath ``@classmethod``
+ (new in 1.6)::
+
+ class MyThing(object):
+ @classmethod
+ @cache_region('short_term', 'load_things')
+ def load(cls, search_term, limit, offset):
+ '''Load from a database given a search term, limit, offset.'''
+ return database.query(search_term)[offset:offset + limit]
+
+ :param region: String name of the region corresponding to the desired
+ caching arguments, established in :attr:`.cache_regions`.
+
+ :param \*args: Optional ``str()``-compatible arguments which will uniquely
+ identify the key used by this decorated function, in addition
+ to the positional arguments passed to the function itself at call time.
+ This is recommended as it is needed to distinguish between any two functions
+ or methods that have the same name (regardless of parent class or not).
+
+ .. note::
+
+ The function being decorated must only be called with
+ positional arguments, and the arguments must support
+ being stringified with ``str()``. The concatenation
+ of the ``str()`` version of each argument, combined
+ with that of the ``*args`` sent to the decorator,
+ forms the unique cache key.
+
+ .. note::
+
+ When a method on a class is decorated, the ``self`` or ``cls``
+ argument in the first position is
+ not included in the "key" used for caching. New in 1.6.
+
+ """
+ return _cache_decorate(args, None, None, region)
+
+
+def region_invalidate(namespace, region, *args):
+ """Invalidate a cache region corresponding to a function
+ decorated with :func:`.cache_region`.
+
+ :param namespace: The namespace of the cache to invalidate. This is typically
+ a reference to the original function (as returned by the :func:`.cache_region`
+ decorator), where the :func:`.cache_region` decorator applies a "memo" to
+ the function in order to locate the string name of the namespace.
+
+ :param region: String name of the region used with the decorator. This can be
+ ``None`` in the usual case that the decorated function itself is passed,
+ not the string name of the namespace.
+
+ :param args: Stringifyable arguments that are used to locate the correct
+ key. This consists of the ``*args`` sent to the :func:`.cache_region`
+ decorator itself, plus the ``*args`` sent to the function itself
+ at runtime.
+
+ Example::
+
+ from beaker.cache import cache_regions, cache_region, region_invalidate
+
+ # configure regions
+ cache_regions.update({
+ 'short_term':{
+ 'expire':'60',
+ 'type':'memory'
+ }
+ })
+
+ @cache_region('short_term', 'load_data')
+ def load(search_term, limit, offset):
+ '''Load from a database given a search term, limit, offset.'''
+ return database.query(search_term)[offset:offset + limit]
+
+ def invalidate_search(search_term, limit, offset):
+ '''Invalidate the cached storage for a given search term, limit, offset.'''
+ region_invalidate(load, 'short_term', 'load_data', search_term, limit, offset)
+
+ Note that when a method on a class is decorated, the first argument ``cls``
+ or ``self`` is not included in the cache key. This means you don't send
+ it to :func:`.region_invalidate`::
+
+ class MyThing(object):
+ @cache_region('short_term', 'some_data')
+ def load(self, search_term, limit, offset):
+ '''Load from a database given a search term, limit, offset.'''
+ return database.query(search_term)[offset:offset + limit]
+
+ def invalidate_search(self, search_term, limit, offset):
+ '''Invalidate the cached storage for a given search term, limit, offset.'''
+ region_invalidate(self.load, 'short_term', 'some_data', search_term, limit, offset)
+
+ """
+ if callable(namespace):
+ if not region:
+ region = namespace._arg_region
+ namespace = namespace._arg_namespace
+
+ if not region:
+ raise BeakerException("Region or callable function "
+ "namespace is required")
+ else:
+ region = cache_regions[region]
+
+ cache = Cache._get_cache(namespace, region)
+ _cache_decorator_invalidate(cache, region['key_length'], args)
+
+
+class Cache(object):
+ """Front-end to the containment API implementing a data cache.
+
+ :param namespace: the namespace of this Cache
+
+ :param type: type of cache to use
+
+ :param expire: seconds to keep cached data
+
+ :param expiretime: seconds to keep cached data (legacy support)
+
+ :param starttime: time when cache was cache was
+
+ """
+ def __init__(self, namespace, type='memory', expiretime=None,
+ starttime=None, expire=None, **nsargs):
+ try:
+ cls = clsmap[type]
+ if isinstance(cls, InvalidCacheBackendError):
+ raise cls
+ except KeyError:
+ raise TypeError("Unknown cache implementation %r" % type)
+ self.namespace_name = namespace
+ self.namespace = cls(namespace, **nsargs)
+ self.expiretime = expiretime or expire
+ self.starttime = starttime
+ self.nsargs = nsargs
+
+ @classmethod
+ def _get_cache(cls, namespace, kw):
+ key = namespace + str(kw)
+ try:
+ return cache_managers[key]
+ except KeyError:
+ cache_managers[key] = cache = cls(namespace, **kw)
+ return cache
+
+ def put(self, key, value, **kw):
+ self._get_value(key, **kw).set_value(value)
+ set_value = put
+
+ def get(self, key, **kw):
+ """Retrieve a cached value from the container"""
+ return self._get_value(key, **kw).get_value()
+ get_value = get
+
+ def remove_value(self, key, **kw):
+ mycontainer = self._get_value(key, **kw)
+ mycontainer.clear_value()
+ remove = remove_value
+
+ def _get_value(self, key, **kw):
+ if isinstance(key, unicode):
+ key = key.encode('ascii', 'backslashreplace')
+
+ if 'type' in kw:
+ return self._legacy_get_value(key, **kw)
+
+ kw.setdefault('expiretime', self.expiretime)
+ kw.setdefault('starttime', self.starttime)
+
+ return container.Value(key, self.namespace, **kw)
+
+ @util.deprecated("Specifying a "
+ "'type' and other namespace configuration with cache.get()/put()/etc. "
+ "is deprecated. Specify 'type' and other namespace configuration to "
+ "cache_manager.get_cache() and/or the Cache constructor instead.")
+ def _legacy_get_value(self, key, type, **kw):
+ expiretime = kw.pop('expiretime', self.expiretime)
+ starttime = kw.pop('starttime', None)
+ createfunc = kw.pop('createfunc', None)
+ kwargs = self.nsargs.copy()
+ kwargs.update(kw)
+ c = Cache(self.namespace.namespace, type=type, **kwargs)
+ return c._get_value(key, expiretime=expiretime, createfunc=createfunc,
+ starttime=starttime)
+
+ def clear(self):
+ """Clear all the values from the namespace"""
+ self.namespace.remove()
+
+ # dict interface
+ def __getitem__(self, key):
+ return self.get(key)
+
+ def __contains__(self, key):
+ return self._get_value(key).has_current_value()
+
+ def has_key(self, key):
+ return key in self
+
+ def __delitem__(self, key):
+ self.remove_value(key)
+
+ def __setitem__(self, key, value):
+ self.put(key, value)
+
+
+class CacheManager(object):
+ def __init__(self, **kwargs):
+ """Initialize a CacheManager object with a set of options
+
+ Options should be parsed with the
+ :func:`~beaker.util.parse_cache_config_options` function to
+ ensure only valid options are used.
+
+ """
+ self.kwargs = kwargs
+ self.regions = kwargs.pop('cache_regions', {})
+
+ # Add these regions to the module global
+ cache_regions.update(self.regions)
+
+ def get_cache(self, name, **kwargs):
+ kw = self.kwargs.copy()
+ kw.update(kwargs)
+ return Cache._get_cache(name, kw)
+
+ def get_cache_region(self, name, region):
+ if region not in self.regions:
+ raise BeakerException('Cache region not configured: %s' % region)
+ kw = self.regions[region]
+ return Cache._get_cache(name, kw)
+
+ def region(self, region, *args):
+ """Decorate a function to cache itself using a cache region
+
+ The region decorator requires arguments if there are more than
+ two of the same named function, in the same module. This is
+ because the namespace used for the functions cache is based on
+ the functions name and the module.
+
+
+ Example::
+
+ # Assuming a cache object is available like:
+ cache = CacheManager(dict_of_config_options)
+
+
+ def populate_things():
+
+ @cache.region('short_term', 'some_data')
+ def load(search_term, limit, offset):
+ return load_the_data(search_term, limit, offset)
+
+ return load('rabbits', 20, 0)
+
+ .. note::
+
+ The function being decorated must only be called with
+ positional arguments.
+
+ """
+ return cache_region(region, *args)
+
+ def region_invalidate(self, namespace, region, *args):
+ """Invalidate a cache region namespace or decorated function
+
+ This function only invalidates cache spaces created with the
+ cache_region decorator.
+
+ :param namespace: Either the namespace of the result to invalidate, or the
+ cached function
+
+ :param region: The region the function was cached to. If the function was
+ cached to a single region then this argument can be None
+
+ :param args: Arguments that were used to differentiate the cached
+ function as well as the arguments passed to the decorated
+ function
+
+ Example::
+
+ # Assuming a cache object is available like:
+ cache = CacheManager(dict_of_config_options)
+
+ def populate_things(invalidate=False):
+
+ @cache.region('short_term', 'some_data')
+ def load(search_term, limit, offset):
+ return load_the_data(search_term, limit, offset)
+
+ # If the results should be invalidated first
+ if invalidate:
+ cache.region_invalidate(load, None, 'some_data',
+ 'rabbits', 20, 0)
+ return load('rabbits', 20, 0)
+
+
+ """
+ return region_invalidate(namespace, region, *args)
+
+ def cache(self, *args, **kwargs):
+ """Decorate a function to cache itself with supplied parameters
+
+ :param args: Used to make the key unique for this function, as in region()
+ above.
+
+ :param kwargs: Parameters to be passed to get_cache(), will override defaults
+
+ Example::
+
+ # Assuming a cache object is available like:
+ cache = CacheManager(dict_of_config_options)
+
+
+ def populate_things():
+
+ @cache.cache('mycache', expire=15)
+ def load(search_term, limit, offset):
+ return load_the_data(search_term, limit, offset)
+
+ return load('rabbits', 20, 0)
+
+ .. note::
+
+ The function being decorated must only be called with
+ positional arguments.
+
+ """
+ return _cache_decorate(args, self, kwargs, None)
+
+ def invalidate(self, func, *args, **kwargs):
+ """Invalidate a cache decorated function
+
+ This function only invalidates cache spaces created with the
+ cache decorator.
+
+ :param func: Decorated function to invalidate
+
+ :param args: Used to make the key unique for this function, as in region()
+ above.
+
+ :param kwargs: Parameters that were passed for use by get_cache(), note that
+ this is only required if a ``type`` was specified for the
+ function
+
+ Example::
+
+ # Assuming a cache object is available like:
+ cache = CacheManager(dict_of_config_options)
+
+
+ def populate_things(invalidate=False):
+
+ @cache.cache('mycache', type="file", expire=15)
+ def load(search_term, limit, offset):
+ return load_the_data(search_term, limit, offset)
+
+ # If the results should be invalidated first
+ if invalidate:
+ cache.invalidate(load, 'mycache', 'rabbits', 20, 0, type="file")
+ return load('rabbits', 20, 0)
+
+ """
+ namespace = func._arg_namespace
+
+ cache = self.get_cache(namespace, **kwargs)
+ if hasattr(func, '_arg_region'):
+ key_length = cache_regions[func._arg_region]['key_length']
+ else:
+ key_length = kwargs.pop('key_length', 250)
+ _cache_decorator_invalidate(cache, key_length, args)
+
+
+def _cache_decorate(deco_args, manager, kwargs, region):
+ """Return a caching function decorator."""
+
+ cache = [None]
+
+ def decorate(func):
+ namespace = util.func_namespace(func)
+ skip_self = util.has_self_arg(func)
+
+ def cached(*args):
+ if not cache[0]:
+ if region is not None:
+ if region not in cache_regions:
+ raise BeakerException(
+ 'Cache region not configured: %s' % region)
+ reg = cache_regions[region]
+ if not reg.get('enabled', True):
+ return func(*args)
+ cache[0] = Cache._get_cache(namespace, reg)
+ elif manager:
+ cache[0] = manager.get_cache(namespace, **kwargs)
+ else:
+ raise Exception("'manager + kwargs' or 'region' "
+ "argument is required")
+
+ if skip_self:
+ try:
+ cache_key = " ".join(map(str, deco_args + args[1:]))
+ except UnicodeEncodeError:
+ cache_key = " ".join(map(unicode, deco_args + args[1:]))
+ else:
+ try:
+ cache_key = " ".join(map(str, deco_args + args))
+ except UnicodeEncodeError:
+ cache_key = " ".join(map(unicode, deco_args + args))
+ if region:
+ key_length = cache_regions[region]['key_length']
+ else:
+ key_length = kwargs.pop('key_length', 250)
+ if len(cache_key) + len(namespace) > int(key_length):
+ cache_key = sha1(cache_key).hexdigest()
+
+ def go():
+ return func(*args)
+
+ return cache[0].get_value(cache_key, createfunc=go)
+ cached._arg_namespace = namespace
+ if region is not None:
+ cached._arg_region = region
+ return cached
+ return decorate
+
+
+def _cache_decorator_invalidate(cache, key_length, args):
+ """Invalidate a cache key based on function arguments."""
+
+ try:
+ cache_key = " ".join(map(str, args))
+ except UnicodeEncodeError:
+ cache_key = " ".join(map(unicode, args))
+ if len(cache_key) + len(cache.namespace_name) > key_length:
+ cache_key = sha1(cache_key).hexdigest()
+ cache.remove_value(cache_key)
diff --git a/pyload/lib/beaker/container.py b/pyload/lib/beaker/container.py
new file mode 100644
index 000000000..5a2e8e75c
--- /dev/null
+++ b/pyload/lib/beaker/container.py
@@ -0,0 +1,750 @@
+"""Container and Namespace classes"""
+
+import beaker.util as util
+if util.py3k:
+ try:
+ import dbm as anydbm
+ except:
+ import dumbdbm as anydbm
+else:
+ import anydbm
+import cPickle
+import logging
+import os
+import time
+
+from beaker.exceptions import CreationAbortedError, MissingCacheParameter
+from beaker.synchronization import _threading, file_synchronizer, \
+ mutex_synchronizer, NameLock, null_synchronizer
+
+__all__ = ['Value', 'Container', 'ContainerContext',
+ 'MemoryContainer', 'DBMContainer', 'NamespaceManager',
+ 'MemoryNamespaceManager', 'DBMNamespaceManager', 'FileContainer',
+ 'OpenResourceNamespaceManager',
+ 'FileNamespaceManager', 'CreationAbortedError']
+
+
+logger = logging.getLogger('beaker.container')
+if logger.isEnabledFor(logging.DEBUG):
+ debug = logger.debug
+else:
+ def debug(message, *args):
+ pass
+
+
+class NamespaceManager(object):
+ """Handles dictionary operations and locking for a namespace of
+ values.
+
+ :class:`.NamespaceManager` provides a dictionary-like interface,
+ implementing ``__getitem__()``, ``__setitem__()``, and
+ ``__contains__()``, as well as functions related to lock
+ acquisition.
+
+ The implementation for setting and retrieving the namespace data is
+ handled by subclasses.
+
+ NamespaceManager may be used alone, or may be accessed by
+ one or more :class:`.Value` objects. :class:`.Value` objects provide per-key
+ services like expiration times and automatic recreation of values.
+
+ Multiple NamespaceManagers created with a particular name will all
+ share access to the same underlying datasource and will attempt to
+ synchronize against a common mutex object. The scope of this
+ sharing may be within a single process or across multiple
+ processes, depending on the type of NamespaceManager used.
+
+ The NamespaceManager itself is generally threadsafe, except in the
+ case of the DBMNamespaceManager in conjunction with the gdbm dbm
+ implementation.
+
+ """
+
+ @classmethod
+ def _init_dependencies(cls):
+ """Initialize module-level dependent libraries required
+ by this :class:`.NamespaceManager`."""
+
+ def __init__(self, namespace):
+ self._init_dependencies()
+ self.namespace = namespace
+
+ def get_creation_lock(self, key):
+ """Return a locking object that is used to synchronize
+ multiple threads or processes which wish to generate a new
+ cache value.
+
+ This function is typically an instance of
+ :class:`.FileSynchronizer`, :class:`.ConditionSynchronizer`,
+ or :class:`.null_synchronizer`.
+
+ The creation lock is only used when a requested value
+ does not exist, or has been expired, and is only used
+ by the :class:`.Value` key-management object in conjunction
+ with a "createfunc" value-creation function.
+
+ """
+ raise NotImplementedError()
+
+ def do_remove(self):
+ """Implement removal of the entire contents of this
+ :class:`.NamespaceManager`.
+
+ e.g. for a file-based namespace, this would remove
+ all the files.
+
+ The front-end to this method is the
+ :meth:`.NamespaceManager.remove` method.
+
+ """
+ raise NotImplementedError()
+
+ def acquire_read_lock(self):
+ """Establish a read lock.
+
+ This operation is called before a key is read. By
+ default the function does nothing.
+
+ """
+
+ def release_read_lock(self):
+ """Release a read lock.
+
+ This operation is called after a key is read. By
+ default the function does nothing.
+
+ """
+
+ def acquire_write_lock(self, wait=True, replace=False):
+ """Establish a write lock.
+
+ This operation is called before a key is written.
+ A return value of ``True`` indicates the lock has
+ been acquired.
+
+ By default the function returns ``True`` unconditionally.
+
+ 'replace' is a hint indicating the full contents
+ of the namespace may be safely discarded. Some backends
+ may implement this (i.e. file backend won't unpickle the
+ current contents).
+
+ """
+ return True
+
+ def release_write_lock(self):
+ """Release a write lock.
+
+ This operation is called after a new value is written.
+ By default this function does nothing.
+
+ """
+
+ def has_key(self, key):
+ """Return ``True`` if the given key is present in this
+ :class:`.Namespace`.
+ """
+ return self.__contains__(key)
+
+ def __getitem__(self, key):
+ raise NotImplementedError()
+
+ def __setitem__(self, key, value):
+ raise NotImplementedError()
+
+ def set_value(self, key, value, expiretime=None):
+ """Sets a value in this :class:`.NamespaceManager`.
+
+ This is the same as ``__setitem__()``, but
+ also allows an expiration time to be passed
+ at the same time.
+
+ """
+ self[key] = value
+
+ def __contains__(self, key):
+ raise NotImplementedError()
+
+ def __delitem__(self, key):
+ raise NotImplementedError()
+
+ def keys(self):
+ """Return the list of all keys.
+
+ This method may not be supported by all
+ :class:`.NamespaceManager` implementations.
+
+ """
+ raise NotImplementedError()
+
+ def remove(self):
+ """Remove the entire contents of this
+ :class:`.NamespaceManager`.
+
+ e.g. for a file-based namespace, this would remove
+ all the files.
+ """
+ self.do_remove()
+
+
+class OpenResourceNamespaceManager(NamespaceManager):
+ """A NamespaceManager where read/write operations require opening/
+ closing of a resource which is possibly mutexed.
+
+ """
+ def __init__(self, namespace):
+ NamespaceManager.__init__(self, namespace)
+ self.access_lock = self.get_access_lock()
+ self.openers = 0
+ self.mutex = _threading.Lock()
+
+ def get_access_lock(self):
+ raise NotImplementedError()
+
+ def do_open(self, flags, replace):
+ raise NotImplementedError()
+
+ def do_close(self):
+ raise NotImplementedError()
+
+ def acquire_read_lock(self):
+ self.access_lock.acquire_read_lock()
+ try:
+ self.open('r', checkcount=True)
+ except:
+ self.access_lock.release_read_lock()
+ raise
+
+ def release_read_lock(self):
+ try:
+ self.close(checkcount=True)
+ finally:
+ self.access_lock.release_read_lock()
+
+ def acquire_write_lock(self, wait=True, replace=False):
+ r = self.access_lock.acquire_write_lock(wait)
+ try:
+ if (wait or r):
+ self.open('c', checkcount=True, replace=replace)
+ return r
+ except:
+ self.access_lock.release_write_lock()
+ raise
+
+ def release_write_lock(self):
+ try:
+ self.close(checkcount=True)
+ finally:
+ self.access_lock.release_write_lock()
+
+ def open(self, flags, checkcount=False, replace=False):
+ self.mutex.acquire()
+ try:
+ if checkcount:
+ if self.openers == 0:
+ self.do_open(flags, replace)
+ self.openers += 1
+ else:
+ self.do_open(flags, replace)
+ self.openers = 1
+ finally:
+ self.mutex.release()
+
+ def close(self, checkcount=False):
+ self.mutex.acquire()
+ try:
+ if checkcount:
+ self.openers -= 1
+ if self.openers == 0:
+ self.do_close()
+ else:
+ if self.openers > 0:
+ self.do_close()
+ self.openers = 0
+ finally:
+ self.mutex.release()
+
+ def remove(self):
+ self.access_lock.acquire_write_lock()
+ try:
+ self.close(checkcount=False)
+ self.do_remove()
+ finally:
+ self.access_lock.release_write_lock()
+
+
+class Value(object):
+ """Implements synchronization, expiration, and value-creation logic
+ for a single value stored in a :class:`.NamespaceManager`.
+
+ """
+
+ __slots__ = 'key', 'createfunc', 'expiretime', 'expire_argument', 'starttime', 'storedtime',\
+ 'namespace'
+
+ def __init__(self, key, namespace, createfunc=None, expiretime=None, starttime=None):
+ self.key = key
+ self.createfunc = createfunc
+ self.expire_argument = expiretime
+ self.starttime = starttime
+ self.storedtime = -1
+ self.namespace = namespace
+
+ def has_value(self):
+ """return true if the container has a value stored.
+
+ This is regardless of it being expired or not.
+
+ """
+ self.namespace.acquire_read_lock()
+ try:
+ return self.key in self.namespace
+ finally:
+ self.namespace.release_read_lock()
+
+ def can_have_value(self):
+ return self.has_current_value() or self.createfunc is not None
+
+ def has_current_value(self):
+ self.namespace.acquire_read_lock()
+ try:
+ has_value = self.key in self.namespace
+ if has_value:
+ try:
+ stored, expired, value = self._get_value()
+ return not self._is_expired(stored, expired)
+ except KeyError:
+ pass
+ return False
+ finally:
+ self.namespace.release_read_lock()
+
+ def _is_expired(self, storedtime, expiretime):
+ """Return true if this container's value is expired."""
+ return (
+ (
+ self.starttime is not None and
+ storedtime < self.starttime
+ )
+ or
+ (
+ expiretime is not None and
+ time.time() >= expiretime + storedtime
+ )
+ )
+
+ def get_value(self):
+ self.namespace.acquire_read_lock()
+ try:
+ has_value = self.has_value()
+ if has_value:
+ try:
+ stored, expired, value = self._get_value()
+ if not self._is_expired(stored, expired):
+ return value
+ except KeyError:
+ # guard against un-mutexed backends raising KeyError
+ has_value = False
+
+ if not self.createfunc:
+ raise KeyError(self.key)
+ finally:
+ self.namespace.release_read_lock()
+
+ has_createlock = False
+ creation_lock = self.namespace.get_creation_lock(self.key)
+ if has_value:
+ if not creation_lock.acquire(wait=False):
+ debug("get_value returning old value while new one is created")
+ return value
+ else:
+ debug("lock_creatfunc (didnt wait)")
+ has_createlock = True
+
+ if not has_createlock:
+ debug("lock_createfunc (waiting)")
+ creation_lock.acquire()
+ debug("lock_createfunc (waited)")
+
+ try:
+ # see if someone created the value already
+ self.namespace.acquire_read_lock()
+ try:
+ if self.has_value():
+ try:
+ stored, expired, value = self._get_value()
+ if not self._is_expired(stored, expired):
+ return value
+ except KeyError:
+ # guard against un-mutexed backends raising KeyError
+ pass
+ finally:
+ self.namespace.release_read_lock()
+
+ debug("get_value creating new value")
+ v = self.createfunc()
+ self.set_value(v)
+ return v
+ finally:
+ creation_lock.release()
+ debug("released create lock")
+
+ def _get_value(self):
+ value = self.namespace[self.key]
+ try:
+ stored, expired, value = value
+ except ValueError:
+ if not len(value) == 2:
+ raise
+ # Old format: upgrade
+ stored, value = value
+ expired = self.expire_argument
+ debug("get_value upgrading time %r expire time %r", stored, self.expire_argument)
+ self.namespace.release_read_lock()
+ self.set_value(value, stored)
+ self.namespace.acquire_read_lock()
+ except TypeError:
+ # occurs when the value is None. memcached
+ # may yank the rug from under us in which case
+ # that's the result
+ raise KeyError(self.key)
+ return stored, expired, value
+
+ def set_value(self, value, storedtime=None):
+ self.namespace.acquire_write_lock()
+ try:
+ if storedtime is None:
+ storedtime = time.time()
+ debug("set_value stored time %r expire time %r", storedtime, self.expire_argument)
+ self.namespace.set_value(self.key, (storedtime, self.expire_argument, value))
+ finally:
+ self.namespace.release_write_lock()
+
+ def clear_value(self):
+ self.namespace.acquire_write_lock()
+ try:
+ debug("clear_value")
+ if self.key in self.namespace:
+ try:
+ del self.namespace[self.key]
+ except KeyError:
+ # guard against un-mutexed backends raising KeyError
+ pass
+ self.storedtime = -1
+ finally:
+ self.namespace.release_write_lock()
+
+
+class AbstractDictionaryNSManager(NamespaceManager):
+ """A subclassable NamespaceManager that places data in a dictionary.
+
+ Subclasses should provide a "dictionary" attribute or descriptor
+ which returns a dict-like object. The dictionary will store keys
+ that are local to the "namespace" attribute of this manager, so
+ ensure that the dictionary will not be used by any other namespace.
+
+ e.g.::
+
+ import collections
+ cached_data = collections.defaultdict(dict)
+
+ class MyDictionaryManager(AbstractDictionaryNSManager):
+ def __init__(self, namespace):
+ AbstractDictionaryNSManager.__init__(self, namespace)
+ self.dictionary = cached_data[self.namespace]
+
+ The above stores data in a global dictionary called "cached_data",
+ which is structured as a dictionary of dictionaries, keyed
+ first on namespace name to a sub-dictionary, then on actual
+ cache key to value.
+
+ """
+
+ def get_creation_lock(self, key):
+ return NameLock(
+ identifier="memorynamespace/funclock/%s/%s" %
+ (self.namespace, key),
+ reentrant=True
+ )
+
+ def __getitem__(self, key):
+ return self.dictionary[key]
+
+ def __contains__(self, key):
+ return self.dictionary.__contains__(key)
+
+ def has_key(self, key):
+ return self.dictionary.__contains__(key)
+
+ def __setitem__(self, key, value):
+ self.dictionary[key] = value
+
+ def __delitem__(self, key):
+ del self.dictionary[key]
+
+ def do_remove(self):
+ self.dictionary.clear()
+
+ def keys(self):
+ return self.dictionary.keys()
+
+
+class MemoryNamespaceManager(AbstractDictionaryNSManager):
+ """:class:`.NamespaceManager` that uses a Python dictionary for storage."""
+
+ namespaces = util.SyncDict()
+
+ def __init__(self, namespace, **kwargs):
+ AbstractDictionaryNSManager.__init__(self, namespace)
+ self.dictionary = MemoryNamespaceManager.\
+ namespaces.get(self.namespace, dict)
+
+
+class DBMNamespaceManager(OpenResourceNamespaceManager):
+ """:class:`.NamespaceManager` that uses ``dbm`` files for storage."""
+
+ def __init__(self, namespace, dbmmodule=None, data_dir=None,
+ dbm_dir=None, lock_dir=None,
+ digest_filenames=True, **kwargs):
+ self.digest_filenames = digest_filenames
+
+ if not dbm_dir and not data_dir:
+ raise MissingCacheParameter("data_dir or dbm_dir is required")
+ elif dbm_dir:
+ self.dbm_dir = dbm_dir
+ else:
+ self.dbm_dir = data_dir + "/container_dbm"
+ util.verify_directory(self.dbm_dir)
+
+ if not lock_dir and not data_dir:
+ raise MissingCacheParameter("data_dir or lock_dir is required")
+ elif lock_dir:
+ self.lock_dir = lock_dir
+ else:
+ self.lock_dir = data_dir + "/container_dbm_lock"
+ util.verify_directory(self.lock_dir)
+
+ self.dbmmodule = dbmmodule or anydbm
+
+ self.dbm = None
+ OpenResourceNamespaceManager.__init__(self, namespace)
+
+ self.file = util.encoded_path(root=self.dbm_dir,
+ identifiers=[self.namespace],
+ extension='.dbm',
+ digest_filenames=self.digest_filenames)
+
+ debug("data file %s", self.file)
+ self._checkfile()
+
+ def get_access_lock(self):
+ return file_synchronizer(identifier=self.namespace,
+ lock_dir=self.lock_dir)
+
+ def get_creation_lock(self, key):
+ return file_synchronizer(
+ identifier="dbmcontainer/funclock/%s/%s" % (
+ self.namespace, key
+ ),
+ lock_dir=self.lock_dir
+ )
+
+ def file_exists(self, file):
+ if os.access(file, os.F_OK):
+ return True
+ else:
+ for ext in ('db', 'dat', 'pag', 'dir'):
+ if os.access(file + os.extsep + ext, os.F_OK):
+ return True
+
+ return False
+
+ def _checkfile(self):
+ if not self.file_exists(self.file):
+ g = self.dbmmodule.open(self.file, 'c')
+ g.close()
+
+ def get_filenames(self):
+ list = []
+ if os.access(self.file, os.F_OK):
+ list.append(self.file)
+
+ for ext in ('pag', 'dir', 'db', 'dat'):
+ if os.access(self.file + os.extsep + ext, os.F_OK):
+ list.append(self.file + os.extsep + ext)
+ return list
+
+ def do_open(self, flags, replace):
+ debug("opening dbm file %s", self.file)
+ try:
+ self.dbm = self.dbmmodule.open(self.file, flags)
+ except:
+ self._checkfile()
+ self.dbm = self.dbmmodule.open(self.file, flags)
+
+ def do_close(self):
+ if self.dbm is not None:
+ debug("closing dbm file %s", self.file)
+ self.dbm.close()
+
+ def do_remove(self):
+ for f in self.get_filenames():
+ os.remove(f)
+
+ def __getitem__(self, key):
+ return cPickle.loads(self.dbm[key])
+
+ def __contains__(self, key):
+ return key in self.dbm
+
+ def __setitem__(self, key, value):
+ self.dbm[key] = cPickle.dumps(value)
+
+ def __delitem__(self, key):
+ del self.dbm[key]
+
+ def keys(self):
+ return self.dbm.keys()
+
+
+class FileNamespaceManager(OpenResourceNamespaceManager):
+ """:class:`.NamespaceManager` that uses binary files for storage.
+
+ Each namespace is implemented as a single file storing a
+ dictionary of key/value pairs, serialized using the Python
+ ``pickle`` module.
+
+ """
+ def __init__(self, namespace, data_dir=None, file_dir=None, lock_dir=None,
+ digest_filenames=True, **kwargs):
+ self.digest_filenames = digest_filenames
+
+ if not file_dir and not data_dir:
+ raise MissingCacheParameter("data_dir or file_dir is required")
+ elif file_dir:
+ self.file_dir = file_dir
+ else:
+ self.file_dir = data_dir + "/container_file"
+ util.verify_directory(self.file_dir)
+
+ if not lock_dir and not data_dir:
+ raise MissingCacheParameter("data_dir or lock_dir is required")
+ elif lock_dir:
+ self.lock_dir = lock_dir
+ else:
+ self.lock_dir = data_dir + "/container_file_lock"
+ util.verify_directory(self.lock_dir)
+ OpenResourceNamespaceManager.__init__(self, namespace)
+
+ self.file = util.encoded_path(root=self.file_dir,
+ identifiers=[self.namespace],
+ extension='.cache',
+ digest_filenames=self.digest_filenames)
+ self.hash = {}
+
+ debug("data file %s", self.file)
+
+ def get_access_lock(self):
+ return file_synchronizer(identifier=self.namespace,
+ lock_dir=self.lock_dir)
+
+ def get_creation_lock(self, key):
+ return file_synchronizer(
+ identifier="dbmcontainer/funclock/%s/%s" % (
+ self.namespace, key
+ ),
+ lock_dir=self.lock_dir
+ )
+
+ def file_exists(self, file):
+ return os.access(file, os.F_OK)
+
+ def do_open(self, flags, replace):
+ if not replace and self.file_exists(self.file):
+ fh = open(self.file, 'rb')
+ self.hash = cPickle.load(fh)
+ fh.close()
+
+ self.flags = flags
+
+ def do_close(self):
+ if self.flags == 'c' or self.flags == 'w':
+ fh = open(self.file, 'wb')
+ cPickle.dump(self.hash, fh)
+ fh.close()
+
+ self.hash = {}
+ self.flags = None
+
+ def do_remove(self):
+ try:
+ os.remove(self.file)
+ except OSError:
+ # for instance, because we haven't yet used this cache,
+ # but client code has asked for a clear() operation...
+ pass
+ self.hash = {}
+
+ def __getitem__(self, key):
+ return self.hash[key]
+
+ def __contains__(self, key):
+ return key in self.hash
+
+ def __setitem__(self, key, value):
+ self.hash[key] = value
+
+ def __delitem__(self, key):
+ del self.hash[key]
+
+ def keys(self):
+ return self.hash.keys()
+
+
+#### legacy stuff to support the old "Container" class interface
+
+namespace_classes = {}
+
+ContainerContext = dict
+
+
+class ContainerMeta(type):
+ def __init__(cls, classname, bases, dict_):
+ namespace_classes[cls] = cls.namespace_class
+ return type.__init__(cls, classname, bases, dict_)
+
+ def __call__(self, key, context, namespace, createfunc=None,
+ expiretime=None, starttime=None, **kwargs):
+ if namespace in context:
+ ns = context[namespace]
+ else:
+ nscls = namespace_classes[self]
+ context[namespace] = ns = nscls(namespace, **kwargs)
+ return Value(key, ns, createfunc=createfunc,
+ expiretime=expiretime, starttime=starttime)
+
+
+class Container(object):
+ """Implements synchronization and value-creation logic
+ for a 'value' stored in a :class:`.NamespaceManager`.
+
+ :class:`.Container` and its subclasses are deprecated. The
+ :class:`.Value` class is now used for this purpose.
+
+ """
+ __metaclass__ = ContainerMeta
+ namespace_class = NamespaceManager
+
+
+class FileContainer(Container):
+ namespace_class = FileNamespaceManager
+
+
+class MemoryContainer(Container):
+ namespace_class = MemoryNamespaceManager
+
+
+class DBMContainer(Container):
+ namespace_class = DBMNamespaceManager
+
+DbmContainer = DBMContainer
diff --git a/pyload/lib/beaker/converters.py b/pyload/lib/beaker/converters.py
new file mode 100644
index 000000000..3fb80692f
--- /dev/null
+++ b/pyload/lib/beaker/converters.py
@@ -0,0 +1,29 @@
+
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+def asbool(obj):
+ if isinstance(obj, (str, unicode)):
+ obj = obj.strip().lower()
+ if obj in ['true', 'yes', 'on', 'y', 't', '1']:
+ return True
+ elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
+ return False
+ else:
+ raise ValueError(
+ "String is not true/false: %r" % obj)
+ return bool(obj)
+
+
+def aslist(obj, sep=None, strip=True):
+ if isinstance(obj, (str, unicode)):
+ lst = obj.split(sep)
+ if strip:
+ lst = [v.strip() for v in lst]
+ return lst
+ elif isinstance(obj, (list, tuple)):
+ return obj
+ elif obj is None:
+ return []
+ else:
+ return [obj]
diff --git a/pyload/lib/beaker/crypto/__init__.py b/pyload/lib/beaker/crypto/__init__.py
new file mode 100644
index 000000000..ac13da527
--- /dev/null
+++ b/pyload/lib/beaker/crypto/__init__.py
@@ -0,0 +1,44 @@
+from warnings import warn
+
+from beaker.crypto.pbkdf2 import PBKDF2, strxor
+from beaker.crypto.util import hmac, sha1, hmac_sha1, md5
+from beaker import util
+
+keyLength = None
+
+if util.jython:
+ try:
+ from beaker.crypto.jcecrypto import getKeyLength, aesEncrypt
+ keyLength = getKeyLength()
+ except ImportError:
+ pass
+else:
+ try:
+ from beaker.crypto.nsscrypto import getKeyLength, aesEncrypt, aesDecrypt
+ keyLength = getKeyLength()
+ except ImportError:
+ try:
+ from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, aesDecrypt
+ keyLength = getKeyLength()
+ except ImportError:
+ pass
+
+if not keyLength:
+ has_aes = False
+else:
+ has_aes = True
+
+if has_aes and keyLength < 32:
+ warn('Crypto implementation only supports key lengths up to %d bits. '
+ 'Generated session cookies may be incompatible with other '
+ 'environments' % (keyLength * 8))
+
+
+def generateCryptoKeys(master_key, salt, iterations):
+ # NB: We XOR parts of the keystream into the randomly-generated parts, just
+ # in case os.urandom() isn't as random as it should be. Note that if
+ # os.urandom() returns truly random data, this will have no effect on the
+ # overall security.
+ keystream = PBKDF2(master_key, salt, iterations=iterations)
+ cipher_key = keystream.read(keyLength)
+ return cipher_key
diff --git a/pyload/lib/beaker/crypto/jcecrypto.py b/pyload/lib/beaker/crypto/jcecrypto.py
new file mode 100644
index 000000000..ce313d6e1
--- /dev/null
+++ b/pyload/lib/beaker/crypto/jcecrypto.py
@@ -0,0 +1,32 @@
+"""
+Encryption module that uses the Java Cryptography Extensions (JCE).
+
+Note that in default installations of the Java Runtime Environment, the
+maximum key length is limited to 128 bits due to US export
+restrictions. This makes the generated keys incompatible with the ones
+generated by pycryptopp, which has no such restrictions. To fix this,
+download the "Unlimited Strength Jurisdiction Policy Files" from Sun,
+which will allow encryption using 256 bit AES keys.
+"""
+from javax.crypto import Cipher
+from javax.crypto.spec import SecretKeySpec, IvParameterSpec
+
+import jarray
+
+# Initialization vector filled with zeros
+_iv = IvParameterSpec(jarray.zeros(16, 'b'))
+
+
+def aesEncrypt(data, key):
+ cipher = Cipher.getInstance('AES/CTR/NoPadding')
+ skeySpec = SecretKeySpec(key, 'AES')
+ cipher.init(Cipher.ENCRYPT_MODE, skeySpec, _iv)
+ return cipher.doFinal(data).tostring()
+
+# magic.
+aesDecrypt = aesEncrypt
+
+
+def getKeyLength():
+ maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding')
+ return min(maxlen, 256) / 8
diff --git a/pyload/lib/beaker/crypto/nsscrypto.py b/pyload/lib/beaker/crypto/nsscrypto.py
new file mode 100644
index 000000000..3a7797877
--- /dev/null
+++ b/pyload/lib/beaker/crypto/nsscrypto.py
@@ -0,0 +1,45 @@
+"""Encryption module that uses nsscrypto"""
+import nss.nss
+
+nss.nss.nss_init_nodb()
+
+# Apparently the rest of beaker doesn't care about the particluar cipher,
+# mode and padding used.
+# NOTE: A constant IV!!! This is only secure if the KEY is never reused!!!
+_mech = nss.nss.CKM_AES_CBC_PAD
+_iv = '\0' * nss.nss.get_iv_length(_mech)
+
+def aesEncrypt(data, key):
+ slot = nss.nss.get_best_slot(_mech)
+
+ key_obj = nss.nss.import_sym_key(slot, _mech, nss.nss.PK11_OriginGenerated,
+ nss.nss.CKA_ENCRYPT, nss.nss.SecItem(key))
+
+ param = nss.nss.param_from_iv(_mech, nss.nss.SecItem(_iv))
+ ctx = nss.nss.create_context_by_sym_key(_mech, nss.nss.CKA_ENCRYPT, key_obj,
+ param)
+ l1 = ctx.cipher_op(data)
+ # Yes, DIGEST. This needs fixing in NSS, but apparently nobody (including
+ # me :( ) cares enough.
+ l2 = ctx.digest_final()
+
+ return l1 + l2
+
+def aesDecrypt(data, key):
+ slot = nss.nss.get_best_slot(_mech)
+
+ key_obj = nss.nss.import_sym_key(slot, _mech, nss.nss.PK11_OriginGenerated,
+ nss.nss.CKA_DECRYPT, nss.nss.SecItem(key))
+
+ param = nss.nss.param_from_iv(_mech, nss.nss.SecItem(_iv))
+ ctx = nss.nss.create_context_by_sym_key(_mech, nss.nss.CKA_DECRYPT, key_obj,
+ param)
+ l1 = ctx.cipher_op(data)
+ # Yes, DIGEST. This needs fixing in NSS, but apparently nobody (including
+ # me :( ) cares enough.
+ l2 = ctx.digest_final()
+
+ return l1 + l2
+
+def getKeyLength():
+ return 32
diff --git a/pyload/lib/beaker/crypto/pbkdf2.py b/pyload/lib/beaker/crypto/pbkdf2.py
new file mode 100644
index 000000000..71df22198
--- /dev/null
+++ b/pyload/lib/beaker/crypto/pbkdf2.py
@@ -0,0 +1,347 @@
+#!/usr/bin/python
+# -*- coding: ascii -*-
+###########################################################################
+# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
+#
+# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# All rights reserved.
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and that
+# both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Country of origin: Canada
+#
+###########################################################################
+# Sample PBKDF2 usage:
+# from Crypto.Cipher import AES
+# from PBKDF2 import PBKDF2
+# import os
+#
+# salt = os.urandom(8) # 64-bit salt
+# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
+# iv = os.urandom(16) # 128-bit IV
+# cipher = AES.new(key, AES.MODE_CBC, iv)
+# ...
+#
+# Sample crypt() usage:
+# from PBKDF2 import crypt
+# pwhash = crypt("secret")
+# alleged_pw = raw_input("Enter password: ")
+# if pwhash == crypt(alleged_pw, pwhash):
+# print "Password good"
+# else:
+# print "Invalid password"
+#
+###########################################################################
+# History:
+#
+# 2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# - Initial Release (v1.0)
+#
+# 2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
+# - Bugfix release (v1.1)
+# - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
+# function in the previous release) silently truncates all keys to 64
+# bytes. The way it was used in the previous release, this would only be
+# problem if the pseudorandom function that returned values larger than
+# 64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
+# anything that silently reduces the security margin from what is
+# expected.
+#
+###########################################################################
+
+__version__ = "1.1"
+
+from struct import pack
+from binascii import b2a_hex
+from random import randint
+
+from base64 import b64encode
+
+from beaker.crypto.util import hmac as HMAC, hmac_sha1 as SHA1
+
+
+def strxor(a, b):
+ return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
+
+
+class PBKDF2(object):
+ """PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
+
+ This implementation takes a passphrase and a salt (and optionally an
+ iteration count, a digest module, and a MAC module) and provides a
+ file-like object from which an arbitrarily-sized key can be read.
+
+ If the passphrase and/or salt are unicode objects, they are encoded as
+ UTF-8 before they are processed.
+
+ The idea behind PBKDF2 is to derive a cryptographic key from a
+ passphrase and a salt.
+
+ PBKDF2 may also be used as a strong salted password hash. The
+ 'crypt' function is provided for that purpose.
+
+ Remember: Keys generated using PBKDF2 are only as strong as the
+ passphrases they are derived from.
+ """
+
+ def __init__(self, passphrase, salt, iterations=1000,
+ digestmodule=SHA1, macmodule=HMAC):
+ if not callable(macmodule):
+ macmodule = macmodule.new
+ self.__macmodule = macmodule
+ self.__digestmodule = digestmodule
+ self._setup(passphrase, salt, iterations, self._pseudorandom)
+
+ def _pseudorandom(self, key, msg):
+ """Pseudorandom function. e.g. HMAC-SHA1"""
+ return self.__macmodule(key=key, msg=msg,
+ digestmod=self.__digestmodule).digest()
+
+ def read(self, bytes):
+ """Read the specified number of key bytes."""
+ if self.closed:
+ raise ValueError("file-like object is closed")
+
+ size = len(self.__buf)
+ blocks = [self.__buf]
+ i = self.__blockNum
+ while size < bytes:
+ i += 1
+ if i > 0xffffffff:
+ # We could return "" here, but
+ raise OverflowError("derived key too long")
+ block = self.__f(i)
+ blocks.append(block)
+ size += len(block)
+ buf = "".join(blocks)
+ retval = buf[:bytes]
+ self.__buf = buf[bytes:]
+ self.__blockNum = i
+ return retval
+
+ def __f(self, i):
+ # i must fit within 32 bits
+ assert (1 <= i and i <= 0xffffffff)
+ U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
+ result = U
+ for j in xrange(2, 1 + self.__iterations):
+ U = self.__prf(self.__passphrase, U)
+ result = strxor(result, U)
+ return result
+
+ def hexread(self, octets):
+ """Read the specified number of octets. Return them as hexadecimal.
+
+ Note that len(obj.hexread(n)) == 2*n.
+ """
+ return b2a_hex(self.read(octets))
+
+ def _setup(self, passphrase, salt, iterations, prf):
+ # Sanity checks:
+
+ # passphrase and salt must be str or unicode (in the latter
+ # case, we convert to UTF-8)
+ if isinstance(passphrase, unicode):
+ passphrase = passphrase.encode("UTF-8")
+ if not isinstance(passphrase, str):
+ raise TypeError("passphrase must be str or unicode")
+ if isinstance(salt, unicode):
+ salt = salt.encode("UTF-8")
+ if not isinstance(salt, str):
+ raise TypeError("salt must be str or unicode")
+
+ # iterations must be an integer >= 1
+ if not isinstance(iterations, (int, long)):
+ raise TypeError("iterations must be an integer")
+ if iterations < 1:
+ raise ValueError("iterations must be at least 1")
+
+ # prf must be callable
+ if not callable(prf):
+ raise TypeError("prf must be callable")
+
+ self.__passphrase = passphrase
+ self.__salt = salt
+ self.__iterations = iterations
+ self.__prf = prf
+ self.__blockNum = 0
+ self.__buf = ""
+ self.closed = False
+
+ def close(self):
+ """Close the stream."""
+ if not self.closed:
+ del self.__passphrase
+ del self.__salt
+ del self.__iterations
+ del self.__prf
+ del self.__blockNum
+ del self.__buf
+ self.closed = True
+
+
+def crypt(word, salt=None, iterations=None):
+ """PBKDF2-based unix crypt(3) replacement.
+
+ The number of iterations specified in the salt overrides the 'iterations'
+ parameter.
+
+ The effective hash length is 192 bits.
+ """
+
+ # Generate a (pseudo-)random salt if the user hasn't provided one.
+ if salt is None:
+ salt = _makesalt()
+
+ # salt must be a string or the us-ascii subset of unicode
+ if isinstance(salt, unicode):
+ salt = salt.encode("us-ascii")
+ if not isinstance(salt, str):
+ raise TypeError("salt must be a string")
+
+ # word must be a string or unicode (in the latter case, we convert to UTF-8)
+ if isinstance(word, unicode):
+ word = word.encode("UTF-8")
+ if not isinstance(word, str):
+ raise TypeError("word must be a string or unicode")
+
+ # Try to extract the real salt and iteration count from the salt
+ if salt.startswith("$p5k2$"):
+ (iterations, salt, dummy) = salt.split("$")[2:5]
+ if iterations == "":
+ iterations = 400
+ else:
+ converted = int(iterations, 16)
+ if iterations != "%x" % converted: # lowercase hex, minimum digits
+ raise ValueError("Invalid salt")
+ iterations = converted
+ if not (iterations >= 1):
+ raise ValueError("Invalid salt")
+
+ # Make sure the salt matches the allowed character set
+ allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
+ for ch in salt:
+ if ch not in allowed:
+ raise ValueError("Illegal character %r in salt" % (ch,))
+
+ if iterations is None or iterations == 400:
+ iterations = 400
+ salt = "$p5k2$$" + salt
+ else:
+ salt = "$p5k2$%x$%s" % (iterations, salt)
+ rawhash = PBKDF2(word, salt, iterations).read(24)
+ return salt + "$" + b64encode(rawhash, "./")
+
+# Add crypt as a static method of the PBKDF2 class
+# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
+# crypt.
+PBKDF2.crypt = staticmethod(crypt)
+
+
+def _makesalt():
+ """Return a 48-bit pseudorandom salt for crypt().
+
+ This function is not suitable for generating cryptographic secrets.
+ """
+ binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
+ return b64encode(binarysalt, "./")
+
+
+def test_pbkdf2():
+ """Module self-test"""
+ from binascii import a2b_hex
+
+ #
+ # Test vectors from RFC 3962
+ #
+
+ # Test 1
+ result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
+ expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # Test 2
+ result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
+ expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
+ "a7e52ddbc5e5142f708a31e2e62b1e13")
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # Test 3
+ result = PBKDF2("X" * 64, "pass phrase equals block size", 1200).hexread(32)
+ expected = ("139c30c0966bc32ba55fdbf212530ac9"
+ "c5ec59f1a452f5cc9ad940fea0598ed1")
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # Test 4
+ result = PBKDF2("X" * 65, "pass phrase exceeds block size", 1200).hexread(32)
+ expected = ("9ccad6d468770cd51b10e6a68721be61"
+ "1a8b4d282601db3b36be9246915ec82a")
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ #
+ # Other test vectors
+ #
+
+ # Chunked read
+ f = PBKDF2("kickstart", "workbench", 256)
+ result = f.read(17)
+ result += f.read(17)
+ result += f.read(1)
+ result += f.read(2)
+ result += f.read(3)
+ expected = PBKDF2("kickstart", "workbench", 256).read(40)
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ #
+ # crypt() test vectors
+ #
+
+ # crypt 1
+ result = crypt("cloadm", "exec")
+ expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # crypt 2
+ result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
+ expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # crypt 3
+ result = crypt("dcl", "tUsch7fU", iterations=13)
+ expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+ # crypt 4 (unicode)
+ result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
+ '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
+ expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
+ if result != expected:
+ raise RuntimeError("self-test failed")
+
+if __name__ == '__main__':
+ test_pbkdf2()
+
+# vim:set ts=4 sw=4 sts=4 expandtab:
diff --git a/pyload/lib/beaker/crypto/pycrypto.py b/pyload/lib/beaker/crypto/pycrypto.py
new file mode 100644
index 000000000..6657bff56
--- /dev/null
+++ b/pyload/lib/beaker/crypto/pycrypto.py
@@ -0,0 +1,34 @@
+"""Encryption module that uses pycryptopp or pycrypto"""
+try:
+ # Pycryptopp is preferred over Crypto because Crypto has had
+ # various periods of not being maintained, and pycryptopp uses
+ # the Crypto++ library which is generally considered the 'gold standard'
+ # of crypto implementations
+ from pycryptopp.cipher import aes
+
+ def aesEncrypt(data, key):
+ cipher = aes.AES(key)
+ return cipher.process(data)
+
+ # magic.
+ aesDecrypt = aesEncrypt
+
+except ImportError:
+ from Crypto.Cipher import AES
+ from Crypto.Util import Counter
+
+ def aesEncrypt(data, key):
+ cipher = AES.new(key, AES.MODE_CTR,
+ counter=Counter.new(128, initial_value=0))
+
+ return cipher.encrypt(data)
+
+ def aesDecrypt(data, key):
+ cipher = AES.new(key, AES.MODE_CTR,
+ counter=Counter.new(128, initial_value=0))
+ return cipher.decrypt(data)
+
+
+
+def getKeyLength():
+ return 32
diff --git a/pyload/lib/beaker/crypto/util.py b/pyload/lib/beaker/crypto/util.py
new file mode 100644
index 000000000..7f96ac856
--- /dev/null
+++ b/pyload/lib/beaker/crypto/util.py
@@ -0,0 +1,30 @@
+from warnings import warn
+from beaker import util
+
+
+try:
+ # Use PyCrypto (if available)
+ from Crypto.Hash import HMAC as hmac, SHA as hmac_sha1
+ sha1 = hmac_sha1.new
+
+except ImportError:
+
+ # PyCrypto not available. Use the Python standard library.
+ import hmac
+
+ # When using the stdlib, we have to make sure the hmac version and sha
+ # version are compatible
+ if util.py24:
+ from sha import sha as sha1
+ import sha as hmac_sha1
+ else:
+ # NOTE: We have to use the callable with hashlib (hashlib.sha1),
+ # otherwise hmac only accepts the sha module object itself
+ from hashlib import sha1
+ hmac_sha1 = sha1
+
+
+if util.py24:
+ from md5 import md5
+else:
+ from hashlib import md5
diff --git a/pyload/lib/beaker/exceptions.py b/pyload/lib/beaker/exceptions.py
new file mode 100644
index 000000000..4f81e456d
--- /dev/null
+++ b/pyload/lib/beaker/exceptions.py
@@ -0,0 +1,29 @@
+"""Beaker exception classes"""
+
+
+class BeakerException(Exception):
+ pass
+
+
+class BeakerWarning(RuntimeWarning):
+ """Issued at runtime."""
+
+
+class CreationAbortedError(Exception):
+ """Deprecated."""
+
+
+class InvalidCacheBackendError(BeakerException, ImportError):
+ pass
+
+
+class MissingCacheParameter(BeakerException):
+ pass
+
+
+class LockError(BeakerException):
+ pass
+
+
+class InvalidCryptoBackendError(BeakerException):
+ pass
diff --git a/module/lib/beaker/ext/__init__.py b/pyload/lib/beaker/ext/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/lib/beaker/ext/__init__.py
+++ b/pyload/lib/beaker/ext/__init__.py
diff --git a/pyload/lib/beaker/ext/database.py b/pyload/lib/beaker/ext/database.py
new file mode 100644
index 000000000..462fb8de4
--- /dev/null
+++ b/pyload/lib/beaker/ext/database.py
@@ -0,0 +1,174 @@
+import cPickle
+import logging
+import pickle
+from datetime import datetime
+
+from beaker.container import OpenResourceNamespaceManager, Container
+from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
+from beaker.synchronization import file_synchronizer, null_synchronizer
+from beaker.util import verify_directory, SyncDict
+
+log = logging.getLogger(__name__)
+
+sa = None
+pool = None
+types = None
+
+
+class DatabaseNamespaceManager(OpenResourceNamespaceManager):
+ metadatas = SyncDict()
+ tables = SyncDict()
+
+ @classmethod
+ def _init_dependencies(cls):
+ global sa, pool, types
+ if sa is not None:
+ return
+ try:
+ import sqlalchemy as sa
+ import sqlalchemy.pool as pool
+ from sqlalchemy import types
+ except ImportError:
+ raise InvalidCacheBackendError("Database cache backend requires "
+ "the 'sqlalchemy' library")
+
+ def __init__(self, namespace, url=None, sa_opts=None, optimistic=False,
+ table_name='beaker_cache', data_dir=None, lock_dir=None,
+ schema_name=None, **params):
+ """Creates a database namespace manager
+
+ ``url``
+ SQLAlchemy compliant db url
+ ``sa_opts``
+ A dictionary of SQLAlchemy keyword options to initialize the engine
+ with.
+ ``optimistic``
+ Use optimistic session locking, note that this will result in an
+ additional select when updating a cache value to compare version
+ numbers.
+ ``table_name``
+ The table name to use in the database for the cache.
+ ``schema_name``
+ The schema name to use in the database for the cache.
+ """
+ OpenResourceNamespaceManager.__init__(self, namespace)
+
+ if sa_opts is None:
+ sa_opts = params
+
+ if lock_dir:
+ self.lock_dir = lock_dir
+ elif data_dir:
+ self.lock_dir = data_dir + "/container_db_lock"
+ if self.lock_dir:
+ verify_directory(self.lock_dir)
+
+ # Check to see if the table's been created before
+ url = url or sa_opts['sa.url']
+ table_key = url + table_name
+
+ def make_cache():
+ # Check to see if we have a connection pool open already
+ meta_key = url + table_name
+
+ def make_meta():
+ # SQLAlchemy pops the url, this ensures it sticks around
+ # later
+ sa_opts['sa.url'] = url
+ engine = sa.engine_from_config(sa_opts, 'sa.')
+ meta = sa.MetaData()
+ meta.bind = engine
+ return meta
+ meta = DatabaseNamespaceManager.metadatas.get(meta_key, make_meta)
+ # Create the table object and cache it now
+ cache = sa.Table(table_name, meta,
+ sa.Column('id', types.Integer, primary_key=True),
+ sa.Column('namespace', types.String(255), nullable=False),
+ sa.Column('accessed', types.DateTime, nullable=False),
+ sa.Column('created', types.DateTime, nullable=False),
+ sa.Column('data', types.PickleType, nullable=False),
+ sa.UniqueConstraint('namespace'),
+ schema=schema_name if schema_name else meta.schema
+ )
+ cache.create(checkfirst=True)
+ return cache
+ self.hash = {}
+ self._is_new = False
+ self.loaded = False
+ self.cache = DatabaseNamespaceManager.tables.get(table_key, make_cache)
+
+ def get_access_lock(self):
+ return null_synchronizer()
+
+ def get_creation_lock(self, key):
+ return file_synchronizer(
+ identifier="databasecontainer/funclock/%s/%s" % (
+ self.namespace, key
+ ),
+ lock_dir=self.lock_dir)
+
+ def do_open(self, flags, replace):
+ # If we already loaded the data, don't bother loading it again
+ if self.loaded:
+ self.flags = flags
+ return
+
+ cache = self.cache
+ result = sa.select([cache.c.data],
+ cache.c.namespace == self.namespace
+ ).execute().fetchone()
+ if not result:
+ self._is_new = True
+ self.hash = {}
+ else:
+ self._is_new = False
+ try:
+ self.hash = result['data']
+ except (IOError, OSError, EOFError, cPickle.PickleError,
+ pickle.PickleError):
+ log.debug("Couln't load pickle data, creating new storage")
+ self.hash = {}
+ self._is_new = True
+ self.flags = flags
+ self.loaded = True
+
+ def do_close(self):
+ if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
+ cache = self.cache
+ if self._is_new:
+ cache.insert().execute(namespace=self.namespace, data=self.hash,
+ accessed=datetime.now(),
+ created=datetime.now())
+ self._is_new = False
+ else:
+ cache.update(cache.c.namespace == self.namespace).execute(
+ data=self.hash, accessed=datetime.now())
+ self.flags = None
+
+ def do_remove(self):
+ cache = self.cache
+ cache.delete(cache.c.namespace == self.namespace).execute()
+ self.hash = {}
+
+ # We can retain the fact that we did a load attempt, but since the
+ # file is gone this will be a new namespace should it be saved.
+ self._is_new = True
+
+ def __getitem__(self, key):
+ return self.hash[key]
+
+ def __contains__(self, key):
+ return key in self.hash
+
+ def __setitem__(self, key, value):
+ self.hash[key] = value
+
+ def __delitem__(self, key):
+ del self.hash[key]
+
+ def keys(self):
+ return self.hash.keys()
+
+
+class DatabaseContainer(Container):
+ namespace_manager = DatabaseNamespaceManager
diff --git a/pyload/lib/beaker/ext/google.py b/pyload/lib/beaker/ext/google.py
new file mode 100644
index 000000000..d0a6205f4
--- /dev/null
+++ b/pyload/lib/beaker/ext/google.py
@@ -0,0 +1,121 @@
+import cPickle
+import logging
+from datetime import datetime
+
+from beaker.container import OpenResourceNamespaceManager, Container
+from beaker.exceptions import InvalidCacheBackendError
+from beaker.synchronization import null_synchronizer
+
+log = logging.getLogger(__name__)
+
+db = None
+
+
+class GoogleNamespaceManager(OpenResourceNamespaceManager):
+ tables = {}
+
+ @classmethod
+ def _init_dependencies(cls):
+ global db
+ if db is not None:
+ return
+ try:
+ db = __import__('google.appengine.ext.db').appengine.ext.db
+ except ImportError:
+ raise InvalidCacheBackendError("Datastore cache backend requires the "
+ "'google.appengine.ext' library")
+
+ def __init__(self, namespace, table_name='beaker_cache', **params):
+ """Creates a datastore namespace manager"""
+ OpenResourceNamespaceManager.__init__(self, namespace)
+
+ def make_cache():
+ table_dict = dict(created=db.DateTimeProperty(),
+ accessed=db.DateTimeProperty(),
+ data=db.BlobProperty())
+ table = type(table_name, (db.Model,), table_dict)
+ return table
+ self.table_name = table_name
+ self.cache = GoogleNamespaceManager.tables.setdefault(table_name, make_cache())
+ self.hash = {}
+ self._is_new = False
+ self.loaded = False
+ self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
+
+ # Google wants namespaces to start with letters, change the namespace
+ # to start with a letter
+ self.namespace = 'p%s' % self.namespace
+
+ def get_access_lock(self):
+ return null_synchronizer()
+
+ def get_creation_lock(self, key):
+ # this is weird, should probably be present
+ return null_synchronizer()
+
+ def do_open(self, flags, replace):
+ # If we already loaded the data, don't bother loading it again
+ if self.loaded:
+ self.flags = flags
+ return
+
+ item = self.cache.get_by_key_name(self.namespace)
+
+ if not item:
+ self._is_new = True
+ self.hash = {}
+ else:
+ self._is_new = False
+ try:
+ self.hash = cPickle.loads(str(item.data))
+ except (IOError, OSError, EOFError, cPickle.PickleError):
+ if self.log_debug:
+ log.debug("Couln't load pickle data, creating new storage")
+ self.hash = {}
+ self._is_new = True
+ self.flags = flags
+ self.loaded = True
+
+ def do_close(self):
+ if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
+ if self._is_new:
+ item = self.cache(key_name=self.namespace)
+ item.data = cPickle.dumps(self.hash)
+ item.created = datetime.now()
+ item.accessed = datetime.now()
+ item.put()
+ self._is_new = False
+ else:
+ item = self.cache.get_by_key_name(self.namespace)
+ item.data = cPickle.dumps(self.hash)
+ item.accessed = datetime.now()
+ item.put()
+ self.flags = None
+
+ def do_remove(self):
+ item = self.cache.get_by_key_name(self.namespace)
+ item.delete()
+ self.hash = {}
+
+ # We can retain the fact that we did a load attempt, but since the
+ # file is gone this will be a new namespace should it be saved.
+ self._is_new = True
+
+ def __getitem__(self, key):
+ return self.hash[key]
+
+ def __contains__(self, key):
+ return key in self.hash
+
+ def __setitem__(self, key, value):
+ self.hash[key] = value
+
+ def __delitem__(self, key):
+ del self.hash[key]
+
+ def keys(self):
+ return self.hash.keys()
+
+
+class GoogleContainer(Container):
+ namespace_class = GoogleNamespaceManager
diff --git a/pyload/lib/beaker/ext/memcached.py b/pyload/lib/beaker/ext/memcached.py
new file mode 100644
index 000000000..94e3da3c9
--- /dev/null
+++ b/pyload/lib/beaker/ext/memcached.py
@@ -0,0 +1,203 @@
+from __future__ import with_statement
+from beaker.container import NamespaceManager, Container
+from beaker.crypto.util import sha1
+from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
+from beaker.synchronization import file_synchronizer
+from beaker.util import verify_directory, SyncDict, parse_memcached_behaviors
+import warnings
+
+MAX_KEY_LENGTH = 250
+
+_client_libs = {}
+
+
+def _load_client(name='auto'):
+ if name in _client_libs:
+ return _client_libs[name]
+
+ def _pylibmc():
+ global pylibmc
+ import pylibmc
+ return pylibmc
+
+ def _cmemcache():
+ global cmemcache
+ import cmemcache
+ warnings.warn("cmemcache is known to have serious "
+ "concurrency issues; consider using 'memcache' "
+ "or 'pylibmc'")
+ return cmemcache
+
+ def _memcache():
+ global memcache
+ import memcache
+ return memcache
+
+ def _auto():
+ for _client in (_pylibmc, _cmemcache, _memcache):
+ try:
+ return _client()
+ except ImportError:
+ pass
+ else:
+ raise InvalidCacheBackendError(
+ "Memcached cache backend requires one "
+ "of: 'pylibmc' or 'memcache' to be installed.")
+
+ clients = {
+ 'pylibmc': _pylibmc,
+ 'cmemcache': _cmemcache,
+ 'memcache': _memcache,
+ 'auto': _auto
+ }
+ _client_libs[name] = clib = clients[name]()
+ return clib
+
+
+def _is_configured_for_pylibmc(memcache_module_config, memcache_client):
+ return memcache_module_config == 'pylibmc' or \
+ memcache_client.__name__.startswith('pylibmc')
+
+
+class MemcachedNamespaceManager(NamespaceManager):
+ """Provides the :class:`.NamespaceManager` API over a memcache client library."""
+
+ clients = SyncDict()
+
+ def __new__(cls, *args, **kw):
+ memcache_module = kw.pop('memcache_module', 'auto')
+
+ memcache_client = _load_client(memcache_module)
+
+ if _is_configured_for_pylibmc(memcache_module, memcache_client):
+ return object.__new__(PyLibMCNamespaceManager)
+ else:
+ return object.__new__(MemcachedNamespaceManager)
+
+ def __init__(self, namespace, url,
+ memcache_module='auto',
+ data_dir=None, lock_dir=None,
+ **kw):
+ NamespaceManager.__init__(self, namespace)
+
+ _memcache_module = _client_libs[memcache_module]
+
+ if not url:
+ raise MissingCacheParameter("url is required")
+
+ if lock_dir:
+ self.lock_dir = lock_dir
+ elif data_dir:
+ self.lock_dir = data_dir + "/container_mcd_lock"
+ if self.lock_dir:
+ verify_directory(self.lock_dir)
+
+ # Check for pylibmc namespace manager, in which case client will be
+ # instantiated by subclass __init__, to handle behavior passing to the
+ # pylibmc client
+ if not _is_configured_for_pylibmc(memcache_module, _memcache_module):
+ self.mc = MemcachedNamespaceManager.clients.get(
+ (memcache_module, url),
+ _memcache_module.Client,
+ url.split(';'))
+
+ def get_creation_lock(self, key):
+ return file_synchronizer(
+ identifier="memcachedcontainer/funclock/%s/%s" %
+ (self.namespace, key), lock_dir=self.lock_dir)
+
+ def _format_key(self, key):
+ if not isinstance(key, str):
+ key = key.decode('ascii')
+ formated_key = (self.namespace + '_' + key).replace(' ', '\302\267')
+ if len(formated_key) > MAX_KEY_LENGTH:
+ formated_key = sha1(formated_key).hexdigest()
+ return formated_key
+
+ def __getitem__(self, key):
+ return self.mc.get(self._format_key(key))
+
+ def __contains__(self, key):
+ value = self.mc.get(self._format_key(key))
+ return value is not None
+
+ def has_key(self, key):
+ return key in self
+
+ def set_value(self, key, value, expiretime=None):
+ if expiretime:
+ self.mc.set(self._format_key(key), value, time=expiretime)
+ else:
+ self.mc.set(self._format_key(key), value)
+
+ def __setitem__(self, key, value):
+ self.set_value(key, value)
+
+ def __delitem__(self, key):
+ self.mc.delete(self._format_key(key))
+
+ def do_remove(self):
+ self.mc.flush_all()
+
+ def keys(self):
+ raise NotImplementedError(
+ "Memcache caching does not "
+ "support iteration of all cache keys")
+
+
+class PyLibMCNamespaceManager(MemcachedNamespaceManager):
+ """Provide thread-local support for pylibmc."""
+
+ def __init__(self, *arg, **kw):
+ super(PyLibMCNamespaceManager, self).__init__(*arg, **kw)
+
+ memcache_module = kw.get('memcache_module', 'auto')
+ _memcache_module = _client_libs[memcache_module]
+ protocol = kw.get('protocol', 'text')
+ username = kw.get('username', None)
+ password = kw.get('password', None)
+ url = kw.get('url')
+ behaviors = parse_memcached_behaviors(kw)
+
+ self.mc = MemcachedNamespaceManager.clients.get(
+ (memcache_module, url),
+ _memcache_module.Client,
+ servers=url.split(';'), behaviors=behaviors,
+ binary=(protocol == 'binary'), username=username,
+ password=password)
+ self.pool = pylibmc.ThreadMappedPool(self.mc)
+
+ def __getitem__(self, key):
+ with self.pool.reserve() as mc:
+ return mc.get(self._format_key(key))
+
+ def __contains__(self, key):
+ with self.pool.reserve() as mc:
+ value = mc.get(self._format_key(key))
+ return value is not None
+
+ def has_key(self, key):
+ return key in self
+
+ def set_value(self, key, value, expiretime=None):
+ with self.pool.reserve() as mc:
+ if expiretime:
+ mc.set(self._format_key(key), value, time=expiretime)
+ else:
+ mc.set(self._format_key(key), value)
+
+ def __setitem__(self, key, value):
+ self.set_value(key, value)
+
+ def __delitem__(self, key):
+ with self.pool.reserve() as mc:
+ mc.delete(self._format_key(key))
+
+ def do_remove(self):
+ with self.pool.reserve() as mc:
+ mc.flush_all()
+
+
+class MemcachedContainer(Container):
+ """Container class which invokes :class:`.MemcacheNamespaceManager`."""
+ namespace_class = MemcachedNamespaceManager
diff --git a/pyload/lib/beaker/ext/sqla.py b/pyload/lib/beaker/ext/sqla.py
new file mode 100644
index 000000000..6405c2919
--- /dev/null
+++ b/pyload/lib/beaker/ext/sqla.py
@@ -0,0 +1,136 @@
+import cPickle
+import logging
+import pickle
+from datetime import datetime
+
+from beaker.container import OpenResourceNamespaceManager, Container
+from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
+from beaker.synchronization import file_synchronizer, null_synchronizer
+from beaker.util import verify_directory, SyncDict
+
+
+log = logging.getLogger(__name__)
+
+sa = None
+
+
+class SqlaNamespaceManager(OpenResourceNamespaceManager):
+ binds = SyncDict()
+ tables = SyncDict()
+
+ @classmethod
+ def _init_dependencies(cls):
+ global sa
+ if sa is not None:
+ return
+ try:
+ import sqlalchemy as sa
+ except ImportError:
+ raise InvalidCacheBackendError("SQLAlchemy, which is required by "
+ "this backend, is not installed")
+
+ def __init__(self, namespace, bind, table, data_dir=None, lock_dir=None,
+ **kwargs):
+ """Create a namespace manager for use with a database table via
+ SQLAlchemy.
+
+ ``bind``
+ SQLAlchemy ``Engine`` or ``Connection`` object
+
+ ``table``
+ SQLAlchemy ``Table`` object in which to store namespace data.
+ This should usually be something created by ``make_cache_table``.
+ """
+ OpenResourceNamespaceManager.__init__(self, namespace)
+
+ if lock_dir:
+ self.lock_dir = lock_dir
+ elif data_dir:
+ self.lock_dir = data_dir + "/container_db_lock"
+ if self.lock_dir:
+ verify_directory(self.lock_dir)
+
+ self.bind = self.__class__.binds.get(str(bind.url), lambda: bind)
+ self.table = self.__class__.tables.get('%s:%s' % (bind.url, table.name),
+ lambda: table)
+ self.hash = {}
+ self._is_new = False
+ self.loaded = False
+
+ def get_access_lock(self):
+ return null_synchronizer()
+
+ def get_creation_lock(self, key):
+ return file_synchronizer(
+ identifier="databasecontainer/funclock/%s" % self.namespace,
+ lock_dir=self.lock_dir)
+
+ def do_open(self, flags, replace):
+ if self.loaded:
+ self.flags = flags
+ return
+ select = sa.select([self.table.c.data],
+ (self.table.c.namespace == self.namespace))
+ result = self.bind.execute(select).fetchone()
+ if not result:
+ self._is_new = True
+ self.hash = {}
+ else:
+ self._is_new = False
+ try:
+ self.hash = result['data']
+ except (IOError, OSError, EOFError, cPickle.PickleError,
+ pickle.PickleError):
+ log.debug("Couln't load pickle data, creating new storage")
+ self.hash = {}
+ self._is_new = True
+ self.flags = flags
+ self.loaded = True
+
+ def do_close(self):
+ if self.flags is not None and (self.flags == 'c' or self.flags == 'w'):
+ if self._is_new:
+ insert = self.table.insert()
+ self.bind.execute(insert, namespace=self.namespace, data=self.hash,
+ accessed=datetime.now(), created=datetime.now())
+ self._is_new = False
+ else:
+ update = self.table.update(self.table.c.namespace == self.namespace)
+ self.bind.execute(update, data=self.hash, accessed=datetime.now())
+ self.flags = None
+
+ def do_remove(self):
+ delete = self.table.delete(self.table.c.namespace == self.namespace)
+ self.bind.execute(delete)
+ self.hash = {}
+ self._is_new = True
+
+ def __getitem__(self, key):
+ return self.hash[key]
+
+ def __contains__(self, key):
+ return key in self.hash
+
+ def __setitem__(self, key, value):
+ self.hash[key] = value
+
+ def __delitem__(self, key):
+ del self.hash[key]
+
+ def keys(self):
+ return self.hash.keys()
+
+
+class SqlaContainer(Container):
+ namespace_manager = SqlaNamespaceManager
+
+
+def make_cache_table(metadata, table_name='beaker_cache', schema_name=None):
+ """Return a ``Table`` object suitable for storing cached values for the
+ namespace manager. Do not create the table."""
+ return sa.Table(table_name, metadata,
+ sa.Column('namespace', sa.String(255), primary_key=True),
+ sa.Column('accessed', sa.DateTime, nullable=False),
+ sa.Column('created', sa.DateTime, nullable=False),
+ sa.Column('data', sa.PickleType, nullable=False),
+ schema=schema_name if schema_name else metadata.schema)
diff --git a/pyload/lib/beaker/middleware.py b/pyload/lib/beaker/middleware.py
new file mode 100644
index 000000000..803398584
--- /dev/null
+++ b/pyload/lib/beaker/middleware.py
@@ -0,0 +1,168 @@
+import warnings
+
+try:
+ from paste.registry import StackedObjectProxy
+ beaker_session = StackedObjectProxy(name="Beaker Session")
+ beaker_cache = StackedObjectProxy(name="Cache Manager")
+except:
+ beaker_cache = None
+ beaker_session = None
+
+from beaker.cache import CacheManager
+from beaker.session import Session, SessionObject
+from beaker.util import coerce_cache_params, coerce_session_params, \
+ parse_cache_config_options
+
+
+class CacheMiddleware(object):
+ cache = beaker_cache
+
+ def __init__(self, app, config=None, environ_key='beaker.cache', **kwargs):
+ """Initialize the Cache Middleware
+
+ The Cache middleware will make a CacheManager instance available
+ every request under the ``environ['beaker.cache']`` key by
+ default. The location in environ can be changed by setting
+ ``environ_key``.
+
+ ``config``
+ dict All settings should be prefixed by 'cache.'. This
+ method of passing variables is intended for Paste and other
+ setups that accumulate multiple component settings in a
+ single dictionary. If config contains *no cache. prefixed
+ args*, then *all* of the config options will be used to
+ intialize the Cache objects.
+
+ ``environ_key``
+ Location where the Cache instance will keyed in the WSGI
+ environ
+
+ ``**kwargs``
+ All keyword arguments are assumed to be cache settings and
+ will override any settings found in ``config``
+
+ """
+ self.app = app
+ config = config or {}
+
+ self.options = {}
+
+ # Update the options with the parsed config
+ self.options.update(parse_cache_config_options(config))
+
+ # Add any options from kwargs, but leave out the defaults this
+ # time
+ self.options.update(
+ parse_cache_config_options(kwargs, include_defaults=False))
+
+ # Assume all keys are intended for cache if none are prefixed with
+ # 'cache.'
+ if not self.options and config:
+ self.options = config
+
+ self.options.update(kwargs)
+ self.cache_manager = CacheManager(**self.options)
+ self.environ_key = environ_key
+
+ def __call__(self, environ, start_response):
+ if environ.get('paste.registry'):
+ if environ['paste.registry'].reglist:
+ environ['paste.registry'].register(self.cache,
+ self.cache_manager)
+ environ[self.environ_key] = self.cache_manager
+ return self.app(environ, start_response)
+
+
+class SessionMiddleware(object):
+ session = beaker_session
+
+ def __init__(self, wrap_app, config=None, environ_key='beaker.session',
+ **kwargs):
+ """Initialize the Session Middleware
+
+ The Session middleware will make a lazy session instance
+ available every request under the ``environ['beaker.session']``
+ key by default. The location in environ can be changed by
+ setting ``environ_key``.
+
+ ``config``
+ dict All settings should be prefixed by 'session.'. This
+ method of passing variables is intended for Paste and other
+ setups that accumulate multiple component settings in a
+ single dictionary. If config contains *no cache. prefixed
+ args*, then *all* of the config options will be used to
+ intialize the Cache objects.
+
+ ``environ_key``
+ Location where the Session instance will keyed in the WSGI
+ environ
+
+ ``**kwargs``
+ All keyword arguments are assumed to be session settings and
+ will override any settings found in ``config``
+
+ """
+ config = config or {}
+
+ # Load up the default params
+ self.options = dict(invalidate_corrupt=True, type=None,
+ data_dir=None, key='beaker.session.id',
+ timeout=None, secret=None, log_file=None)
+
+ # Pull out any config args meant for beaker session. if there are any
+ for dct in [config, kwargs]:
+ for key, val in dct.iteritems():
+ if key.startswith('beaker.session.'):
+ self.options[key[15:]] = val
+ if key.startswith('session.'):
+ self.options[key[8:]] = val
+ if key.startswith('session_'):
+ warnings.warn('Session options should start with session. '
+ 'instead of session_.', DeprecationWarning, 2)
+ self.options[key[8:]] = val
+
+ # Coerce and validate session params
+ coerce_session_params(self.options)
+
+ # Assume all keys are intended for cache if none are prefixed with
+ # 'cache.'
+ if not self.options and config:
+ self.options = config
+
+ self.options.update(kwargs)
+ self.wrap_app = self.app = wrap_app
+ self.environ_key = environ_key
+
+ def __call__(self, environ, start_response):
+ session = SessionObject(environ, **self.options)
+ if environ.get('paste.registry'):
+ if environ['paste.registry'].reglist:
+ environ['paste.registry'].register(self.session, session)
+ environ[self.environ_key] = session
+ environ['beaker.get_session'] = self._get_session
+
+ if 'paste.testing_variables' in environ and 'webtest_varname' in self.options:
+ environ['paste.testing_variables'][self.options['webtest_varname']] = session
+
+ def session_start_response(status, headers, exc_info=None):
+ if session.accessed():
+ session.persist()
+ if session.__dict__['_headers']['set_cookie']:
+ cookie = session.__dict__['_headers']['cookie_out']
+ if cookie:
+ headers.append(('Set-cookie', cookie))
+ return start_response(status, headers, exc_info)
+ return self.wrap_app(environ, session_start_response)
+
+ def _get_session(self):
+ return Session({}, use_cookies=False, **self.options)
+
+
+def session_filter_factory(global_conf, **kwargs):
+ def filter(app):
+ return SessionMiddleware(app, global_conf, **kwargs)
+ return filter
+
+
+def session_filter_app_factory(app, global_conf, **kwargs):
+ return SessionMiddleware(app, global_conf, **kwargs)
diff --git a/pyload/lib/beaker/session.py b/pyload/lib/beaker/session.py
new file mode 100644
index 000000000..d70a670eb
--- /dev/null
+++ b/pyload/lib/beaker/session.py
@@ -0,0 +1,726 @@
+import Cookie
+import os
+from datetime import datetime, timedelta
+import time
+from beaker.crypto import hmac as HMAC, hmac_sha1 as SHA1, md5
+from beaker import crypto, util
+from beaker.cache import clsmap
+from beaker.exceptions import BeakerException, InvalidCryptoBackendError
+from base64 import b64encode, b64decode
+
+
+__all__ = ['SignedCookie', 'Session']
+
+
+try:
+ import uuid
+
+ def _session_id():
+ return uuid.uuid4().hex
+except ImportError:
+ import random
+ if hasattr(os, 'getpid'):
+ getpid = os.getpid
+ else:
+ def getpid():
+ return ''
+
+ def _session_id():
+ id_str = "%f%s%f%s" % (
+ time.time(),
+ id({}),
+ random.random(),
+ getpid()
+ )
+ if util.py3k:
+ return md5(
+ md5(
+ id_str.encode('ascii')
+ ).hexdigest().encode('ascii')
+ ).hexdigest()
+ else:
+ return md5(md5(id_str).hexdigest()).hexdigest()
+
+
+class SignedCookie(Cookie.BaseCookie):
+ """Extends python cookie to give digital signature support"""
+ def __init__(self, secret, input=None):
+ self.secret = secret.encode('UTF-8')
+ Cookie.BaseCookie.__init__(self, input)
+
+ def value_decode(self, val):
+ val = val.strip('"')
+ sig = HMAC.new(self.secret, val[40:].encode('UTF-8'), SHA1).hexdigest()
+
+ # Avoid timing attacks
+ invalid_bits = 0
+ input_sig = val[:40]
+ if len(sig) != len(input_sig):
+ return None, val
+
+ for a, b in zip(sig, input_sig):
+ invalid_bits += a != b
+
+ if invalid_bits:
+ return None, val
+ else:
+ return val[40:], val
+
+ def value_encode(self, val):
+ sig = HMAC.new(self.secret, val.encode('UTF-8'), SHA1).hexdigest()
+ return str(val), ("%s%s" % (sig, val))
+
+
+class Session(dict):
+ """Session object that uses container package for storage.
+
+ :param invalidate_corrupt: How to handle corrupt data when loading. When
+ set to True, then corrupt data will be silently
+ invalidated and a new session created,
+ otherwise invalid data will cause an exception.
+ :type invalidate_corrupt: bool
+ :param use_cookies: Whether or not cookies should be created. When set to
+ False, it is assumed the user will handle storing the
+ session on their own.
+ :type use_cookies: bool
+ :param type: What data backend type should be used to store the underlying
+ session data
+ :param key: The name the cookie should be set to.
+ :param timeout: How long session data is considered valid. This is used
+ regardless of the cookie being present or not to determine
+ whether session data is still valid.
+ :type timeout: int
+ :param cookie_expires: Expiration date for cookie
+ :param cookie_domain: Domain to use for the cookie.
+ :param cookie_path: Path to use for the cookie.
+ :param secure: Whether or not the cookie should only be sent over SSL.
+ :param httponly: Whether or not the cookie should only be accessible by
+ the browser not by JavaScript.
+ :param encrypt_key: The key to use for the local session encryption, if not
+ provided the session will not be encrypted.
+ :param validate_key: The key used to sign the local encrypted session
+
+ """
+ def __init__(self, request, id=None, invalidate_corrupt=False,
+ use_cookies=True, type=None, data_dir=None,
+ key='beaker.session.id', timeout=None, cookie_expires=True,
+ cookie_domain=None, cookie_path='/', secret=None,
+ secure=False, namespace_class=None, httponly=False,
+ encrypt_key=None, validate_key=None, **namespace_args):
+ if not type:
+ if data_dir:
+ self.type = 'file'
+ else:
+ self.type = 'memory'
+ else:
+ self.type = type
+
+ self.namespace_class = namespace_class or clsmap[self.type]
+
+ self.namespace_args = namespace_args
+
+ self.request = request
+ self.data_dir = data_dir
+ self.key = key
+
+ self.timeout = timeout
+ self.use_cookies = use_cookies
+ self.cookie_expires = cookie_expires
+
+ # Default cookie domain/path
+ self._domain = cookie_domain
+ self._path = cookie_path
+ self.was_invalidated = False
+ self.secret = secret
+ self.secure = secure
+ self.httponly = httponly
+ self.encrypt_key = encrypt_key
+ self.validate_key = validate_key
+ self.id = id
+ self.accessed_dict = {}
+ self.invalidate_corrupt = invalidate_corrupt
+
+ if self.use_cookies:
+ cookieheader = request.get('cookie', '')
+ if secret:
+ try:
+ self.cookie = SignedCookie(secret, input=cookieheader)
+ except Cookie.CookieError:
+ self.cookie = SignedCookie(secret, input=None)
+ else:
+ self.cookie = Cookie.SimpleCookie(input=cookieheader)
+
+ if not self.id and self.key in self.cookie:
+ self.id = self.cookie[self.key].value
+
+ self.is_new = self.id is None
+ if self.is_new:
+ self._create_id()
+ self['_accessed_time'] = self['_creation_time'] = time.time()
+ else:
+ try:
+ self.load()
+ except Exception, e:
+ if invalidate_corrupt:
+ util.warn(
+ "Invalidating corrupt session %s; "
+ "error was: %s. Set invalidate_corrupt=False "
+ "to propagate this exception." % (self.id, e))
+ self.invalidate()
+ else:
+ raise
+
+ def has_key(self, name):
+ return name in self
+
+ def _set_cookie_values(self, expires=None):
+ self.cookie[self.key] = self.id
+ if self._domain:
+ self.cookie[self.key]['domain'] = self._domain
+ if self.secure:
+ self.cookie[self.key]['secure'] = True
+ self._set_cookie_http_only()
+ self.cookie[self.key]['path'] = self._path
+
+ self._set_cookie_expires(expires)
+
+ def _set_cookie_expires(self, expires):
+ if expires is None:
+ if self.cookie_expires is not True:
+ if self.cookie_expires is False:
+ expires = datetime.fromtimestamp(0x7FFFFFFF)
+ elif isinstance(self.cookie_expires, timedelta):
+ expires = datetime.utcnow() + self.cookie_expires
+ elif isinstance(self.cookie_expires, datetime):
+ expires = self.cookie_expires
+ else:
+ raise ValueError("Invalid argument for cookie_expires: %s"
+ % repr(self.cookie_expires))
+ else:
+ expires = None
+ if expires is not None:
+ if not self.cookie or self.key not in self.cookie:
+ self.cookie[self.key] = self.id
+ self.cookie[self.key]['expires'] = \
+ expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
+ return expires
+
+ def _update_cookie_out(self, set_cookie=True):
+ self.request['cookie_out'] = self.cookie[self.key].output(header='')
+ self.request['set_cookie'] = set_cookie
+
+ def _set_cookie_http_only(self):
+ try:
+ if self.httponly:
+ self.cookie[self.key]['httponly'] = True
+ except Cookie.CookieError, e:
+ if 'Invalid Attribute httponly' not in str(e):
+ raise
+ util.warn('Python 2.6+ is required to use httponly')
+
+ def _create_id(self, set_new=True):
+ self.id = _session_id()
+
+ if set_new:
+ self.is_new = True
+ self.last_accessed = None
+ if self.use_cookies:
+ self._set_cookie_values()
+ sc = set_new == False
+ self._update_cookie_out(set_cookie=sc)
+
+ @property
+ def created(self):
+ return self['_creation_time']
+
+ def _set_domain(self, domain):
+ self['_domain'] = domain
+ self.cookie[self.key]['domain'] = domain
+ self._update_cookie_out()
+
+ def _get_domain(self):
+ return self._domain
+
+ domain = property(_get_domain, _set_domain)
+
+ def _set_path(self, path):
+ self['_path'] = self._path = path
+ self.cookie[self.key]['path'] = path
+ self._update_cookie_out()
+
+ def _get_path(self):
+ return self._path
+
+ path = property(_get_path, _set_path)
+
+ def _encrypt_data(self, session_data=None):
+ """Serialize, encipher, and base64 the session dict"""
+ session_data = session_data or self.copy()
+ if self.encrypt_key:
+ nonce = b64encode(os.urandom(6))[:8]
+ encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
+ self.validate_key + nonce, 1)
+ data = util.pickle.dumps(session_data, 2)
+ return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key))
+ else:
+ data = util.pickle.dumps(session_data, 2)
+ return b64encode(data)
+
+ def _decrypt_data(self, session_data):
+ """Bas64, decipher, then un-serialize the data for the session
+ dict"""
+ if self.encrypt_key:
+ try:
+ nonce = session_data[:8]
+ encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
+ self.validate_key + nonce, 1)
+ payload = b64decode(session_data[8:])
+ data = crypto.aesDecrypt(payload, encrypt_key)
+ except:
+ # As much as I hate a bare except, we get some insane errors
+ # here that get tossed when crypto fails, so we raise the
+ # 'right' exception
+ if self.invalidate_corrupt:
+ return None
+ else:
+ raise
+ try:
+ return util.pickle.loads(data)
+ except:
+ if self.invalidate_corrupt:
+ return None
+ else:
+ raise
+ else:
+ data = b64decode(session_data)
+ return util.pickle.loads(data)
+
+ def _delete_cookie(self):
+ self.request['set_cookie'] = True
+ expires = datetime.utcnow() - timedelta(365)
+ self._set_cookie_values(expires)
+ self._update_cookie_out()
+
+ def delete(self):
+ """Deletes the session from the persistent storage, and sends
+ an expired cookie out"""
+ if self.use_cookies:
+ self._delete_cookie()
+ self.clear()
+
+ def invalidate(self):
+ """Invalidates this session, creates a new session id, returns
+ to the is_new state"""
+ self.clear()
+ self.was_invalidated = True
+ self._create_id()
+ self.load()
+
+ def load(self):
+ "Loads the data from this session from persistent storage"
+ self.namespace = self.namespace_class(self.id,
+ data_dir=self.data_dir,
+ digest_filenames=False,
+ **self.namespace_args)
+ now = time.time()
+ if self.use_cookies:
+ self.request['set_cookie'] = True
+
+ self.namespace.acquire_read_lock()
+ timed_out = False
+ try:
+ self.clear()
+ try:
+ session_data = self.namespace['session']
+
+ if (session_data is not None and self.encrypt_key):
+ session_data = self._decrypt_data(session_data)
+
+ # Memcached always returns a key, its None when its not
+ # present
+ if session_data is None:
+ session_data = {
+ '_creation_time': now,
+ '_accessed_time': now
+ }
+ self.is_new = True
+ except (KeyError, TypeError):
+ session_data = {
+ '_creation_time': now,
+ '_accessed_time': now
+ }
+ self.is_new = True
+
+ if session_data is None or len(session_data) == 0:
+ session_data = {
+ '_creation_time': now,
+ '_accessed_time': now
+ }
+ self.is_new = True
+
+ if self.timeout is not None and \
+ now - session_data['_accessed_time'] > self.timeout:
+ timed_out = True
+ else:
+ # Properly set the last_accessed time, which is different
+ # than the *currently* _accessed_time
+ if self.is_new or '_accessed_time' not in session_data:
+ self.last_accessed = None
+ else:
+ self.last_accessed = session_data['_accessed_time']
+
+ # Update the current _accessed_time
+ session_data['_accessed_time'] = now
+
+ # Set the path if applicable
+ if '_path' in session_data:
+ self._path = session_data['_path']
+ self.update(session_data)
+ self.accessed_dict = session_data.copy()
+ finally:
+ self.namespace.release_read_lock()
+ if timed_out:
+ self.invalidate()
+
+ def save(self, accessed_only=False):
+ """Saves the data for this session to persistent storage
+
+ If accessed_only is True, then only the original data loaded
+ at the beginning of the request will be saved, with the updated
+ last accessed time.
+
+ """
+ # Look to see if its a new session that was only accessed
+ # Don't save it under that case
+ if accessed_only and self.is_new:
+ return None
+
+ # this session might not have a namespace yet or the session id
+ # might have been regenerated
+ if not hasattr(self, 'namespace') or self.namespace.namespace != self.id:
+ self.namespace = self.namespace_class(
+ self.id,
+ data_dir=self.data_dir,
+ digest_filenames=False,
+ **self.namespace_args)
+
+ self.namespace.acquire_write_lock(replace=True)
+ try:
+ if accessed_only:
+ data = dict(self.accessed_dict.items())
+ else:
+ data = dict(self.items())
+
+ if self.encrypt_key:
+ data = self._encrypt_data(data)
+
+ # Save the data
+ if not data and 'session' in self.namespace:
+ del self.namespace['session']
+ else:
+ self.namespace['session'] = data
+ finally:
+ self.namespace.release_write_lock()
+ if self.use_cookies and self.is_new:
+ self.request['set_cookie'] = True
+
+ def revert(self):
+ """Revert the session to its original state from its first
+ access in the request"""
+ self.clear()
+ self.update(self.accessed_dict)
+
+ def regenerate_id(self):
+ """
+ creates a new session id, retains all session data
+
+ Its a good security practice to regnerate the id after a client
+ elevates priviliges.
+
+ """
+ self._create_id(set_new=False)
+
+ # TODO: I think both these methods should be removed. They're from
+ # the original mod_python code i was ripping off but they really
+ # have no use here.
+ def lock(self):
+ """Locks this session against other processes/threads. This is
+ automatic when load/save is called.
+
+ ***use with caution*** and always with a corresponding 'unlock'
+ inside a "finally:" block, as a stray lock typically cannot be
+ unlocked without shutting down the whole application.
+
+ """
+ self.namespace.acquire_write_lock()
+
+ def unlock(self):
+ """Unlocks this session against other processes/threads. This
+ is automatic when load/save is called.
+
+ ***use with caution*** and always within a "finally:" block, as
+ a stray lock typically cannot be unlocked without shutting down
+ the whole application.
+
+ """
+ self.namespace.release_write_lock()
+
+
+class CookieSession(Session):
+ """Pure cookie-based session
+
+ Options recognized when using cookie-based sessions are slightly
+ more restricted than general sessions.
+
+ :param key: The name the cookie should be set to.
+ :param timeout: How long session data is considered valid. This is used
+ regardless of the cookie being present or not to determine
+ whether session data is still valid.
+ :type timeout: int
+ :param cookie_expires: Expiration date for cookie
+ :param cookie_domain: Domain to use for the cookie.
+ :param cookie_path: Path to use for the cookie.
+ :param secure: Whether or not the cookie should only be sent over SSL.
+ :param httponly: Whether or not the cookie should only be accessible by
+ the browser not by JavaScript.
+ :param encrypt_key: The key to use for the local session encryption, if not
+ provided the session will not be encrypted.
+ :param validate_key: The key used to sign the local encrypted session
+
+ """
+ def __init__(self, request, key='beaker.session.id', timeout=None,
+ cookie_expires=True, cookie_domain=None, cookie_path='/',
+ encrypt_key=None, validate_key=None, secure=False,
+ httponly=False, **kwargs):
+
+ if not crypto.has_aes and encrypt_key:
+ raise InvalidCryptoBackendError("No AES library is installed, can't generate "
+ "encrypted cookie-only Session.")
+
+ self.request = request
+ self.key = key
+ self.timeout = timeout
+ self.cookie_expires = cookie_expires
+ self.encrypt_key = encrypt_key
+ self.validate_key = validate_key
+ self.request['set_cookie'] = False
+ self.secure = secure
+ self.httponly = httponly
+ self._domain = cookie_domain
+ self._path = cookie_path
+
+ try:
+ cookieheader = request['cookie']
+ except KeyError:
+ cookieheader = ''
+
+ if validate_key is None:
+ raise BeakerException("No validate_key specified for Cookie only "
+ "Session.")
+
+ try:
+ self.cookie = SignedCookie(validate_key, input=cookieheader)
+ except Cookie.CookieError:
+ self.cookie = SignedCookie(validate_key, input=None)
+
+ self['_id'] = _session_id()
+ self.is_new = True
+
+ # If we have a cookie, load it
+ if self.key in self.cookie and self.cookie[self.key].value is not None:
+ self.is_new = False
+ try:
+ cookie_data = self.cookie[self.key].value
+ self.update(self._decrypt_data(cookie_data))
+ self._path = self.get('_path', '/')
+ except:
+ pass
+ if self.timeout is not None and time.time() - \
+ self['_accessed_time'] > self.timeout:
+ self.clear()
+ self.accessed_dict = self.copy()
+ self._create_cookie()
+
+ def created(self):
+ return self['_creation_time']
+ created = property(created)
+
+ def id(self):
+ return self['_id']
+ id = property(id)
+
+ def _set_domain(self, domain):
+ self['_domain'] = domain
+ self._domain = domain
+
+ def _get_domain(self):
+ return self._domain
+
+ domain = property(_get_domain, _set_domain)
+
+ def _set_path(self, path):
+ self['_path'] = self._path = path
+
+ def _get_path(self):
+ return self._path
+
+ path = property(_get_path, _set_path)
+
+ def save(self, accessed_only=False):
+ """Saves the data for this session to persistent storage"""
+ if accessed_only and self.is_new:
+ return
+ if accessed_only:
+ self.clear()
+ self.update(self.accessed_dict)
+ self._create_cookie()
+
+ def expire(self):
+ """Delete the 'expires' attribute on this Session, if any."""
+
+ self.pop('_expires', None)
+
+ def _create_cookie(self):
+ if '_creation_time' not in self:
+ self['_creation_time'] = time.time()
+ if '_id' not in self:
+ self['_id'] = _session_id()
+ self['_accessed_time'] = time.time()
+
+ val = self._encrypt_data()
+ if len(val) > 4064:
+ raise BeakerException("Cookie value is too long to store")
+
+ self.cookie[self.key] = val
+
+ if '_expires' in self:
+ expires = self['_expires']
+ else:
+ expires = None
+ expires = self._set_cookie_expires(expires)
+ if expires is not None:
+ self['_expires'] = expires
+
+ if '_domain' in self:
+ self.cookie[self.key]['domain'] = self['_domain']
+ elif self._domain:
+ self.cookie[self.key]['domain'] = self._domain
+ if self.secure:
+ self.cookie[self.key]['secure'] = True
+ self._set_cookie_http_only()
+
+ self.cookie[self.key]['path'] = self.get('_path', '/')
+
+ self.request['cookie_out'] = self.cookie[self.key].output(header='')
+ self.request['set_cookie'] = True
+
+ def delete(self):
+ """Delete the cookie, and clear the session"""
+ # Send a delete cookie request
+ self._delete_cookie()
+ self.clear()
+
+ def invalidate(self):
+ """Clear the contents and start a new session"""
+ self.clear()
+ self['_id'] = _session_id()
+
+
+class SessionObject(object):
+ """Session proxy/lazy creator
+
+ This object proxies access to the actual session object, so that in
+ the case that the session hasn't been used before, it will be
+ setup. This avoid creating and loading the session from persistent
+ storage unless its actually used during the request.
+
+ """
+ def __init__(self, environ, **params):
+ self.__dict__['_params'] = params
+ self.__dict__['_environ'] = environ
+ self.__dict__['_sess'] = None
+ self.__dict__['_headers'] = {}
+
+ def _session(self):
+ """Lazy initial creation of session object"""
+ if self.__dict__['_sess'] is None:
+ params = self.__dict__['_params']
+ environ = self.__dict__['_environ']
+ self.__dict__['_headers'] = req = {'cookie_out': None}
+ req['cookie'] = environ.get('HTTP_COOKIE')
+ if params.get('type') == 'cookie':
+ self.__dict__['_sess'] = CookieSession(req, **params)
+ else:
+ self.__dict__['_sess'] = Session(req, use_cookies=True,
+ **params)
+ return self.__dict__['_sess']
+
+ def __getattr__(self, attr):
+ return getattr(self._session(), attr)
+
+ def __setattr__(self, attr, value):
+ setattr(self._session(), attr, value)
+
+ def __delattr__(self, name):
+ self._session().__delattr__(name)
+
+ def __getitem__(self, key):
+ return self._session()[key]
+
+ def __setitem__(self, key, value):
+ self._session()[key] = value
+
+ def __delitem__(self, key):
+ self._session().__delitem__(key)
+
+ def __repr__(self):
+ return self._session().__repr__()
+
+ def __iter__(self):
+ """Only works for proxying to a dict"""
+ return iter(self._session().keys())
+
+ def __contains__(self, key):
+ return key in self._session()
+
+ def has_key(self, key):
+ return key in self._session()
+
+ def get_by_id(self, id):
+ """Loads a session given a session ID"""
+ params = self.__dict__['_params']
+ session = Session({}, use_cookies=False, id=id, **params)
+ if session.is_new:
+ return None
+ return session
+
+ def save(self):
+ self.__dict__['_dirty'] = True
+
+ def delete(self):
+ self.__dict__['_dirty'] = True
+ self._session().delete()
+
+ def persist(self):
+ """Persist the session to the storage
+
+ If its set to autosave, then the entire session will be saved
+ regardless of if save() has been called. Otherwise, just the
+ accessed time will be updated if save() was not called, or
+ the session will be saved if save() was called.
+
+ """
+ if self.__dict__['_params'].get('auto'):
+ self._session().save()
+ else:
+ if self.__dict__.get('_dirty'):
+ self._session().save()
+ else:
+ self._session().save(accessed_only=True)
+
+ def dirty(self):
+ return self.__dict__.get('_dirty', False)
+
+ def accessed(self):
+ """Returns whether or not the session has been accessed"""
+ return self.__dict__['_sess'] is not None
diff --git a/pyload/lib/beaker/synchronization.py b/pyload/lib/beaker/synchronization.py
new file mode 100644
index 000000000..f236b8cfe
--- /dev/null
+++ b/pyload/lib/beaker/synchronization.py
@@ -0,0 +1,386 @@
+"""Synchronization functions.
+
+File- and mutex-based mutual exclusion synchronizers are provided,
+as well as a name-based mutex which locks within an application
+based on a string name.
+
+"""
+
+import os
+import sys
+import tempfile
+
+try:
+ import threading as _threading
+except ImportError:
+ import dummy_threading as _threading
+
+# check for fcntl module
+try:
+ sys.getwindowsversion()
+ has_flock = False
+except:
+ try:
+ import fcntl
+ has_flock = True
+ except ImportError:
+ has_flock = False
+
+from beaker import util
+from beaker.exceptions import LockError
+
+__all__ = ["file_synchronizer", "mutex_synchronizer", "null_synchronizer",
+ "NameLock", "_threading"]
+
+
+class NameLock(object):
+ """a proxy for an RLock object that is stored in a name based
+ registry.
+
+ Multiple threads can get a reference to the same RLock based on the
+ name alone, and synchronize operations related to that name.
+
+ """
+ locks = util.WeakValuedRegistry()
+
+ class NLContainer(object):
+ def __init__(self, reentrant):
+ if reentrant:
+ self.lock = _threading.RLock()
+ else:
+ self.lock = _threading.Lock()
+
+ def __call__(self):
+ return self.lock
+
+ def __init__(self, identifier=None, reentrant=False):
+ if identifier is None:
+ self._lock = NameLock.NLContainer(reentrant)
+ else:
+ self._lock = NameLock.locks.get(identifier, NameLock.NLContainer,
+ reentrant)
+
+ def acquire(self, wait=True):
+ return self._lock().acquire(wait)
+
+ def release(self):
+ self._lock().release()
+
+
+_synchronizers = util.WeakValuedRegistry()
+
+
+def _synchronizer(identifier, cls, **kwargs):
+ return _synchronizers.sync_get((identifier, cls), cls, identifier, **kwargs)
+
+
+def file_synchronizer(identifier, **kwargs):
+ if not has_flock or 'lock_dir' not in kwargs:
+ return mutex_synchronizer(identifier)
+ else:
+ return _synchronizer(identifier, FileSynchronizer, **kwargs)
+
+
+def mutex_synchronizer(identifier, **kwargs):
+ return _synchronizer(identifier, ConditionSynchronizer, **kwargs)
+
+
+class null_synchronizer(object):
+ """A 'null' synchronizer, which provides the :class:`.SynchronizerImpl` interface
+ without any locking.
+
+ """
+ def acquire_write_lock(self, wait=True):
+ return True
+
+ def acquire_read_lock(self):
+ pass
+
+ def release_write_lock(self):
+ pass
+
+ def release_read_lock(self):
+ pass
+ acquire = acquire_write_lock
+ release = release_write_lock
+
+
+class SynchronizerImpl(object):
+ """Base class for a synchronization object that allows
+ multiple readers, single writers.
+
+ """
+ def __init__(self):
+ self._state = util.ThreadLocal()
+
+ class SyncState(object):
+ __slots__ = 'reentrantcount', 'writing', 'reading'
+
+ def __init__(self):
+ self.reentrantcount = 0
+ self.writing = False
+ self.reading = False
+
+ def state(self):
+ if not self._state.has():
+ state = SynchronizerImpl.SyncState()
+ self._state.put(state)
+ return state
+ else:
+ return self._state.get()
+ state = property(state)
+
+ def release_read_lock(self):
+ state = self.state
+
+ if state.writing:
+ raise LockError("lock is in writing state")
+ if not state.reading:
+ raise LockError("lock is not in reading state")
+
+ if state.reentrantcount == 1:
+ self.do_release_read_lock()
+ state.reading = False
+
+ state.reentrantcount -= 1
+
+ def acquire_read_lock(self, wait=True):
+ state = self.state
+
+ if state.writing:
+ raise LockError("lock is in writing state")
+
+ if state.reentrantcount == 0:
+ x = self.do_acquire_read_lock(wait)
+ if (wait or x):
+ state.reentrantcount += 1
+ state.reading = True
+ return x
+ elif state.reading:
+ state.reentrantcount += 1
+ return True
+
+ def release_write_lock(self):
+ state = self.state
+
+ if state.reading:
+ raise LockError("lock is in reading state")
+ if not state.writing:
+ raise LockError("lock is not in writing state")
+
+ if state.reentrantcount == 1:
+ self.do_release_write_lock()
+ state.writing = False
+
+ state.reentrantcount -= 1
+
+ release = release_write_lock
+
+ def acquire_write_lock(self, wait=True):
+ state = self.state
+
+ if state.reading:
+ raise LockError("lock is in reading state")
+
+ if state.reentrantcount == 0:
+ x = self.do_acquire_write_lock(wait)
+ if (wait or x):
+ state.reentrantcount += 1
+ state.writing = True
+ return x
+ elif state.writing:
+ state.reentrantcount += 1
+ return True
+
+ acquire = acquire_write_lock
+
+ def do_release_read_lock(self):
+ raise NotImplementedError()
+
+ def do_acquire_read_lock(self):
+ raise NotImplementedError()
+
+ def do_release_write_lock(self):
+ raise NotImplementedError()
+
+ def do_acquire_write_lock(self):
+ raise NotImplementedError()
+
+
+class FileSynchronizer(SynchronizerImpl):
+ """A synchronizer which locks using flock().
+
+ """
+ def __init__(self, identifier, lock_dir):
+ super(FileSynchronizer, self).__init__()
+ self._filedescriptor = util.ThreadLocal()
+
+ if lock_dir is None:
+ lock_dir = tempfile.gettempdir()
+ else:
+ lock_dir = lock_dir
+
+ self.filename = util.encoded_path(
+ lock_dir,
+ [identifier],
+ extension='.lock'
+ )
+
+ def _filedesc(self):
+ return self._filedescriptor.get()
+ _filedesc = property(_filedesc)
+
+ def _open(self, mode):
+ filedescriptor = self._filedesc
+ if filedescriptor is None:
+ filedescriptor = os.open(self.filename, mode)
+ self._filedescriptor.put(filedescriptor)
+ return filedescriptor
+
+ def do_acquire_read_lock(self, wait):
+ filedescriptor = self._open(os.O_CREAT | os.O_RDONLY)
+ if not wait:
+ try:
+ fcntl.flock(filedescriptor, fcntl.LOCK_SH | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ os.close(filedescriptor)
+ self._filedescriptor.remove()
+ return False
+ else:
+ fcntl.flock(filedescriptor, fcntl.LOCK_SH)
+ return True
+
+ def do_acquire_write_lock(self, wait):
+ filedescriptor = self._open(os.O_CREAT | os.O_WRONLY)
+ if not wait:
+ try:
+ fcntl.flock(filedescriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ return True
+ except IOError:
+ os.close(filedescriptor)
+ self._filedescriptor.remove()
+ return False
+ else:
+ fcntl.flock(filedescriptor, fcntl.LOCK_EX)
+ return True
+
+ def do_release_read_lock(self):
+ self._release_all_locks()
+
+ def do_release_write_lock(self):
+ self._release_all_locks()
+
+ def _release_all_locks(self):
+ filedescriptor = self._filedesc
+ if filedescriptor is not None:
+ fcntl.flock(filedescriptor, fcntl.LOCK_UN)
+ os.close(filedescriptor)
+ self._filedescriptor.remove()
+
+
+class ConditionSynchronizer(SynchronizerImpl):
+ """a synchronizer using a Condition."""
+
+ def __init__(self, identifier):
+ super(ConditionSynchronizer, self).__init__()
+
+ # counts how many asynchronous methods are executing
+ self.async = 0
+
+ # pointer to thread that is the current sync operation
+ self.current_sync_operation = None
+
+ # condition object to lock on
+ self.condition = _threading.Condition(_threading.Lock())
+
+ def do_acquire_read_lock(self, wait=True):
+ self.condition.acquire()
+ try:
+ # see if a synchronous operation is waiting to start
+ # or is already running, in which case we wait (or just
+ # give up and return)
+ if wait:
+ while self.current_sync_operation is not None:
+ self.condition.wait()
+ else:
+ if self.current_sync_operation is not None:
+ return False
+
+ self.async += 1
+ finally:
+ self.condition.release()
+
+ if not wait:
+ return True
+
+ def do_release_read_lock(self):
+ self.condition.acquire()
+ try:
+ self.async -= 1
+
+ # check if we are the last asynchronous reader thread
+ # out the door.
+ if self.async == 0:
+ # yes. so if a sync operation is waiting, notifyAll to wake
+ # it up
+ if self.current_sync_operation is not None:
+ self.condition.notifyAll()
+ elif self.async < 0:
+ raise LockError("Synchronizer error - too many "
+ "release_read_locks called")
+ finally:
+ self.condition.release()
+
+ def do_acquire_write_lock(self, wait=True):
+ self.condition.acquire()
+ try:
+ # here, we are not a synchronous reader, and after returning,
+ # assuming waiting or immediate availability, we will be.
+
+ if wait:
+ # if another sync is working, wait
+ while self.current_sync_operation is not None:
+ self.condition.wait()
+ else:
+ # if another sync is working,
+ # we dont want to wait, so forget it
+ if self.current_sync_operation is not None:
+ return False
+
+ # establish ourselves as the current sync
+ # this indicates to other read/write operations
+ # that they should wait until this is None again
+ self.current_sync_operation = _threading.currentThread()
+
+ # now wait again for asyncs to finish
+ if self.async > 0:
+ if wait:
+ # wait
+ self.condition.wait()
+ else:
+ # we dont want to wait, so forget it
+ self.current_sync_operation = None
+ return False
+ finally:
+ self.condition.release()
+
+ if not wait:
+ return True
+
+ def do_release_write_lock(self):
+ self.condition.acquire()
+ try:
+ if self.current_sync_operation is not _threading.currentThread():
+ raise LockError("Synchronizer error - current thread doesnt "
+ "have the write lock")
+
+ # reset the current sync operation so
+ # another can get it
+ self.current_sync_operation = None
+
+ # tell everyone to get ready
+ self.condition.notifyAll()
+ finally:
+ # everyone go !!
+ self.condition.release()
diff --git a/pyload/lib/beaker/util.py b/pyload/lib/beaker/util.py
new file mode 100644
index 000000000..c7002cd92
--- /dev/null
+++ b/pyload/lib/beaker/util.py
@@ -0,0 +1,462 @@
+"""Beaker utilities"""
+
+try:
+ import thread as _thread
+ import threading as _threading
+except ImportError:
+ import dummy_thread as _thread
+ import dummy_threading as _threading
+
+from datetime import datetime, timedelta
+import os
+import re
+import string
+import types
+import weakref
+import warnings
+import sys
+import inspect
+
+py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
+py24 = sys.version_info < (2, 5)
+jython = sys.platform.startswith('java')
+
+if py3k or jython:
+ import pickle
+else:
+ import cPickle as pickle
+
+from beaker.converters import asbool
+from beaker import exceptions
+from threading import local as _tlocal
+
+
+__all__ = ["ThreadLocal", "WeakValuedRegistry", "SyncDict", "encoded_path",
+ "verify_directory"]
+
+
+def function_named(fn, name):
+ """Return a function with a given __name__.
+
+ Will assign to __name__ and return the original function if possible on
+ the Python implementation, otherwise a new function will be constructed.
+
+ """
+ fn.__name__ = name
+ return fn
+
+
+def skip_if(predicate, reason=None):
+ """Skip a test if predicate is true."""
+ reason = reason or predicate.__name__
+
+ from nose import SkipTest
+
+ def decorate(fn):
+ fn_name = fn.__name__
+
+ def maybe(*args, **kw):
+ if predicate():
+ msg = "'%s' skipped: %s" % (
+ fn_name, reason)
+ raise SkipTest(msg)
+ else:
+ return fn(*args, **kw)
+ return function_named(maybe, fn_name)
+ return decorate
+
+
+def assert_raises(except_cls, callable_, *args, **kw):
+ """Assert the given exception is raised by the given function + arguments."""
+
+ try:
+ callable_(*args, **kw)
+ success = False
+ except except_cls:
+ success = True
+
+ # assert outside the block so it works for AssertionError too !
+ assert success, "Callable did not raise an exception"
+
+
+def verify_directory(dir):
+ """verifies and creates a directory. tries to
+ ignore collisions with other threads and processes."""
+
+ tries = 0
+ while not os.access(dir, os.F_OK):
+ try:
+ tries += 1
+ os.makedirs(dir)
+ except:
+ if tries > 5:
+ raise
+
+
+def has_self_arg(func):
+ """Return True if the given function has a 'self' argument."""
+ args = inspect.getargspec(func)
+ if args and args[0] and args[0][0] in ('self', 'cls'):
+ return True
+ else:
+ return False
+
+
+def warn(msg, stacklevel=3):
+ """Issue a warning."""
+ if isinstance(msg, basestring):
+ warnings.warn(msg, exceptions.BeakerWarning, stacklevel=stacklevel)
+ else:
+ warnings.warn(msg, stacklevel=stacklevel)
+
+
+def deprecated(message):
+ def wrapper(fn):
+ def deprecated_method(*args, **kargs):
+ warnings.warn(message, DeprecationWarning, 2)
+ return fn(*args, **kargs)
+ # TODO: use decorator ? functools.wrapper ?
+ deprecated_method.__name__ = fn.__name__
+ deprecated_method.__doc__ = "%s\n\n%s" % (message, fn.__doc__)
+ return deprecated_method
+ return wrapper
+
+
+class ThreadLocal(object):
+ """stores a value on a per-thread basis"""
+
+ __slots__ = '_tlocal'
+
+ def __init__(self):
+ self._tlocal = _tlocal()
+
+ def put(self, value):
+ self._tlocal.value = value
+
+ def has(self):
+ return hasattr(self._tlocal, 'value')
+
+ def get(self, default=None):
+ return getattr(self._tlocal, 'value', default)
+
+ def remove(self):
+ del self._tlocal.value
+
+
+class SyncDict(object):
+ """
+ An efficient/threadsafe singleton map algorithm, a.k.a.
+ "get a value based on this key, and create if not found or not
+ valid" paradigm:
+
+ exists && isvalid ? get : create
+
+ Designed to work with weakref dictionaries to expect items
+ to asynchronously disappear from the dictionary.
+
+ Use python 2.3.3 or greater ! a major bug was just fixed in Nov.
+ 2003 that was driving me nuts with garbage collection/weakrefs in
+ this section.
+
+ """
+ def __init__(self):
+ self.mutex = _thread.allocate_lock()
+ self.dict = {}
+
+ def get(self, key, createfunc, *args, **kwargs):
+ try:
+ if key in self.dict:
+ return self.dict[key]
+ else:
+ return self.sync_get(key, createfunc, *args, **kwargs)
+ except KeyError:
+ return self.sync_get(key, createfunc, *args, **kwargs)
+
+ def sync_get(self, key, createfunc, *args, **kwargs):
+ self.mutex.acquire()
+ try:
+ try:
+ if key in self.dict:
+ return self.dict[key]
+ else:
+ return self._create(key, createfunc, *args, **kwargs)
+ except KeyError:
+ return self._create(key, createfunc, *args, **kwargs)
+ finally:
+ self.mutex.release()
+
+ def _create(self, key, createfunc, *args, **kwargs):
+ self[key] = obj = createfunc(*args, **kwargs)
+ return obj
+
+ def has_key(self, key):
+ return key in self.dict
+
+ def __contains__(self, key):
+ return self.dict.__contains__(key)
+
+ def __getitem__(self, key):
+ return self.dict.__getitem__(key)
+
+ def __setitem__(self, key, value):
+ self.dict.__setitem__(key, value)
+
+ def __delitem__(self, key):
+ return self.dict.__delitem__(key)
+
+ def clear(self):
+ self.dict.clear()
+
+
+class WeakValuedRegistry(SyncDict):
+ def __init__(self):
+ self.mutex = _threading.RLock()
+ self.dict = weakref.WeakValueDictionary()
+
+sha1 = None
+
+
+def encoded_path(root, identifiers, extension=".enc", depth=3,
+ digest_filenames=True):
+
+ """Generate a unique file-accessible path from the given list of
+ identifiers starting at the given root directory."""
+ ident = "_".join(identifiers)
+
+ global sha1
+ if sha1 is None:
+ from beaker.crypto import sha1
+
+ if digest_filenames:
+ if py3k:
+ ident = sha1(ident.encode('utf-8')).hexdigest()
+ else:
+ ident = sha1(ident).hexdigest()
+
+ ident = os.path.basename(ident)
+
+ tokens = []
+ for d in range(1, depth):
+ tokens.append(ident[0:d])
+
+ dir = os.path.join(root, *tokens)
+ verify_directory(dir)
+
+ return os.path.join(dir, ident + extension)
+
+
+def asint(obj):
+ if isinstance(obj, int):
+ return obj
+ elif isinstance(obj, basestring) and re.match(r'^\d+$', obj):
+ return int(obj)
+ else:
+ raise Exception("This is not a proper int")
+
+
+def verify_options(opt, types, error):
+ if not isinstance(opt, types):
+ if not isinstance(types, tuple):
+ types = (types,)
+ coerced = False
+ for typ in types:
+ try:
+ if typ in (list, tuple):
+ opt = [x.strip() for x in opt.split(',')]
+ else:
+ if typ == bool:
+ typ = asbool
+ elif typ == int:
+ typ = asint
+ elif typ in (timedelta, datetime):
+ if not isinstance(opt, typ):
+ raise Exception("%s requires a timedelta type", typ)
+ opt = typ(opt)
+ coerced = True
+ except:
+ pass
+ if coerced:
+ break
+ if not coerced:
+ raise Exception(error)
+ elif isinstance(opt, str) and not opt.strip():
+ raise Exception("Empty strings are invalid for: %s" % error)
+ return opt
+
+
+def verify_rules(params, ruleset):
+ for key, types, message in ruleset:
+ if key in params:
+ params[key] = verify_options(params[key], types, message)
+ return params
+
+
+def coerce_session_params(params):
+ rules = [
+ ('data_dir', (str, types.NoneType), "data_dir must be a string "
+ "referring to a directory."),
+ ('lock_dir', (str, types.NoneType), "lock_dir must be a string referring to a "
+ "directory."),
+ ('type', (str, types.NoneType), "Session type must be a string."),
+ ('cookie_expires', (bool, datetime, timedelta, int), "Cookie expires was "
+ "not a boolean, datetime, int, or timedelta instance."),
+ ('cookie_domain', (str, types.NoneType), "Cookie domain must be a "
+ "string."),
+ ('cookie_path', (str, types.NoneType), "Cookie path must be a "
+ "string."),
+ ('id', (str,), "Session id must be a string."),
+ ('key', (str,), "Session key must be a string."),
+ ('secret', (str, types.NoneType), "Session secret must be a string."),
+ ('validate_key', (str, types.NoneType), "Session encrypt_key must be "
+ "a string."),
+ ('encrypt_key', (str, types.NoneType), "Session validate_key must be "
+ "a string."),
+ ('secure', (bool, types.NoneType), "Session secure must be a boolean."),
+ ('httponly', (bool, types.NoneType), "Session httponly must be a boolean."),
+ ('timeout', (int, types.NoneType), "Session timeout must be an "
+ "integer."),
+ ('auto', (bool, types.NoneType), "Session is created if accessed."),
+ ('webtest_varname', (str, types.NoneType), "Session varname must be "
+ "a string."),
+ ]
+ opts = verify_rules(params, rules)
+ cookie_expires = opts.get('cookie_expires')
+ if cookie_expires and isinstance(cookie_expires, int) and \
+ not isinstance(cookie_expires, bool):
+ opts['cookie_expires'] = timedelta(seconds=cookie_expires)
+ return opts
+
+
+def coerce_cache_params(params):
+ rules = [
+ ('data_dir', (str, types.NoneType), "data_dir must be a string "
+ "referring to a directory."),
+ ('lock_dir', (str, types.NoneType), "lock_dir must be a string referring to a "
+ "directory."),
+ ('type', (str,), "Cache type must be a string."),
+ ('enabled', (bool, types.NoneType), "enabled must be true/false "
+ "if present."),
+ ('expire', (int, types.NoneType), "expire must be an integer representing "
+ "how many seconds the cache is valid for"),
+ ('regions', (list, tuple, types.NoneType), "Regions must be a "
+ "comma seperated list of valid regions"),
+ ('key_length', (int, types.NoneType), "key_length must be an integer "
+ "which indicates the longest a key can be before hashing"),
+ ]
+ return verify_rules(params, rules)
+
+
+def coerce_memcached_behaviors(behaviors):
+ rules = [
+ ('cas', (bool, int), 'cas must be a boolean or an integer'),
+ ('no_block', (bool, int), 'no_block must be a boolean or an integer'),
+ ('receive_timeout', (int,), 'receive_timeout must be an integer'),
+ ('send_timeout', (int,), 'send_timeout must be an integer'),
+ ('ketama_hash', (str,), 'ketama_hash must be a string designating '
+ 'a valid hashing strategy option'),
+ ('_poll_timeout', (int,), '_poll_timeout must be an integer'),
+ ('auto_eject', (bool, int), 'auto_eject must be an integer'),
+ ('retry_timeout', (int,), 'retry_timeout must be an integer'),
+ ('_sort_hosts', (bool, int), '_sort_hosts must be an integer'),
+ ('_io_msg_watermark', (int,), '_io_msg_watermark must be an integer'),
+ ('ketama', (bool, int), 'ketama must be a boolean or an integer'),
+ ('ketama_weighted', (bool, int), 'ketama_weighted must be a boolean or '
+ 'an integer'),
+ ('_io_key_prefetch', (int, bool), '_io_key_prefetch must be a boolean '
+ 'or an integer'),
+ ('_hash_with_prefix_key', (bool, int), '_hash_with_prefix_key must be '
+ 'a boolean or an integer'),
+ ('tcp_nodelay', (bool, int), 'tcp_nodelay must be a boolean or an '
+ 'integer'),
+ ('failure_limit', (int,), 'failure_limit must be an integer'),
+ ('buffer_requests', (bool, int), 'buffer_requests must be a boolean '
+ 'or an integer'),
+ ('_socket_send_size', (int,), '_socket_send_size must be an integer'),
+ ('num_replicas', (int,), 'num_replicas must be an integer'),
+ ('remove_failed', (int,), 'remove_failed must be an integer'),
+ ('_noreply', (bool, int), '_noreply must be a boolean or an integer'),
+ ('_io_bytes_watermark', (int,), '_io_bytes_watermark must be an '
+ 'integer'),
+ ('_socket_recv_size', (int,), '_socket_recv_size must be an integer'),
+ ('distribution', (str,), 'distribution must be a string designating '
+ 'a valid distribution option'),
+ ('connect_timeout', (int,), 'connect_timeout must be an integer'),
+ ('hash', (str,), 'hash must be a string designating a valid hashing '
+ 'option'),
+ ('verify_keys', (bool, int), 'verify_keys must be a boolean or an integer'),
+ ('dead_timeout', (int,), 'dead_timeout must be an integer')
+ ]
+ return verify_rules(behaviors, rules)
+
+
+def parse_cache_config_options(config, include_defaults=True):
+ """Parse configuration options and validate for use with the
+ CacheManager"""
+
+ # Load default cache options
+ if include_defaults:
+ options = dict(type='memory', data_dir=None, expire=None,
+ log_file=None)
+ else:
+ options = {}
+ for key, val in config.iteritems():
+ if key.startswith('beaker.cache.'):
+ options[key[13:]] = val
+ if key.startswith('cache.'):
+ options[key[6:]] = val
+ coerce_cache_params(options)
+
+ # Set cache to enabled if not turned off
+ if 'enabled' not in options and include_defaults:
+ options['enabled'] = True
+
+ # Configure region dict if regions are available
+ regions = options.pop('regions', None)
+ if regions:
+ region_configs = {}
+ for region in regions:
+ if not region: # ensure region name is valid
+ continue
+ # Setup the default cache options
+ region_options = dict(data_dir=options.get('data_dir'),
+ lock_dir=options.get('lock_dir'),
+ type=options.get('type'),
+ enabled=options['enabled'],
+ expire=options.get('expire'),
+ key_length=options.get('key_length', 250))
+ region_prefix = '%s.' % region
+ region_len = len(region_prefix)
+ for key in options.keys():
+ if key.startswith(region_prefix):
+ region_options[key[region_len:]] = options.pop(key)
+ coerce_cache_params(region_options)
+ region_configs[region] = region_options
+ options['cache_regions'] = region_configs
+ return options
+
+
+def parse_memcached_behaviors(config):
+ """Parse behavior options and validate for use with pylibmc
+ client/PylibMCNamespaceManager, or potentially other memcached
+ NamespaceManagers that support behaviors"""
+ behaviors = {}
+
+ for key, val in config.iteritems():
+ if key.startswith('behavior.'):
+ behaviors[key[9:]] = val
+
+ coerce_memcached_behaviors(behaviors)
+ return behaviors
+
+
+def func_namespace(func):
+ """Generates a unique namespace for a function"""
+ kls = None
+ if hasattr(func, 'im_func'):
+ kls = func.im_class
+ func = func.im_func
+
+ if kls:
+ return '%s.%s' % (kls.__module__, kls.__name__)
+ else:
+ return '%s|%s' % (inspect.getsourcefile(func), func.__name__)
diff --git a/pyload/lib/bottle.py b/pyload/lib/bottle.py
new file mode 100644
index 000000000..bcc284c1d
--- /dev/null
+++ b/pyload/lib/bottle.py
@@ -0,0 +1,3732 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Bottle is a fast and simple micro-framework for small web applications. It
+offers request dispatching (Routes) with url parameter support, templates,
+a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
+template engines - all in a single file and with no dependencies other than the
+Python Standard Library.
+
+Homepage and documentation: http://bottlepy.org/
+
+Copyright (c) 2013, Marcel Hellkamp.
+License: MIT (see LICENSE for details)
+"""
+
+from __future__ import with_statement
+
+__author__ = 'Marcel Hellkamp'
+__version__ = '0.12.7'
+__license__ = 'MIT'
+
+# The gevent server adapter needs to patch some modules before they are imported
+# This is why we parse the commandline parameters here but handle them later
+if __name__ == '__main__':
+ from optparse import OptionParser
+ _cmd_parser = OptionParser(usage="usage: %prog [options] package.module:app")
+ _opt = _cmd_parser.add_option
+ _opt("--version", action="store_true", help="show version number.")
+ _opt("-b", "--bind", metavar="ADDRESS", help="bind socket to ADDRESS.")
+ _opt("-s", "--server", default='wsgiref', help="use SERVER as backend.")
+ _opt("-p", "--plugin", action="append", help="install additional plugin/s.")
+ _opt("--debug", action="store_true", help="start server in debug mode.")
+ _opt("--reload", action="store_true", help="auto-reload on file changes.")
+ _cmd_options, _cmd_args = _cmd_parser.parse_args()
+ if _cmd_options.server and _cmd_options.server.startswith('gevent'):
+ import gevent.monkey; gevent.monkey.patch_all()
+
+import base64, cgi, email.utils, functools, hmac, imp, itertools, mimetypes,\
+ os, re, subprocess, sys, tempfile, threading, time, warnings
+
+from datetime import date as datedate, datetime, timedelta
+from tempfile import TemporaryFile
+from traceback import format_exc, print_exc
+from inspect import getargspec
+from unicodedata import normalize
+
+
+try: from simplejson import dumps as json_dumps, loads as json_lds
+except ImportError: # pragma: no cover
+ try: from json import dumps as json_dumps, loads as json_lds
+ except ImportError:
+ try: from django.utils.simplejson import dumps as json_dumps, loads as json_lds
+ except ImportError:
+ def json_dumps(data):
+ raise ImportError("JSON support requires Python 2.6 or simplejson.")
+ json_lds = json_dumps
+
+
+
+# We now try to fix 2.5/2.6/3.1/3.2 incompatibilities.
+# It ain't pretty but it works... Sorry for the mess.
+
+py = sys.version_info
+py3k = py >= (3, 0, 0)
+py25 = py < (2, 6, 0)
+py31 = (3, 1, 0) <= py < (3, 2, 0)
+
+# Workaround for the missing "as" keyword in py3k.
+def _e(): return sys.exc_info()[1]
+
+# Workaround for the "print is a keyword/function" Python 2/3 dilemma
+# and a fallback for mod_wsgi (resticts stdout/err attribute access)
+try:
+ _stdout, _stderr = sys.stdout.write, sys.stderr.write
+except IOError:
+ _stdout = lambda x: sys.stdout.write(x)
+ _stderr = lambda x: sys.stderr.write(x)
+
+# Lots of stdlib and builtin differences.
+if py3k:
+ import http.client as httplib
+ import _thread as thread
+ from urllib.parse import urljoin, SplitResult as UrlSplitResult
+ from urllib.parse import urlencode, quote as urlquote, unquote as urlunquote
+ urlunquote = functools.partial(urlunquote, encoding='latin1')
+ from http.cookies import SimpleCookie
+ from collections import MutableMapping as DictMixin
+ import pickle
+ from io import BytesIO
+ from configparser import ConfigParser
+ basestring = str
+ unicode = str
+ json_loads = lambda s: json_lds(touni(s))
+ callable = lambda x: hasattr(x, '__call__')
+ imap = map
+ def _raise(*a): raise a[0](a[1]).with_traceback(a[2])
+else: # 2.x
+ import httplib
+ import thread
+ from urlparse import urljoin, SplitResult as UrlSplitResult
+ from urllib import urlencode, quote as urlquote, unquote as urlunquote
+ from Cookie import SimpleCookie
+ from itertools import imap
+ import cPickle as pickle
+ from StringIO import StringIO as BytesIO
+ from ConfigParser import SafeConfigParser as ConfigParser
+ if py25:
+ msg = "Python 2.5 support may be dropped in future versions of Bottle."
+ warnings.warn(msg, DeprecationWarning)
+ from UserDict import DictMixin
+ def next(it): return it.next()
+ bytes = str
+ else: # 2.6, 2.7
+ from collections import MutableMapping as DictMixin
+ unicode = unicode
+ json_loads = json_lds
+ eval(compile('def _raise(*a): raise a[0], a[1], a[2]', '<py3fix>', 'exec'))
+
+# Some helpers for string/byte handling
+def tob(s, enc='utf8'):
+ return s.encode(enc) if isinstance(s, unicode) else bytes(s)
+def touni(s, enc='utf8', err='strict'):
+ return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
+tonat = touni if py3k else tob
+
+# 3.2 fixes cgi.FieldStorage to accept bytes (which makes a lot of sense).
+# 3.1 needs a workaround.
+if py31:
+ from io import TextIOWrapper
+ class NCTextIOWrapper(TextIOWrapper):
+ def close(self): pass # Keep wrapped buffer open.
+
+
+# A bug in functools causes it to break if the wrapper is an instance method
+def update_wrapper(wrapper, wrapped, *a, **ka):
+ try: functools.update_wrapper(wrapper, wrapped, *a, **ka)
+ except AttributeError: pass
+
+
+
+# These helpers are used at module level and need to be defined first.
+# And yes, I know PEP-8, but sometimes a lower-case classname makes more sense.
+
+def depr(message, hard=False):
+ warnings.warn(message, DeprecationWarning, stacklevel=3)
+
+def makelist(data): # This is just to handy
+ if isinstance(data, (tuple, list, set, dict)): return list(data)
+ elif data: return [data]
+ else: return []
+
+
+class DictProperty(object):
+ ''' Property that maps to a key in a local dict-like attribute. '''
+ def __init__(self, attr, key=None, read_only=False):
+ self.attr, self.key, self.read_only = attr, key, read_only
+
+ def __call__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter, self.key = func, self.key or func.__name__
+ return self
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ key, storage = self.key, getattr(obj, self.attr)
+ if key not in storage: storage[key] = self.getter(obj)
+ return storage[key]
+
+ def __set__(self, obj, value):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ getattr(obj, self.attr)[self.key] = value
+
+ def __delete__(self, obj):
+ if self.read_only: raise AttributeError("Read-Only property.")
+ del getattr(obj, self.attr)[self.key]
+
+
+class cached_property(object):
+ ''' A property that is only computed once per instance and then replaces
+ itself with an ordinary attribute. Deleting the attribute resets the
+ property. '''
+
+ def __init__(self, func):
+ self.__doc__ = getattr(func, '__doc__')
+ self.func = func
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ value = obj.__dict__[self.func.__name__] = self.func(obj)
+ return value
+
+
+class lazy_attribute(object):
+ ''' A property that caches itself to the class object. '''
+ def __init__(self, func):
+ functools.update_wrapper(self, func, updated=[])
+ self.getter = func
+
+ def __get__(self, obj, cls):
+ value = self.getter(cls)
+ setattr(cls, self.__name__, value)
+ return value
+
+
+
+
+
+
+###############################################################################
+# Exceptions and Events ########################################################
+###############################################################################
+
+
+class BottleException(Exception):
+ """ A base class for exceptions used by bottle. """
+ pass
+
+
+
+
+
+
+###############################################################################
+# Routing ######################################################################
+###############################################################################
+
+
+class RouteError(BottleException):
+ """ This is a base class for all routing related exceptions """
+
+
+class RouteReset(BottleException):
+ """ If raised by a plugin or request handler, the route is reset and all
+ plugins are re-applied. """
+
+class RouterUnknownModeError(RouteError): pass
+
+
+class RouteSyntaxError(RouteError):
+ """ The route parser found something not supported by this router. """
+
+
+class RouteBuildError(RouteError):
+ """ The route could not be built. """
+
+
+def _re_flatten(p):
+ ''' Turn all capturing groups in a regular expression pattern into
+ non-capturing groups. '''
+ if '(' not in p: return p
+ return re.sub(r'(\\*)(\(\?P<[^>]+>|\((?!\?))',
+ lambda m: m.group(0) if len(m.group(1)) % 2 else m.group(1) + '(?:', p)
+
+
+class Router(object):
+ ''' A Router is an ordered collection of route->target pairs. It is used to
+ efficiently match WSGI requests against a number of routes and return
+ the first target that satisfies the request. The target may be anything,
+ usually a string, ID or callable object. A route consists of a path-rule
+ and a HTTP method.
+
+ The path-rule is either a static path (e.g. `/contact`) or a dynamic
+ path that contains wildcards (e.g. `/wiki/<page>`). The wildcard syntax
+ and details on the matching order are described in docs:`routing`.
+ '''
+
+ default_pattern = '[^/]+'
+ default_filter = 're'
+
+ #: The current CPython regexp implementation does not allow more
+ #: than 99 matching groups per regular expression.
+ _MAX_GROUPS_PER_PATTERN = 99
+
+ def __init__(self, strict=False):
+ self.rules = [] # All rules in order
+ self._groups = {} # index of regexes to find them in dyna_routes
+ self.builder = {} # Data structure for the url builder
+ self.static = {} # Search structure for static routes
+ self.dyna_routes = {}
+ self.dyna_regexes = {} # Search structure for dynamic routes
+ #: If true, static routes are no longer checked first.
+ self.strict_order = strict
+ self.filters = {
+ 're': lambda conf:
+ (_re_flatten(conf or self.default_pattern), None, None),
+ 'int': lambda conf: (r'-?\d+', int, lambda x: str(int(x))),
+ 'float': lambda conf: (r'-?[\d.]+', float, lambda x: str(float(x))),
+ 'path': lambda conf: (r'.+?', None, None)}
+
+ def add_filter(self, name, func):
+ ''' Add a filter. The provided function is called with the configuration
+ string as parameter and must return a (regexp, to_python, to_url) tuple.
+ The first element is a string, the last two are callables or None. '''
+ self.filters[name] = func
+
+ rule_syntax = re.compile('(\\\\*)'\
+ '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)'\
+ '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)'\
+ '(?::((?:\\\\.|[^\\\\>]+)+)?)?)?>))')
+
+ def _itertokens(self, rule):
+ offset, prefix = 0, ''
+ for match in self.rule_syntax.finditer(rule):
+ prefix += rule[offset:match.start()]
+ g = match.groups()
+ if len(g[0])%2: # Escaped wildcard
+ prefix += match.group(0)[len(g[0]):]
+ offset = match.end()
+ continue
+ if prefix:
+ yield prefix, None, None
+ name, filtr, conf = g[4:7] if g[2] is None else g[1:4]
+ yield name, filtr or 'default', conf or None
+ offset, prefix = match.end(), ''
+ if offset <= len(rule) or prefix:
+ yield prefix+rule[offset:], None, None
+
+ def add(self, rule, method, target, name=None):
+ ''' Add a new rule or replace the target for an existing rule. '''
+ anons = 0 # Number of anonymous wildcards found
+ keys = [] # Names of keys
+ pattern = '' # Regular expression pattern with named groups
+ filters = [] # Lists of wildcard input filters
+ builder = [] # Data structure for the URL builder
+ is_static = True
+
+ for key, mode, conf in self._itertokens(rule):
+ if mode:
+ is_static = False
+ if mode == 'default': mode = self.default_filter
+ mask, in_filter, out_filter = self.filters[mode](conf)
+ if not key:
+ pattern += '(?:%s)' % mask
+ key = 'anon%d' % anons
+ anons += 1
+ else:
+ pattern += '(?P<%s>%s)' % (key, mask)
+ keys.append(key)
+ if in_filter: filters.append((key, in_filter))
+ builder.append((key, out_filter or str))
+ elif key:
+ pattern += re.escape(key)
+ builder.append((None, key))
+
+ self.builder[rule] = builder
+ if name: self.builder[name] = builder
+
+ if is_static and not self.strict_order:
+ self.static.setdefault(method, {})
+ self.static[method][self.build(rule)] = (target, None)
+ return
+
+ try:
+ re_pattern = re.compile('^(%s)$' % pattern)
+ re_match = re_pattern.match
+ except re.error:
+ raise RouteSyntaxError("Could not add Route: %s (%s)" % (rule, _e()))
+
+ if filters:
+ def getargs(path):
+ url_args = re_match(path).groupdict()
+ for name, wildcard_filter in filters:
+ try:
+ url_args[name] = wildcard_filter(url_args[name])
+ except ValueError:
+ raise HTTPError(400, 'Path has wrong format.')
+ return url_args
+ elif re_pattern.groupindex:
+ def getargs(path):
+ return re_match(path).groupdict()
+ else:
+ getargs = None
+
+ flatpat = _re_flatten(pattern)
+ whole_rule = (rule, flatpat, target, getargs)
+
+ if (flatpat, method) in self._groups:
+ if DEBUG:
+ msg = 'Route <%s %s> overwrites a previously defined route'
+ warnings.warn(msg % (method, rule), RuntimeWarning)
+ self.dyna_routes[method][self._groups[flatpat, method]] = whole_rule
+ else:
+ self.dyna_routes.setdefault(method, []).append(whole_rule)
+ self._groups[flatpat, method] = len(self.dyna_routes[method]) - 1
+
+ self._compile(method)
+
+ def _compile(self, method):
+ all_rules = self.dyna_routes[method]
+ comborules = self.dyna_regexes[method] = []
+ maxgroups = self._MAX_GROUPS_PER_PATTERN
+ for x in range(0, len(all_rules), maxgroups):
+ some = all_rules[x:x+maxgroups]
+ combined = (flatpat for (_, flatpat, _, _) in some)
+ combined = '|'.join('(^%s$)' % flatpat for flatpat in combined)
+ combined = re.compile(combined).match
+ rules = [(target, getargs) for (_, _, target, getargs) in some]
+ comborules.append((combined, rules))
+
+ def build(self, _name, *anons, **query):
+ ''' Build an URL by filling the wildcards in a rule. '''
+ builder = self.builder.get(_name)
+ if not builder: raise RouteBuildError("No route with that name.", _name)
+ try:
+ for i, value in enumerate(anons): query['anon%d'%i] = value
+ url = ''.join([f(query.pop(n)) if n else f for (n,f) in builder])
+ return url if not query else url+'?'+urlencode(query)
+ except KeyError:
+ raise RouteBuildError('Missing URL argument: %r' % _e().args[0])
+
+ def match(self, environ):
+ ''' Return a (target, url_agrs) tuple or raise HTTPError(400/404/405). '''
+ verb = environ['REQUEST_METHOD'].upper()
+ path = environ['PATH_INFO'] or '/'
+ target = None
+ if verb == 'HEAD':
+ methods = ['PROXY', verb, 'GET', 'ANY']
+ else:
+ methods = ['PROXY', verb, 'ANY']
+
+ for method in methods:
+ if method in self.static and path in self.static[method]:
+ target, getargs = self.static[method][path]
+ return target, getargs(path) if getargs else {}
+ elif method in self.dyna_regexes:
+ for combined, rules in self.dyna_regexes[method]:
+ match = combined(path)
+ if match:
+ target, getargs = rules[match.lastindex - 1]
+ return target, getargs(path) if getargs else {}
+
+ # No matching route found. Collect alternative methods for 405 response
+ allowed = set([])
+ nocheck = set(methods)
+ for method in set(self.static) - nocheck:
+ if path in self.static[method]:
+ allowed.add(verb)
+ for method in set(self.dyna_regexes) - allowed - nocheck:
+ for combined, rules in self.dyna_regexes[method]:
+ match = combined(path)
+ if match:
+ allowed.add(method)
+ if allowed:
+ allow_header = ",".join(sorted(allowed))
+ raise HTTPError(405, "Method not allowed.", Allow=allow_header)
+
+ # No matching route and no alternative method found. We give up
+ raise HTTPError(404, "Not found: " + repr(path))
+
+
+
+
+
+
+class Route(object):
+ ''' This class wraps a route callback along with route specific metadata and
+ configuration and applies Plugins on demand. It is also responsible for
+ turing an URL path rule into a regular expression usable by the Router.
+ '''
+
+ def __init__(self, app, rule, method, callback, name=None,
+ plugins=None, skiplist=None, **config):
+ #: The application this route is installed to.
+ self.app = app
+ #: The path-rule string (e.g. ``/wiki/:page``).
+ self.rule = rule
+ #: The HTTP method as a string (e.g. ``GET``).
+ self.method = method
+ #: The original callback with no plugins applied. Useful for introspection.
+ self.callback = callback
+ #: The name of the route (if specified) or ``None``.
+ self.name = name or None
+ #: A list of route-specific plugins (see :meth:`Bottle.route`).
+ self.plugins = plugins or []
+ #: A list of plugins to not apply to this route (see :meth:`Bottle.route`).
+ self.skiplist = skiplist or []
+ #: Additional keyword arguments passed to the :meth:`Bottle.route`
+ #: decorator are stored in this dictionary. Used for route-specific
+ #: plugin configuration and meta-data.
+ self.config = ConfigDict().load_dict(config, make_namespaces=True)
+
+ def __call__(self, *a, **ka):
+ depr("Some APIs changed to return Route() instances instead of"\
+ " callables. Make sure to use the Route.call method and not to"\
+ " call Route instances directly.") #0.12
+ return self.call(*a, **ka)
+
+ @cached_property
+ def call(self):
+ ''' The route callback with all plugins applied. This property is
+ created on demand and then cached to speed up subsequent requests.'''
+ return self._make_callback()
+
+ def reset(self):
+ ''' Forget any cached values. The next time :attr:`call` is accessed,
+ all plugins are re-applied. '''
+ self.__dict__.pop('call', None)
+
+ def prepare(self):
+ ''' Do all on-demand work immediately (useful for debugging).'''
+ self.call
+
+ @property
+ def _context(self):
+ depr('Switch to Plugin API v2 and access the Route object directly.') #0.12
+ return dict(rule=self.rule, method=self.method, callback=self.callback,
+ name=self.name, app=self.app, config=self.config,
+ apply=self.plugins, skip=self.skiplist)
+
+ def all_plugins(self):
+ ''' Yield all Plugins affecting this route. '''
+ unique = set()
+ for p in reversed(self.app.plugins + self.plugins):
+ if True in self.skiplist: break
+ name = getattr(p, 'name', False)
+ if name and (name in self.skiplist or name in unique): continue
+ if p in self.skiplist or type(p) in self.skiplist: continue
+ if name: unique.add(name)
+ yield p
+
+ def _make_callback(self):
+ callback = self.callback
+ for plugin in self.all_plugins():
+ try:
+ if hasattr(plugin, 'apply'):
+ api = getattr(plugin, 'api', 1)
+ context = self if api > 1 else self._context
+ callback = plugin.apply(callback, context)
+ else:
+ callback = plugin(callback)
+ except RouteReset: # Try again with changed configuration.
+ return self._make_callback()
+ if not callback is self.callback:
+ update_wrapper(callback, self.callback)
+ return callback
+
+ def get_undecorated_callback(self):
+ ''' Return the callback. If the callback is a decorated function, try to
+ recover the original function. '''
+ func = self.callback
+ func = getattr(func, '__func__' if py3k else 'im_func', func)
+ closure_attr = '__closure__' if py3k else 'func_closure'
+ while hasattr(func, closure_attr) and getattr(func, closure_attr):
+ func = getattr(func, closure_attr)[0].cell_contents
+ return func
+
+ def get_callback_args(self):
+ ''' Return a list of argument names the callback (most likely) accepts
+ as keyword arguments. If the callback is a decorated function, try
+ to recover the original function before inspection. '''
+ return getargspec(self.get_undecorated_callback())[0]
+
+ def get_config(self, key, default=None):
+ ''' Lookup a config field and return its value, first checking the
+ route.config, then route.app.config.'''
+ for conf in (self.config, self.app.conifg):
+ if key in conf: return conf[key]
+ return default
+
+ def __repr__(self):
+ cb = self.get_undecorated_callback()
+ return '<%s %r %r>' % (self.method, self.rule, cb)
+
+
+
+
+
+
+###############################################################################
+# Application Object ###########################################################
+###############################################################################
+
+
+class Bottle(object):
+ """ Each Bottle object represents a single, distinct web application and
+ consists of routes, callbacks, plugins, resources and configuration.
+ Instances are callable WSGI applications.
+
+ :param catchall: If true (default), handle all exceptions. Turn off to
+ let debugging middleware handle exceptions.
+ """
+
+ def __init__(self, catchall=True, autojson=True):
+
+ #: A :class:`ConfigDict` for app specific configuration.
+ self.config = ConfigDict()
+ self.config._on_change = functools.partial(self.trigger_hook, 'config')
+ self.config.meta_set('autojson', 'validate', bool)
+ self.config.meta_set('catchall', 'validate', bool)
+ self.config['catchall'] = catchall
+ self.config['autojson'] = autojson
+
+ #: A :class:`ResourceManager` for application files
+ self.resources = ResourceManager()
+
+ self.routes = [] # List of installed :class:`Route` instances.
+ self.router = Router() # Maps requests to :class:`Route` instances.
+ self.error_handler = {}
+
+ # Core plugins
+ self.plugins = [] # List of installed plugins.
+ if self.config['autojson']:
+ self.install(JSONPlugin())
+ self.install(TemplatePlugin())
+
+ #: If true, most exceptions are caught and returned as :exc:`HTTPError`
+ catchall = DictProperty('config', 'catchall')
+
+ __hook_names = 'before_request', 'after_request', 'app_reset', 'config'
+ __hook_reversed = 'after_request'
+
+ @cached_property
+ def _hooks(self):
+ return dict((name, []) for name in self.__hook_names)
+
+ def add_hook(self, name, func):
+ ''' Attach a callback to a hook. Three hooks are currently implemented:
+
+ before_request
+ Executed once before each request. The request context is
+ available, but no routing has happened yet.
+ after_request
+ Executed once after each request regardless of its outcome.
+ app_reset
+ Called whenever :meth:`Bottle.reset` is called.
+ '''
+ if name in self.__hook_reversed:
+ self._hooks[name].insert(0, func)
+ else:
+ self._hooks[name].append(func)
+
+ def remove_hook(self, name, func):
+ ''' Remove a callback from a hook. '''
+ if name in self._hooks and func in self._hooks[name]:
+ self._hooks[name].remove(func)
+ return True
+
+ def trigger_hook(self, __name, *args, **kwargs):
+ ''' Trigger a hook and return a list of results. '''
+ return [hook(*args, **kwargs) for hook in self._hooks[__name][:]]
+
+ def hook(self, name):
+ """ Return a decorator that attaches a callback to a hook. See
+ :meth:`add_hook` for details."""
+ def decorator(func):
+ self.add_hook(name, func)
+ return func
+ return decorator
+
+ def mount(self, prefix, app, **options):
+ ''' Mount an application (:class:`Bottle` or plain WSGI) to a specific
+ URL prefix. Example::
+
+ root_app.mount('/admin/', admin_app)
+
+ :param prefix: path prefix or `mount-point`. If it ends in a slash,
+ that slash is mandatory.
+ :param app: an instance of :class:`Bottle` or a WSGI application.
+
+ All other parameters are passed to the underlying :meth:`route` call.
+ '''
+ if isinstance(app, basestring):
+ depr('Parameter order of Bottle.mount() changed.', True) # 0.10
+
+ segments = [p for p in prefix.split('/') if p]
+ if not segments: raise ValueError('Empty path prefix.')
+ path_depth = len(segments)
+
+ def mountpoint_wrapper():
+ try:
+ request.path_shift(path_depth)
+ rs = HTTPResponse([])
+ def start_response(status, headerlist, exc_info=None):
+ if exc_info:
+ try:
+ _raise(*exc_info)
+ finally:
+ exc_info = None
+ rs.status = status
+ for name, value in headerlist: rs.add_header(name, value)
+ return rs.body.append
+ body = app(request.environ, start_response)
+ if body and rs.body: body = itertools.chain(rs.body, body)
+ rs.body = body or rs.body
+ return rs
+ finally:
+ request.path_shift(-path_depth)
+
+ options.setdefault('skip', True)
+ options.setdefault('method', 'PROXY')
+ options.setdefault('mountpoint', {'prefix': prefix, 'target': app})
+ options['callback'] = mountpoint_wrapper
+
+ self.route('/%s/<:re:.*>' % '/'.join(segments), **options)
+ if not prefix.endswith('/'):
+ self.route('/' + '/'.join(segments), **options)
+
+ def merge(self, routes):
+ ''' Merge the routes of another :class:`Bottle` application or a list of
+ :class:`Route` objects into this application. The routes keep their
+ 'owner', meaning that the :data:`Route.app` attribute is not
+ changed. '''
+ if isinstance(routes, Bottle):
+ routes = routes.routes
+ for route in routes:
+ self.add_route(route)
+
+ def install(self, plugin):
+ ''' Add a plugin to the list of plugins and prepare it for being
+ applied to all routes of this application. A plugin may be a simple
+ decorator or an object that implements the :class:`Plugin` API.
+ '''
+ if hasattr(plugin, 'setup'): plugin.setup(self)
+ if not callable(plugin) and not hasattr(plugin, 'apply'):
+ raise TypeError("Plugins must be callable or implement .apply()")
+ self.plugins.append(plugin)
+ self.reset()
+ return plugin
+
+ def uninstall(self, plugin):
+ ''' Uninstall plugins. Pass an instance to remove a specific plugin, a type
+ object to remove all plugins that match that type, a string to remove
+ all plugins with a matching ``name`` attribute or ``True`` to remove all
+ plugins. Return the list of removed plugins. '''
+ removed, remove = [], plugin
+ for i, plugin in list(enumerate(self.plugins))[::-1]:
+ if remove is True or remove is plugin or remove is type(plugin) \
+ or getattr(plugin, 'name', True) == remove:
+ removed.append(plugin)
+ del self.plugins[i]
+ if hasattr(plugin, 'close'): plugin.close()
+ if removed: self.reset()
+ return removed
+
+ def reset(self, route=None):
+ ''' Reset all routes (force plugins to be re-applied) and clear all
+ caches. If an ID or route object is given, only that specific route
+ is affected. '''
+ if route is None: routes = self.routes
+ elif isinstance(route, Route): routes = [route]
+ else: routes = [self.routes[route]]
+ for route in routes: route.reset()
+ if DEBUG:
+ for route in routes: route.prepare()
+ self.trigger_hook('app_reset')
+
+ def close(self):
+ ''' Close the application and all installed plugins. '''
+ for plugin in self.plugins:
+ if hasattr(plugin, 'close'): plugin.close()
+ self.stopped = True
+
+ def run(self, **kwargs):
+ ''' Calls :func:`run` with the same parameters. '''
+ run(self, **kwargs)
+
+ def match(self, environ):
+ """ Search for a matching route and return a (:class:`Route` , urlargs)
+ tuple. The second value is a dictionary with parameters extracted
+ from the URL. Raise :exc:`HTTPError` (404/405) on a non-match."""
+ return self.router.match(environ)
+
+ def get_url(self, routename, **kargs):
+ """ Return a string that matches a named route """
+ scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
+ location = self.router.build(routename, **kargs).lstrip('/')
+ return urljoin(urljoin('/', scriptname), location)
+
+ def add_route(self, route):
+ ''' Add a route object, but do not change the :data:`Route.app`
+ attribute.'''
+ self.routes.append(route)
+ self.router.add(route.rule, route.method, route, name=route.name)
+ if DEBUG: route.prepare()
+
+ def route(self, path=None, method='GET', callback=None, name=None,
+ apply=None, skip=None, **config):
+ """ A decorator to bind a function to a request URL. Example::
+
+ @app.route('/hello/:name')
+ def hello(name):
+ return 'Hello %s' % name
+
+ The ``:name`` part is a wildcard. See :class:`Router` for syntax
+ details.
+
+ :param path: Request path or a list of paths to listen to. If no
+ path is specified, it is automatically generated from the
+ signature of the function.
+ :param method: HTTP method (`GET`, `POST`, `PUT`, ...) or a list of
+ methods to listen to. (default: `GET`)
+ :param callback: An optional shortcut to avoid the decorator
+ syntax. ``route(..., callback=func)`` equals ``route(...)(func)``
+ :param name: The name for this route. (default: None)
+ :param apply: A decorator or plugin or a list of plugins. These are
+ applied to the route callback in addition to installed plugins.
+ :param skip: A list of plugins, plugin classes or names. Matching
+ plugins are not installed to this route. ``True`` skips all.
+
+ Any additional keyword arguments are stored as route-specific
+ configuration and passed to plugins (see :meth:`Plugin.apply`).
+ """
+ if callable(path): path, callback = None, path
+ plugins = makelist(apply)
+ skiplist = makelist(skip)
+ def decorator(callback):
+ # TODO: Documentation and tests
+ if isinstance(callback, basestring): callback = load(callback)
+ for rule in makelist(path) or yieldroutes(callback):
+ for verb in makelist(method):
+ verb = verb.upper()
+ route = Route(self, rule, verb, callback, name=name,
+ plugins=plugins, skiplist=skiplist, **config)
+ self.add_route(route)
+ return callback
+ return decorator(callback) if callback else decorator
+
+ def get(self, path=None, method='GET', **options):
+ """ Equals :meth:`route`. """
+ return self.route(path, method, **options)
+
+ def post(self, path=None, method='POST', **options):
+ """ Equals :meth:`route` with a ``POST`` method parameter. """
+ return self.route(path, method, **options)
+
+ def put(self, path=None, method='PUT', **options):
+ """ Equals :meth:`route` with a ``PUT`` method parameter. """
+ return self.route(path, method, **options)
+
+ def delete(self, path=None, method='DELETE', **options):
+ """ Equals :meth:`route` with a ``DELETE`` method parameter. """
+ return self.route(path, method, **options)
+
+ def error(self, code=500):
+ """ Decorator: Register an output handler for a HTTP error code"""
+ def wrapper(handler):
+ self.error_handler[int(code)] = handler
+ return handler
+ return wrapper
+
+ def default_error_handler(self, res):
+ return tob(template(ERROR_PAGE_TEMPLATE, e=res))
+
+ def _handle(self, environ):
+ path = environ['bottle.raw_path'] = environ['PATH_INFO']
+ if py3k:
+ try:
+ environ['PATH_INFO'] = path.encode('latin1').decode('utf8')
+ except UnicodeError:
+ return HTTPError(400, 'Invalid path string. Expected UTF-8')
+
+ try:
+ environ['bottle.app'] = self
+ request.bind(environ)
+ response.bind()
+ try:
+ self.trigger_hook('before_request')
+ route, args = self.router.match(environ)
+ environ['route.handle'] = route
+ environ['bottle.route'] = route
+ environ['route.url_args'] = args
+ return route.call(**args)
+ finally:
+ self.trigger_hook('after_request')
+
+ except HTTPResponse:
+ return _e()
+ except RouteReset:
+ route.reset()
+ return self._handle(environ)
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ stacktrace = format_exc()
+ environ['wsgi.errors'].write(stacktrace)
+ return HTTPError(500, "Internal Server Error", _e(), stacktrace)
+
+ def _cast(self, out, peek=None):
+ """ Try to convert the parameter into something WSGI compatible and set
+ correct HTTP headers when possible.
+ Support: False, str, unicode, dict, HTTPResponse, HTTPError, file-like,
+ iterable of strings and iterable of unicodes
+ """
+
+ # Empty output is done here
+ if not out:
+ if 'Content-Length' not in response:
+ response['Content-Length'] = 0
+ return []
+ # Join lists of byte or unicode strings. Mixed lists are NOT supported
+ if isinstance(out, (tuple, list))\
+ and isinstance(out[0], (bytes, unicode)):
+ out = out[0][0:0].join(out) # b'abc'[0:0] -> b''
+ # Encode unicode strings
+ if isinstance(out, unicode):
+ out = out.encode(response.charset)
+ # Byte Strings are just returned
+ if isinstance(out, bytes):
+ if 'Content-Length' not in response:
+ response['Content-Length'] = len(out)
+ return [out]
+ # HTTPError or HTTPException (recursive, because they may wrap anything)
+ # TODO: Handle these explicitly in handle() or make them iterable.
+ if isinstance(out, HTTPError):
+ out.apply(response)
+ out = self.error_handler.get(out.status_code, self.default_error_handler)(out)
+ return self._cast(out)
+ if isinstance(out, HTTPResponse):
+ out.apply(response)
+ return self._cast(out.body)
+
+ # File-like objects.
+ if hasattr(out, 'read'):
+ if 'wsgi.file_wrapper' in request.environ:
+ return request.environ['wsgi.file_wrapper'](out)
+ elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
+ return WSGIFileWrapper(out)
+
+ # Handle Iterables. We peek into them to detect their inner type.
+ try:
+ iout = iter(out)
+ first = next(iout)
+ while not first:
+ first = next(iout)
+ except StopIteration:
+ return self._cast('')
+ except HTTPResponse:
+ first = _e()
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ first = HTTPError(500, 'Unhandled exception', _e(), format_exc())
+
+ # These are the inner types allowed in iterator or generator objects.
+ if isinstance(first, HTTPResponse):
+ return self._cast(first)
+ elif isinstance(first, bytes):
+ new_iter = itertools.chain([first], iout)
+ elif isinstance(first, unicode):
+ encoder = lambda x: x.encode(response.charset)
+ new_iter = imap(encoder, itertools.chain([first], iout))
+ else:
+ msg = 'Unsupported response type: %s' % type(first)
+ return self._cast(HTTPError(500, msg))
+ if hasattr(out, 'close'):
+ new_iter = _closeiter(new_iter, out.close)
+ return new_iter
+
+ def wsgi(self, environ, start_response):
+ """ The bottle WSGI-interface. """
+ try:
+ out = self._cast(self._handle(environ))
+ # rfc2616 section 4.3
+ if response._status_code in (100, 101, 204, 304)\
+ or environ['REQUEST_METHOD'] == 'HEAD':
+ if hasattr(out, 'close'): out.close()
+ out = []
+ start_response(response._status_line, response.headerlist)
+ return out
+ except (KeyboardInterrupt, SystemExit, MemoryError):
+ raise
+ except Exception:
+ if not self.catchall: raise
+ err = '<h1>Critical error while processing request: %s</h1>' \
+ % html_escape(environ.get('PATH_INFO', '/'))
+ if DEBUG:
+ err += '<h2>Error:</h2>\n<pre>\n%s\n</pre>\n' \
+ '<h2>Traceback:</h2>\n<pre>\n%s\n</pre>\n' \
+ % (html_escape(repr(_e())), html_escape(format_exc()))
+ environ['wsgi.errors'].write(err)
+ headers = [('Content-Type', 'text/html; charset=UTF-8')]
+ start_response('500 INTERNAL SERVER ERROR', headers, sys.exc_info())
+ return [tob(err)]
+
+ def __call__(self, environ, start_response):
+ ''' Each instance of :class:'Bottle' is a WSGI application. '''
+ return self.wsgi(environ, start_response)
+
+
+
+
+
+
+###############################################################################
+# HTTP and WSGI Tools ##########################################################
+###############################################################################
+
+class BaseRequest(object):
+ """ A wrapper for WSGI environment dictionaries that adds a lot of
+ convenient access methods and properties. Most of them are read-only.
+
+ Adding new attributes to a request actually adds them to the environ
+ dictionary (as 'bottle.request.ext.<name>'). This is the recommended
+ way to store and access request-specific data.
+ """
+
+ __slots__ = ('environ')
+
+ #: Maximum size of memory buffer for :attr:`body` in bytes.
+ MEMFILE_MAX = 102400
+
+ def __init__(self, environ=None):
+ """ Wrap a WSGI environ dictionary. """
+ #: The wrapped WSGI environ dictionary. This is the only real attribute.
+ #: All other attributes actually are read-only properties.
+ self.environ = {} if environ is None else environ
+ self.environ['bottle.request'] = self
+
+ @DictProperty('environ', 'bottle.app', read_only=True)
+ def app(self):
+ ''' Bottle application handling this request. '''
+ raise RuntimeError('This request is not connected to an application.')
+
+ @DictProperty('environ', 'bottle.route', read_only=True)
+ def route(self):
+ """ The bottle :class:`Route` object that matches this request. """
+ raise RuntimeError('This request is not connected to a route.')
+
+ @DictProperty('environ', 'route.url_args', read_only=True)
+ def url_args(self):
+ """ The arguments extracted from the URL. """
+ raise RuntimeError('This request is not connected to a route.')
+
+ @property
+ def path(self):
+ ''' The value of ``PATH_INFO`` with exactly one prefixed slash (to fix
+ broken clients and avoid the "empty path" edge case). '''
+ return '/' + self.environ.get('PATH_INFO','').lstrip('/')
+
+ @property
+ def method(self):
+ ''' The ``REQUEST_METHOD`` value as an uppercase string. '''
+ return self.environ.get('REQUEST_METHOD', 'GET').upper()
+
+ @DictProperty('environ', 'bottle.request.headers', read_only=True)
+ def headers(self):
+ ''' A :class:`WSGIHeaderDict` that provides case-insensitive access to
+ HTTP request headers. '''
+ return WSGIHeaderDict(self.environ)
+
+ def get_header(self, name, default=None):
+ ''' Return the value of a request header, or a given default value. '''
+ return self.headers.get(name, default)
+
+ @DictProperty('environ', 'bottle.request.cookies', read_only=True)
+ def cookies(self):
+ """ Cookies parsed into a :class:`FormsDict`. Signed cookies are NOT
+ decoded. Use :meth:`get_cookie` if you expect signed cookies. """
+ cookies = SimpleCookie(self.environ.get('HTTP_COOKIE','')).values()
+ return FormsDict((c.key, c.value) for c in cookies)
+
+ def get_cookie(self, key, default=None, secret=None):
+ """ Return the content of a cookie. To read a `Signed Cookie`, the
+ `secret` must match the one used to create the cookie (see
+ :meth:`BaseResponse.set_cookie`). If anything goes wrong (missing
+ cookie or wrong signature), return a default value. """
+ value = self.cookies.get(key)
+ if secret and value:
+ dec = cookie_decode(value, secret) # (key, value) tuple or None
+ return dec[1] if dec and dec[0] == key else default
+ return value or default
+
+ @DictProperty('environ', 'bottle.request.query', read_only=True)
+ def query(self):
+ ''' The :attr:`query_string` parsed into a :class:`FormsDict`. These
+ values are sometimes called "URL arguments" or "GET parameters", but
+ not to be confused with "URL wildcards" as they are provided by the
+ :class:`Router`. '''
+ get = self.environ['bottle.get'] = FormsDict()
+ pairs = _parse_qsl(self.environ.get('QUERY_STRING', ''))
+ for key, value in pairs:
+ get[key] = value
+ return get
+
+ @DictProperty('environ', 'bottle.request.forms', read_only=True)
+ def forms(self):
+ """ Form values parsed from an `url-encoded` or `multipart/form-data`
+ encoded POST or PUT request body. The result is returned as a
+ :class:`FormsDict`. All keys and values are strings. File uploads
+ are stored separately in :attr:`files`. """
+ forms = FormsDict()
+ for name, item in self.POST.allitems():
+ if not isinstance(item, FileUpload):
+ forms[name] = item
+ return forms
+
+ @DictProperty('environ', 'bottle.request.params', read_only=True)
+ def params(self):
+ """ A :class:`FormsDict` with the combined values of :attr:`query` and
+ :attr:`forms`. File uploads are stored in :attr:`files`. """
+ params = FormsDict()
+ for key, value in self.query.allitems():
+ params[key] = value
+ for key, value in self.forms.allitems():
+ params[key] = value
+ return params
+
+ @DictProperty('environ', 'bottle.request.files', read_only=True)
+ def files(self):
+ """ File uploads parsed from `multipart/form-data` encoded POST or PUT
+ request body. The values are instances of :class:`FileUpload`.
+
+ """
+ files = FormsDict()
+ for name, item in self.POST.allitems():
+ if isinstance(item, FileUpload):
+ files[name] = item
+ return files
+
+ @DictProperty('environ', 'bottle.request.json', read_only=True)
+ def json(self):
+ ''' If the ``Content-Type`` header is ``application/json``, this
+ property holds the parsed content of the request body. Only requests
+ smaller than :attr:`MEMFILE_MAX` are processed to avoid memory
+ exhaustion. '''
+ ctype = self.environ.get('CONTENT_TYPE', '').lower().split(';')[0]
+ if ctype == 'application/json':
+ return json_loads(self._get_body_string())
+ return None
+
+ def _iter_body(self, read, bufsize):
+ maxread = max(0, self.content_length)
+ while maxread:
+ part = read(min(maxread, bufsize))
+ if not part: break
+ yield part
+ maxread -= len(part)
+
+ def _iter_chunked(self, read, bufsize):
+ err = HTTPError(400, 'Error while parsing chunked transfer body.')
+ rn, sem, bs = tob('\r\n'), tob(';'), tob('')
+ while True:
+ header = read(1)
+ while header[-2:] != rn:
+ c = read(1)
+ header += c
+ if not c: raise err
+ if len(header) > bufsize: raise err
+ size, _, _ = header.partition(sem)
+ try:
+ maxread = int(tonat(size.strip()), 16)
+ except ValueError:
+ raise err
+ if maxread == 0: break
+ buff = bs
+ while maxread > 0:
+ if not buff:
+ buff = read(min(maxread, bufsize))
+ part, buff = buff[:maxread], buff[maxread:]
+ if not part: raise err
+ yield part
+ maxread -= len(part)
+ if read(2) != rn:
+ raise err
+
+ @DictProperty('environ', 'bottle.request.body', read_only=True)
+ def _body(self):
+ body_iter = self._iter_chunked if self.chunked else self._iter_body
+ read_func = self.environ['wsgi.input'].read
+ body, body_size, is_temp_file = BytesIO(), 0, False
+ for part in body_iter(read_func, self.MEMFILE_MAX):
+ body.write(part)
+ body_size += len(part)
+ if not is_temp_file and body_size > self.MEMFILE_MAX:
+ body, tmp = TemporaryFile(mode='w+b'), body
+ body.write(tmp.getvalue())
+ del tmp
+ is_temp_file = True
+ self.environ['wsgi.input'] = body
+ body.seek(0)
+ return body
+
+ def _get_body_string(self):
+ ''' read body until content-length or MEMFILE_MAX into a string. Raise
+ HTTPError(413) on requests that are to large. '''
+ clen = self.content_length
+ if clen > self.MEMFILE_MAX:
+ raise HTTPError(413, 'Request to large')
+ if clen < 0: clen = self.MEMFILE_MAX + 1
+ data = self.body.read(clen)
+ if len(data) > self.MEMFILE_MAX: # Fail fast
+ raise HTTPError(413, 'Request to large')
+ return data
+
+ @property
+ def body(self):
+ """ The HTTP request body as a seek-able file-like object. Depending on
+ :attr:`MEMFILE_MAX`, this is either a temporary file or a
+ :class:`io.BytesIO` instance. Accessing this property for the first
+ time reads and replaces the ``wsgi.input`` environ variable.
+ Subsequent accesses just do a `seek(0)` on the file object. """
+ self._body.seek(0)
+ return self._body
+
+ @property
+ def chunked(self):
+ ''' True if Chunked transfer encoding was. '''
+ return 'chunked' in self.environ.get('HTTP_TRANSFER_ENCODING', '').lower()
+
+ #: An alias for :attr:`query`.
+ GET = query
+
+ @DictProperty('environ', 'bottle.request.post', read_only=True)
+ def POST(self):
+ """ The values of :attr:`forms` and :attr:`files` combined into a single
+ :class:`FormsDict`. Values are either strings (form values) or
+ instances of :class:`cgi.FieldStorage` (file uploads).
+ """
+ post = FormsDict()
+ # We default to application/x-www-form-urlencoded for everything that
+ # is not multipart and take the fast path (also: 3.1 workaround)
+ if not self.content_type.startswith('multipart/'):
+ pairs = _parse_qsl(tonat(self._get_body_string(), 'latin1'))
+ for key, value in pairs:
+ post[key] = value
+ return post
+
+ safe_env = {'QUERY_STRING':''} # Build a safe environment for cgi
+ for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
+ if key in self.environ: safe_env[key] = self.environ[key]
+ args = dict(fp=self.body, environ=safe_env, keep_blank_values=True)
+ if py31:
+ args['fp'] = NCTextIOWrapper(args['fp'], encoding='utf8',
+ newline='\n')
+ elif py3k:
+ args['encoding'] = 'utf8'
+ data = cgi.FieldStorage(**args)
+ self['_cgi.FieldStorage'] = data #http://bugs.python.org/issue18394#msg207958
+ data = data.list or []
+ for item in data:
+ if item.filename:
+ post[item.name] = FileUpload(item.file, item.name,
+ item.filename, item.headers)
+ else:
+ post[item.name] = item.value
+ return post
+
+ @property
+ def url(self):
+ """ The full request URI including hostname and scheme. If your app
+ lives behind a reverse proxy or load balancer and you get confusing
+ results, make sure that the ``X-Forwarded-Host`` header is set
+ correctly. """
+ return self.urlparts.geturl()
+
+ @DictProperty('environ', 'bottle.request.urlparts', read_only=True)
+ def urlparts(self):
+ ''' The :attr:`url` string as an :class:`urlparse.SplitResult` tuple.
+ The tuple contains (scheme, host, path, query_string and fragment),
+ but the fragment is always empty because it is not visible to the
+ server. '''
+ env = self.environ
+ http = env.get('HTTP_X_FORWARDED_PROTO') or env.get('wsgi.url_scheme', 'http')
+ host = env.get('HTTP_X_FORWARDED_HOST') or env.get('HTTP_HOST')
+ if not host:
+ # HTTP 1.1 requires a Host-header. This is for HTTP/1.0 clients.
+ host = env.get('SERVER_NAME', '127.0.0.1')
+ port = env.get('SERVER_PORT')
+ if port and port != ('80' if http == 'http' else '443'):
+ host += ':' + port
+ path = urlquote(self.fullpath)
+ return UrlSplitResult(http, host, path, env.get('QUERY_STRING'), '')
+
+ @property
+ def fullpath(self):
+ """ Request path including :attr:`script_name` (if present). """
+ return urljoin(self.script_name, self.path.lstrip('/'))
+
+ @property
+ def query_string(self):
+ """ The raw :attr:`query` part of the URL (everything in between ``?``
+ and ``#``) as a string. """
+ return self.environ.get('QUERY_STRING', '')
+
+ @property
+ def script_name(self):
+ ''' The initial portion of the URL's `path` that was removed by a higher
+ level (server or routing middleware) before the application was
+ called. This script path is returned with leading and tailing
+ slashes. '''
+ script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
+ return '/' + script_name + '/' if script_name else '/'
+
+ def path_shift(self, shift=1):
+ ''' Shift path segments from :attr:`path` to :attr:`script_name` and
+ vice versa.
+
+ :param shift: The number of path segments to shift. May be negative
+ to change the shift direction. (default: 1)
+ '''
+ script = self.environ.get('SCRIPT_NAME','/')
+ self['SCRIPT_NAME'], self['PATH_INFO'] = path_shift(script, self.path, shift)
+
+ @property
+ def content_length(self):
+ ''' The request body length as an integer. The client is responsible to
+ set this header. Otherwise, the real length of the body is unknown
+ and -1 is returned. In this case, :attr:`body` will be empty. '''
+ return int(self.environ.get('CONTENT_LENGTH') or -1)
+
+ @property
+ def content_type(self):
+ ''' The Content-Type header as a lowercase-string (default: empty). '''
+ return self.environ.get('CONTENT_TYPE', '').lower()
+
+ @property
+ def is_xhr(self):
+ ''' True if the request was triggered by a XMLHttpRequest. This only
+ works with JavaScript libraries that support the `X-Requested-With`
+ header (most of the popular libraries do). '''
+ requested_with = self.environ.get('HTTP_X_REQUESTED_WITH','')
+ return requested_with.lower() == 'xmlhttprequest'
+
+ @property
+ def is_ajax(self):
+ ''' Alias for :attr:`is_xhr`. "Ajax" is not the right term. '''
+ return self.is_xhr
+
+ @property
+ def auth(self):
+ """ HTTP authentication data as a (user, password) tuple. This
+ implementation currently supports basic (not digest) authentication
+ only. If the authentication happened at a higher level (e.g. in the
+ front web-server or a middleware), the password field is None, but
+ the user field is looked up from the ``REMOTE_USER`` environ
+ variable. On any errors, None is returned. """
+ basic = parse_auth(self.environ.get('HTTP_AUTHORIZATION',''))
+ if basic: return basic
+ ruser = self.environ.get('REMOTE_USER')
+ if ruser: return (ruser, None)
+ return None
+
+ @property
+ def remote_route(self):
+ """ A list of all IPs that were involved in this request, starting with
+ the client IP and followed by zero or more proxies. This does only
+ work if all proxies support the ```X-Forwarded-For`` header. Note
+ that this information can be forged by malicious clients. """
+ proxy = self.environ.get('HTTP_X_FORWARDED_FOR')
+ if proxy: return [ip.strip() for ip in proxy.split(',')]
+ remote = self.environ.get('REMOTE_ADDR')
+ return [remote] if remote else []
+
+ @property
+ def remote_addr(self):
+ """ The client IP as a string. Note that this information can be forged
+ by malicious clients. """
+ route = self.remote_route
+ return route[0] if route else None
+
+ def copy(self):
+ """ Return a new :class:`Request` with a shallow :attr:`environ` copy. """
+ return Request(self.environ.copy())
+
+ def get(self, value, default=None): return self.environ.get(value, default)
+ def __getitem__(self, key): return self.environ[key]
+ def __delitem__(self, key): self[key] = ""; del(self.environ[key])
+ def __iter__(self): return iter(self.environ)
+ def __len__(self): return len(self.environ)
+ def keys(self): return self.environ.keys()
+ def __setitem__(self, key, value):
+ """ Change an environ value and clear all caches that depend on it. """
+
+ if self.environ.get('bottle.request.readonly'):
+ raise KeyError('The environ dictionary is read-only.')
+
+ self.environ[key] = value
+ todelete = ()
+
+ if key == 'wsgi.input':
+ todelete = ('body', 'forms', 'files', 'params', 'post', 'json')
+ elif key == 'QUERY_STRING':
+ todelete = ('query', 'params')
+ elif key.startswith('HTTP_'):
+ todelete = ('headers', 'cookies')
+
+ for key in todelete:
+ self.environ.pop('bottle.request.'+key, None)
+
+ def __repr__(self):
+ return '<%s: %s %s>' % (self.__class__.__name__, self.method, self.url)
+
+ def __getattr__(self, name):
+ ''' Search in self.environ for additional user defined attributes. '''
+ try:
+ var = self.environ['bottle.request.ext.%s'%name]
+ return var.__get__(self) if hasattr(var, '__get__') else var
+ except KeyError:
+ raise AttributeError('Attribute %r not defined.' % name)
+
+ def __setattr__(self, name, value):
+ if name == 'environ': return object.__setattr__(self, name, value)
+ self.environ['bottle.request.ext.%s'%name] = value
+
+
+
+
+def _hkey(s):
+ return s.title().replace('_','-')
+
+
+class HeaderProperty(object):
+ def __init__(self, name, reader=None, writer=str, default=''):
+ self.name, self.default = name, default
+ self.reader, self.writer = reader, writer
+ self.__doc__ = 'Current value of the %r header.' % name.title()
+
+ def __get__(self, obj, cls):
+ if obj is None: return self
+ value = obj.headers.get(self.name, self.default)
+ return self.reader(value) if self.reader else value
+
+ def __set__(self, obj, value):
+ obj.headers[self.name] = self.writer(value)
+
+ def __delete__(self, obj):
+ del obj.headers[self.name]
+
+
+class BaseResponse(object):
+ """ Storage class for a response body as well as headers and cookies.
+
+ This class does support dict-like case-insensitive item-access to
+ headers, but is NOT a dict. Most notably, iterating over a response
+ yields parts of the body and not the headers.
+
+ :param body: The response body as one of the supported types.
+ :param status: Either an HTTP status code (e.g. 200) or a status line
+ including the reason phrase (e.g. '200 OK').
+ :param headers: A dictionary or a list of name-value pairs.
+
+ Additional keyword arguments are added to the list of headers.
+ Underscores in the header name are replaced with dashes.
+ """
+
+ default_status = 200
+ default_content_type = 'text/html; charset=UTF-8'
+
+ # Header blacklist for specific response codes
+ # (rfc2616 section 10.2.3 and 10.3.5)
+ bad_headers = {
+ 204: set(('Content-Type',)),
+ 304: set(('Allow', 'Content-Encoding', 'Content-Language',
+ 'Content-Length', 'Content-Range', 'Content-Type',
+ 'Content-Md5', 'Last-Modified'))}
+
+ def __init__(self, body='', status=None, headers=None, **more_headers):
+ self._cookies = None
+ self._headers = {}
+ self.body = body
+ self.status = status or self.default_status
+ if headers:
+ if isinstance(headers, dict):
+ headers = headers.items()
+ for name, value in headers:
+ self.add_header(name, value)
+ if more_headers:
+ for name, value in more_headers.items():
+ self.add_header(name, value)
+
+ def copy(self, cls=None):
+ ''' Returns a copy of self. '''
+ cls = cls or BaseResponse
+ assert issubclass(cls, BaseResponse)
+ copy = cls()
+ copy.status = self.status
+ copy._headers = dict((k, v[:]) for (k, v) in self._headers.items())
+ if self._cookies:
+ copy._cookies = SimpleCookie()
+ copy._cookies.load(self._cookies.output())
+ return copy
+
+ def __iter__(self):
+ return iter(self.body)
+
+ def close(self):
+ if hasattr(self.body, 'close'):
+ self.body.close()
+
+ @property
+ def status_line(self):
+ ''' The HTTP status line as a string (e.g. ``404 Not Found``).'''
+ return self._status_line
+
+ @property
+ def status_code(self):
+ ''' The HTTP status code as an integer (e.g. 404).'''
+ return self._status_code
+
+ def _set_status(self, status):
+ if isinstance(status, int):
+ code, status = status, _HTTP_STATUS_LINES.get(status)
+ elif ' ' in status:
+ status = status.strip()
+ code = int(status.split()[0])
+ else:
+ raise ValueError('String status line without a reason phrase.')
+ if not 100 <= code <= 999: raise ValueError('Status code out of range.')
+ self._status_code = code
+ self._status_line = str(status or ('%d Unknown' % code))
+
+ def _get_status(self):
+ return self._status_line
+
+ status = property(_get_status, _set_status, None,
+ ''' A writeable property to change the HTTP response status. It accepts
+ either a numeric code (100-999) or a string with a custom reason
+ phrase (e.g. "404 Brain not found"). Both :data:`status_line` and
+ :data:`status_code` are updated accordingly. The return value is
+ always a status string. ''')
+ del _get_status, _set_status
+
+ @property
+ def headers(self):
+ ''' An instance of :class:`HeaderDict`, a case-insensitive dict-like
+ view on the response headers. '''
+ hdict = HeaderDict()
+ hdict.dict = self._headers
+ return hdict
+
+ def __contains__(self, name): return _hkey(name) in self._headers
+ def __delitem__(self, name): del self._headers[_hkey(name)]
+ def __getitem__(self, name): return self._headers[_hkey(name)][-1]
+ def __setitem__(self, name, value): self._headers[_hkey(name)] = [str(value)]
+
+ def get_header(self, name, default=None):
+ ''' Return the value of a previously defined header. If there is no
+ header with that name, return a default value. '''
+ return self._headers.get(_hkey(name), [default])[-1]
+
+ def set_header(self, name, value):
+ ''' Create a new response header, replacing any previously defined
+ headers with the same name. '''
+ self._headers[_hkey(name)] = [str(value)]
+
+ def add_header(self, name, value):
+ ''' Add an additional response header, not removing duplicates. '''
+ self._headers.setdefault(_hkey(name), []).append(str(value))
+
+ def iter_headers(self):
+ ''' Yield (header, value) tuples, skipping headers that are not
+ allowed with the current response status code. '''
+ return self.headerlist
+
+ @property
+ def headerlist(self):
+ ''' WSGI conform list of (header, value) tuples. '''
+ out = []
+ headers = list(self._headers.items())
+ if 'Content-Type' not in self._headers:
+ headers.append(('Content-Type', [self.default_content_type]))
+ if self._status_code in self.bad_headers:
+ bad_headers = self.bad_headers[self._status_code]
+ headers = [h for h in headers if h[0] not in bad_headers]
+ out += [(name, val) for name, vals in headers for val in vals]
+ if self._cookies:
+ for c in self._cookies.values():
+ out.append(('Set-Cookie', c.OutputString()))
+ return out
+
+ content_type = HeaderProperty('Content-Type')
+ content_length = HeaderProperty('Content-Length', reader=int)
+ expires = HeaderProperty('Expires',
+ reader=lambda x: datetime.utcfromtimestamp(parse_date(x)),
+ writer=lambda x: http_date(x))
+
+ @property
+ def charset(self, default='UTF-8'):
+ """ Return the charset specified in the content-type header (default: utf8). """
+ if 'charset=' in self.content_type:
+ return self.content_type.split('charset=')[-1].split(';')[0].strip()
+ return default
+
+ def set_cookie(self, name, value, secret=None, **options):
+ ''' Create a new cookie or replace an old one. If the `secret` parameter is
+ set, create a `Signed Cookie` (described below).
+
+ :param name: the name of the cookie.
+ :param value: the value of the cookie.
+ :param secret: a signature key required for signed cookies.
+
+ Additionally, this method accepts all RFC 2109 attributes that are
+ supported by :class:`cookie.Morsel`, including:
+
+ :param max_age: maximum age in seconds. (default: None)
+ :param expires: a datetime object or UNIX timestamp. (default: None)
+ :param domain: the domain that is allowed to read the cookie.
+ (default: current domain)
+ :param path: limits the cookie to a given path (default: current path)
+ :param secure: limit the cookie to HTTPS connections (default: off).
+ :param httponly: prevents client-side javascript to read this cookie
+ (default: off, requires Python 2.6 or newer).
+
+ If neither `expires` nor `max_age` is set (default), the cookie will
+ expire at the end of the browser session (as soon as the browser
+ window is closed).
+
+ Signed cookies may store any pickle-able object and are
+ cryptographically signed to prevent manipulation. Keep in mind that
+ cookies are limited to 4kb in most browsers.
+
+ Warning: Signed cookies are not encrypted (the client can still see
+ the content) and not copy-protected (the client can restore an old
+ cookie). The main intention is to make pickling and unpickling
+ save, not to store secret information at client side.
+ '''
+ if not self._cookies:
+ self._cookies = SimpleCookie()
+
+ if secret:
+ value = touni(cookie_encode((name, value), secret))
+ elif not isinstance(value, basestring):
+ raise TypeError('Secret key missing for non-string Cookie.')
+
+ if len(value) > 4096: raise ValueError('Cookie value to long.')
+ self._cookies[name] = value
+
+ for key, value in options.items():
+ if key == 'max_age':
+ if isinstance(value, timedelta):
+ value = value.seconds + value.days * 24 * 3600
+ if key == 'expires':
+ if isinstance(value, (datedate, datetime)):
+ value = value.timetuple()
+ elif isinstance(value, (int, float)):
+ value = time.gmtime(value)
+ value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
+ self._cookies[name][key.replace('_', '-')] = value
+
+ def delete_cookie(self, key, **kwargs):
+ ''' Delete a cookie. Be sure to use the same `domain` and `path`
+ settings as used to create the cookie. '''
+ kwargs['max_age'] = -1
+ kwargs['expires'] = 0
+ self.set_cookie(key, '', **kwargs)
+
+ def __repr__(self):
+ out = ''
+ for name, value in self.headerlist:
+ out += '%s: %s\n' % (name.title(), value.strip())
+ return out
+
+
+def local_property(name=None):
+ if name: depr('local_property() is deprecated and will be removed.') #0.12
+ ls = threading.local()
+ def fget(self):
+ try: return ls.var
+ except AttributeError:
+ raise RuntimeError("Request context not initialized.")
+ def fset(self, value): ls.var = value
+ def fdel(self): del ls.var
+ return property(fget, fset, fdel, 'Thread-local property')
+
+
+class LocalRequest(BaseRequest):
+ ''' A thread-local subclass of :class:`BaseRequest` with a different
+ set of attributes for each thread. There is usually only one global
+ instance of this class (:data:`request`). If accessed during a
+ request/response cycle, this instance always refers to the *current*
+ request (even on a multithreaded server). '''
+ bind = BaseRequest.__init__
+ environ = local_property()
+
+
+class LocalResponse(BaseResponse):
+ ''' A thread-local subclass of :class:`BaseResponse` with a different
+ set of attributes for each thread. There is usually only one global
+ instance of this class (:data:`response`). Its attributes are used
+ to build the HTTP response at the end of the request/response cycle.
+ '''
+ bind = BaseResponse.__init__
+ _status_line = local_property()
+ _status_code = local_property()
+ _cookies = local_property()
+ _headers = local_property()
+ body = local_property()
+
+
+Request = BaseRequest
+Response = BaseResponse
+
+
+class HTTPResponse(Response, BottleException):
+ def __init__(self, body='', status=None, headers=None, **more_headers):
+ super(HTTPResponse, self).__init__(body, status, headers, **more_headers)
+
+ def apply(self, response):
+ response._status_code = self._status_code
+ response._status_line = self._status_line
+ response._headers = self._headers
+ response._cookies = self._cookies
+ response.body = self.body
+
+
+class HTTPError(HTTPResponse):
+ default_status = 500
+ def __init__(self, status=None, body=None, exception=None, traceback=None,
+ **options):
+ self.exception = exception
+ self.traceback = traceback
+ super(HTTPError, self).__init__(body, status, **options)
+
+
+
+
+
+###############################################################################
+# Plugins ######################################################################
+###############################################################################
+
+class PluginError(BottleException): pass
+
+
+class JSONPlugin(object):
+ name = 'json'
+ api = 2
+
+ def __init__(self, json_dumps=json_dumps):
+ self.json_dumps = json_dumps
+
+ def apply(self, callback, route):
+ dumps = self.json_dumps
+ if not dumps: return callback
+ def wrapper(*a, **ka):
+ try:
+ rv = callback(*a, **ka)
+ except HTTPError:
+ rv = _e()
+
+ if isinstance(rv, dict):
+ #Attempt to serialize, raises exception on failure
+ json_response = dumps(rv)
+ #Set content type only if serialization succesful
+ response.content_type = 'application/json'
+ return json_response
+ elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
+ rv.body = dumps(rv.body)
+ rv.content_type = 'application/json'
+ return rv
+
+ return wrapper
+
+
+class TemplatePlugin(object):
+ ''' This plugin applies the :func:`view` decorator to all routes with a
+ `template` config parameter. If the parameter is a tuple, the second
+ element must be a dict with additional options (e.g. `template_engine`)
+ or default variables for the template. '''
+ name = 'template'
+ api = 2
+
+ def apply(self, callback, route):
+ conf = route.config.get('template')
+ if isinstance(conf, (tuple, list)) and len(conf) == 2:
+ return view(conf[0], **conf[1])(callback)
+ elif isinstance(conf, str):
+ return view(conf)(callback)
+ else:
+ return callback
+
+
+#: Not a plugin, but part of the plugin API. TODO: Find a better place.
+class _ImportRedirect(object):
+ def __init__(self, name, impmask):
+ ''' Create a virtual package that redirects imports (see PEP 302). '''
+ self.name = name
+ self.impmask = impmask
+ self.module = sys.modules.setdefault(name, imp.new_module(name))
+ self.module.__dict__.update({'__file__': __file__, '__path__': [],
+ '__all__': [], '__loader__': self})
+ sys.meta_path.append(self)
+
+ def find_module(self, fullname, path=None):
+ if '.' not in fullname: return
+ packname = fullname.rsplit('.', 1)[0]
+ if packname != self.name: return
+ return self
+
+ def load_module(self, fullname):
+ if fullname in sys.modules: return sys.modules[fullname]
+ modname = fullname.rsplit('.', 1)[1]
+ realname = self.impmask % modname
+ __import__(realname)
+ module = sys.modules[fullname] = sys.modules[realname]
+ setattr(self.module, modname, module)
+ module.__loader__ = self
+ return module
+
+
+
+
+
+
+###############################################################################
+# Common Utilities #############################################################
+###############################################################################
+
+
+class MultiDict(DictMixin):
+ """ This dict stores multiple values per key, but behaves exactly like a
+ normal dict in that it returns only the newest value for any given key.
+ There are special methods available to access the full list of values.
+ """
+
+ def __init__(self, *a, **k):
+ self.dict = dict((k, [v]) for (k, v) in dict(*a, **k).items())
+
+ def __len__(self): return len(self.dict)
+ def __iter__(self): return iter(self.dict)
+ def __contains__(self, key): return key in self.dict
+ def __delitem__(self, key): del self.dict[key]
+ def __getitem__(self, key): return self.dict[key][-1]
+ def __setitem__(self, key, value): self.append(key, value)
+ def keys(self): return self.dict.keys()
+
+ if py3k:
+ def values(self): return (v[-1] for v in self.dict.values())
+ def items(self): return ((k, v[-1]) for k, v in self.dict.items())
+ def allitems(self):
+ return ((k, v) for k, vl in self.dict.items() for v in vl)
+ iterkeys = keys
+ itervalues = values
+ iteritems = items
+ iterallitems = allitems
+
+ else:
+ def values(self): return [v[-1] for v in self.dict.values()]
+ def items(self): return [(k, v[-1]) for k, v in self.dict.items()]
+ def iterkeys(self): return self.dict.iterkeys()
+ def itervalues(self): return (v[-1] for v in self.dict.itervalues())
+ def iteritems(self):
+ return ((k, v[-1]) for k, v in self.dict.iteritems())
+ def iterallitems(self):
+ return ((k, v) for k, vl in self.dict.iteritems() for v in vl)
+ def allitems(self):
+ return [(k, v) for k, vl in self.dict.iteritems() for v in vl]
+
+ def get(self, key, default=None, index=-1, type=None):
+ ''' Return the most recent value for a key.
+
+ :param default: The default value to be returned if the key is not
+ present or the type conversion fails.
+ :param index: An index for the list of available values.
+ :param type: If defined, this callable is used to cast the value
+ into a specific type. Exception are suppressed and result in
+ the default value to be returned.
+ '''
+ try:
+ val = self.dict[key][index]
+ return type(val) if type else val
+ except Exception:
+ pass
+ return default
+
+ def append(self, key, value):
+ ''' Add a new value to the list of values for this key. '''
+ self.dict.setdefault(key, []).append(value)
+
+ def replace(self, key, value):
+ ''' Replace the list of values with a single value. '''
+ self.dict[key] = [value]
+
+ def getall(self, key):
+ ''' Return a (possibly empty) list of values for a key. '''
+ return self.dict.get(key) or []
+
+ #: Aliases for WTForms to mimic other multi-dict APIs (Django)
+ getone = get
+ getlist = getall
+
+
+class FormsDict(MultiDict):
+ ''' This :class:`MultiDict` subclass is used to store request form data.
+ Additionally to the normal dict-like item access methods (which return
+ unmodified data as native strings), this container also supports
+ attribute-like access to its values. Attributes are automatically de-
+ or recoded to match :attr:`input_encoding` (default: 'utf8'). Missing
+ attributes default to an empty string. '''
+
+ #: Encoding used for attribute values.
+ input_encoding = 'utf8'
+ #: If true (default), unicode strings are first encoded with `latin1`
+ #: and then decoded to match :attr:`input_encoding`.
+ recode_unicode = True
+
+ def _fix(self, s, encoding=None):
+ if isinstance(s, unicode) and self.recode_unicode: # Python 3 WSGI
+ return s.encode('latin1').decode(encoding or self.input_encoding)
+ elif isinstance(s, bytes): # Python 2 WSGI
+ return s.decode(encoding or self.input_encoding)
+ else:
+ return s
+
+ def decode(self, encoding=None):
+ ''' Returns a copy with all keys and values de- or recoded to match
+ :attr:`input_encoding`. Some libraries (e.g. WTForms) want a
+ unicode dictionary. '''
+ copy = FormsDict()
+ enc = copy.input_encoding = encoding or self.input_encoding
+ copy.recode_unicode = False
+ for key, value in self.allitems():
+ copy.append(self._fix(key, enc), self._fix(value, enc))
+ return copy
+
+ def getunicode(self, name, default=None, encoding=None):
+ ''' Return the value as a unicode string, or the default. '''
+ try:
+ return self._fix(self[name], encoding)
+ except (UnicodeError, KeyError):
+ return default
+
+ def __getattr__(self, name, default=unicode()):
+ # Without this guard, pickle generates a cryptic TypeError:
+ if name.startswith('__') and name.endswith('__'):
+ return super(FormsDict, self).__getattr__(name)
+ return self.getunicode(name, default=default)
+
+
+class HeaderDict(MultiDict):
+ """ A case-insensitive version of :class:`MultiDict` that defaults to
+ replace the old value instead of appending it. """
+
+ def __init__(self, *a, **ka):
+ self.dict = {}
+ if a or ka: self.update(*a, **ka)
+
+ def __contains__(self, key): return _hkey(key) in self.dict
+ def __delitem__(self, key): del self.dict[_hkey(key)]
+ def __getitem__(self, key): return self.dict[_hkey(key)][-1]
+ def __setitem__(self, key, value): self.dict[_hkey(key)] = [str(value)]
+ def append(self, key, value):
+ self.dict.setdefault(_hkey(key), []).append(str(value))
+ def replace(self, key, value): self.dict[_hkey(key)] = [str(value)]
+ def getall(self, key): return self.dict.get(_hkey(key)) or []
+ def get(self, key, default=None, index=-1):
+ return MultiDict.get(self, _hkey(key), default, index)
+ def filter(self, names):
+ for name in [_hkey(n) for n in names]:
+ if name in self.dict:
+ del self.dict[name]
+
+
+class WSGIHeaderDict(DictMixin):
+ ''' This dict-like class wraps a WSGI environ dict and provides convenient
+ access to HTTP_* fields. Keys and values are native strings
+ (2.x bytes or 3.x unicode) and keys are case-insensitive. If the WSGI
+ environment contains non-native string values, these are de- or encoded
+ using a lossless 'latin1' character set.
+
+ The API will remain stable even on changes to the relevant PEPs.
+ Currently PEP 333, 444 and 3333 are supported. (PEP 444 is the only one
+ that uses non-native strings.)
+ '''
+ #: List of keys that do not have a ``HTTP_`` prefix.
+ cgikeys = ('CONTENT_TYPE', 'CONTENT_LENGTH')
+
+ def __init__(self, environ):
+ self.environ = environ
+
+ def _ekey(self, key):
+ ''' Translate header field name to CGI/WSGI environ key. '''
+ key = key.replace('-','_').upper()
+ if key in self.cgikeys:
+ return key
+ return 'HTTP_' + key
+
+ def raw(self, key, default=None):
+ ''' Return the header value as is (may be bytes or unicode). '''
+ return self.environ.get(self._ekey(key), default)
+
+ def __getitem__(self, key):
+ return tonat(self.environ[self._ekey(key)], 'latin1')
+
+ def __setitem__(self, key, value):
+ raise TypeError("%s is read-only." % self.__class__)
+
+ def __delitem__(self, key):
+ raise TypeError("%s is read-only." % self.__class__)
+
+ def __iter__(self):
+ for key in self.environ:
+ if key[:5] == 'HTTP_':
+ yield key[5:].replace('_', '-').title()
+ elif key in self.cgikeys:
+ yield key.replace('_', '-').title()
+
+ def keys(self): return [x for x in self]
+ def __len__(self): return len(self.keys())
+ def __contains__(self, key): return self._ekey(key) in self.environ
+
+
+
+class ConfigDict(dict):
+ ''' A dict-like configuration storage with additional support for
+ namespaces, validators, meta-data, on_change listeners and more.
+
+ This storage is optimized for fast read access. Retrieving a key
+ or using non-altering dict methods (e.g. `dict.get()`) has no overhead
+ compared to a native dict.
+ '''
+ __slots__ = ('_meta', '_on_change')
+
+ class Namespace(DictMixin):
+
+ def __init__(self, config, namespace):
+ self._config = config
+ self._prefix = namespace
+
+ def __getitem__(self, key):
+ depr('Accessing namespaces as dicts is discouraged. '
+ 'Only use flat item access: '
+ 'cfg["names"]["pace"]["key"] -> cfg["name.space.key"]') #0.12
+ return self._config[self._prefix + '.' + key]
+
+ def __setitem__(self, key, value):
+ self._config[self._prefix + '.' + key] = value
+
+ def __delitem__(self, key):
+ del self._config[self._prefix + '.' + key]
+
+ def __iter__(self):
+ ns_prefix = self._prefix + '.'
+ for key in self._config:
+ ns, dot, name = key.rpartition('.')
+ if ns == self._prefix and name:
+ yield name
+
+ def keys(self): return [x for x in self]
+ def __len__(self): return len(self.keys())
+ def __contains__(self, key): return self._prefix + '.' + key in self._config
+ def __repr__(self): return '<Config.Namespace %s.*>' % self._prefix
+ def __str__(self): return '<Config.Namespace %s.*>' % self._prefix
+
+ # Deprecated ConfigDict features
+ def __getattr__(self, key):
+ depr('Attribute access is deprecated.') #0.12
+ if key not in self and key[0].isupper():
+ self[key] = ConfigDict.Namespace(self._config, self._prefix + '.' + key)
+ if key not in self and key.startswith('__'):
+ raise AttributeError(key)
+ return self.get(key)
+
+ def __setattr__(self, key, value):
+ if key in ('_config', '_prefix'):
+ self.__dict__[key] = value
+ return
+ depr('Attribute assignment is deprecated.') #0.12
+ if hasattr(DictMixin, key):
+ raise AttributeError('Read-only attribute.')
+ if key in self and self[key] and isinstance(self[key], self.__class__):
+ raise AttributeError('Non-empty namespace attribute.')
+ self[key] = value
+
+ def __delattr__(self, key):
+ if key in self:
+ val = self.pop(key)
+ if isinstance(val, self.__class__):
+ prefix = key + '.'
+ for key in self:
+ if key.startswith(prefix):
+ del self[prefix+key]
+
+ def __call__(self, *a, **ka):
+ depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
+ self.update(*a, **ka)
+ return self
+
+ def __init__(self, *a, **ka):
+ self._meta = {}
+ self._on_change = lambda name, value: None
+ if a or ka:
+ depr('Constructor does no longer accept parameters.') #0.12
+ self.update(*a, **ka)
+
+ def load_config(self, filename):
+ ''' Load values from an *.ini style config file.
+
+ If the config file contains sections, their names are used as
+ namespaces for the values within. The two special sections
+ ``DEFAULT`` and ``bottle`` refer to the root namespace (no prefix).
+ '''
+ conf = ConfigParser()
+ conf.read(filename)
+ for section in conf.sections():
+ for key, value in conf.items(section):
+ if section not in ('DEFAULT', 'bottle'):
+ key = section + '.' + key
+ self[key] = value
+ return self
+
+ def load_dict(self, source, namespace='', make_namespaces=False):
+ ''' Import values from a dictionary structure. Nesting can be used to
+ represent namespaces.
+
+ >>> ConfigDict().load_dict({'name': {'space': {'key': 'value'}}})
+ {'name.space.key': 'value'}
+ '''
+ stack = [(namespace, source)]
+ while stack:
+ prefix, source = stack.pop()
+ if not isinstance(source, dict):
+ raise TypeError('Source is not a dict (r)' % type(key))
+ for key, value in source.items():
+ if not isinstance(key, str):
+ raise TypeError('Key is not a string (%r)' % type(key))
+ full_key = prefix + '.' + key if prefix else key
+ if isinstance(value, dict):
+ stack.append((full_key, value))
+ if make_namespaces:
+ self[full_key] = self.Namespace(self, full_key)
+ else:
+ self[full_key] = value
+ return self
+
+ def update(self, *a, **ka):
+ ''' If the first parameter is a string, all keys are prefixed with this
+ namespace. Apart from that it works just as the usual dict.update().
+ Example: ``update('some.namespace', key='value')`` '''
+ prefix = ''
+ if a and isinstance(a[0], str):
+ prefix = a[0].strip('.') + '.'
+ a = a[1:]
+ for key, value in dict(*a, **ka).items():
+ self[prefix+key] = value
+
+ def setdefault(self, key, value):
+ if key not in self:
+ self[key] = value
+ return self[key]
+
+ def __setitem__(self, key, value):
+ if not isinstance(key, str):
+ raise TypeError('Key has type %r (not a string)' % type(key))
+
+ value = self.meta_get(key, 'filter', lambda x: x)(value)
+ if key in self and self[key] is value:
+ return
+ self._on_change(key, value)
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ dict.__delitem__(self, key)
+
+ def clear(self):
+ for key in self:
+ del self[key]
+
+ def meta_get(self, key, metafield, default=None):
+ ''' Return the value of a meta field for a key. '''
+ return self._meta.get(key, {}).get(metafield, default)
+
+ def meta_set(self, key, metafield, value):
+ ''' Set the meta field for a key to a new value. This triggers the
+ on-change handler for existing keys. '''
+ self._meta.setdefault(key, {})[metafield] = value
+ if key in self:
+ self[key] = self[key]
+
+ def meta_list(self, key):
+ ''' Return an iterable of meta field names defined for a key. '''
+ return self._meta.get(key, {}).keys()
+
+ # Deprecated ConfigDict features
+ def __getattr__(self, key):
+ depr('Attribute access is deprecated.') #0.12
+ if key not in self and key[0].isupper():
+ self[key] = self.Namespace(self, key)
+ if key not in self and key.startswith('__'):
+ raise AttributeError(key)
+ return self.get(key)
+
+ def __setattr__(self, key, value):
+ if key in self.__slots__:
+ return dict.__setattr__(self, key, value)
+ depr('Attribute assignment is deprecated.') #0.12
+ if hasattr(dict, key):
+ raise AttributeError('Read-only attribute.')
+ if key in self and self[key] and isinstance(self[key], self.Namespace):
+ raise AttributeError('Non-empty namespace attribute.')
+ self[key] = value
+
+ def __delattr__(self, key):
+ if key in self:
+ val = self.pop(key)
+ if isinstance(val, self.Namespace):
+ prefix = key + '.'
+ for key in self:
+ if key.startswith(prefix):
+ del self[prefix+key]
+
+ def __call__(self, *a, **ka):
+ depr('Calling ConfDict is deprecated. Use the update() method.') #0.12
+ self.update(*a, **ka)
+ return self
+
+
+
+class AppStack(list):
+ """ A stack-like list. Calling it returns the head of the stack. """
+
+ def __call__(self):
+ """ Return the current default application. """
+ return self[-1]
+
+ def push(self, value=None):
+ """ Add a new :class:`Bottle` instance to the stack """
+ if not isinstance(value, Bottle):
+ value = Bottle()
+ self.append(value)
+ return value
+
+
+class WSGIFileWrapper(object):
+
+ def __init__(self, fp, buffer_size=1024*64):
+ self.fp, self.buffer_size = fp, buffer_size
+ for attr in ('fileno', 'close', 'read', 'readlines', 'tell', 'seek'):
+ if hasattr(fp, attr): setattr(self, attr, getattr(fp, attr))
+
+ def __iter__(self):
+ buff, read = self.buffer_size, self.read
+ while True:
+ part = read(buff)
+ if not part: return
+ yield part
+
+
+class _closeiter(object):
+ ''' This only exists to be able to attach a .close method to iterators that
+ do not support attribute assignment (most of itertools). '''
+
+ def __init__(self, iterator, close=None):
+ self.iterator = iterator
+ self.close_callbacks = makelist(close)
+
+ def __iter__(self):
+ return iter(self.iterator)
+
+ def close(self):
+ for func in self.close_callbacks:
+ func()
+
+
+class ResourceManager(object):
+ ''' This class manages a list of search paths and helps to find and open
+ application-bound resources (files).
+
+ :param base: default value for :meth:`add_path` calls.
+ :param opener: callable used to open resources.
+ :param cachemode: controls which lookups are cached. One of 'all',
+ 'found' or 'none'.
+ '''
+
+ def __init__(self, base='./', opener=open, cachemode='all'):
+ self.opener = open
+ self.base = base
+ self.cachemode = cachemode
+
+ #: A list of search paths. See :meth:`add_path` for details.
+ self.path = []
+ #: A cache for resolved paths. ``res.cache.clear()`` clears the cache.
+ self.cache = {}
+
+ def add_path(self, path, base=None, index=None, create=False):
+ ''' Add a new path to the list of search paths. Return False if the
+ path does not exist.
+
+ :param path: The new search path. Relative paths are turned into
+ an absolute and normalized form. If the path looks like a file
+ (not ending in `/`), the filename is stripped off.
+ :param base: Path used to absolutize relative search paths.
+ Defaults to :attr:`base` which defaults to ``os.getcwd()``.
+ :param index: Position within the list of search paths. Defaults
+ to last index (appends to the list).
+
+ The `base` parameter makes it easy to reference files installed
+ along with a python module or package::
+
+ res.add_path('./resources/', __file__)
+ '''
+ base = os.path.abspath(os.path.dirname(base or self.base))
+ path = os.path.abspath(os.path.join(base, os.path.dirname(path)))
+ path += os.sep
+ if path in self.path:
+ self.path.remove(path)
+ if create and not os.path.isdir(path):
+ os.makedirs(path)
+ if index is None:
+ self.path.append(path)
+ else:
+ self.path.insert(index, path)
+ self.cache.clear()
+ return os.path.exists(path)
+
+ def __iter__(self):
+ ''' Iterate over all existing files in all registered paths. '''
+ search = self.path[:]
+ while search:
+ path = search.pop()
+ if not os.path.isdir(path): continue
+ for name in os.listdir(path):
+ full = os.path.join(path, name)
+ if os.path.isdir(full): search.append(full)
+ else: yield full
+
+ def lookup(self, name):
+ ''' Search for a resource and return an absolute file path, or `None`.
+
+ The :attr:`path` list is searched in order. The first match is
+ returend. Symlinks are followed. The result is cached to speed up
+ future lookups. '''
+ if name not in self.cache or DEBUG:
+ for path in self.path:
+ fpath = os.path.join(path, name)
+ if os.path.isfile(fpath):
+ if self.cachemode in ('all', 'found'):
+ self.cache[name] = fpath
+ return fpath
+ if self.cachemode == 'all':
+ self.cache[name] = None
+ return self.cache[name]
+
+ def open(self, name, mode='r', *args, **kwargs):
+ ''' Find a resource and return a file object, or raise IOError. '''
+ fname = self.lookup(name)
+ if not fname: raise IOError("Resource %r not found." % name)
+ return self.opener(fname, mode=mode, *args, **kwargs)
+
+
+class FileUpload(object):
+
+ def __init__(self, fileobj, name, filename, headers=None):
+ ''' Wrapper for file uploads. '''
+ #: Open file(-like) object (BytesIO buffer or temporary file)
+ self.file = fileobj
+ #: Name of the upload form field
+ self.name = name
+ #: Raw filename as sent by the client (may contain unsafe characters)
+ self.raw_filename = filename
+ #: A :class:`HeaderDict` with additional headers (e.g. content-type)
+ self.headers = HeaderDict(headers) if headers else HeaderDict()
+
+ content_type = HeaderProperty('Content-Type')
+ content_length = HeaderProperty('Content-Length', reader=int, default=-1)
+
+ @cached_property
+ def filename(self):
+ ''' Name of the file on the client file system, but normalized to ensure
+ file system compatibility. An empty filename is returned as 'empty'.
+
+ Only ASCII letters, digits, dashes, underscores and dots are
+ allowed in the final filename. Accents are removed, if possible.
+ Whitespace is replaced by a single dash. Leading or tailing dots
+ or dashes are removed. The filename is limited to 255 characters.
+ '''
+ fname = self.raw_filename
+ if not isinstance(fname, unicode):
+ fname = fname.decode('utf8', 'ignore')
+ fname = normalize('NFKD', fname).encode('ASCII', 'ignore').decode('ASCII')
+ fname = os.path.basename(fname.replace('\\', os.path.sep))
+ fname = re.sub(r'[^a-zA-Z0-9-_.\s]', '', fname).strip()
+ fname = re.sub(r'[-\s]+', '-', fname).strip('.-')
+ return fname[:255] or 'empty'
+
+ def _copy_file(self, fp, chunk_size=2**16):
+ read, write, offset = self.file.read, fp.write, self.file.tell()
+ while 1:
+ buf = read(chunk_size)
+ if not buf: break
+ write(buf)
+ self.file.seek(offset)
+
+ def save(self, destination, overwrite=False, chunk_size=2**16):
+ ''' Save file to disk or copy its content to an open file(-like) object.
+ If *destination* is a directory, :attr:`filename` is added to the
+ path. Existing files are not overwritten by default (IOError).
+
+ :param destination: File path, directory or file(-like) object.
+ :param overwrite: If True, replace existing files. (default: False)
+ :param chunk_size: Bytes to read at a time. (default: 64kb)
+ '''
+ if isinstance(destination, basestring): # Except file-likes here
+ if os.path.isdir(destination):
+ destination = os.path.join(destination, self.filename)
+ if not overwrite and os.path.exists(destination):
+ raise IOError('File exists.')
+ with open(destination, 'wb') as fp:
+ self._copy_file(fp, chunk_size)
+ else:
+ self._copy_file(destination, chunk_size)
+
+
+
+
+
+
+###############################################################################
+# Application Helper ###########################################################
+###############################################################################
+
+
+def abort(code=500, text='Unknown Error.'):
+ """ Aborts execution and causes a HTTP error. """
+ raise HTTPError(code, text)
+
+
+def redirect(url, code=None):
+ """ Aborts execution and causes a 303 or 302 redirect, depending on
+ the HTTP protocol version. """
+ if not code:
+ code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
+ res = response.copy(cls=HTTPResponse)
+ res.status = code
+ res.body = ""
+ res.set_header('Location', urljoin(request.url, url))
+ raise res
+
+
+def _file_iter_range(fp, offset, bytes, maxread=1024*1024):
+ ''' Yield chunks from a range in a file. No chunk is bigger than maxread.'''
+ fp.seek(offset)
+ while bytes > 0:
+ part = fp.read(min(bytes, maxread))
+ if not part: break
+ bytes -= len(part)
+ yield part
+
+
+def static_file(filename, root, mimetype='auto', download=False, charset='UTF-8'):
+ """ Open a file in a safe way and return :exc:`HTTPResponse` with status
+ code 200, 305, 403 or 404. The ``Content-Type``, ``Content-Encoding``,
+ ``Content-Length`` and ``Last-Modified`` headers are set if possible.
+ Special support for ``If-Modified-Since``, ``Range`` and ``HEAD``
+ requests.
+
+ :param filename: Name or path of the file to send.
+ :param root: Root path for file lookups. Should be an absolute directory
+ path.
+ :param mimetype: Defines the content-type header (default: guess from
+ file extension)
+ :param download: If True, ask the browser to open a `Save as...` dialog
+ instead of opening the file with the associated program. You can
+ specify a custom filename as a string. If not specified, the
+ original filename is used (default: False).
+ :param charset: The charset to use for files with a ``text/*``
+ mime-type. (default: UTF-8)
+ """
+
+ root = os.path.abspath(root) + os.sep
+ filename = os.path.abspath(os.path.join(root, filename.strip('/\\')))
+ headers = dict()
+
+ if not filename.startswith(root):
+ return HTTPError(403, "Access denied.")
+ if not os.path.exists(filename) or not os.path.isfile(filename):
+ return HTTPError(404, "File does not exist.")
+ if not os.access(filename, os.R_OK):
+ return HTTPError(403, "You do not have permission to access this file.")
+
+ if mimetype == 'auto':
+ mimetype, encoding = mimetypes.guess_type(filename)
+ if encoding: headers['Content-Encoding'] = encoding
+
+ if mimetype:
+ if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
+ mimetype += '; charset=%s' % charset
+ headers['Content-Type'] = mimetype
+
+ if download:
+ download = os.path.basename(filename if download == True else download)
+ headers['Content-Disposition'] = 'attachment; filename="%s"' % download
+
+ stats = os.stat(filename)
+ headers['Content-Length'] = clen = stats.st_size
+ lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))
+ headers['Last-Modified'] = lm
+
+ ims = request.environ.get('HTTP_IF_MODIFIED_SINCE')
+ if ims:
+ ims = parse_date(ims.split(";")[0].strip())
+ if ims is not None and ims >= int(stats.st_mtime):
+ headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+ return HTTPResponse(status=304, **headers)
+
+ body = '' if request.method == 'HEAD' else open(filename, 'rb')
+
+ headers["Accept-Ranges"] = "bytes"
+ ranges = request.environ.get('HTTP_RANGE')
+ if 'HTTP_RANGE' in request.environ:
+ ranges = list(parse_range_header(request.environ['HTTP_RANGE'], clen))
+ if not ranges:
+ return HTTPError(416, "Requested Range Not Satisfiable")
+ offset, end = ranges[0]
+ headers["Content-Range"] = "bytes %d-%d/%d" % (offset, end-1, clen)
+ headers["Content-Length"] = str(end-offset)
+ if body: body = _file_iter_range(body, offset, end-offset)
+ return HTTPResponse(body, status=206, **headers)
+ return HTTPResponse(body, **headers)
+
+
+
+
+
+
+###############################################################################
+# HTTP Utilities and MISC (TODO) ###############################################
+###############################################################################
+
+
+def debug(mode=True):
+ """ Change the debug level.
+ There is only one debug level supported at the moment."""
+ global DEBUG
+ if mode: warnings.simplefilter('default')
+ DEBUG = bool(mode)
+
+def http_date(value):
+ if isinstance(value, (datedate, datetime)):
+ value = value.utctimetuple()
+ elif isinstance(value, (int, float)):
+ value = time.gmtime(value)
+ if not isinstance(value, basestring):
+ value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", value)
+ return value
+
+def parse_date(ims):
+ """ Parse rfc1123, rfc850 and asctime timestamps and return UTC epoch. """
+ try:
+ ts = email.utils.parsedate_tz(ims)
+ return time.mktime(ts[:8] + (0,)) - (ts[9] or 0) - time.timezone
+ except (TypeError, ValueError, IndexError, OverflowError):
+ return None
+
+def parse_auth(header):
+ """ Parse rfc2617 HTTP authentication header string (basic) and return (user,pass) tuple or None"""
+ try:
+ method, data = header.split(None, 1)
+ if method.lower() == 'basic':
+ user, pwd = touni(base64.b64decode(tob(data))).split(':',1)
+ return user, pwd
+ except (KeyError, ValueError):
+ return None
+
+def parse_range_header(header, maxlen=0):
+ ''' Yield (start, end) ranges parsed from a HTTP Range header. Skip
+ unsatisfiable ranges. The end index is non-inclusive.'''
+ if not header or header[:6] != 'bytes=': return
+ ranges = [r.split('-', 1) for r in header[6:].split(',') if '-' in r]
+ for start, end in ranges:
+ try:
+ if not start: # bytes=-100 -> last 100 bytes
+ start, end = max(0, maxlen-int(end)), maxlen
+ elif not end: # bytes=100- -> all but the first 99 bytes
+ start, end = int(start), maxlen
+ else: # bytes=100-200 -> bytes 100-200 (inclusive)
+ start, end = int(start), min(int(end)+1, maxlen)
+ if 0 <= start < end <= maxlen:
+ yield start, end
+ except ValueError:
+ pass
+
+def _parse_qsl(qs):
+ r = []
+ for pair in qs.replace(';','&').split('&'):
+ if not pair: continue
+ nv = pair.split('=', 1)
+ if len(nv) != 2: nv.append('')
+ key = urlunquote(nv[0].replace('+', ' '))
+ value = urlunquote(nv[1].replace('+', ' '))
+ r.append((key, value))
+ return r
+
+def _lscmp(a, b):
+ ''' Compares two strings in a cryptographically safe way:
+ Runtime is not affected by length of common prefix. '''
+ return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a) == len(b)
+
+
+def cookie_encode(data, key):
+ ''' Encode and sign a pickle-able object. Return a (byte) string '''
+ msg = base64.b64encode(pickle.dumps(data, -1))
+ sig = base64.b64encode(hmac.new(tob(key), msg).digest())
+ return tob('!') + sig + tob('?') + msg
+
+
+def cookie_decode(data, key):
+ ''' Verify and decode an encoded string. Return an object or None.'''
+ data = tob(data)
+ if cookie_is_encoded(data):
+ sig, msg = data.split(tob('?'), 1)
+ if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg).digest())):
+ return pickle.loads(base64.b64decode(msg))
+ return None
+
+
+def cookie_is_encoded(data):
+ ''' Return True if the argument looks like a encoded cookie.'''
+ return bool(data.startswith(tob('!')) and tob('?') in data)
+
+
+def html_escape(string):
+ ''' Escape HTML special characters ``&<>`` and quotes ``'"``. '''
+ return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')\
+ .replace('"','&quot;').replace("'",'&#039;')
+
+
+def html_quote(string):
+ ''' Escape and quote a string to be used as an HTTP attribute.'''
+ return '"%s"' % html_escape(string).replace('\n','&#10;')\
+ .replace('\r','&#13;').replace('\t','&#9;')
+
+
+def yieldroutes(func):
+ """ Return a generator for routes that match the signature (name, args)
+ of the func parameter. This may yield more than one route if the function
+ takes optional keyword arguments. The output is best described by example::
+
+ a() -> '/a'
+ b(x, y) -> '/b/<x>/<y>'
+ c(x, y=5) -> '/c/<x>' and '/c/<x>/<y>'
+ d(x=5, y=6) -> '/d' and '/d/<x>' and '/d/<x>/<y>'
+ """
+ path = '/' + func.__name__.replace('__','/').lstrip('/')
+ spec = getargspec(func)
+ argc = len(spec[0]) - len(spec[3] or [])
+ path += ('/<%s>' * argc) % tuple(spec[0][:argc])
+ yield path
+ for arg in spec[0][argc:]:
+ path += '/<%s>' % arg
+ yield path
+
+
+def path_shift(script_name, path_info, shift=1):
+ ''' Shift path fragments from PATH_INFO to SCRIPT_NAME and vice versa.
+
+ :return: The modified paths.
+ :param script_name: The SCRIPT_NAME path.
+ :param script_name: The PATH_INFO path.
+ :param shift: The number of path fragments to shift. May be negative to
+ change the shift direction. (default: 1)
+ '''
+ if shift == 0: return script_name, path_info
+ pathlist = path_info.strip('/').split('/')
+ scriptlist = script_name.strip('/').split('/')
+ if pathlist and pathlist[0] == '': pathlist = []
+ if scriptlist and scriptlist[0] == '': scriptlist = []
+ if shift > 0 and shift <= len(pathlist):
+ moved = pathlist[:shift]
+ scriptlist = scriptlist + moved
+ pathlist = pathlist[shift:]
+ elif shift < 0 and shift >= -len(scriptlist):
+ moved = scriptlist[shift:]
+ pathlist = moved + pathlist
+ scriptlist = scriptlist[:shift]
+ else:
+ empty = 'SCRIPT_NAME' if shift < 0 else 'PATH_INFO'
+ raise AssertionError("Cannot shift. Nothing left from %s" % empty)
+ new_script_name = '/' + '/'.join(scriptlist)
+ new_path_info = '/' + '/'.join(pathlist)
+ if path_info.endswith('/') and pathlist: new_path_info += '/'
+ return new_script_name, new_path_info
+
+
+def auth_basic(check, realm="private", text="Access denied"):
+ ''' Callback decorator to require HTTP auth (basic).
+ TODO: Add route(check_auth=...) parameter. '''
+ def decorator(func):
+ def wrapper(*a, **ka):
+ user, password = request.auth or (None, None)
+ if user is None or not check(user, password):
+ err = HTTPError(401, text)
+ err.add_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
+ return err
+ return func(*a, **ka)
+ return wrapper
+ return decorator
+
+
+# Shortcuts for common Bottle methods.
+# They all refer to the current default application.
+
+def make_default_app_wrapper(name):
+ ''' Return a callable that relays calls to the current default app. '''
+ @functools.wraps(getattr(Bottle, name))
+ def wrapper(*a, **ka):
+ return getattr(app(), name)(*a, **ka)
+ return wrapper
+
+route = make_default_app_wrapper('route')
+get = make_default_app_wrapper('get')
+post = make_default_app_wrapper('post')
+put = make_default_app_wrapper('put')
+delete = make_default_app_wrapper('delete')
+error = make_default_app_wrapper('error')
+mount = make_default_app_wrapper('mount')
+hook = make_default_app_wrapper('hook')
+install = make_default_app_wrapper('install')
+uninstall = make_default_app_wrapper('uninstall')
+url = make_default_app_wrapper('get_url')
+
+
+
+
+
+
+
+###############################################################################
+# Server Adapter ###############################################################
+###############################################################################
+
+
+class ServerAdapter(object):
+ quiet = False
+ def __init__(self, host='127.0.0.1', port=8080, **options):
+ self.options = options
+ self.host = host
+ self.port = int(port)
+
+ def run(self, handler): # pragma: no cover
+ pass
+
+ def __repr__(self):
+ args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()])
+ return "%s(%s)" % (self.__class__.__name__, args)
+
+
+class CGIServer(ServerAdapter):
+ quiet = True
+ def run(self, handler): # pragma: no cover
+ from wsgiref.handlers import CGIHandler
+ def fixed_environ(environ, start_response):
+ environ.setdefault('PATH_INFO', '')
+ return handler(environ, start_response)
+ CGIHandler().run(fixed_environ)
+
+
+class FlupFCGIServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ import flup.server.fcgi
+ self.options.setdefault('bindAddress', (self.host, self.port))
+ flup.server.fcgi.WSGIServer(handler, **self.options).run()
+
+
+class WSGIRefServer(ServerAdapter):
+ def run(self, app): # pragma: no cover
+ from wsgiref.simple_server import WSGIRequestHandler, WSGIServer
+ from wsgiref.simple_server import make_server
+ import socket
+
+ class FixedHandler(WSGIRequestHandler):
+ def address_string(self): # Prevent reverse DNS lookups please.
+ return self.client_address[0]
+ def log_request(*args, **kw):
+ if not self.quiet:
+ return WSGIRequestHandler.log_request(*args, **kw)
+
+ handler_cls = self.options.get('handler_class', FixedHandler)
+ server_cls = self.options.get('server_class', WSGIServer)
+
+ if ':' in self.host: # Fix wsgiref for IPv6 addresses.
+ if getattr(server_cls, 'address_family') == socket.AF_INET:
+ class server_cls(server_cls):
+ address_family = socket.AF_INET6
+
+ srv = make_server(self.host, self.port, app, server_cls, handler_cls)
+ srv.serve_forever()
+
+
+class CherryPyServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ from cherrypy import wsgiserver
+ self.options['bind_addr'] = (self.host, self.port)
+ self.options['wsgi_app'] = handler
+
+ certfile = self.options.get('certfile')
+ if certfile:
+ del self.options['certfile']
+ keyfile = self.options.get('keyfile')
+ if keyfile:
+ del self.options['keyfile']
+
+ server = wsgiserver.CherryPyWSGIServer(**self.options)
+ if certfile:
+ server.ssl_certificate = certfile
+ if keyfile:
+ server.ssl_private_key = keyfile
+
+ try:
+ server.start()
+ finally:
+ server.stop()
+
+
+class WaitressServer(ServerAdapter):
+ def run(self, handler):
+ from waitress import serve
+ serve(handler, host=self.host, port=self.port)
+
+
+class PasteServer(ServerAdapter):
+ def run(self, handler): # pragma: no cover
+ from paste import httpserver
+ from paste.translogger import TransLogger
+ handler = TransLogger(handler, setup_console_handler=(not self.quiet))
+ httpserver.serve(handler, host=self.host, port=str(self.port),
+ **self.options)
+
+
+class MeinheldServer(ServerAdapter):
+ def run(self, handler):
+ from meinheld import server
+ server.listen((self.host, self.port))
+ server.run(handler)
+
+
+class FapwsServer(ServerAdapter):
+ """ Extremely fast webserver using libev. See http://www.fapws.org/ """
+ def run(self, handler): # pragma: no cover
+ import fapws._evwsgi as evwsgi
+ from fapws import base, config
+ port = self.port
+ if float(config.SERVER_IDENT[-2:]) > 0.4:
+ # fapws3 silently changed its API in 0.5
+ port = str(port)
+ evwsgi.start(self.host, port)
+ # fapws3 never releases the GIL. Complain upstream. I tried. No luck.
+ if 'BOTTLE_CHILD' in os.environ and not self.quiet:
+ _stderr("WARNING: Auto-reloading does not work with Fapws3.\n")
+ _stderr(" (Fapws3 breaks python thread support)\n")
+ evwsgi.set_base_module(base)
+ def app(environ, start_response):
+ environ['wsgi.multiprocess'] = False
+ return handler(environ, start_response)
+ evwsgi.wsgi_cb(('', app))
+ evwsgi.run()
+
+
+class TornadoServer(ServerAdapter):
+ """ The super hyped asynchronous server by facebook. Untested. """
+ def run(self, handler): # pragma: no cover
+ import tornado.wsgi, tornado.httpserver, tornado.ioloop
+ container = tornado.wsgi.WSGIContainer(handler)
+ server = tornado.httpserver.HTTPServer(container)
+ server.listen(port=self.port,address=self.host)
+ tornado.ioloop.IOLoop.instance().start()
+
+
+class AppEngineServer(ServerAdapter):
+ """ Adapter for Google App Engine. """
+ quiet = True
+ def run(self, handler):
+ from google.appengine.ext.webapp import util
+ # A main() function in the handler script enables 'App Caching'.
+ # Lets makes sure it is there. This _really_ improves performance.
+ module = sys.modules.get('__main__')
+ if module and not hasattr(module, 'main'):
+ module.main = lambda: util.run_wsgi_app(handler)
+ util.run_wsgi_app(handler)
+
+
+class TwistedServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from twisted.web import server, wsgi
+ from twisted.python.threadpool import ThreadPool
+ from twisted.internet import reactor
+ thread_pool = ThreadPool()
+ thread_pool.start()
+ reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
+ factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, handler))
+ reactor.listenTCP(self.port, factory, interface=self.host)
+ reactor.run()
+
+
+class DieselServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from diesel.protocols.wsgi import WSGIApplication
+ app = WSGIApplication(handler, port=self.port)
+ app.run()
+
+
+class GeventServer(ServerAdapter):
+ """ Untested. Options:
+
+ * `fast` (default: False) uses libevent's http server, but has some
+ issues: No streaming, no pipelining, no SSL.
+ * See gevent.wsgi.WSGIServer() documentation for more options.
+ """
+ def run(self, handler):
+ from gevent import wsgi, pywsgi, local
+ if not isinstance(threading.local(), local.local):
+ msg = "Bottle requires gevent.monkey.patch_all() (before import)"
+ raise RuntimeError(msg)
+ if not self.options.pop('fast', None): wsgi = pywsgi
+ self.options['log'] = None if self.quiet else 'default'
+ address = (self.host, self.port)
+ server = wsgi.WSGIServer(address, handler, **self.options)
+ if 'BOTTLE_CHILD' in os.environ:
+ import signal
+ signal.signal(signal.SIGINT, lambda s, f: server.stop())
+ server.serve_forever()
+
+
+class GeventSocketIOServer(ServerAdapter):
+ def run(self,handler):
+ from socketio import server
+ address = (self.host, self.port)
+ server.SocketIOServer(address, handler, **self.options).serve_forever()
+
+
+class GunicornServer(ServerAdapter):
+ """ Untested. See http://gunicorn.org/configure.html for options. """
+ def run(self, handler):
+ from gunicorn.app.base import Application
+
+ config = {'bind': "%s:%d" % (self.host, int(self.port))}
+ config.update(self.options)
+
+ class GunicornApplication(Application):
+ def init(self, parser, opts, args):
+ return config
+
+ def load(self):
+ return handler
+
+ GunicornApplication().run()
+
+
+class EventletServer(ServerAdapter):
+ """ Untested """
+ def run(self, handler):
+ from eventlet import wsgi, listen
+ try:
+ wsgi.server(listen((self.host, self.port)), handler,
+ log_output=(not self.quiet))
+ except TypeError:
+ # Fallback, if we have old version of eventlet
+ wsgi.server(listen((self.host, self.port)), handler)
+
+
+class RocketServer(ServerAdapter):
+ """ Untested. """
+ def run(self, handler):
+ from rocket import Rocket
+ server = Rocket((self.host, self.port), 'wsgi', { 'wsgi_app' : handler })
+ server.start()
+
+
+class BjoernServer(ServerAdapter):
+ """ Fast server written in C: https://github.com/jonashaag/bjoern """
+ def run(self, handler):
+ from bjoern import run
+ run(handler, self.host, self.port)
+
+
+class AutoServer(ServerAdapter):
+ """ Untested. """
+ adapters = [WaitressServer, PasteServer, TwistedServer, CherryPyServer, WSGIRefServer]
+ def run(self, handler):
+ for sa in self.adapters:
+ try:
+ return sa(self.host, self.port, **self.options).run(handler)
+ except ImportError:
+ pass
+
+server_names = {
+ 'cgi': CGIServer,
+ 'flup': FlupFCGIServer,
+ 'wsgiref': WSGIRefServer,
+ 'waitress': WaitressServer,
+ 'cherrypy': CherryPyServer,
+ 'paste': PasteServer,
+ 'fapws3': FapwsServer,
+ 'tornado': TornadoServer,
+ 'gae': AppEngineServer,
+ 'twisted': TwistedServer,
+ 'diesel': DieselServer,
+ 'meinheld': MeinheldServer,
+ 'gunicorn': GunicornServer,
+ 'eventlet': EventletServer,
+ 'gevent': GeventServer,
+ 'geventSocketIO':GeventSocketIOServer,
+ 'rocket': RocketServer,
+ 'bjoern' : BjoernServer,
+ 'auto': AutoServer,
+}
+
+
+
+
+
+
+###############################################################################
+# Application Control ##########################################################
+###############################################################################
+
+
+def load(target, **namespace):
+ """ Import a module or fetch an object from a module.
+
+ * ``package.module`` returns `module` as a module object.
+ * ``pack.mod:name`` returns the module variable `name` from `pack.mod`.
+ * ``pack.mod:func()`` calls `pack.mod.func()` and returns the result.
+
+ The last form accepts not only function calls, but any type of
+ expression. Keyword arguments passed to this function are available as
+ local variables. Example: ``import_string('re:compile(x)', x='[a-z]')``
+ """
+ module, target = target.split(":", 1) if ':' in target else (target, None)
+ if module not in sys.modules: __import__(module)
+ if not target: return sys.modules[module]
+ if target.isalnum(): return getattr(sys.modules[module], target)
+ package_name = module.split('.')[0]
+ namespace[package_name] = sys.modules[package_name]
+ return eval('%s.%s' % (module, target), namespace)
+
+
+def load_app(target):
+ """ Load a bottle application from a module and make sure that the import
+ does not affect the current default application, but returns a separate
+ application object. See :func:`load` for the target parameter. """
+ global NORUN; NORUN, nr_old = True, NORUN
+ try:
+ tmp = default_app.push() # Create a new "default application"
+ rv = load(target) # Import the target module
+ return rv if callable(rv) else tmp
+ finally:
+ default_app.remove(tmp) # Remove the temporary added default application
+ NORUN = nr_old
+
+_debug = debug
+def run(app=None, server='wsgiref', host='127.0.0.1', port=8080,
+ interval=1, reloader=False, quiet=False, plugins=None,
+ debug=None, **kargs):
+ """ Start a server instance. This method blocks until the server terminates.
+
+ :param app: WSGI application or target string supported by
+ :func:`load_app`. (default: :func:`default_app`)
+ :param server: Server adapter to use. See :data:`server_names` keys
+ for valid names or pass a :class:`ServerAdapter` subclass.
+ (default: `wsgiref`)
+ :param host: Server address to bind to. Pass ``0.0.0.0`` to listens on
+ all interfaces including the external one. (default: 127.0.0.1)
+ :param port: Server port to bind to. Values below 1024 require root
+ privileges. (default: 8080)
+ :param reloader: Start auto-reloading server? (default: False)
+ :param interval: Auto-reloader interval in seconds (default: 1)
+ :param quiet: Suppress output to stdout and stderr? (default: False)
+ :param options: Options passed to the server adapter.
+ """
+ if NORUN: return
+ if reloader and not os.environ.get('BOTTLE_CHILD'):
+ try:
+ lockfile = None
+ fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock')
+ os.close(fd) # We only need this file to exist. We never write to it
+ while os.path.exists(lockfile):
+ args = [sys.executable] + sys.argv
+ environ = os.environ.copy()
+ environ['BOTTLE_CHILD'] = 'true'
+ environ['BOTTLE_LOCKFILE'] = lockfile
+ p = subprocess.Popen(args, env=environ)
+ while p.poll() is None: # Busy wait...
+ os.utime(lockfile, None) # I am alive!
+ time.sleep(interval)
+ if p.poll() != 3:
+ if os.path.exists(lockfile): os.unlink(lockfile)
+ sys.exit(p.poll())
+ except KeyboardInterrupt:
+ pass
+ finally:
+ if os.path.exists(lockfile):
+ os.unlink(lockfile)
+ return
+
+ try:
+ if debug is not None: _debug(debug)
+ app = app or default_app()
+ if isinstance(app, basestring):
+ app = load_app(app)
+ if not callable(app):
+ raise ValueError("Application is not callable: %r" % app)
+
+ for plugin in plugins or []:
+ app.install(plugin)
+
+ if server in server_names:
+ server = server_names.get(server)
+ if isinstance(server, basestring):
+ server = load(server)
+ if isinstance(server, type):
+ server = server(host=host, port=port, **kargs)
+ if not isinstance(server, ServerAdapter):
+ raise ValueError("Unknown or unsupported server: %r" % server)
+
+ server.quiet = server.quiet or quiet
+ if not server.quiet:
+ _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server)))
+ _stderr("Listening on http://%s:%d/\n" % (server.host, server.port))
+ _stderr("Hit Ctrl-C to quit.\n\n")
+
+ if reloader:
+ lockfile = os.environ.get('BOTTLE_LOCKFILE')
+ bgcheck = FileCheckerThread(lockfile, interval)
+ with bgcheck:
+ server.run(app)
+ if bgcheck.status == 'reload':
+ sys.exit(3)
+ else:
+ server.run(app)
+ except KeyboardInterrupt:
+ pass
+ except (SystemExit, MemoryError):
+ raise
+ except:
+ if not reloader: raise
+ if not getattr(server, 'quiet', quiet):
+ print_exc()
+ time.sleep(interval)
+ sys.exit(3)
+
+
+
+class FileCheckerThread(threading.Thread):
+ ''' Interrupt main-thread as soon as a changed module file is detected,
+ the lockfile gets deleted or gets to old. '''
+
+ def __init__(self, lockfile, interval):
+ threading.Thread.__init__(self)
+ self.lockfile, self.interval = lockfile, interval
+ #: Is one of 'reload', 'error' or 'exit'
+ self.status = None
+
+ def run(self):
+ exists = os.path.exists
+ mtime = lambda path: os.stat(path).st_mtime
+ files = dict()
+
+ for module in list(sys.modules.values()):
+ path = getattr(module, '__file__', '')
+ if path[-4:] in ('.pyo', '.pyc'): path = path[:-1]
+ if path and exists(path): files[path] = mtime(path)
+
+ while not self.status:
+ if not exists(self.lockfile)\
+ or mtime(self.lockfile) < time.time() - self.interval - 5:
+ self.status = 'error'
+ thread.interrupt_main()
+ for path, lmtime in list(files.items()):
+ if not exists(path) or mtime(path) > lmtime:
+ self.status = 'reload'
+ thread.interrupt_main()
+ break
+ time.sleep(self.interval)
+
+ def __enter__(self):
+ self.start()
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if not self.status: self.status = 'exit' # silent exit
+ self.join()
+ return exc_type is not None and issubclass(exc_type, KeyboardInterrupt)
+
+
+
+
+
+###############################################################################
+# Template Adapters ############################################################
+###############################################################################
+
+
+class TemplateError(HTTPError):
+ def __init__(self, message):
+ HTTPError.__init__(self, 500, message)
+
+
+class BaseTemplate(object):
+ """ Base class and minimal API for template adapters """
+ extensions = ['tpl','html','thtml','stpl']
+ settings = {} #used in prepare()
+ defaults = {} #used in render()
+
+ def __init__(self, source=None, name=None, lookup=[], encoding='utf8', **settings):
+ """ Create a new template.
+ If the source parameter (str or buffer) is missing, the name argument
+ is used to guess a template filename. Subclasses can assume that
+ self.source and/or self.filename are set. Both are strings.
+ The lookup, encoding and settings parameters are stored as instance
+ variables.
+ The lookup parameter stores a list containing directory paths.
+ The encoding parameter should be used to decode byte strings or files.
+ The settings parameter contains a dict for engine-specific settings.
+ """
+ self.name = name
+ self.source = source.read() if hasattr(source, 'read') else source
+ self.filename = source.filename if hasattr(source, 'filename') else None
+ self.lookup = [os.path.abspath(x) for x in lookup]
+ self.encoding = encoding
+ self.settings = self.settings.copy() # Copy from class variable
+ self.settings.update(settings) # Apply
+ if not self.source and self.name:
+ self.filename = self.search(self.name, self.lookup)
+ if not self.filename:
+ raise TemplateError('Template %s not found.' % repr(name))
+ if not self.source and not self.filename:
+ raise TemplateError('No template specified.')
+ self.prepare(**self.settings)
+
+ @classmethod
+ def search(cls, name, lookup=[]):
+ """ Search name in all directories specified in lookup.
+ First without, then with common extensions. Return first hit. """
+ if not lookup:
+ depr('The template lookup path list should not be empty.') #0.12
+ lookup = ['.']
+
+ if os.path.isabs(name) and os.path.isfile(name):
+ depr('Absolute template path names are deprecated.') #0.12
+ return os.path.abspath(name)
+
+ for spath in lookup:
+ spath = os.path.abspath(spath) + os.sep
+ fname = os.path.abspath(os.path.join(spath, name))
+ if not fname.startswith(spath): continue
+ if os.path.isfile(fname): return fname
+ for ext in cls.extensions:
+ if os.path.isfile('%s.%s' % (fname, ext)):
+ return '%s.%s' % (fname, ext)
+
+ @classmethod
+ def global_config(cls, key, *args):
+ ''' This reads or sets the global settings stored in class.settings. '''
+ if args:
+ cls.settings = cls.settings.copy() # Make settings local to class
+ cls.settings[key] = args[0]
+ else:
+ return cls.settings[key]
+
+ def prepare(self, **options):
+ """ Run preparations (parsing, caching, ...).
+ It should be possible to call this again to refresh a template or to
+ update settings.
+ """
+ raise NotImplementedError
+
+ def render(self, *args, **kwargs):
+ """ Render the template with the specified local variables and return
+ a single byte or unicode string. If it is a byte string, the encoding
+ must match self.encoding. This method must be thread-safe!
+ Local variables may be provided in dictionaries (args)
+ or directly, as keywords (kwargs).
+ """
+ raise NotImplementedError
+
+
+class MakoTemplate(BaseTemplate):
+ def prepare(self, **options):
+ from mako.template import Template
+ from mako.lookup import TemplateLookup
+ options.update({'input_encoding':self.encoding})
+ options.setdefault('format_exceptions', bool(DEBUG))
+ lookup = TemplateLookup(directories=self.lookup, **options)
+ if self.source:
+ self.tpl = Template(self.source, lookup=lookup, **options)
+ else:
+ self.tpl = Template(uri=self.name, filename=self.filename, lookup=lookup, **options)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ _defaults = self.defaults.copy()
+ _defaults.update(kwargs)
+ return self.tpl.render(**_defaults)
+
+
+class CheetahTemplate(BaseTemplate):
+ def prepare(self, **options):
+ from Cheetah.Template import Template
+ self.context = threading.local()
+ self.context.vars = {}
+ options['searchList'] = [self.context.vars]
+ if self.source:
+ self.tpl = Template(source=self.source, **options)
+ else:
+ self.tpl = Template(file=self.filename, **options)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ self.context.vars.update(self.defaults)
+ self.context.vars.update(kwargs)
+ out = str(self.tpl)
+ self.context.vars.clear()
+ return out
+
+
+class Jinja2Template(BaseTemplate):
+ def prepare(self, filters=None, tests=None, globals={}, **kwargs):
+ from jinja2 import Environment, FunctionLoader
+ if 'prefix' in kwargs: # TODO: to be removed after a while
+ raise RuntimeError('The keyword argument `prefix` has been removed. '
+ 'Use the full jinja2 environment name line_statement_prefix instead.')
+ self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
+ if filters: self.env.filters.update(filters)
+ if tests: self.env.tests.update(tests)
+ if globals: self.env.globals.update(globals)
+ if self.source:
+ self.tpl = self.env.from_string(self.source)
+ else:
+ self.tpl = self.env.get_template(self.filename)
+
+ def render(self, *args, **kwargs):
+ for dictarg in args: kwargs.update(dictarg)
+ _defaults = self.defaults.copy()
+ _defaults.update(kwargs)
+ return self.tpl.render(**_defaults)
+
+ def loader(self, name):
+ fname = self.search(name, self.lookup)
+ if not fname: return
+ with open(fname, "rb") as f:
+ return f.read().decode(self.encoding)
+
+
+class SimpleTemplate(BaseTemplate):
+
+ def prepare(self, escape_func=html_escape, noescape=False, syntax=None, **ka):
+ self.cache = {}
+ enc = self.encoding
+ self._str = lambda x: touni(x, enc)
+ self._escape = lambda x: escape_func(touni(x, enc))
+ self.syntax = syntax
+ if noescape:
+ self._str, self._escape = self._escape, self._str
+
+ @cached_property
+ def co(self):
+ return compile(self.code, self.filename or '<string>', 'exec')
+
+ @cached_property
+ def code(self):
+ source = self.source
+ if not source:
+ with open(self.filename, 'rb') as f:
+ source = f.read()
+ try:
+ source, encoding = touni(source), 'utf8'
+ except UnicodeError:
+ depr('Template encodings other than utf8 are no longer supported.') #0.11
+ source, encoding = touni(source, 'latin1'), 'latin1'
+ parser = StplParser(source, encoding=encoding, syntax=self.syntax)
+ code = parser.translate()
+ self.encoding = parser.encoding
+ return code
+
+ def _rebase(self, _env, _name=None, **kwargs):
+ if _name is None:
+ depr('Rebase function called without arguments.'
+ ' You were probably looking for {{base}}?', True) #0.12
+ _env['_rebase'] = (_name, kwargs)
+
+ def _include(self, _env, _name=None, **kwargs):
+ if _name is None:
+ depr('Rebase function called without arguments.'
+ ' You were probably looking for {{base}}?', True) #0.12
+ env = _env.copy()
+ env.update(kwargs)
+ if _name not in self.cache:
+ self.cache[_name] = self.__class__(name=_name, lookup=self.lookup)
+ return self.cache[_name].execute(env['_stdout'], env)
+
+ def execute(self, _stdout, kwargs):
+ env = self.defaults.copy()
+ env.update(kwargs)
+ env.update({'_stdout': _stdout, '_printlist': _stdout.extend,
+ 'include': functools.partial(self._include, env),
+ 'rebase': functools.partial(self._rebase, env), '_rebase': None,
+ '_str': self._str, '_escape': self._escape, 'get': env.get,
+ 'setdefault': env.setdefault, 'defined': env.__contains__ })
+ eval(self.co, env)
+ if env.get('_rebase'):
+ subtpl, rargs = env.pop('_rebase')
+ rargs['base'] = ''.join(_stdout) #copy stdout
+ del _stdout[:] # clear stdout
+ return self._include(env, subtpl, **rargs)
+ return env
+
+ def render(self, *args, **kwargs):
+ """ Render the template using keyword arguments as local variables. """
+ env = {}; stdout = []
+ for dictarg in args: env.update(dictarg)
+ env.update(kwargs)
+ self.execute(stdout, env)
+ return ''.join(stdout)
+
+
+class StplSyntaxError(TemplateError): pass
+
+
+class StplParser(object):
+ ''' Parser for stpl templates. '''
+ _re_cache = {} #: Cache for compiled re patterns
+ # This huge pile of voodoo magic splits python code into 8 different tokens.
+ # 1: All kinds of python strings (trust me, it works)
+ _re_tok = '((?m)[urbURB]?(?:\'\'(?!\')|""(?!")|\'{6}|"{6}' \
+ '|\'(?:[^\\\\\']|\\\\.)+?\'|"(?:[^\\\\"]|\\\\.)+?"' \
+ '|\'{3}(?:[^\\\\]|\\\\.|\\n)+?\'{3}' \
+ '|"{3}(?:[^\\\\]|\\\\.|\\n)+?"{3}))'
+ _re_inl = _re_tok.replace('|\\n','') # We re-use this string pattern later
+ # 2: Comments (until end of line, but not the newline itself)
+ _re_tok += '|(#.*)'
+ # 3,4: Keywords that start or continue a python block (only start of line)
+ _re_tok += '|^([ \\t]*(?:if|for|while|with|try|def|class)\\b)' \
+ '|^([ \\t]*(?:elif|else|except|finally)\\b)'
+ # 5: Our special 'end' keyword (but only if it stands alone)
+ _re_tok += '|((?:^|;)[ \\t]*end[ \\t]*(?=(?:%(block_close)s[ \\t]*)?\\r?$|;|#))'
+ # 6: A customizable end-of-code-block template token (only end of line)
+ _re_tok += '|(%(block_close)s[ \\t]*(?=$))'
+ # 7: And finally, a single newline. The 8th token is 'everything else'
+ _re_tok += '|(\\r?\\n)'
+ # Match the start tokens of code areas in a template
+ _re_split = '(?m)^[ \t]*(\\\\?)((%(line_start)s)|(%(block_start)s))(%%?)'
+ # Match inline statements (may contain python strings)
+ _re_inl = '%%(inline_start)s((?:%s|[^\'"\n]*?)+)%%(inline_end)s' % _re_inl
+
+ default_syntax = '<% %> % {{ }}'
+
+ def __init__(self, source, syntax=None, encoding='utf8'):
+ self.source, self.encoding = touni(source, encoding), encoding
+ self.set_syntax(syntax or self.default_syntax)
+ self.code_buffer, self.text_buffer = [], []
+ self.lineno, self.offset = 1, 0
+ self.indent, self.indent_mod = 0, 0
+
+ def get_syntax(self):
+ ''' Tokens as a space separated string (default: <% %> % {{ }}) '''
+ return self._syntax
+
+ def set_syntax(self, syntax):
+ self._syntax = syntax
+ self._tokens = syntax.split()
+ if not syntax in self._re_cache:
+ names = 'block_start block_close line_start inline_start inline_end'
+ etokens = map(re.escape, self._tokens)
+ pattern_vars = dict(zip(names.split(), etokens))
+ patterns = (self._re_split, self._re_tok, self._re_inl)
+ patterns = [re.compile(p%pattern_vars) for p in patterns]
+ self._re_cache[syntax] = patterns
+ self.re_split, self.re_tok, self.re_inl = self._re_cache[syntax]
+
+ syntax = property(get_syntax, set_syntax)
+
+ def translate(self):
+ if self.offset: raise RuntimeError('Parser is a one time instance.')
+ while True:
+ m = self.re_split.search(self.source[self.offset:])
+ if m:
+ text = self.source[self.offset:self.offset+m.start()]
+ self.text_buffer.append(text)
+ self.offset += m.end()
+ if m.group(1): # New escape syntax
+ line, sep, _ = self.source[self.offset:].partition('\n')
+ self.text_buffer.append(m.group(2)+m.group(5)+line+sep)
+ self.offset += len(line+sep)+1
+ continue
+ elif m.group(5): # Old escape syntax
+ depr('Escape code lines with a backslash.') #0.12
+ line, sep, _ = self.source[self.offset:].partition('\n')
+ self.text_buffer.append(m.group(2)+line+sep)
+ self.offset += len(line+sep)+1
+ continue
+ self.flush_text()
+ self.read_code(multiline=bool(m.group(4)))
+ else: break
+ self.text_buffer.append(self.source[self.offset:])
+ self.flush_text()
+ return ''.join(self.code_buffer)
+
+ def read_code(self, multiline):
+ code_line, comment = '', ''
+ while True:
+ m = self.re_tok.search(self.source[self.offset:])
+ if not m:
+ code_line += self.source[self.offset:]
+ self.offset = len(self.source)
+ self.write_code(code_line.strip(), comment)
+ return
+ code_line += self.source[self.offset:self.offset+m.start()]
+ self.offset += m.end()
+ _str, _com, _blk1, _blk2, _end, _cend, _nl = m.groups()
+ if code_line and (_blk1 or _blk2): # a if b else c
+ code_line += _blk1 or _blk2
+ continue
+ if _str: # Python string
+ code_line += _str
+ elif _com: # Python comment (up to EOL)
+ comment = _com
+ if multiline and _com.strip().endswith(self._tokens[1]):
+ multiline = False # Allow end-of-block in comments
+ elif _blk1: # Start-block keyword (if/for/while/def/try/...)
+ code_line, self.indent_mod = _blk1, -1
+ self.indent += 1
+ elif _blk2: # Continue-block keyword (else/elif/except/...)
+ code_line, self.indent_mod = _blk2, -1
+ elif _end: # The non-standard 'end'-keyword (ends a block)
+ self.indent -= 1
+ elif _cend: # The end-code-block template token (usually '%>')
+ if multiline: multiline = False
+ else: code_line += _cend
+ else: # \n
+ self.write_code(code_line.strip(), comment)
+ self.lineno += 1
+ code_line, comment, self.indent_mod = '', '', 0
+ if not multiline:
+ break
+
+ def flush_text(self):
+ text = ''.join(self.text_buffer)
+ del self.text_buffer[:]
+ if not text: return
+ parts, pos, nl = [], 0, '\\\n'+' '*self.indent
+ for m in self.re_inl.finditer(text):
+ prefix, pos = text[pos:m.start()], m.end()
+ if prefix:
+ parts.append(nl.join(map(repr, prefix.splitlines(True))))
+ if prefix.endswith('\n'): parts[-1] += nl
+ parts.append(self.process_inline(m.group(1).strip()))
+ if pos < len(text):
+ prefix = text[pos:]
+ lines = prefix.splitlines(True)
+ if lines[-1].endswith('\\\\\n'): lines[-1] = lines[-1][:-3]
+ elif lines[-1].endswith('\\\\\r\n'): lines[-1] = lines[-1][:-4]
+ parts.append(nl.join(map(repr, lines)))
+ code = '_printlist((%s,))' % ', '.join(parts)
+ self.lineno += code.count('\n')+1
+ self.write_code(code)
+
+ def process_inline(self, chunk):
+ if chunk[0] == '!': return '_str(%s)' % chunk[1:]
+ return '_escape(%s)' % chunk
+
+ def write_code(self, line, comment=''):
+ line, comment = self.fix_backward_compatibility(line, comment)
+ code = ' ' * (self.indent+self.indent_mod)
+ code += line.lstrip() + comment + '\n'
+ self.code_buffer.append(code)
+
+ def fix_backward_compatibility(self, line, comment):
+ parts = line.strip().split(None, 2)
+ if parts and parts[0] in ('include', 'rebase'):
+ depr('The include and rebase keywords are functions now.') #0.12
+ if len(parts) == 1: return "_printlist([base])", comment
+ elif len(parts) == 2: return "_=%s(%r)" % tuple(parts), comment
+ else: return "_=%s(%r, %s)" % tuple(parts), comment
+ if self.lineno <= 2 and not line.strip() and 'coding' in comment:
+ m = re.match(r"#.*coding[:=]\s*([-\w.]+)", comment)
+ if m:
+ depr('PEP263 encoding strings in templates are deprecated.') #0.12
+ enc = m.group(1)
+ self.source = self.source.encode(self.encoding).decode(enc)
+ self.encoding = enc
+ return line, comment.replace('coding','coding*')
+ return line, comment
+
+
+def template(*args, **kwargs):
+ '''
+ Get a rendered template as a string iterator.
+ You can use a name, a filename or a template string as first parameter.
+ Template rendering arguments can be passed as dictionaries
+ or directly (as keyword arguments).
+ '''
+ tpl = args[0] if args else None
+ adapter = kwargs.pop('template_adapter', SimpleTemplate)
+ lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
+ tplid = (id(lookup), tpl)
+ if tplid not in TEMPLATES or DEBUG:
+ settings = kwargs.pop('template_settings', {})
+ if isinstance(tpl, adapter):
+ TEMPLATES[tplid] = tpl
+ if settings: TEMPLATES[tplid].prepare(**settings)
+ elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
+ TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
+ else:
+ TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
+ if not TEMPLATES[tplid]:
+ abort(500, 'Template (%s) not found' % tpl)
+ for dictarg in args[1:]: kwargs.update(dictarg)
+ return TEMPLATES[tplid].render(kwargs)
+
+mako_template = functools.partial(template, template_adapter=MakoTemplate)
+cheetah_template = functools.partial(template, template_adapter=CheetahTemplate)
+jinja2_template = functools.partial(template, template_adapter=Jinja2Template)
+
+
+def view(tpl_name, **defaults):
+ ''' Decorator: renders a template for a handler.
+ The handler can control its behavior like that:
+
+ - return a dict of template vars to fill out the template
+ - return something other than a dict and the view decorator will not
+ process the template, but return the handler result as is.
+ This includes returning a HTTPResponse(dict) to get,
+ for instance, JSON with autojson or other castfilters.
+ '''
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ result = func(*args, **kwargs)
+ if isinstance(result, (dict, DictMixin)):
+ tplvars = defaults.copy()
+ tplvars.update(result)
+ return template(tpl_name, **tplvars)
+ elif result is None:
+ return template(tpl_name, defaults)
+ return result
+ return wrapper
+ return decorator
+
+mako_view = functools.partial(view, template_adapter=MakoTemplate)
+cheetah_view = functools.partial(view, template_adapter=CheetahTemplate)
+jinja2_view = functools.partial(view, template_adapter=Jinja2Template)
+
+
+
+
+
+
+###############################################################################
+# Constants and Globals ########################################################
+###############################################################################
+
+
+TEMPLATE_PATH = ['./', './views/']
+TEMPLATES = {}
+DEBUG = False
+NORUN = False # If set, run() does nothing. Used by load_app()
+
+#: A dict to map HTTP status codes (e.g. 404) to phrases (e.g. 'Not Found')
+HTTP_CODES = httplib.responses
+HTTP_CODES[418] = "I'm a teapot" # RFC 2324
+HTTP_CODES[428] = "Precondition Required"
+HTTP_CODES[429] = "Too Many Requests"
+HTTP_CODES[431] = "Request Header Fields Too Large"
+HTTP_CODES[511] = "Network Authentication Required"
+_HTTP_STATUS_LINES = dict((k, '%d %s'%(k,v)) for (k,v) in HTTP_CODES.items())
+
+#: The default template used for error pages. Override with @error()
+ERROR_PAGE_TEMPLATE = """
+%%try:
+ %%from %s import DEBUG, HTTP_CODES, request, touni
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
+ <html>
+ <head>
+ <title>Error: {{e.status}}</title>
+ <style type="text/css">
+ html {background-color: #eee; font-family: sans;}
+ body {background-color: #fff; border: 1px solid #ddd;
+ padding: 15px; margin: 15px;}
+ pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
+ </style>
+ </head>
+ <body>
+ <h1>Error: {{e.status}}</h1>
+ <p>Sorry, the requested URL <tt>{{repr(request.url)}}</tt>
+ caused an error:</p>
+ <pre>{{e.body}}</pre>
+ %%if DEBUG and e.exception:
+ <h2>Exception:</h2>
+ <pre>{{repr(e.exception)}}</pre>
+ %%end
+ %%if DEBUG and e.traceback:
+ <h2>Traceback:</h2>
+ <pre>{{e.traceback}}</pre>
+ %%end
+ </body>
+ </html>
+%%except ImportError:
+ <b>ImportError:</b> Could not generate the error page. Please add bottle to
+ the import path.
+%%end
+""" % __name__
+
+#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a
+#: request callback, this instance always refers to the *current* request
+#: (even on a multithreaded server).
+request = LocalRequest()
+
+#: A thread-safe instance of :class:`LocalResponse`. It is used to change the
+#: HTTP response for the *current* request.
+response = LocalResponse()
+
+#: A thread-safe namespace. Not used by Bottle.
+local = threading.local()
+
+# Initialize app stack (create first empty Bottle app)
+# BC: 0.6.4 and needed for run()
+app = default_app = AppStack()
+app.push()
+
+#: A virtual package that redirects import statements.
+#: Example: ``import bottle.ext.sqlite`` actually imports `bottle_sqlite`.
+ext = _ImportRedirect('bottle.ext' if __name__ == '__main__' else __name__+".ext", 'bottle_%s').module
+
+if __name__ == '__main__':
+ opt, args, parser = _cmd_options, _cmd_args, _cmd_parser
+ if opt.version:
+ _stdout('Bottle %s\n'%__version__)
+ sys.exit(0)
+ if not args:
+ parser.print_help()
+ _stderr('\nError: No application specified.\n')
+ sys.exit(1)
+
+ sys.path.insert(0, '.')
+ sys.modules.setdefault('bottle', sys.modules['__main__'])
+
+ host, port = (opt.bind or 'localhost'), 8080
+ if ':' in host and host.rfind(']') < host.rfind(':'):
+ host, port = host.rsplit(':', 1)
+ host = host.strip('[]')
+
+ run(args[0], host=host, port=int(port), server=opt.server,
+ reloader=opt.reload, plugins=opt.plugin, debug=opt.debug)
+
+
+
+
+# THE END
diff --git a/pyload/lib/colorama/__init__.py b/pyload/lib/colorama/__init__.py
new file mode 100644
index 000000000..5322f8b16
--- /dev/null
+++ b/pyload/lib/colorama/__init__.py
@@ -0,0 +1,7 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from .initialise import init, deinit, reinit
+from .ansi import Fore, Back, Style
+from .ansitowin32 import AnsiToWin32
+
+__version__ = '0.3.2'
+
diff --git a/pyload/lib/colorama/ansi.py b/pyload/lib/colorama/ansi.py
new file mode 100644
index 000000000..5dfe374ce
--- /dev/null
+++ b/pyload/lib/colorama/ansi.py
@@ -0,0 +1,50 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+'''
+This module generates ANSI character codes to printing colors to terminals.
+See: http://en.wikipedia.org/wiki/ANSI_escape_code
+'''
+
+CSI = '\033['
+
+def code_to_chars(code):
+ return CSI + str(code) + 'm'
+
+class AnsiCodes(object):
+ def __init__(self, codes):
+ for name in dir(codes):
+ if not name.startswith('_'):
+ value = getattr(codes, name)
+ setattr(self, name, code_to_chars(value))
+
+class AnsiFore:
+ BLACK = 30
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+ WHITE = 37
+ RESET = 39
+
+class AnsiBack:
+ BLACK = 40
+ RED = 41
+ GREEN = 42
+ YELLOW = 43
+ BLUE = 44
+ MAGENTA = 45
+ CYAN = 46
+ WHITE = 47
+ RESET = 49
+
+class AnsiStyle:
+ BRIGHT = 1
+ DIM = 2
+ NORMAL = 22
+ RESET_ALL = 0
+
+Fore = AnsiCodes( AnsiFore )
+Back = AnsiCodes( AnsiBack )
+Style = AnsiCodes( AnsiStyle )
+
diff --git a/pyload/lib/colorama/ansitowin32.py b/pyload/lib/colorama/ansitowin32.py
new file mode 100644
index 000000000..e7eb8441d
--- /dev/null
+++ b/pyload/lib/colorama/ansitowin32.py
@@ -0,0 +1,191 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import re
+import sys
+
+from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
+from .winterm import WinTerm, WinColor, WinStyle
+from .win32 import windll
+
+
+winterm = None
+if windll is not None:
+ winterm = WinTerm()
+
+
+def is_a_tty(stream):
+ return hasattr(stream, 'isatty') and stream.isatty()
+
+
+class StreamWrapper(object):
+ '''
+ Wraps a stream (such as stdout), acting as a transparent proxy for all
+ attribute access apart from method 'write()', which is delegated to our
+ Converter instance.
+ '''
+ def __init__(self, wrapped, converter):
+ # double-underscore everything to prevent clashes with names of
+ # attributes on the wrapped stream object.
+ self.__wrapped = wrapped
+ self.__convertor = converter
+
+ def __getattr__(self, name):
+ return getattr(self.__wrapped, name)
+
+ def write(self, text):
+ self.__convertor.write(text)
+
+
+class AnsiToWin32(object):
+ '''
+ Implements a 'write()' method which, on Windows, will strip ANSI character
+ sequences from the text, and if outputting to a tty, will convert them into
+ win32 function calls.
+ '''
+ ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
+
+ def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
+ # The wrapped stream (normally sys.stdout or sys.stderr)
+ self.wrapped = wrapped
+
+ # should we reset colors to defaults after every .write()
+ self.autoreset = autoreset
+
+ # create the proxy wrapping our output stream
+ self.stream = StreamWrapper(wrapped, self)
+
+ on_windows = sys.platform.startswith('win')
+
+ # should we strip ANSI sequences from our output?
+ if strip is None:
+ strip = on_windows
+ self.strip = strip
+
+ # should we should convert ANSI sequences into win32 calls?
+ if convert is None:
+ convert = on_windows and not wrapped.closed and is_a_tty(wrapped)
+ self.convert = convert
+
+ # dict of ansi codes to win32 functions and parameters
+ self.win32_calls = self.get_win32_calls()
+
+ # are we wrapping stderr?
+ self.on_stderr = self.wrapped is sys.stderr
+
+
+ def should_wrap(self):
+ '''
+ True if this class is actually needed. If false, then the output
+ stream will not be affected, nor will win32 calls be issued, so
+ wrapping stdout is not actually required. This will generally be
+ False on non-Windows platforms, unless optional functionality like
+ autoreset has been requested using kwargs to init()
+ '''
+ return self.convert or self.strip or self.autoreset
+
+
+ def get_win32_calls(self):
+ if self.convert and winterm:
+ return {
+ AnsiStyle.RESET_ALL: (winterm.reset_all, ),
+ AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
+ AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
+ AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
+ AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
+ AnsiFore.RED: (winterm.fore, WinColor.RED),
+ AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
+ AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
+ AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
+ AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
+ AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
+ AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
+ AnsiFore.RESET: (winterm.fore, ),
+ AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
+ AnsiBack.RED: (winterm.back, WinColor.RED),
+ AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
+ AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
+ AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
+ AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
+ AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
+ AnsiBack.WHITE: (winterm.back, WinColor.GREY),
+ AnsiBack.RESET: (winterm.back, ),
+ }
+ return dict()
+
+
+ def write(self, text):
+ if self.strip or self.convert:
+ self.write_and_convert(text)
+ else:
+ self.wrapped.write(text)
+ self.wrapped.flush()
+ if self.autoreset:
+ self.reset_all()
+
+
+ def reset_all(self):
+ if self.convert:
+ self.call_win32('m', (0,))
+ elif not self.wrapped.closed and is_a_tty(self.wrapped):
+ self.wrapped.write(Style.RESET_ALL)
+
+
+ def write_and_convert(self, text):
+ '''
+ Write the given text to our wrapped stream, stripping any ANSI
+ sequences from the text, and optionally converting them into win32
+ calls.
+ '''
+ cursor = 0
+ for match in self.ANSI_RE.finditer(text):
+ start, end = match.span()
+ self.write_plain_text(text, cursor, start)
+ self.convert_ansi(*match.groups())
+ cursor = end
+ self.write_plain_text(text, cursor, len(text))
+
+
+ def write_plain_text(self, text, start, end):
+ if start < end:
+ self.wrapped.write(text[start:end])
+ self.wrapped.flush()
+
+
+ def convert_ansi(self, paramstring, command):
+ if self.convert:
+ params = self.extract_params(paramstring)
+ self.call_win32(command, params)
+
+
+ def extract_params(self, paramstring):
+ def split(paramstring):
+ for p in paramstring.split(';'):
+ if p != '':
+ yield int(p)
+ return tuple(split(paramstring))
+
+
+ def call_win32(self, command, params):
+ if params == []:
+ params = [0]
+ if command == 'm':
+ for param in params:
+ if param in self.win32_calls:
+ func_args = self.win32_calls[param]
+ func = func_args[0]
+ args = func_args[1:]
+ kwargs = dict(on_stderr=self.on_stderr)
+ func(*args, **kwargs)
+ elif command in ('H', 'f'): # set cursor position
+ func = winterm.set_cursor_position
+ func(params, on_stderr=self.on_stderr)
+ elif command in ('J'):
+ func = winterm.erase_data
+ func(params, on_stderr=self.on_stderr)
+ elif command == 'A':
+ if params == () or params == None:
+ num_rows = 1
+ else:
+ num_rows = params[0]
+ func = winterm.cursor_up
+ func(num_rows, on_stderr=self.on_stderr)
+
diff --git a/pyload/lib/colorama/initialise.py b/pyload/lib/colorama/initialise.py
new file mode 100644
index 000000000..7e27f84f8
--- /dev/null
+++ b/pyload/lib/colorama/initialise.py
@@ -0,0 +1,66 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import atexit
+import sys
+
+from .ansitowin32 import AnsiToWin32
+
+
+orig_stdout = sys.stdout
+orig_stderr = sys.stderr
+
+wrapped_stdout = sys.stdout
+wrapped_stderr = sys.stderr
+
+atexit_done = False
+
+
+def reset_all():
+ AnsiToWin32(orig_stdout).reset_all()
+
+
+def init(autoreset=False, convert=None, strip=None, wrap=True):
+
+ if not wrap and any([autoreset, convert, strip]):
+ raise ValueError('wrap=False conflicts with any other arg=True')
+
+ global wrapped_stdout, wrapped_stderr
+ if sys.stdout is None:
+ wrapped_stdout = None
+ else:
+ sys.stdout = wrapped_stdout = \
+ wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
+ if sys.stderr is None:
+ wrapped_stderr = None
+ else:
+ sys.stderr = wrapped_stderr = \
+ wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
+
+ global atexit_done
+ if not atexit_done:
+ atexit.register(reset_all)
+ atexit_done = True
+
+
+def deinit():
+ if orig_stdout is not None:
+ sys.stdout = orig_stdout
+ if orig_stderr is not None:
+ sys.stderr = orig_stderr
+
+
+def reinit():
+ if wrapped_stdout is not None:
+ sys.stdout = wrapped_stdout
+ if wrapped_stderr is not None:
+ sys.stderr = wrapped_stderr
+
+
+def wrap_stream(stream, convert, strip, autoreset, wrap):
+ if wrap:
+ wrapper = AnsiToWin32(stream,
+ convert=convert, strip=strip, autoreset=autoreset)
+ if wrapper.should_wrap():
+ stream = wrapper.stream
+ return stream
+
+
diff --git a/pyload/lib/colorama/win32.py b/pyload/lib/colorama/win32.py
new file mode 100644
index 000000000..18f7e44ac
--- /dev/null
+++ b/pyload/lib/colorama/win32.py
@@ -0,0 +1,136 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+
+# from winbase.h
+STDOUT = -11
+STDERR = -12
+
+try:
+ import ctypes
+ from ctypes import LibraryLoader
+ windll = LibraryLoader(ctypes.WinDLL)
+ from ctypes import wintypes
+except (AttributeError, ImportError):
+ windll = None
+ SetConsoleTextAttribute = lambda *_: None
+else:
+ from ctypes import (
+ byref, Structure, c_char, c_short, c_uint32, c_ushort, POINTER
+ )
+
+ class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ """struct in wincon.h."""
+ _fields_ = [
+ ("dwSize", wintypes._COORD),
+ ("dwCursorPosition", wintypes._COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", wintypes._COORD),
+ ]
+ def __str__(self):
+ return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
+ self.dwSize.Y, self.dwSize.X
+ , self.dwCursorPosition.Y, self.dwCursorPosition.X
+ , self.wAttributes
+ , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
+ , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
+ )
+
+ _GetStdHandle = windll.kernel32.GetStdHandle
+ _GetStdHandle.argtypes = [
+ wintypes.DWORD,
+ ]
+ _GetStdHandle.restype = wintypes.HANDLE
+
+ _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+ _GetConsoleScreenBufferInfo.argtypes = [
+ wintypes.HANDLE,
+ POINTER(CONSOLE_SCREEN_BUFFER_INFO),
+ ]
+ _GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+
+ _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+ _SetConsoleTextAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ ]
+ _SetConsoleTextAttribute.restype = wintypes.BOOL
+
+ _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
+ _SetConsoleCursorPosition.argtypes = [
+ wintypes.HANDLE,
+ wintypes._COORD,
+ ]
+ _SetConsoleCursorPosition.restype = wintypes.BOOL
+
+ _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
+ _FillConsoleOutputCharacterA.argtypes = [
+ wintypes.HANDLE,
+ c_char,
+ wintypes.DWORD,
+ wintypes._COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputCharacterA.restype = wintypes.BOOL
+
+ _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+ _FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ wintypes._COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+ handles = {
+ STDOUT: _GetStdHandle(STDOUT),
+ STDERR: _GetStdHandle(STDERR),
+ }
+
+ def GetConsoleScreenBufferInfo(stream_id=STDOUT):
+ handle = handles[stream_id]
+ csbi = CONSOLE_SCREEN_BUFFER_INFO()
+ success = _GetConsoleScreenBufferInfo(
+ handle, byref(csbi))
+ return csbi
+
+ def SetConsoleTextAttribute(stream_id, attrs):
+ handle = handles[stream_id]
+ return _SetConsoleTextAttribute(handle, attrs)
+
+ def SetConsoleCursorPosition(stream_id, position):
+ position = wintypes._COORD(*position)
+ # If the position is out of range, do nothing.
+ if position.Y <= 0 or position.X <= 0:
+ return
+ # Adjust for Windows' SetConsoleCursorPosition:
+ # 1. being 0-based, while ANSI is 1-based.
+ # 2. expecting (x,y), while ANSI uses (y,x).
+ adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1)
+ # Adjust for viewport's scroll position
+ sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
+ adjusted_position.Y += sr.Top
+ adjusted_position.X += sr.Left
+ # Resume normal processing
+ handle = handles[stream_id]
+ return _SetConsoleCursorPosition(handle, adjusted_position)
+
+ def FillConsoleOutputCharacter(stream_id, char, length, start):
+ handle = handles[stream_id]
+ char = c_char(char)
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ success = _FillConsoleOutputCharacterA(
+ handle, char, length, start, byref(num_written))
+ return num_written.value
+
+ def FillConsoleOutputAttribute(stream_id, attr, length, start):
+ ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
+ handle = handles[stream_id]
+ attribute = wintypes.WORD(attr)
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ return _FillConsoleOutputAttribute(
+ handle, attribute, length, start, byref(num_written))
diff --git a/pyload/lib/colorama/winterm.py b/pyload/lib/colorama/winterm.py
new file mode 100644
index 000000000..270881154
--- /dev/null
+++ b/pyload/lib/colorama/winterm.py
@@ -0,0 +1,120 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from . import win32
+
+
+# from wincon.h
+class WinColor(object):
+ BLACK = 0
+ BLUE = 1
+ GREEN = 2
+ CYAN = 3
+ RED = 4
+ MAGENTA = 5
+ YELLOW = 6
+ GREY = 7
+
+# from wincon.h
+class WinStyle(object):
+ NORMAL = 0x00 # dim text, dim background
+ BRIGHT = 0x08 # bright text, dim background
+
+
+class WinTerm(object):
+
+ def __init__(self):
+ self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
+ self.set_attrs(self._default)
+ self._default_fore = self._fore
+ self._default_back = self._back
+ self._default_style = self._style
+
+ def get_attrs(self):
+ return self._fore + self._back * 16 + self._style
+
+ def set_attrs(self, value):
+ self._fore = value & 7
+ self._back = (value >> 4) & 7
+ self._style = value & WinStyle.BRIGHT
+
+ def reset_all(self, on_stderr=None):
+ self.set_attrs(self._default)
+ self.set_console(attrs=self._default)
+
+ def fore(self, fore=None, on_stderr=False):
+ if fore is None:
+ fore = self._default_fore
+ self._fore = fore
+ self.set_console(on_stderr=on_stderr)
+
+ def back(self, back=None, on_stderr=False):
+ if back is None:
+ back = self._default_back
+ self._back = back
+ self.set_console(on_stderr=on_stderr)
+
+ def style(self, style=None, on_stderr=False):
+ if style is None:
+ style = self._default_style
+ self._style = style
+ self.set_console(on_stderr=on_stderr)
+
+ def set_console(self, attrs=None, on_stderr=False):
+ if attrs is None:
+ attrs = self.get_attrs()
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleTextAttribute(handle, attrs)
+
+ def get_position(self, handle):
+ position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
+ # Because Windows coordinates are 0-based,
+ # and win32.SetConsoleCursorPosition expects 1-based.
+ position.X += 1
+ position.Y += 1
+ return position
+
+ def set_cursor_position(self, position=None, on_stderr=False):
+ if position is None:
+ #I'm not currently tracking the position, so there is no default.
+ #position = self.get_position()
+ return
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleCursorPosition(handle, position)
+
+ def cursor_up(self, num_rows=0, on_stderr=False):
+ if num_rows == 0:
+ return
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ position = self.get_position(handle)
+ adjusted_position = (position.Y - num_rows, position.X)
+ self.set_cursor_position(adjusted_position, on_stderr)
+
+ def erase_data(self, mode=0, on_stderr=False):
+ # 0 (or None) should clear from the cursor to the end of the screen.
+ # 1 should clear from the cursor to the beginning of the screen.
+ # 2 should clear the entire screen. (And maybe move cursor to (1,1)?)
+ #
+ # At the moment, I only support mode 2. From looking at the API, it
+ # should be possible to calculate a different number of bytes to clear,
+ # and to do so relative to the cursor position.
+ if mode[0] not in (2,):
+ return
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ # here's where we'll home the cursor
+ coord_screen = win32.COORD(0,0)
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ # get the number of character cells in the current buffer
+ dw_con_size = csbi.dwSize.X * csbi.dwSize.Y
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen );
+ # put the cursor at (0, 0)
+ win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))
diff --git a/pyload/lib/feedparser.py b/pyload/lib/feedparser.py
new file mode 100644
index 000000000..c78e6a39b
--- /dev/null
+++ b/pyload/lib/feedparser.py
@@ -0,0 +1,4013 @@
+"""Universal feed parser
+
+Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds
+
+Visit https://code.google.com/p/feedparser/ for the latest version
+Visit http://packages.python.org/feedparser/ for the latest documentation
+
+Required: Python 2.4 or later
+Recommended: iconv_codec <http://cjkpython.i18n.org/>
+"""
+
+__version__ = "5.1.3"
+__license__ = """
+Copyright (c) 2010-2012 Kurt McKee <contactme@kurtmckee.org>
+Copyright (c) 2002-2008 Mark Pilgrim
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE."""
+__author__ = "Mark Pilgrim <http://diveintomark.org/>"
+__contributors__ = ["Jason Diamond <http://injektilo.org/>",
+ "John Beimler <http://john.beimler.org/>",
+ "Fazal Majid <http://www.majid.info/mylos/weblog/>",
+ "Aaron Swartz <http://aaronsw.com/>",
+ "Kevin Marks <http://epeus.blogspot.com/>",
+ "Sam Ruby <http://intertwingly.net/>",
+ "Ade Oshineye <http://blog.oshineye.com/>",
+ "Martin Pool <http://sourcefrog.net/>",
+ "Kurt McKee <http://kurtmckee.org/>",
+ "Bernd Schlapsi <https://github.com/brot>",]
+
+# HTTP "User-Agent" header to send to servers when downloading feeds.
+# If you are embedding feedparser in a larger application, you should
+# change this to your application name and URL.
+USER_AGENT = "UniversalFeedParser/%s +https://code.google.com/p/feedparser/" % __version__
+
+# HTTP "Accept" header to send to servers when downloading feeds. If you don't
+# want to send an Accept header, set this to None.
+ACCEPT_HEADER = "application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1"
+
+# List of preferred XML parsers, by SAX driver name. These will be tried first,
+# but if they're not installed, Python will keep searching through its own list
+# of pre-installed parsers until it finds one that supports everything we need.
+PREFERRED_XML_PARSERS = ["drv_libxml2"]
+
+# If you want feedparser to automatically run HTML markup through HTML Tidy, set
+# this to 1. Requires mxTidy <http://www.egenix.com/files/python/mxTidy.html>
+# or utidylib <http://utidylib.berlios.de/>.
+TIDY_MARKUP = 0
+
+# List of Python interfaces for HTML Tidy, in order of preference. Only useful
+# if TIDY_MARKUP = 1
+PREFERRED_TIDY_INTERFACES = ["uTidy", "mxTidy"]
+
+# If you want feedparser to automatically resolve all relative URIs, set this
+# to 1.
+RESOLVE_RELATIVE_URIS = 1
+
+# If you want feedparser to automatically sanitize all potentially unsafe
+# HTML content, set this to 1.
+SANITIZE_HTML = 1
+
+# If you want feedparser to automatically parse microformat content embedded
+# in entry contents, set this to 1
+PARSE_MICROFORMATS = 1
+
+# ---------- Python 3 modules (make it work if possible) ----------
+try:
+ import rfc822
+except ImportError:
+ from email import _parseaddr as rfc822
+
+try:
+ # Python 3.1 introduces bytes.maketrans and simultaneously
+ # deprecates string.maketrans; use bytes.maketrans if possible
+ _maketrans = bytes.maketrans
+except (NameError, AttributeError):
+ import string
+ _maketrans = string.maketrans
+
+# base64 support for Atom feeds that contain embedded binary data
+try:
+ import base64, binascii
+except ImportError:
+ base64 = binascii = None
+else:
+ # Python 3.1 deprecates decodestring in favor of decodebytes
+ _base64decode = getattr(base64, 'decodebytes', base64.decodestring)
+
+# _s2bytes: convert a UTF-8 str to bytes if the interpreter is Python 3
+# _l2bytes: convert a list of ints to bytes if the interpreter is Python 3
+try:
+ if bytes is str:
+ # In Python 2.5 and below, bytes doesn't exist (NameError)
+ # In Python 2.6 and above, bytes and str are the same type
+ raise NameError
+except NameError:
+ # Python 2
+ def _s2bytes(s):
+ return s
+ def _l2bytes(l):
+ return ''.join(map(chr, l))
+else:
+ # Python 3
+ def _s2bytes(s):
+ return bytes(s, 'utf8')
+ def _l2bytes(l):
+ return bytes(l)
+
+# If you want feedparser to allow all URL schemes, set this to ()
+# List culled from Python's urlparse documentation at:
+# http://docs.python.org/library/urlparse.html
+# as well as from "URI scheme" at Wikipedia:
+# https://secure.wikimedia.org/wikipedia/en/wiki/URI_scheme
+# Many more will likely need to be added!
+ACCEPTABLE_URI_SCHEMES = (
+ 'file', 'ftp', 'gopher', 'h323', 'hdl', 'http', 'https', 'imap', 'magnet',
+ 'mailto', 'mms', 'news', 'nntp', 'prospero', 'rsync', 'rtsp', 'rtspu',
+ 'sftp', 'shttp', 'sip', 'sips', 'snews', 'svn', 'svn+ssh', 'telnet',
+ 'wais',
+ # Additional common-but-unofficial schemes
+ 'aim', 'callto', 'cvs', 'facetime', 'feed', 'git', 'gtalk', 'irc', 'ircs',
+ 'irc6', 'itms', 'mms', 'msnim', 'skype', 'ssh', 'smb', 'svn', 'ymsg',
+)
+#ACCEPTABLE_URI_SCHEMES = ()
+
+# ---------- required modules (should come with any Python distribution) ----------
+import cgi
+import codecs
+import copy
+import datetime
+import re
+import struct
+import time
+import types
+import urllib
+import urllib2
+import urlparse
+import warnings
+
+from htmlentitydefs import name2codepoint, codepoint2name, entitydefs
+
+try:
+ from io import BytesIO as _StringIO
+except ImportError:
+ try:
+ from cStringIO import StringIO as _StringIO
+ except ImportError:
+ from StringIO import StringIO as _StringIO
+
+# ---------- optional modules (feedparser will work without these, but with reduced functionality) ----------
+
+# gzip is included with most Python distributions, but may not be available if you compiled your own
+try:
+ import gzip
+except ImportError:
+ gzip = None
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+# If a real XML parser is available, feedparser will attempt to use it. feedparser has
+# been tested with the built-in SAX parser and libxml2. On platforms where the
+# Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some
+# versions of FreeBSD), feedparser will quietly fall back on regex-based parsing.
+try:
+ import xml.sax
+ from xml.sax.saxutils import escape as _xmlescape
+except ImportError:
+ _XML_AVAILABLE = 0
+ def _xmlescape(data,entities={}):
+ data = data.replace('&', '&amp;')
+ data = data.replace('>', '&gt;')
+ data = data.replace('<', '&lt;')
+ for char, entity in entities:
+ data = data.replace(char, entity)
+ return data
+else:
+ try:
+ xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers
+ except xml.sax.SAXReaderNotAvailable:
+ _XML_AVAILABLE = 0
+ else:
+ _XML_AVAILABLE = 1
+
+# sgmllib is not available by default in Python 3; if the end user doesn't have
+# it available then we'll lose illformed XML parsing, content santizing, and
+# microformat support (at least while feedparser depends on BeautifulSoup).
+try:
+ import sgmllib
+except ImportError:
+ # This is probably Python 3, which doesn't include sgmllib anymore
+ _SGML_AVAILABLE = 0
+
+ # Mock sgmllib enough to allow subclassing later on
+ class sgmllib(object):
+ class SGMLParser(object):
+ def goahead(self, i):
+ pass
+ def parse_starttag(self, i):
+ pass
+else:
+ _SGML_AVAILABLE = 1
+
+ # sgmllib defines a number of module-level regular expressions that are
+ # insufficient for the XML parsing feedparser needs. Rather than modify
+ # the variables directly in sgmllib, they're defined here using the same
+ # names, and the compiled code objects of several sgmllib.SGMLParser
+ # methods are copied into _BaseHTMLProcessor so that they execute in
+ # feedparser's scope instead of sgmllib's scope.
+ charref = re.compile('&#(\d+|[xX][0-9a-fA-F]+);')
+ tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
+ attrfind = re.compile(
+ r'\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)[$]?(\s*=\s*'
+ r'(\'[^\']*\'|"[^"]*"|[][\-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~\'"@]*))?'
+ )
+
+ # Unfortunately, these must be copied over to prevent NameError exceptions
+ entityref = sgmllib.entityref
+ incomplete = sgmllib.incomplete
+ interesting = sgmllib.interesting
+ shorttag = sgmllib.shorttag
+ shorttagopen = sgmllib.shorttagopen
+ starttagopen = sgmllib.starttagopen
+
+ class _EndBracketRegEx:
+ def __init__(self):
+ # Overriding the built-in sgmllib.endbracket regex allows the
+ # parser to find angle brackets embedded in element attributes.
+ self.endbracket = re.compile('''([^'"<>]|"[^"]*"(?=>|/|\s|\w+=)|'[^']*'(?=>|/|\s|\w+=))*(?=[<>])|.*?(?=[<>])''')
+ def search(self, target, index=0):
+ match = self.endbracket.match(target, index)
+ if match is not None:
+ # Returning a new object in the calling thread's context
+ # resolves a thread-safety.
+ return EndBracketMatch(match)
+ return None
+ class EndBracketMatch:
+ def __init__(self, match):
+ self.match = match
+ def start(self, n):
+ return self.match.end(n)
+ endbracket = _EndBracketRegEx()
+
+
+# iconv_codec provides support for more character encodings.
+# It's available from http://cjkpython.i18n.org/
+try:
+ import iconv_codec
+except ImportError:
+ pass
+
+# chardet library auto-detects character encodings
+# Download from http://chardet.feedparser.org/
+try:
+ import chardet
+except ImportError:
+ chardet = None
+
+# BeautifulSoup is used to extract microformat content from HTML
+# feedparser is tested using BeautifulSoup 3.2.0
+# http://www.crummy.com/software/BeautifulSoup/
+try:
+ import BeautifulSoup
+except ImportError:
+ BeautifulSoup = None
+ PARSE_MICROFORMATS = False
+
+# ---------- don't touch these ----------
+class ThingsNobodyCaresAboutButMe(Exception): pass
+class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): pass
+class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): pass
+class NonXMLContentType(ThingsNobodyCaresAboutButMe): pass
+class UndeclaredNamespace(Exception): pass
+
+SUPPORTED_VERSIONS = {'': u'unknown',
+ 'rss090': u'RSS 0.90',
+ 'rss091n': u'RSS 0.91 (Netscape)',
+ 'rss091u': u'RSS 0.91 (Userland)',
+ 'rss092': u'RSS 0.92',
+ 'rss093': u'RSS 0.93',
+ 'rss094': u'RSS 0.94',
+ 'rss20': u'RSS 2.0',
+ 'rss10': u'RSS 1.0',
+ 'rss': u'RSS (unknown version)',
+ 'atom01': u'Atom 0.1',
+ 'atom02': u'Atom 0.2',
+ 'atom03': u'Atom 0.3',
+ 'atom10': u'Atom 1.0',
+ 'atom': u'Atom (unknown version)',
+ 'cdf': u'CDF',
+ }
+
+class FeedParserDict(dict):
+ keymap = {'channel': 'feed',
+ 'items': 'entries',
+ 'guid': 'id',
+ 'date': 'updated',
+ 'date_parsed': 'updated_parsed',
+ 'description': ['summary', 'subtitle'],
+ 'description_detail': ['summary_detail', 'subtitle_detail'],
+ 'url': ['href'],
+ 'modified': 'updated',
+ 'modified_parsed': 'updated_parsed',
+ 'issued': 'published',
+ 'issued_parsed': 'published_parsed',
+ 'copyright': 'rights',
+ 'copyright_detail': 'rights_detail',
+ 'tagline': 'subtitle',
+ 'tagline_detail': 'subtitle_detail'}
+ def __getitem__(self, key):
+ if key == 'category':
+ try:
+ return dict.__getitem__(self, 'tags')[0]['term']
+ except IndexError:
+ raise KeyError, "object doesn't have key 'category'"
+ elif key == 'enclosures':
+ norel = lambda link: FeedParserDict([(name,value) for (name,value) in link.items() if name!='rel'])
+ return [norel(link) for link in dict.__getitem__(self, 'links') if link['rel']==u'enclosure']
+ elif key == 'license':
+ for link in dict.__getitem__(self, 'links'):
+ if link['rel']==u'license' and 'href' in link:
+ return link['href']
+ elif key == 'updated':
+ # Temporarily help developers out by keeping the old
+ # broken behavior that was reported in issue 310.
+ # This fix was proposed in issue 328.
+ if not dict.__contains__(self, 'updated') and \
+ dict.__contains__(self, 'published'):
+ warnings.warn("To avoid breaking existing software while "
+ "fixing issue 310, a temporary mapping has been created "
+ "from `updated` to `published` if `updated` doesn't "
+ "exist. This fallback will be removed in a future version "
+ "of feedparser.", DeprecationWarning)
+ return dict.__getitem__(self, 'published')
+ return dict.__getitem__(self, 'updated')
+ elif key == 'updated_parsed':
+ if not dict.__contains__(self, 'updated_parsed') and \
+ dict.__contains__(self, 'published_parsed'):
+ warnings.warn("To avoid breaking existing software while "
+ "fixing issue 310, a temporary mapping has been created "
+ "from `updated_parsed` to `published_parsed` if "
+ "`updated_parsed` doesn't exist. This fallback will be "
+ "removed in a future version of feedparser.",
+ DeprecationWarning)
+ return dict.__getitem__(self, 'published_parsed')
+ return dict.__getitem__(self, 'updated_parsed')
+ else:
+ realkey = self.keymap.get(key, key)
+ if isinstance(realkey, list):
+ for k in realkey:
+ if dict.__contains__(self, k):
+ return dict.__getitem__(self, k)
+ elif dict.__contains__(self, realkey):
+ return dict.__getitem__(self, realkey)
+ return dict.__getitem__(self, key)
+
+ def __contains__(self, key):
+ if key in ('updated', 'updated_parsed'):
+ # Temporarily help developers out by keeping the old
+ # broken behavior that was reported in issue 310.
+ # This fix was proposed in issue 328.
+ return dict.__contains__(self, key)
+ try:
+ self.__getitem__(key)
+ except KeyError:
+ return False
+ else:
+ return True
+
+ has_key = __contains__
+
+ def get(self, key, default=None):
+ try:
+ return self.__getitem__(key)
+ except KeyError:
+ return default
+
+ def __setitem__(self, key, value):
+ key = self.keymap.get(key, key)
+ if isinstance(key, list):
+ key = key[0]
+ return dict.__setitem__(self, key, value)
+
+ def setdefault(self, key, value):
+ if key not in self:
+ self[key] = value
+ return value
+ return self[key]
+
+ def __getattr__(self, key):
+ # __getattribute__() is called first; this will be called
+ # only if an attribute was not already found
+ try:
+ return self.__getitem__(key)
+ except KeyError:
+ raise AttributeError, "object has no attribute '%s'" % key
+
+ def __hash__(self):
+ return id(self)
+
+_cp1252 = {
+ 128: unichr(8364), # euro sign
+ 130: unichr(8218), # single low-9 quotation mark
+ 131: unichr( 402), # latin small letter f with hook
+ 132: unichr(8222), # double low-9 quotation mark
+ 133: unichr(8230), # horizontal ellipsis
+ 134: unichr(8224), # dagger
+ 135: unichr(8225), # double dagger
+ 136: unichr( 710), # modifier letter circumflex accent
+ 137: unichr(8240), # per mille sign
+ 138: unichr( 352), # latin capital letter s with caron
+ 139: unichr(8249), # single left-pointing angle quotation mark
+ 140: unichr( 338), # latin capital ligature oe
+ 142: unichr( 381), # latin capital letter z with caron
+ 145: unichr(8216), # left single quotation mark
+ 146: unichr(8217), # right single quotation mark
+ 147: unichr(8220), # left double quotation mark
+ 148: unichr(8221), # right double quotation mark
+ 149: unichr(8226), # bullet
+ 150: unichr(8211), # en dash
+ 151: unichr(8212), # em dash
+ 152: unichr( 732), # small tilde
+ 153: unichr(8482), # trade mark sign
+ 154: unichr( 353), # latin small letter s with caron
+ 155: unichr(8250), # single right-pointing angle quotation mark
+ 156: unichr( 339), # latin small ligature oe
+ 158: unichr( 382), # latin small letter z with caron
+ 159: unichr( 376), # latin capital letter y with diaeresis
+}
+
+_urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
+def _urljoin(base, uri):
+ uri = _urifixer.sub(r'\1\3', uri)
+ #try:
+ if not isinstance(uri, unicode):
+ uri = uri.decode('utf-8', 'ignore')
+ uri = urlparse.urljoin(base, uri)
+ if not isinstance(uri, unicode):
+ return uri.decode('utf-8', 'ignore')
+ return uri
+ #except:
+ # uri = urlparse.urlunparse([urllib.quote(part) for part in urlparse.urlparse(uri)])
+ # return urlparse.urljoin(base, uri)
+
+class _FeedParserMixin:
+ namespaces = {
+ '': '',
+ 'http://backend.userland.com/rss': '',
+ 'http://blogs.law.harvard.edu/tech/rss': '',
+ 'http://purl.org/rss/1.0/': '',
+ 'http://my.netscape.com/rdf/simple/0.9/': '',
+ 'http://example.com/newformat#': '',
+ 'http://example.com/necho': '',
+ 'http://purl.org/echo/': '',
+ 'uri/of/echo/namespace#': '',
+ 'http://purl.org/pie/': '',
+ 'http://purl.org/atom/ns#': '',
+ 'http://www.w3.org/2005/Atom': '',
+ 'http://purl.org/rss/1.0/modules/rss091#': '',
+
+ 'http://webns.net/mvcb/': 'admin',
+ 'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
+ 'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
+ 'http://media.tangent.org/rss/1.0/': 'audio',
+ 'http://backend.userland.com/blogChannelModule': 'blogChannel',
+ 'http://web.resource.org/cc/': 'cc',
+ 'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
+ 'http://purl.org/rss/1.0/modules/company': 'co',
+ 'http://purl.org/rss/1.0/modules/content/': 'content',
+ 'http://my.theinfo.org/changed/1.0/rss/': 'cp',
+ 'http://purl.org/dc/elements/1.1/': 'dc',
+ 'http://purl.org/dc/terms/': 'dcterms',
+ 'http://purl.org/rss/1.0/modules/email/': 'email',
+ 'http://purl.org/rss/1.0/modules/event/': 'ev',
+ 'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
+ 'http://freshmeat.net/rss/fm/': 'fm',
+ 'http://xmlns.com/foaf/0.1/': 'foaf',
+ 'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
+ 'http://postneo.com/icbm/': 'icbm',
+ 'http://purl.org/rss/1.0/modules/image/': 'image',
+ 'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
+ 'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
+ 'http://purl.org/rss/1.0/modules/link/': 'l',
+ 'http://search.yahoo.com/mrss': 'media',
+ # Version 1.1.2 of the Media RSS spec added the trailing slash on the namespace
+ 'http://search.yahoo.com/mrss/': 'media',
+ 'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
+ 'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
+ 'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
+ 'http://purl.org/rss/1.0/modules/reference/': 'ref',
+ 'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
+ 'http://purl.org/rss/1.0/modules/search/': 'search',
+ 'http://purl.org/rss/1.0/modules/slash/': 'slash',
+ 'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
+ 'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
+ 'http://hacks.benhammersley.com/rss/streaming/': 'str',
+ 'http://purl.org/rss/1.0/modules/subscription/': 'sub',
+ 'http://purl.org/rss/1.0/modules/syndication/': 'sy',
+ 'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf',
+ 'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
+ 'http://purl.org/rss/1.0/modules/threading/': 'thr',
+ 'http://purl.org/rss/1.0/modules/textinput/': 'ti',
+ 'http://madskills.com/public/xml/rss/module/trackback/': 'trackback',
+ 'http://wellformedweb.org/commentAPI/': 'wfw',
+ 'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
+ 'http://www.w3.org/1999/xhtml': 'xhtml',
+ 'http://www.w3.org/1999/xlink': 'xlink',
+ 'http://www.w3.org/XML/1998/namespace': 'xml',
+ }
+ _matchnamespaces = {}
+
+ can_be_relative_uri = set(['link', 'id', 'wfw_comment', 'wfw_commentrss', 'docs', 'url', 'href', 'comments', 'icon', 'logo'])
+ can_contain_relative_uris = set(['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description'])
+ can_contain_dangerous_markup = set(['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description'])
+ html_types = [u'text/html', u'application/xhtml+xml']
+
+ def __init__(self, baseuri=None, baselang=None, encoding=u'utf-8'):
+ if not self._matchnamespaces:
+ for k, v in self.namespaces.items():
+ self._matchnamespaces[k.lower()] = v
+ self.feeddata = FeedParserDict() # feed-level data
+ self.encoding = encoding # character encoding
+ self.entries = [] # list of entry-level data
+ self.version = u'' # feed type/version, see SUPPORTED_VERSIONS
+ self.namespacesInUse = {} # dictionary of namespaces defined by the feed
+
+ # the following are used internally to track state;
+ # this is really out of control and should be refactored
+ self.infeed = 0
+ self.inentry = 0
+ self.incontent = 0
+ self.intextinput = 0
+ self.inimage = 0
+ self.inauthor = 0
+ self.incontributor = 0
+ self.inpublisher = 0
+ self.insource = 0
+ self.sourcedata = FeedParserDict()
+ self.contentparams = FeedParserDict()
+ self._summaryKey = None
+ self.namespacemap = {}
+ self.elementstack = []
+ self.basestack = []
+ self.langstack = []
+ self.baseuri = baseuri or u''
+ self.lang = baselang or None
+ self.svgOK = 0
+ self.title_depth = -1
+ self.depth = 0
+ if baselang:
+ self.feeddata['language'] = baselang.replace('_','-')
+
+ # A map of the following form:
+ # {
+ # object_that_value_is_set_on: {
+ # property_name: depth_of_node_property_was_extracted_from,
+ # other_property: depth_of_node_property_was_extracted_from,
+ # },
+ # }
+ self.property_depth_map = {}
+
+ def _normalize_attributes(self, kv):
+ k = kv[0].lower()
+ v = k in ('rel', 'type') and kv[1].lower() or kv[1]
+ # the sgml parser doesn't handle entities in attributes, nor
+ # does it pass the attribute values through as unicode, while
+ # strict xml parsers do -- account for this difference
+ if isinstance(self, _LooseFeedParser):
+ v = v.replace('&amp;', '&')
+ if not isinstance(v, unicode):
+ v = v.decode('utf-8')
+ return (k, v)
+
+ def unknown_starttag(self, tag, attrs):
+ # increment depth counter
+ self.depth += 1
+
+ # normalize attrs
+ attrs = map(self._normalize_attributes, attrs)
+
+ # track xml:base and xml:lang
+ attrsD = dict(attrs)
+ baseuri = attrsD.get('xml:base', attrsD.get('base')) or self.baseuri
+ if not isinstance(baseuri, unicode):
+ baseuri = baseuri.decode(self.encoding, 'ignore')
+ # ensure that self.baseuri is always an absolute URI that
+ # uses a whitelisted URI scheme (e.g. not `javscript:`)
+ if self.baseuri:
+ self.baseuri = _makeSafeAbsoluteURI(self.baseuri, baseuri) or self.baseuri
+ else:
+ self.baseuri = _urljoin(self.baseuri, baseuri)
+ lang = attrsD.get('xml:lang', attrsD.get('lang'))
+ if lang == '':
+ # xml:lang could be explicitly set to '', we need to capture that
+ lang = None
+ elif lang is None:
+ # if no xml:lang is specified, use parent lang
+ lang = self.lang
+ if lang:
+ if tag in ('feed', 'rss', 'rdf:RDF'):
+ self.feeddata['language'] = lang.replace('_','-')
+ self.lang = lang
+ self.basestack.append(self.baseuri)
+ self.langstack.append(lang)
+
+ # track namespaces
+ for prefix, uri in attrs:
+ if prefix.startswith('xmlns:'):
+ self.trackNamespace(prefix[6:], uri)
+ elif prefix == 'xmlns':
+ self.trackNamespace(None, uri)
+
+ # track inline content
+ if self.incontent and not self.contentparams.get('type', u'xml').endswith(u'xml'):
+ if tag in ('xhtml:div', 'div'):
+ return # typepad does this 10/2007
+ # element declared itself as escaped markup, but it isn't really
+ self.contentparams['type'] = u'application/xhtml+xml'
+ if self.incontent and self.contentparams.get('type') == u'application/xhtml+xml':
+ if tag.find(':') <> -1:
+ prefix, tag = tag.split(':', 1)
+ namespace = self.namespacesInUse.get(prefix, '')
+ if tag=='math' and namespace=='http://www.w3.org/1998/Math/MathML':
+ attrs.append(('xmlns',namespace))
+ if tag=='svg' and namespace=='http://www.w3.org/2000/svg':
+ attrs.append(('xmlns',namespace))
+ if tag == 'svg':
+ self.svgOK += 1
+ return self.handle_data('<%s%s>' % (tag, self.strattrs(attrs)), escape=0)
+
+ # match namespaces
+ if tag.find(':') <> -1:
+ prefix, suffix = tag.split(':', 1)
+ else:
+ prefix, suffix = '', tag
+ prefix = self.namespacemap.get(prefix, prefix)
+ if prefix:
+ prefix = prefix + '_'
+
+ # special hack for better tracking of empty textinput/image elements in illformed feeds
+ if (not prefix) and tag not in ('title', 'link', 'description', 'name'):
+ self.intextinput = 0
+ if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
+ self.inimage = 0
+
+ # call special handler (if defined) or default handler
+ methodname = '_start_' + prefix + suffix
+ try:
+ method = getattr(self, methodname)
+ return method(attrsD)
+ except AttributeError:
+ # Since there's no handler or something has gone wrong we explicitly add the element and its attributes
+ unknown_tag = prefix + suffix
+ if len(attrsD) == 0:
+ # No attributes so merge it into the encosing dictionary
+ return self.push(unknown_tag, 1)
+ else:
+ # Has attributes so create it in its own dictionary
+ context = self._getContext()
+ context[unknown_tag] = attrsD
+
+ def unknown_endtag(self, tag):
+ # match namespaces
+ if tag.find(':') <> -1:
+ prefix, suffix = tag.split(':', 1)
+ else:
+ prefix, suffix = '', tag
+ prefix = self.namespacemap.get(prefix, prefix)
+ if prefix:
+ prefix = prefix + '_'
+ if suffix == 'svg' and self.svgOK:
+ self.svgOK -= 1
+
+ # call special handler (if defined) or default handler
+ methodname = '_end_' + prefix + suffix
+ try:
+ if self.svgOK:
+ raise AttributeError()
+ method = getattr(self, methodname)
+ method()
+ except AttributeError:
+ self.pop(prefix + suffix)
+
+ # track inline content
+ if self.incontent and not self.contentparams.get('type', u'xml').endswith(u'xml'):
+ # element declared itself as escaped markup, but it isn't really
+ if tag in ('xhtml:div', 'div'):
+ return # typepad does this 10/2007
+ self.contentparams['type'] = u'application/xhtml+xml'
+ if self.incontent and self.contentparams.get('type') == u'application/xhtml+xml':
+ tag = tag.split(':')[-1]
+ self.handle_data('</%s>' % tag, escape=0)
+
+ # track xml:base and xml:lang going out of scope
+ if self.basestack:
+ self.basestack.pop()
+ if self.basestack and self.basestack[-1]:
+ self.baseuri = self.basestack[-1]
+ if self.langstack:
+ self.langstack.pop()
+ if self.langstack: # and (self.langstack[-1] is not None):
+ self.lang = self.langstack[-1]
+
+ self.depth -= 1
+
+ def handle_charref(self, ref):
+ # called for each character reference, e.g. for '&#160;', ref will be '160'
+ if not self.elementstack:
+ return
+ ref = ref.lower()
+ if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
+ text = '&#%s;' % ref
+ else:
+ if ref[0] == 'x':
+ c = int(ref[1:], 16)
+ else:
+ c = int(ref)
+ text = unichr(c).encode('utf-8')
+ self.elementstack[-1][2].append(text)
+
+ def handle_entityref(self, ref):
+ # called for each entity reference, e.g. for '&copy;', ref will be 'copy'
+ if not self.elementstack:
+ return
+ if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
+ text = '&%s;' % ref
+ elif ref in self.entities:
+ text = self.entities[ref]
+ if text.startswith('&#') and text.endswith(';'):
+ return self.handle_entityref(text)
+ else:
+ try:
+ name2codepoint[ref]
+ except KeyError:
+ text = '&%s;' % ref
+ else:
+ text = unichr(name2codepoint[ref]).encode('utf-8')
+ self.elementstack[-1][2].append(text)
+
+ def handle_data(self, text, escape=1):
+ # called for each block of plain text, i.e. outside of any tag and
+ # not containing any character or entity references
+ if not self.elementstack:
+ return
+ if escape and self.contentparams.get('type') == u'application/xhtml+xml':
+ text = _xmlescape(text)
+ self.elementstack[-1][2].append(text)
+
+ def handle_comment(self, text):
+ # called for each comment, e.g. <!-- insert message here -->
+ pass
+
+ def handle_pi(self, text):
+ # called for each processing instruction, e.g. <?instruction>
+ pass
+
+ def handle_decl(self, text):
+ pass
+
+ def parse_declaration(self, i):
+ # override internal declaration handler to handle CDATA blocks
+ if self.rawdata[i:i+9] == '<![CDATA[':
+ k = self.rawdata.find(']]>', i)
+ if k == -1:
+ # CDATA block began but didn't finish
+ k = len(self.rawdata)
+ return k
+ self.handle_data(_xmlescape(self.rawdata[i+9:k]), 0)
+ return k+3
+ else:
+ k = self.rawdata.find('>', i)
+ if k >= 0:
+ return k+1
+ else:
+ # We have an incomplete CDATA block.
+ return k
+
+ def mapContentType(self, contentType):
+ contentType = contentType.lower()
+ if contentType == 'text' or contentType == 'plain':
+ contentType = u'text/plain'
+ elif contentType == 'html':
+ contentType = u'text/html'
+ elif contentType == 'xhtml':
+ contentType = u'application/xhtml+xml'
+ return contentType
+
+ def trackNamespace(self, prefix, uri):
+ loweruri = uri.lower()
+ if not self.version:
+ if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/'):
+ self.version = u'rss090'
+ elif loweruri == 'http://purl.org/rss/1.0/':
+ self.version = u'rss10'
+ elif loweruri == 'http://www.w3.org/2005/atom':
+ self.version = u'atom10'
+ if loweruri.find(u'backend.userland.com/rss') <> -1:
+ # match any backend.userland.com namespace
+ uri = u'http://backend.userland.com/rss'
+ loweruri = uri
+ if loweruri in self._matchnamespaces:
+ self.namespacemap[prefix] = self._matchnamespaces[loweruri]
+ self.namespacesInUse[self._matchnamespaces[loweruri]] = uri
+ else:
+ self.namespacesInUse[prefix or ''] = uri
+
+ def resolveURI(self, uri):
+ return _urljoin(self.baseuri or u'', uri)
+
+ def decodeEntities(self, element, data):
+ return data
+
+ def strattrs(self, attrs):
+ return ''.join([' %s="%s"' % (t[0],_xmlescape(t[1],{'"':'&quot;'})) for t in attrs])
+
+ def push(self, element, expectingText):
+ self.elementstack.append([element, expectingText, []])
+
+ def pop(self, element, stripWhitespace=1):
+ if not self.elementstack:
+ return
+ if self.elementstack[-1][0] != element:
+ return
+
+ element, expectingText, pieces = self.elementstack.pop()
+
+ if self.version == u'atom10' and self.contentparams.get('type', u'text') == u'application/xhtml+xml':
+ # remove enclosing child element, but only if it is a <div> and
+ # only if all the remaining content is nested underneath it.
+ # This means that the divs would be retained in the following:
+ # <div>foo</div><div>bar</div>
+ while pieces and len(pieces)>1 and not pieces[-1].strip():
+ del pieces[-1]
+ while pieces and len(pieces)>1 and not pieces[0].strip():
+ del pieces[0]
+ if pieces and (pieces[0] == '<div>' or pieces[0].startswith('<div ')) and pieces[-1]=='</div>':
+ depth = 0
+ for piece in pieces[:-1]:
+ if piece.startswith('</'):
+ depth -= 1
+ if depth == 0:
+ break
+ elif piece.startswith('<') and not piece.endswith('/>'):
+ depth += 1
+ else:
+ pieces = pieces[1:-1]
+
+ # Ensure each piece is a str for Python 3
+ for (i, v) in enumerate(pieces):
+ if not isinstance(v, unicode):
+ pieces[i] = v.decode('utf-8')
+
+ output = u''.join(pieces)
+ if stripWhitespace:
+ output = output.strip()
+ if not expectingText:
+ return output
+
+ # decode base64 content
+ if base64 and self.contentparams.get('base64', 0):
+ try:
+ output = _base64decode(output)
+ except binascii.Error:
+ pass
+ except binascii.Incomplete:
+ pass
+ except TypeError:
+ # In Python 3, base64 takes and outputs bytes, not str
+ # This may not be the most correct way to accomplish this
+ output = _base64decode(output.encode('utf-8')).decode('utf-8')
+
+ # resolve relative URIs
+ if (element in self.can_be_relative_uri) and output:
+ output = self.resolveURI(output)
+
+ # decode entities within embedded markup
+ if not self.contentparams.get('base64', 0):
+ output = self.decodeEntities(element, output)
+
+ # some feed formats require consumers to guess
+ # whether the content is html or plain text
+ if not self.version.startswith(u'atom') and self.contentparams.get('type') == u'text/plain':
+ if self.lookslikehtml(output):
+ self.contentparams['type'] = u'text/html'
+
+ # remove temporary cruft from contentparams
+ try:
+ del self.contentparams['mode']
+ except KeyError:
+ pass
+ try:
+ del self.contentparams['base64']
+ except KeyError:
+ pass
+
+ is_htmlish = self.mapContentType(self.contentparams.get('type', u'text/html')) in self.html_types
+ # resolve relative URIs within embedded markup
+ if is_htmlish and RESOLVE_RELATIVE_URIS:
+ if element in self.can_contain_relative_uris:
+ output = _resolveRelativeURIs(output, self.baseuri, self.encoding, self.contentparams.get('type', u'text/html'))
+
+ # parse microformats
+ # (must do this before sanitizing because some microformats
+ # rely on elements that we sanitize)
+ if PARSE_MICROFORMATS and is_htmlish and element in ['content', 'description', 'summary']:
+ mfresults = _parseMicroformats(output, self.baseuri, self.encoding)
+ if mfresults:
+ for tag in mfresults.get('tags', []):
+ self._addTag(tag['term'], tag['scheme'], tag['label'])
+ for enclosure in mfresults.get('enclosures', []):
+ self._start_enclosure(enclosure)
+ for xfn in mfresults.get('xfn', []):
+ self._addXFN(xfn['relationships'], xfn['href'], xfn['name'])
+ vcard = mfresults.get('vcard')
+ if vcard:
+ self._getContext()['vcard'] = vcard
+
+ # sanitize embedded markup
+ if is_htmlish and SANITIZE_HTML:
+ if element in self.can_contain_dangerous_markup:
+ output = _sanitizeHTML(output, self.encoding, self.contentparams.get('type', u'text/html'))
+
+ if self.encoding and not isinstance(output, unicode):
+ output = output.decode(self.encoding, 'ignore')
+
+ # address common error where people take data that is already
+ # utf-8, presume that it is iso-8859-1, and re-encode it.
+ if self.encoding in (u'utf-8', u'utf-8_INVALID_PYTHON_3') and isinstance(output, unicode):
+ try:
+ output = output.encode('iso-8859-1').decode('utf-8')
+ except (UnicodeEncodeError, UnicodeDecodeError):
+ pass
+
+ # map win-1252 extensions to the proper code points
+ if isinstance(output, unicode):
+ output = output.translate(_cp1252)
+
+ # categories/tags/keywords/whatever are handled in _end_category
+ if element == 'category':
+ return output
+
+ if element == 'title' and -1 < self.title_depth <= self.depth:
+ return output
+
+ # store output in appropriate place(s)
+ if self.inentry and not self.insource:
+ if element == 'content':
+ self.entries[-1].setdefault(element, [])
+ contentparams = copy.deepcopy(self.contentparams)
+ contentparams['value'] = output
+ self.entries[-1][element].append(contentparams)
+ elif element == 'link':
+ if not self.inimage:
+ # query variables in urls in link elements are improperly
+ # converted from `?a=1&b=2` to `?a=1&b;=2` as if they're
+ # unhandled character references. fix this special case.
+ output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output)
+ self.entries[-1][element] = output
+ if output:
+ self.entries[-1]['links'][-1]['href'] = output
+ else:
+ if element == 'description':
+ element = 'summary'
+ old_value_depth = self.property_depth_map.setdefault(self.entries[-1], {}).get(element)
+ if old_value_depth is None or self.depth <= old_value_depth:
+ self.property_depth_map[self.entries[-1]][element] = self.depth
+ self.entries[-1][element] = output
+ if self.incontent:
+ contentparams = copy.deepcopy(self.contentparams)
+ contentparams['value'] = output
+ self.entries[-1][element + '_detail'] = contentparams
+ elif (self.infeed or self.insource):# and (not self.intextinput) and (not self.inimage):
+ context = self._getContext()
+ if element == 'description':
+ element = 'subtitle'
+ context[element] = output
+ if element == 'link':
+ # fix query variables; see above for the explanation
+ output = re.sub("&([A-Za-z0-9_]+);", "&\g<1>", output)
+ context[element] = output
+ context['links'][-1]['href'] = output
+ elif self.incontent:
+ contentparams = copy.deepcopy(self.contentparams)
+ contentparams['value'] = output
+ context[element + '_detail'] = contentparams
+ return output
+
+ def pushContent(self, tag, attrsD, defaultContentType, expectingText):
+ self.incontent += 1
+ if self.lang:
+ self.lang=self.lang.replace('_','-')
+ self.contentparams = FeedParserDict({
+ 'type': self.mapContentType(attrsD.get('type', defaultContentType)),
+ 'language': self.lang,
+ 'base': self.baseuri})
+ self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
+ self.push(tag, expectingText)
+
+ def popContent(self, tag):
+ value = self.pop(tag)
+ self.incontent -= 1
+ self.contentparams.clear()
+ return value
+
+ # a number of elements in a number of RSS variants are nominally plain
+ # text, but this is routinely ignored. This is an attempt to detect
+ # the most common cases. As false positives often result in silent
+ # data loss, this function errs on the conservative side.
+ @staticmethod
+ def lookslikehtml(s):
+ # must have a close tag or an entity reference to qualify
+ if not (re.search(r'</(\w+)>',s) or re.search("&#?\w+;",s)):
+ return
+
+ # all tags must be in a restricted subset of valid HTML tags
+ if filter(lambda t: t.lower() not in _HTMLSanitizer.acceptable_elements,
+ re.findall(r'</?(\w+)',s)):
+ return
+
+ # all entities must have been defined as valid HTML entities
+ if filter(lambda e: e not in entitydefs.keys(), re.findall(r'&(\w+);', s)):
+ return
+
+ return 1
+
+ def _mapToStandardPrefix(self, name):
+ colonpos = name.find(':')
+ if colonpos <> -1:
+ prefix = name[:colonpos]
+ suffix = name[colonpos+1:]
+ prefix = self.namespacemap.get(prefix, prefix)
+ name = prefix + ':' + suffix
+ return name
+
+ def _getAttribute(self, attrsD, name):
+ return attrsD.get(self._mapToStandardPrefix(name))
+
+ def _isBase64(self, attrsD, contentparams):
+ if attrsD.get('mode', '') == 'base64':
+ return 1
+ if self.contentparams['type'].startswith(u'text/'):
+ return 0
+ if self.contentparams['type'].endswith(u'+xml'):
+ return 0
+ if self.contentparams['type'].endswith(u'/xml'):
+ return 0
+ return 1
+
+ def _itsAnHrefDamnIt(self, attrsD):
+ href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
+ if href:
+ try:
+ del attrsD['url']
+ except KeyError:
+ pass
+ try:
+ del attrsD['uri']
+ except KeyError:
+ pass
+ attrsD['href'] = href
+ return attrsD
+
+ def _save(self, key, value, overwrite=False):
+ context = self._getContext()
+ if overwrite:
+ context[key] = value
+ else:
+ context.setdefault(key, value)
+
+ def _start_rss(self, attrsD):
+ versionmap = {'0.91': u'rss091u',
+ '0.92': u'rss092',
+ '0.93': u'rss093',
+ '0.94': u'rss094'}
+ #If we're here then this is an RSS feed.
+ #If we don't have a version or have a version that starts with something
+ #other than RSS then there's been a mistake. Correct it.
+ if not self.version or not self.version.startswith(u'rss'):
+ attr_version = attrsD.get('version', '')
+ version = versionmap.get(attr_version)
+ if version:
+ self.version = version
+ elif attr_version.startswith('2.'):
+ self.version = u'rss20'
+ else:
+ self.version = u'rss'
+
+ def _start_channel(self, attrsD):
+ self.infeed = 1
+ self._cdf_common(attrsD)
+
+ def _cdf_common(self, attrsD):
+ if 'lastmod' in attrsD:
+ self._start_modified({})
+ self.elementstack[-1][-1] = attrsD['lastmod']
+ self._end_modified()
+ if 'href' in attrsD:
+ self._start_link({})
+ self.elementstack[-1][-1] = attrsD['href']
+ self._end_link()
+
+ def _start_feed(self, attrsD):
+ self.infeed = 1
+ versionmap = {'0.1': u'atom01',
+ '0.2': u'atom02',
+ '0.3': u'atom03'}
+ if not self.version:
+ attr_version = attrsD.get('version')
+ version = versionmap.get(attr_version)
+ if version:
+ self.version = version
+ else:
+ self.version = u'atom'
+
+ def _end_channel(self):
+ self.infeed = 0
+ _end_feed = _end_channel
+
+ def _start_image(self, attrsD):
+ context = self._getContext()
+ if not self.inentry:
+ context.setdefault('image', FeedParserDict())
+ self.inimage = 1
+ self.title_depth = -1
+ self.push('image', 0)
+
+ def _end_image(self):
+ self.pop('image')
+ self.inimage = 0
+
+ def _start_textinput(self, attrsD):
+ context = self._getContext()
+ context.setdefault('textinput', FeedParserDict())
+ self.intextinput = 1
+ self.title_depth = -1
+ self.push('textinput', 0)
+ _start_textInput = _start_textinput
+
+ def _end_textinput(self):
+ self.pop('textinput')
+ self.intextinput = 0
+ _end_textInput = _end_textinput
+
+ def _start_author(self, attrsD):
+ self.inauthor = 1
+ self.push('author', 1)
+ # Append a new FeedParserDict when expecting an author
+ context = self._getContext()
+ context.setdefault('authors', [])
+ context['authors'].append(FeedParserDict())
+ _start_managingeditor = _start_author
+ _start_dc_author = _start_author
+ _start_dc_creator = _start_author
+ _start_itunes_author = _start_author
+
+ def _end_author(self):
+ self.pop('author')
+ self.inauthor = 0
+ self._sync_author_detail()
+ _end_managingeditor = _end_author
+ _end_dc_author = _end_author
+ _end_dc_creator = _end_author
+ _end_itunes_author = _end_author
+
+ def _start_itunes_owner(self, attrsD):
+ self.inpublisher = 1
+ self.push('publisher', 0)
+
+ def _end_itunes_owner(self):
+ self.pop('publisher')
+ self.inpublisher = 0
+ self._sync_author_detail('publisher')
+
+ def _start_contributor(self, attrsD):
+ self.incontributor = 1
+ context = self._getContext()
+ context.setdefault('contributors', [])
+ context['contributors'].append(FeedParserDict())
+ self.push('contributor', 0)
+
+ def _end_contributor(self):
+ self.pop('contributor')
+ self.incontributor = 0
+
+ def _start_dc_contributor(self, attrsD):
+ self.incontributor = 1
+ context = self._getContext()
+ context.setdefault('contributors', [])
+ context['contributors'].append(FeedParserDict())
+ self.push('name', 0)
+
+ def _end_dc_contributor(self):
+ self._end_name()
+ self.incontributor = 0
+
+ def _start_name(self, attrsD):
+ self.push('name', 0)
+ _start_itunes_name = _start_name
+
+ def _end_name(self):
+ value = self.pop('name')
+ if self.inpublisher:
+ self._save_author('name', value, 'publisher')
+ elif self.inauthor:
+ self._save_author('name', value)
+ elif self.incontributor:
+ self._save_contributor('name', value)
+ elif self.intextinput:
+ context = self._getContext()
+ context['name'] = value
+ _end_itunes_name = _end_name
+
+ def _start_width(self, attrsD):
+ self.push('width', 0)
+
+ def _end_width(self):
+ value = self.pop('width')
+ try:
+ value = int(value)
+ except ValueError:
+ value = 0
+ if self.inimage:
+ context = self._getContext()
+ context['width'] = value
+
+ def _start_height(self, attrsD):
+ self.push('height', 0)
+
+ def _end_height(self):
+ value = self.pop('height')
+ try:
+ value = int(value)
+ except ValueError:
+ value = 0
+ if self.inimage:
+ context = self._getContext()
+ context['height'] = value
+
+ def _start_url(self, attrsD):
+ self.push('href', 1)
+ _start_homepage = _start_url
+ _start_uri = _start_url
+
+ def _end_url(self):
+ value = self.pop('href')
+ if self.inauthor:
+ self._save_author('href', value)
+ elif self.incontributor:
+ self._save_contributor('href', value)
+ _end_homepage = _end_url
+ _end_uri = _end_url
+
+ def _start_email(self, attrsD):
+ self.push('email', 0)
+ _start_itunes_email = _start_email
+
+ def _end_email(self):
+ value = self.pop('email')
+ if self.inpublisher:
+ self._save_author('email', value, 'publisher')
+ elif self.inauthor:
+ self._save_author('email', value)
+ elif self.incontributor:
+ self._save_contributor('email', value)
+ _end_itunes_email = _end_email
+
+ def _getContext(self):
+ if self.insource:
+ context = self.sourcedata
+ elif self.inimage and 'image' in self.feeddata:
+ context = self.feeddata['image']
+ elif self.intextinput:
+ context = self.feeddata['textinput']
+ elif self.inentry:
+ context = self.entries[-1]
+ else:
+ context = self.feeddata
+ return context
+
+ def _save_author(self, key, value, prefix='author'):
+ context = self._getContext()
+ context.setdefault(prefix + '_detail', FeedParserDict())
+ context[prefix + '_detail'][key] = value
+ self._sync_author_detail()
+ context.setdefault('authors', [FeedParserDict()])
+ context['authors'][-1][key] = value
+
+ def _save_contributor(self, key, value):
+ context = self._getContext()
+ context.setdefault('contributors', [FeedParserDict()])
+ context['contributors'][-1][key] = value
+
+ def _sync_author_detail(self, key='author'):
+ context = self._getContext()
+ detail = context.get('%s_detail' % key)
+ if detail:
+ name = detail.get('name')
+ email = detail.get('email')
+ if name and email:
+ context[key] = u'%s (%s)' % (name, email)
+ elif name:
+ context[key] = name
+ elif email:
+ context[key] = email
+ else:
+ author, email = context.get(key), None
+ if not author:
+ return
+ emailmatch = re.search(ur'''(([a-zA-Z0-9\_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))(\?subject=\S+)?''', author)
+ if emailmatch:
+ email = emailmatch.group(0)
+ # probably a better way to do the following, but it passes all the tests
+ author = author.replace(email, u'')
+ author = author.replace(u'()', u'')
+ author = author.replace(u'<>', u'')
+ author = author.replace(u'&lt;&gt;', u'')
+ author = author.strip()
+ if author and (author[0] == u'('):
+ author = author[1:]
+ if author and (author[-1] == u')'):
+ author = author[:-1]
+ author = author.strip()
+ if author or email:
+ context.setdefault('%s_detail' % key, FeedParserDict())
+ if author:
+ context['%s_detail' % key]['name'] = author
+ if email:
+ context['%s_detail' % key]['email'] = email
+
+ def _start_subtitle(self, attrsD):
+ self.pushContent('subtitle', attrsD, u'text/plain', 1)
+ _start_tagline = _start_subtitle
+ _start_itunes_subtitle = _start_subtitle
+
+ def _end_subtitle(self):
+ self.popContent('subtitle')
+ _end_tagline = _end_subtitle
+ _end_itunes_subtitle = _end_subtitle
+
+ def _start_rights(self, attrsD):
+ self.pushContent('rights', attrsD, u'text/plain', 1)
+ _start_dc_rights = _start_rights
+ _start_copyright = _start_rights
+
+ def _end_rights(self):
+ self.popContent('rights')
+ _end_dc_rights = _end_rights
+ _end_copyright = _end_rights
+
+ def _start_item(self, attrsD):
+ self.entries.append(FeedParserDict())
+ self.push('item', 0)
+ self.inentry = 1
+ self.guidislink = 0
+ self.title_depth = -1
+ id = self._getAttribute(attrsD, 'rdf:about')
+ if id:
+ context = self._getContext()
+ context['id'] = id
+ self._cdf_common(attrsD)
+ _start_entry = _start_item
+
+ def _end_item(self):
+ self.pop('item')
+ self.inentry = 0
+ _end_entry = _end_item
+
+ def _start_dc_language(self, attrsD):
+ self.push('language', 1)
+ _start_language = _start_dc_language
+
+ def _end_dc_language(self):
+ self.lang = self.pop('language')
+ _end_language = _end_dc_language
+
+ def _start_dc_publisher(self, attrsD):
+ self.push('publisher', 1)
+ _start_webmaster = _start_dc_publisher
+
+ def _end_dc_publisher(self):
+ self.pop('publisher')
+ self._sync_author_detail('publisher')
+ _end_webmaster = _end_dc_publisher
+
+ def _start_published(self, attrsD):
+ self.push('published', 1)
+ _start_dcterms_issued = _start_published
+ _start_issued = _start_published
+ _start_pubdate = _start_published
+
+ def _end_published(self):
+ value = self.pop('published')
+ self._save('published_parsed', _parse_date(value), overwrite=True)
+ _end_dcterms_issued = _end_published
+ _end_issued = _end_published
+ _end_pubdate = _end_published
+
+ def _start_updated(self, attrsD):
+ self.push('updated', 1)
+ _start_modified = _start_updated
+ _start_dcterms_modified = _start_updated
+ _start_dc_date = _start_updated
+ _start_lastbuilddate = _start_updated
+
+ def _end_updated(self):
+ value = self.pop('updated')
+ parsed_value = _parse_date(value)
+ self._save('updated_parsed', parsed_value, overwrite=True)
+ _end_modified = _end_updated
+ _end_dcterms_modified = _end_updated
+ _end_dc_date = _end_updated
+ _end_lastbuilddate = _end_updated
+
+ def _start_created(self, attrsD):
+ self.push('created', 1)
+ _start_dcterms_created = _start_created
+
+ def _end_created(self):
+ value = self.pop('created')
+ self._save('created_parsed', _parse_date(value), overwrite=True)
+ _end_dcterms_created = _end_created
+
+ def _start_expirationdate(self, attrsD):
+ self.push('expired', 1)
+
+ def _end_expirationdate(self):
+ self._save('expired_parsed', _parse_date(self.pop('expired')), overwrite=True)
+
+ def _start_cc_license(self, attrsD):
+ context = self._getContext()
+ value = self._getAttribute(attrsD, 'rdf:resource')
+ attrsD = FeedParserDict()
+ attrsD['rel'] = u'license'
+ if value:
+ attrsD['href']=value
+ context.setdefault('links', []).append(attrsD)
+
+ def _start_creativecommons_license(self, attrsD):
+ self.push('license', 1)
+ _start_creativeCommons_license = _start_creativecommons_license
+
+ def _end_creativecommons_license(self):
+ value = self.pop('license')
+ context = self._getContext()
+ attrsD = FeedParserDict()
+ attrsD['rel'] = u'license'
+ if value:
+ attrsD['href'] = value
+ context.setdefault('links', []).append(attrsD)
+ del context['license']
+ _end_creativeCommons_license = _end_creativecommons_license
+
+ def _addXFN(self, relationships, href, name):
+ context = self._getContext()
+ xfn = context.setdefault('xfn', [])
+ value = FeedParserDict({'relationships': relationships, 'href': href, 'name': name})
+ if value not in xfn:
+ xfn.append(value)
+
+ def _addTag(self, term, scheme, label):
+ context = self._getContext()
+ tags = context.setdefault('tags', [])
+ if (not term) and (not scheme) and (not label):
+ return
+ value = FeedParserDict({'term': term, 'scheme': scheme, 'label': label})
+ if value not in tags:
+ tags.append(value)
+
+ def _start_category(self, attrsD):
+ term = attrsD.get('term')
+ scheme = attrsD.get('scheme', attrsD.get('domain'))
+ label = attrsD.get('label')
+ self._addTag(term, scheme, label)
+ self.push('category', 1)
+ _start_dc_subject = _start_category
+ _start_keywords = _start_category
+
+ def _start_media_category(self, attrsD):
+ attrsD.setdefault('scheme', u'http://search.yahoo.com/mrss/category_schema')
+ self._start_category(attrsD)
+
+ def _end_itunes_keywords(self):
+ for term in self.pop('itunes_keywords').split(','):
+ if term.strip():
+ self._addTag(term.strip(), u'http://www.itunes.com/', None)
+
+ def _start_itunes_category(self, attrsD):
+ self._addTag(attrsD.get('text'), u'http://www.itunes.com/', None)
+ self.push('category', 1)
+
+ def _end_category(self):
+ value = self.pop('category')
+ if not value:
+ return
+ context = self._getContext()
+ tags = context['tags']
+ if value and len(tags) and not tags[-1]['term']:
+ tags[-1]['term'] = value
+ else:
+ self._addTag(value, None, None)
+ _end_dc_subject = _end_category
+ _end_keywords = _end_category
+ _end_itunes_category = _end_category
+ _end_media_category = _end_category
+
+ def _start_cloud(self, attrsD):
+ self._getContext()['cloud'] = FeedParserDict(attrsD)
+
+ def _start_link(self, attrsD):
+ attrsD.setdefault('rel', u'alternate')
+ if attrsD['rel'] == u'self':
+ attrsD.setdefault('type', u'application/atom+xml')
+ else:
+ attrsD.setdefault('type', u'text/html')
+ context = self._getContext()
+ attrsD = self._itsAnHrefDamnIt(attrsD)
+ if 'href' in attrsD:
+ attrsD['href'] = self.resolveURI(attrsD['href'])
+ expectingText = self.infeed or self.inentry or self.insource
+ context.setdefault('links', [])
+ if not (self.inentry and self.inimage):
+ context['links'].append(FeedParserDict(attrsD))
+ if 'href' in attrsD:
+ expectingText = 0
+ if (attrsD.get('rel') == u'alternate') and (self.mapContentType(attrsD.get('type')) in self.html_types):
+ context['link'] = attrsD['href']
+ else:
+ self.push('link', expectingText)
+
+ def _end_link(self):
+ value = self.pop('link')
+
+ def _start_guid(self, attrsD):
+ self.guidislink = (attrsD.get('ispermalink', 'true') == 'true')
+ self.push('id', 1)
+ _start_id = _start_guid
+
+ def _end_guid(self):
+ value = self.pop('id')
+ self._save('guidislink', self.guidislink and 'link' not in self._getContext())
+ if self.guidislink:
+ # guid acts as link, but only if 'ispermalink' is not present or is 'true',
+ # and only if the item doesn't already have a link element
+ self._save('link', value)
+ _end_id = _end_guid
+
+ def _start_title(self, attrsD):
+ if self.svgOK:
+ return self.unknown_starttag('title', attrsD.items())
+ self.pushContent('title', attrsD, u'text/plain', self.infeed or self.inentry or self.insource)
+ _start_dc_title = _start_title
+ _start_media_title = _start_title
+
+ def _end_title(self):
+ if self.svgOK:
+ return
+ value = self.popContent('title')
+ if not value:
+ return
+ self.title_depth = self.depth
+ _end_dc_title = _end_title
+
+ def _end_media_title(self):
+ title_depth = self.title_depth
+ self._end_title()
+ self.title_depth = title_depth
+
+ def _start_description(self, attrsD):
+ context = self._getContext()
+ if 'summary' in context:
+ self._summaryKey = 'content'
+ self._start_content(attrsD)
+ else:
+ self.pushContent('description', attrsD, u'text/html', self.infeed or self.inentry or self.insource)
+ _start_dc_description = _start_description
+
+ def _start_abstract(self, attrsD):
+ self.pushContent('description', attrsD, u'text/plain', self.infeed or self.inentry or self.insource)
+
+ def _end_description(self):
+ if self._summaryKey == 'content':
+ self._end_content()
+ else:
+ value = self.popContent('description')
+ self._summaryKey = None
+ _end_abstract = _end_description
+ _end_dc_description = _end_description
+
+ def _start_info(self, attrsD):
+ self.pushContent('info', attrsD, u'text/plain', 1)
+ _start_feedburner_browserfriendly = _start_info
+
+ def _end_info(self):
+ self.popContent('info')
+ _end_feedburner_browserfriendly = _end_info
+
+ def _start_generator(self, attrsD):
+ if attrsD:
+ attrsD = self._itsAnHrefDamnIt(attrsD)
+ if 'href' in attrsD:
+ attrsD['href'] = self.resolveURI(attrsD['href'])
+ self._getContext()['generator_detail'] = FeedParserDict(attrsD)
+ self.push('generator', 1)
+
+ def _end_generator(self):
+ value = self.pop('generator')
+ context = self._getContext()
+ if 'generator_detail' in context:
+ context['generator_detail']['name'] = value
+
+ def _start_admin_generatoragent(self, attrsD):
+ self.push('generator', 1)
+ value = self._getAttribute(attrsD, 'rdf:resource')
+ if value:
+ self.elementstack[-1][2].append(value)
+ self.pop('generator')
+ self._getContext()['generator_detail'] = FeedParserDict({'href': value})
+
+ def _start_admin_errorreportsto(self, attrsD):
+ self.push('errorreportsto', 1)
+ value = self._getAttribute(attrsD, 'rdf:resource')
+ if value:
+ self.elementstack[-1][2].append(value)
+ self.pop('errorreportsto')
+
+ def _start_summary(self, attrsD):
+ context = self._getContext()
+ if 'summary' in context:
+ self._summaryKey = 'content'
+ self._start_content(attrsD)
+ else:
+ self._summaryKey = 'summary'
+ self.pushContent(self._summaryKey, attrsD, u'text/plain', 1)
+ _start_itunes_summary = _start_summary
+
+ def _end_summary(self):
+ if self._summaryKey == 'content':
+ self._end_content()
+ else:
+ self.popContent(self._summaryKey or 'summary')
+ self._summaryKey = None
+ _end_itunes_summary = _end_summary
+
+ def _start_enclosure(self, attrsD):
+ attrsD = self._itsAnHrefDamnIt(attrsD)
+ context = self._getContext()
+ attrsD['rel'] = u'enclosure'
+ context.setdefault('links', []).append(FeedParserDict(attrsD))
+
+ def _start_source(self, attrsD):
+ if 'url' in attrsD:
+ # This means that we're processing a source element from an RSS 2.0 feed
+ self.sourcedata['href'] = attrsD[u'url']
+ self.push('source', 1)
+ self.insource = 1
+ self.title_depth = -1
+
+ def _end_source(self):
+ self.insource = 0
+ value = self.pop('source')
+ if value:
+ self.sourcedata['title'] = value
+ self._getContext()['source'] = copy.deepcopy(self.sourcedata)
+ self.sourcedata.clear()
+
+ def _start_content(self, attrsD):
+ self.pushContent('content', attrsD, u'text/plain', 1)
+ src = attrsD.get('src')
+ if src:
+ self.contentparams['src'] = src
+ self.push('content', 1)
+
+ def _start_body(self, attrsD):
+ self.pushContent('content', attrsD, u'application/xhtml+xml', 1)
+ _start_xhtml_body = _start_body
+
+ def _start_content_encoded(self, attrsD):
+ self.pushContent('content', attrsD, u'text/html', 1)
+ _start_fullitem = _start_content_encoded
+
+ def _end_content(self):
+ copyToSummary = self.mapContentType(self.contentparams.get('type')) in ([u'text/plain'] + self.html_types)
+ value = self.popContent('content')
+ if copyToSummary:
+ self._save('summary', value)
+
+ _end_body = _end_content
+ _end_xhtml_body = _end_content
+ _end_content_encoded = _end_content
+ _end_fullitem = _end_content
+
+ def _start_itunes_image(self, attrsD):
+ self.push('itunes_image', 0)
+ if attrsD.get('href'):
+ self._getContext()['image'] = FeedParserDict({'href': attrsD.get('href')})
+ elif attrsD.get('url'):
+ self._getContext()['image'] = FeedParserDict({'href': attrsD.get('url')})
+ _start_itunes_link = _start_itunes_image
+
+ def _end_itunes_block(self):
+ value = self.pop('itunes_block', 0)
+ self._getContext()['itunes_block'] = (value == 'yes') and 1 or 0
+
+ def _end_itunes_explicit(self):
+ value = self.pop('itunes_explicit', 0)
+ # Convert 'yes' -> True, 'clean' to False, and any other value to None
+ # False and None both evaluate as False, so the difference can be ignored
+ # by applications that only need to know if the content is explicit.
+ self._getContext()['itunes_explicit'] = (None, False, True)[(value == 'yes' and 2) or value == 'clean' or 0]
+
+ def _start_media_content(self, attrsD):
+ context = self._getContext()
+ context.setdefault('media_content', [])
+ context['media_content'].append(attrsD)
+
+ def _start_media_thumbnail(self, attrsD):
+ context = self._getContext()
+ context.setdefault('media_thumbnail', [])
+ self.push('url', 1) # new
+ context['media_thumbnail'].append(attrsD)
+
+ def _end_media_thumbnail(self):
+ url = self.pop('url')
+ context = self._getContext()
+ if url != None and len(url.strip()) != 0:
+ if 'url' not in context['media_thumbnail'][-1]:
+ context['media_thumbnail'][-1]['url'] = url
+
+ def _start_media_player(self, attrsD):
+ self.push('media_player', 0)
+ self._getContext()['media_player'] = FeedParserDict(attrsD)
+
+ def _end_media_player(self):
+ value = self.pop('media_player')
+ context = self._getContext()
+ context['media_player']['content'] = value
+
+ def _start_newlocation(self, attrsD):
+ self.push('newlocation', 1)
+
+ def _end_newlocation(self):
+ url = self.pop('newlocation')
+ context = self._getContext()
+ # don't set newlocation if the context isn't right
+ if context is not self.feeddata:
+ return
+ context['newlocation'] = _makeSafeAbsoluteURI(self.baseuri, url.strip())
+
+if _XML_AVAILABLE:
+ class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
+ def __init__(self, baseuri, baselang, encoding):
+ xml.sax.handler.ContentHandler.__init__(self)
+ _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
+ self.bozo = 0
+ self.exc = None
+ self.decls = {}
+
+ def startPrefixMapping(self, prefix, uri):
+ if not uri:
+ return
+ # Jython uses '' instead of None; standardize on None
+ prefix = prefix or None
+ self.trackNamespace(prefix, uri)
+ if prefix and uri == 'http://www.w3.org/1999/xlink':
+ self.decls['xmlns:' + prefix] = uri
+
+ def startElementNS(self, name, qname, attrs):
+ namespace, localname = name
+ lowernamespace = str(namespace or '').lower()
+ if lowernamespace.find(u'backend.userland.com/rss') <> -1:
+ # match any backend.userland.com namespace
+ namespace = u'http://backend.userland.com/rss'
+ lowernamespace = namespace
+ if qname and qname.find(':') > 0:
+ givenprefix = qname.split(':')[0]
+ else:
+ givenprefix = None
+ prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
+ if givenprefix and (prefix == None or (prefix == '' and lowernamespace == '')) and givenprefix not in self.namespacesInUse:
+ raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
+ localname = str(localname).lower()
+
+ # qname implementation is horribly broken in Python 2.1 (it
+ # doesn't report any), and slightly broken in Python 2.2 (it
+ # doesn't report the xml: namespace). So we match up namespaces
+ # with a known list first, and then possibly override them with
+ # the qnames the SAX parser gives us (if indeed it gives us any
+ # at all). Thanks to MatejC for helping me test this and
+ # tirelessly telling me that it didn't work yet.
+ attrsD, self.decls = self.decls, {}
+ if localname=='math' and namespace=='http://www.w3.org/1998/Math/MathML':
+ attrsD['xmlns']=namespace
+ if localname=='svg' and namespace=='http://www.w3.org/2000/svg':
+ attrsD['xmlns']=namespace
+
+ if prefix:
+ localname = prefix.lower() + ':' + localname
+ elif namespace and not qname: #Expat
+ for name,value in self.namespacesInUse.items():
+ if name and value == namespace:
+ localname = name + ':' + localname
+ break
+
+ for (namespace, attrlocalname), attrvalue in attrs.items():
+ lowernamespace = (namespace or '').lower()
+ prefix = self._matchnamespaces.get(lowernamespace, '')
+ if prefix:
+ attrlocalname = prefix + ':' + attrlocalname
+ attrsD[str(attrlocalname).lower()] = attrvalue
+ for qname in attrs.getQNames():
+ attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
+ self.unknown_starttag(localname, attrsD.items())
+
+ def characters(self, text):
+ self.handle_data(text)
+
+ def endElementNS(self, name, qname):
+ namespace, localname = name
+ lowernamespace = str(namespace or '').lower()
+ if qname and qname.find(':') > 0:
+ givenprefix = qname.split(':')[0]
+ else:
+ givenprefix = ''
+ prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
+ if prefix:
+ localname = prefix + ':' + localname
+ elif namespace and not qname: #Expat
+ for name,value in self.namespacesInUse.items():
+ if name and value == namespace:
+ localname = name + ':' + localname
+ break
+ localname = str(localname).lower()
+ self.unknown_endtag(localname)
+
+ def error(self, exc):
+ self.bozo = 1
+ self.exc = exc
+
+ # drv_libxml2 calls warning() in some cases
+ warning = error
+
+ def fatalError(self, exc):
+ self.error(exc)
+ raise exc
+
+class _BaseHTMLProcessor(sgmllib.SGMLParser):
+ special = re.compile('''[<>'"]''')
+ bare_ampersand = re.compile("&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)")
+ elements_no_end_tag = set([
+ 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame',
+ 'hr', 'img', 'input', 'isindex', 'keygen', 'link', 'meta', 'param',
+ 'source', 'track', 'wbr'
+ ])
+
+ def __init__(self, encoding, _type):
+ self.encoding = encoding
+ self._type = _type
+ sgmllib.SGMLParser.__init__(self)
+
+ def reset(self):
+ self.pieces = []
+ sgmllib.SGMLParser.reset(self)
+
+ def _shorttag_replace(self, match):
+ tag = match.group(1)
+ if tag in self.elements_no_end_tag:
+ return '<' + tag + ' />'
+ else:
+ return '<' + tag + '></' + tag + '>'
+
+ # By declaring these methods and overriding their compiled code
+ # with the code from sgmllib, the original code will execute in
+ # feedparser's scope instead of sgmllib's. This means that the
+ # `tagfind` and `charref` regular expressions will be found as
+ # they're declared above, not as they're declared in sgmllib.
+ def goahead(self, i):
+ pass
+ goahead.func_code = sgmllib.SGMLParser.goahead.func_code
+
+ def __parse_starttag(self, i):
+ pass
+ __parse_starttag.func_code = sgmllib.SGMLParser.parse_starttag.func_code
+
+ def parse_starttag(self,i):
+ j = self.__parse_starttag(i)
+ if self._type == 'application/xhtml+xml':
+ if j>2 and self.rawdata[j-2:j]=='/>':
+ self.unknown_endtag(self.lasttag)
+ return j
+
+ def feed(self, data):
+ data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'&lt;!\1', data)
+ data = re.sub(r'<([^<>\s]+?)\s*/>', self._shorttag_replace, data)
+ data = data.replace('&#39;', "'")
+ data = data.replace('&#34;', '"')
+ try:
+ bytes
+ if bytes is str:
+ raise NameError
+ self.encoding = self.encoding + u'_INVALID_PYTHON_3'
+ except NameError:
+ if self.encoding and isinstance(data, unicode):
+ data = data.encode(self.encoding)
+ sgmllib.SGMLParser.feed(self, data)
+ sgmllib.SGMLParser.close(self)
+
+ def normalize_attrs(self, attrs):
+ if not attrs:
+ return attrs
+ # utility method to be called by descendants
+ attrs = dict([(k.lower(), v) for k, v in attrs]).items()
+ attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs]
+ attrs.sort()
+ return attrs
+
+ def unknown_starttag(self, tag, attrs):
+ # called for each start tag
+ # attrs is a list of (attr, value) tuples
+ # e.g. for <pre class='screen'>, tag='pre', attrs=[('class', 'screen')]
+ uattrs = []
+ strattrs=''
+ if attrs:
+ for key, value in attrs:
+ value=value.replace('>','&gt;').replace('<','&lt;').replace('"','&quot;')
+ value = self.bare_ampersand.sub("&amp;", value)
+ # thanks to Kevin Marks for this breathtaking hack to deal with (valid) high-bit attribute values in UTF-8 feeds
+ if not isinstance(value, unicode):
+ value = value.decode(self.encoding, 'ignore')
+ try:
+ # Currently, in Python 3 the key is already a str, and cannot be decoded again
+ uattrs.append((unicode(key, self.encoding), value))
+ except TypeError:
+ uattrs.append((key, value))
+ strattrs = u''.join([u' %s="%s"' % (key, value) for key, value in uattrs])
+ if self.encoding:
+ try:
+ strattrs = strattrs.encode(self.encoding)
+ except (UnicodeEncodeError, LookupError):
+ pass
+ if tag in self.elements_no_end_tag:
+ self.pieces.append('<%s%s />' % (tag, strattrs))
+ else:
+ self.pieces.append('<%s%s>' % (tag, strattrs))
+
+ def unknown_endtag(self, tag):
+ # called for each end tag, e.g. for </pre>, tag will be 'pre'
+ # Reconstruct the original end tag.
+ if tag not in self.elements_no_end_tag:
+ self.pieces.append("</%s>" % tag)
+
+ def handle_charref(self, ref):
+ # called for each character reference, e.g. for '&#160;', ref will be '160'
+ # Reconstruct the original character reference.
+ ref = ref.lower()
+ if ref.startswith('x'):
+ value = int(ref[1:], 16)
+ else:
+ value = int(ref)
+
+ if value in _cp1252:
+ self.pieces.append('&#%s;' % hex(ord(_cp1252[value]))[1:])
+ else:
+ self.pieces.append('&#%s;' % ref)
+
+ def handle_entityref(self, ref):
+ # called for each entity reference, e.g. for '&copy;', ref will be 'copy'
+ # Reconstruct the original entity reference.
+ if ref in name2codepoint or ref == 'apos':
+ self.pieces.append('&%s;' % ref)
+ else:
+ self.pieces.append('&amp;%s' % ref)
+
+ def handle_data(self, text):
+ # called for each block of plain text, i.e. outside of any tag and
+ # not containing any character or entity references
+ # Store the original text verbatim.
+ self.pieces.append(text)
+
+ def handle_comment(self, text):
+ # called for each HTML comment, e.g. <!-- insert Javascript code here -->
+ # Reconstruct the original comment.
+ self.pieces.append('<!--%s-->' % text)
+
+ def handle_pi(self, text):
+ # called for each processing instruction, e.g. <?instruction>
+ # Reconstruct original processing instruction.
+ self.pieces.append('<?%s>' % text)
+
+ def handle_decl(self, text):
+ # called for the DOCTYPE, if present, e.g.
+ # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ # "http://www.w3.org/TR/html4/loose.dtd">
+ # Reconstruct original DOCTYPE
+ self.pieces.append('<!%s>' % text)
+
+ _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match
+ def _scan_name(self, i, declstartpos):
+ rawdata = self.rawdata
+ n = len(rawdata)
+ if i == n:
+ return None, -1
+ m = self._new_declname_match(rawdata, i)
+ if m:
+ s = m.group()
+ name = s.strip()
+ if (i + len(s)) == n:
+ return None, -1 # end of buffer
+ return name.lower(), m.end()
+ else:
+ self.handle_data(rawdata)
+# self.updatepos(declstartpos, i)
+ return None, -1
+
+ def convert_charref(self, name):
+ return '&#%s;' % name
+
+ def convert_entityref(self, name):
+ return '&%s;' % name
+
+ def output(self):
+ '''Return processed HTML as a single string'''
+ return ''.join([str(p) for p in self.pieces])
+
+ def parse_declaration(self, i):
+ try:
+ return sgmllib.SGMLParser.parse_declaration(self, i)
+ except sgmllib.SGMLParseError:
+ # escape the doctype declaration and continue parsing
+ self.handle_data('&lt;')
+ return i+1
+
+class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
+ def __init__(self, baseuri, baselang, encoding, entities):
+ sgmllib.SGMLParser.__init__(self)
+ _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
+ _BaseHTMLProcessor.__init__(self, encoding, 'application/xhtml+xml')
+ self.entities=entities
+
+ def decodeEntities(self, element, data):
+ data = data.replace('&#60;', '&lt;')
+ data = data.replace('&#x3c;', '&lt;')
+ data = data.replace('&#x3C;', '&lt;')
+ data = data.replace('&#62;', '&gt;')
+ data = data.replace('&#x3e;', '&gt;')
+ data = data.replace('&#x3E;', '&gt;')
+ data = data.replace('&#38;', '&amp;')
+ data = data.replace('&#x26;', '&amp;')
+ data = data.replace('&#34;', '&quot;')
+ data = data.replace('&#x22;', '&quot;')
+ data = data.replace('&#39;', '&apos;')
+ data = data.replace('&#x27;', '&apos;')
+ if not self.contentparams.get('type', u'xml').endswith(u'xml'):
+ data = data.replace('&lt;', '<')
+ data = data.replace('&gt;', '>')
+ data = data.replace('&amp;', '&')
+ data = data.replace('&quot;', '"')
+ data = data.replace('&apos;', "'")
+ return data
+
+ def strattrs(self, attrs):
+ return ''.join([' %s="%s"' % (n,v.replace('"','&quot;')) for n,v in attrs])
+
+class _MicroformatsParser:
+ STRING = 1
+ DATE = 2
+ URI = 3
+ NODE = 4
+ EMAIL = 5
+
+ known_xfn_relationships = set(['contact', 'acquaintance', 'friend', 'met', 'co-worker', 'coworker', 'colleague', 'co-resident', 'coresident', 'neighbor', 'child', 'parent', 'sibling', 'brother', 'sister', 'spouse', 'wife', 'husband', 'kin', 'relative', 'muse', 'crush', 'date', 'sweetheart', 'me'])
+ known_binary_extensions = set(['zip','rar','exe','gz','tar','tgz','tbz2','bz2','z','7z','dmg','img','sit','sitx','hqx','deb','rpm','bz2','jar','rar','iso','bin','msi','mp2','mp3','ogg','ogm','mp4','m4v','m4a','avi','wma','wmv'])
+
+ def __init__(self, data, baseuri, encoding):
+ self.document = BeautifulSoup.BeautifulSoup(data)
+ self.baseuri = baseuri
+ self.encoding = encoding
+ if isinstance(data, unicode):
+ data = data.encode(encoding)
+ self.tags = []
+ self.enclosures = []
+ self.xfn = []
+ self.vcard = None
+
+ def vcardEscape(self, s):
+ if isinstance(s, basestring):
+ s = s.replace(',', '\\,').replace(';', '\\;').replace('\n', '\\n')
+ return s
+
+ def vcardFold(self, s):
+ s = re.sub(';+$', '', s)
+ sFolded = ''
+ iMax = 75
+ sPrefix = ''
+ while len(s) > iMax:
+ sFolded += sPrefix + s[:iMax] + '\n'
+ s = s[iMax:]
+ sPrefix = ' '
+ iMax = 74
+ sFolded += sPrefix + s
+ return sFolded
+
+ def normalize(self, s):
+ return re.sub(r'\s+', ' ', s).strip()
+
+ def unique(self, aList):
+ results = []
+ for element in aList:
+ if element not in results:
+ results.append(element)
+ return results
+
+ def toISO8601(self, dt):
+ return time.strftime('%Y-%m-%dT%H:%M:%SZ', dt)
+
+ def getPropertyValue(self, elmRoot, sProperty, iPropertyType=4, bAllowMultiple=0, bAutoEscape=0):
+ all = lambda x: 1
+ sProperty = sProperty.lower()
+ bFound = 0
+ bNormalize = 1
+ propertyMatch = {'class': re.compile(r'\b%s\b' % sProperty)}
+ if bAllowMultiple and (iPropertyType != self.NODE):
+ snapResults = []
+ containers = elmRoot(['ul', 'ol'], propertyMatch)
+ for container in containers:
+ snapResults.extend(container('li'))
+ bFound = (len(snapResults) != 0)
+ if not bFound:
+ snapResults = elmRoot(all, propertyMatch)
+ bFound = (len(snapResults) != 0)
+ if (not bFound) and (sProperty == 'value'):
+ snapResults = elmRoot('pre')
+ bFound = (len(snapResults) != 0)
+ bNormalize = not bFound
+ if not bFound:
+ snapResults = [elmRoot]
+ bFound = (len(snapResults) != 0)
+ arFilter = []
+ if sProperty == 'vcard':
+ snapFilter = elmRoot(all, propertyMatch)
+ for node in snapFilter:
+ if node.findParent(all, propertyMatch):
+ arFilter.append(node)
+ arResults = []
+ for node in snapResults:
+ if node not in arFilter:
+ arResults.append(node)
+ bFound = (len(arResults) != 0)
+ if not bFound:
+ if bAllowMultiple:
+ return []
+ elif iPropertyType == self.STRING:
+ return ''
+ elif iPropertyType == self.DATE:
+ return None
+ elif iPropertyType == self.URI:
+ return ''
+ elif iPropertyType == self.NODE:
+ return None
+ else:
+ return None
+ arValues = []
+ for elmResult in arResults:
+ sValue = None
+ if iPropertyType == self.NODE:
+ if bAllowMultiple:
+ arValues.append(elmResult)
+ continue
+ else:
+ return elmResult
+ sNodeName = elmResult.name.lower()
+ if (iPropertyType == self.EMAIL) and (sNodeName == 'a'):
+ sValue = (elmResult.get('href') or '').split('mailto:').pop().split('?')[0]
+ if sValue:
+ sValue = bNormalize and self.normalize(sValue) or sValue.strip()
+ if (not sValue) and (sNodeName == 'abbr'):
+ sValue = elmResult.get('title')
+ if sValue:
+ sValue = bNormalize and self.normalize(sValue) or sValue.strip()
+ if (not sValue) and (iPropertyType == self.URI):
+ if sNodeName == 'a':
+ sValue = elmResult.get('href')
+ elif sNodeName == 'img':
+ sValue = elmResult.get('src')
+ elif sNodeName == 'object':
+ sValue = elmResult.get('data')
+ if sValue:
+ sValue = bNormalize and self.normalize(sValue) or sValue.strip()
+ if (not sValue) and (sNodeName == 'img'):
+ sValue = elmResult.get('alt')
+ if sValue:
+ sValue = bNormalize and self.normalize(sValue) or sValue.strip()
+ if not sValue:
+ sValue = elmResult.renderContents()
+ sValue = re.sub(r'<\S[^>]*>', '', sValue)
+ sValue = sValue.replace('\r\n', '\n')
+ sValue = sValue.replace('\r', '\n')
+ if sValue:
+ sValue = bNormalize and self.normalize(sValue) or sValue.strip()
+ if not sValue:
+ continue
+ if iPropertyType == self.DATE:
+ sValue = _parse_date_iso8601(sValue)
+ if bAllowMultiple:
+ arValues.append(bAutoEscape and self.vcardEscape(sValue) or sValue)
+ else:
+ return bAutoEscape and self.vcardEscape(sValue) or sValue
+ return arValues
+
+ def findVCards(self, elmRoot, bAgentParsing=0):
+ sVCards = ''
+
+ if not bAgentParsing:
+ arCards = self.getPropertyValue(elmRoot, 'vcard', bAllowMultiple=1)
+ else:
+ arCards = [elmRoot]
+
+ for elmCard in arCards:
+ arLines = []
+
+ def processSingleString(sProperty):
+ sValue = self.getPropertyValue(elmCard, sProperty, self.STRING, bAutoEscape=1).decode(self.encoding)
+ if sValue:
+ arLines.append(self.vcardFold(sProperty.upper() + ':' + sValue))
+ return sValue or u''
+
+ def processSingleURI(sProperty):
+ sValue = self.getPropertyValue(elmCard, sProperty, self.URI)
+ if sValue:
+ sContentType = ''
+ sEncoding = ''
+ sValueKey = ''
+ if sValue.startswith('data:'):
+ sEncoding = ';ENCODING=b'
+ sContentType = sValue.split(';')[0].split('/').pop()
+ sValue = sValue.split(',', 1).pop()
+ else:
+ elmValue = self.getPropertyValue(elmCard, sProperty)
+ if elmValue:
+ if sProperty != 'url':
+ sValueKey = ';VALUE=uri'
+ sContentType = elmValue.get('type', '').strip().split('/').pop().strip()
+ sContentType = sContentType.upper()
+ if sContentType == 'OCTET-STREAM':
+ sContentType = ''
+ if sContentType:
+ sContentType = ';TYPE=' + sContentType.upper()
+ arLines.append(self.vcardFold(sProperty.upper() + sEncoding + sContentType + sValueKey + ':' + sValue))
+
+ def processTypeValue(sProperty, arDefaultType, arForceType=None):
+ arResults = self.getPropertyValue(elmCard, sProperty, bAllowMultiple=1)
+ for elmResult in arResults:
+ arType = self.getPropertyValue(elmResult, 'type', self.STRING, 1, 1)
+ if arForceType:
+ arType = self.unique(arForceType + arType)
+ if not arType:
+ arType = arDefaultType
+ sValue = self.getPropertyValue(elmResult, 'value', self.EMAIL, 0)
+ if sValue:
+ arLines.append(self.vcardFold(sProperty.upper() + ';TYPE=' + ','.join(arType) + ':' + sValue))
+
+ # AGENT
+ # must do this before all other properties because it is destructive
+ # (removes nested class="vcard" nodes so they don't interfere with
+ # this vcard's other properties)
+ arAgent = self.getPropertyValue(elmCard, 'agent', bAllowMultiple=1)
+ for elmAgent in arAgent:
+ if re.compile(r'\bvcard\b').search(elmAgent.get('class')):
+ sAgentValue = self.findVCards(elmAgent, 1) + '\n'
+ sAgentValue = sAgentValue.replace('\n', '\\n')
+ sAgentValue = sAgentValue.replace(';', '\\;')
+ if sAgentValue:
+ arLines.append(self.vcardFold('AGENT:' + sAgentValue))
+ # Completely remove the agent element from the parse tree
+ elmAgent.extract()
+ else:
+ sAgentValue = self.getPropertyValue(elmAgent, 'value', self.URI, bAutoEscape=1);
+ if sAgentValue:
+ arLines.append(self.vcardFold('AGENT;VALUE=uri:' + sAgentValue))
+
+ # FN (full name)
+ sFN = processSingleString('fn')
+
+ # N (name)
+ elmName = self.getPropertyValue(elmCard, 'n')
+ if elmName:
+ sFamilyName = self.getPropertyValue(elmName, 'family-name', self.STRING, bAutoEscape=1)
+ sGivenName = self.getPropertyValue(elmName, 'given-name', self.STRING, bAutoEscape=1)
+ arAdditionalNames = self.getPropertyValue(elmName, 'additional-name', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'additional-names', self.STRING, 1, 1)
+ arHonorificPrefixes = self.getPropertyValue(elmName, 'honorific-prefix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-prefixes', self.STRING, 1, 1)
+ arHonorificSuffixes = self.getPropertyValue(elmName, 'honorific-suffix', self.STRING, 1, 1) + self.getPropertyValue(elmName, 'honorific-suffixes', self.STRING, 1, 1)
+ arLines.append(self.vcardFold('N:' + sFamilyName + ';' +
+ sGivenName + ';' +
+ ','.join(arAdditionalNames) + ';' +
+ ','.join(arHonorificPrefixes) + ';' +
+ ','.join(arHonorificSuffixes)))
+ elif sFN:
+ # implied "N" optimization
+ # http://microformats.org/wiki/hcard#Implied_.22N.22_Optimization
+ arNames = self.normalize(sFN).split()
+ if len(arNames) == 2:
+ bFamilyNameFirst = (arNames[0].endswith(',') or
+ len(arNames[1]) == 1 or
+ ((len(arNames[1]) == 2) and (arNames[1].endswith('.'))))
+ if bFamilyNameFirst:
+ arLines.append(self.vcardFold('N:' + arNames[0] + ';' + arNames[1]))
+ else:
+ arLines.append(self.vcardFold('N:' + arNames[1] + ';' + arNames[0]))
+
+ # SORT-STRING
+ sSortString = self.getPropertyValue(elmCard, 'sort-string', self.STRING, bAutoEscape=1)
+ if sSortString:
+ arLines.append(self.vcardFold('SORT-STRING:' + sSortString))
+
+ # NICKNAME
+ arNickname = self.getPropertyValue(elmCard, 'nickname', self.STRING, 1, 1)
+ if arNickname:
+ arLines.append(self.vcardFold('NICKNAME:' + ','.join(arNickname)))
+
+ # PHOTO
+ processSingleURI('photo')
+
+ # BDAY
+ dtBday = self.getPropertyValue(elmCard, 'bday', self.DATE)
+ if dtBday:
+ arLines.append(self.vcardFold('BDAY:' + self.toISO8601(dtBday)))
+
+ # ADR (address)
+ arAdr = self.getPropertyValue(elmCard, 'adr', bAllowMultiple=1)
+ for elmAdr in arAdr:
+ arType = self.getPropertyValue(elmAdr, 'type', self.STRING, 1, 1)
+ if not arType:
+ arType = ['intl','postal','parcel','work'] # default adr types, see RFC 2426 section 3.2.1
+ sPostOfficeBox = self.getPropertyValue(elmAdr, 'post-office-box', self.STRING, 0, 1)
+ sExtendedAddress = self.getPropertyValue(elmAdr, 'extended-address', self.STRING, 0, 1)
+ sStreetAddress = self.getPropertyValue(elmAdr, 'street-address', self.STRING, 0, 1)
+ sLocality = self.getPropertyValue(elmAdr, 'locality', self.STRING, 0, 1)
+ sRegion = self.getPropertyValue(elmAdr, 'region', self.STRING, 0, 1)
+ sPostalCode = self.getPropertyValue(elmAdr, 'postal-code', self.STRING, 0, 1)
+ sCountryName = self.getPropertyValue(elmAdr, 'country-name', self.STRING, 0, 1)
+ arLines.append(self.vcardFold('ADR;TYPE=' + ','.join(arType) + ':' +
+ sPostOfficeBox + ';' +
+ sExtendedAddress + ';' +
+ sStreetAddress + ';' +
+ sLocality + ';' +
+ sRegion + ';' +
+ sPostalCode + ';' +
+ sCountryName))
+
+ # LABEL
+ processTypeValue('label', ['intl','postal','parcel','work'])
+
+ # TEL (phone number)
+ processTypeValue('tel', ['voice'])
+
+ # EMAIL
+ processTypeValue('email', ['internet'], ['internet'])
+
+ # MAILER
+ processSingleString('mailer')
+
+ # TZ (timezone)
+ processSingleString('tz')
+
+ # GEO (geographical information)
+ elmGeo = self.getPropertyValue(elmCard, 'geo')
+ if elmGeo:
+ sLatitude = self.getPropertyValue(elmGeo, 'latitude', self.STRING, 0, 1)
+ sLongitude = self.getPropertyValue(elmGeo, 'longitude', self.STRING, 0, 1)
+ arLines.append(self.vcardFold('GEO:' + sLatitude + ';' + sLongitude))
+
+ # TITLE
+ processSingleString('title')
+
+ # ROLE
+ processSingleString('role')
+
+ # LOGO
+ processSingleURI('logo')
+
+ # ORG (organization)
+ elmOrg = self.getPropertyValue(elmCard, 'org')
+ if elmOrg:
+ sOrganizationName = self.getPropertyValue(elmOrg, 'organization-name', self.STRING, 0, 1)
+ if not sOrganizationName:
+ # implied "organization-name" optimization
+ # http://microformats.org/wiki/hcard#Implied_.22organization-name.22_Optimization
+ sOrganizationName = self.getPropertyValue(elmCard, 'org', self.STRING, 0, 1)
+ if sOrganizationName:
+ arLines.append(self.vcardFold('ORG:' + sOrganizationName))
+ else:
+ arOrganizationUnit = self.getPropertyValue(elmOrg, 'organization-unit', self.STRING, 1, 1)
+ arLines.append(self.vcardFold('ORG:' + sOrganizationName + ';' + ';'.join(arOrganizationUnit)))
+
+ # CATEGORY
+ arCategory = self.getPropertyValue(elmCard, 'category', self.STRING, 1, 1) + self.getPropertyValue(elmCard, 'categories', self.STRING, 1, 1)
+ if arCategory:
+ arLines.append(self.vcardFold('CATEGORIES:' + ','.join(arCategory)))
+
+ # NOTE
+ processSingleString('note')
+
+ # REV
+ processSingleString('rev')
+
+ # SOUND
+ processSingleURI('sound')
+
+ # UID
+ processSingleString('uid')
+
+ # URL
+ processSingleURI('url')
+
+ # CLASS
+ processSingleString('class')
+
+ # KEY
+ processSingleURI('key')
+
+ if arLines:
+ arLines = [u'BEGIN:vCard',u'VERSION:3.0'] + arLines + [u'END:vCard']
+ # XXX - this is super ugly; properly fix this with issue 148
+ for i, s in enumerate(arLines):
+ if not isinstance(s, unicode):
+ arLines[i] = s.decode('utf-8', 'ignore')
+ sVCards += u'\n'.join(arLines) + u'\n'
+
+ return sVCards.strip()
+
+ def isProbablyDownloadable(self, elm):
+ attrsD = elm.attrMap
+ if 'href' not in attrsD:
+ return 0
+ linktype = attrsD.get('type', '').strip()
+ if linktype.startswith('audio/') or \
+ linktype.startswith('video/') or \
+ (linktype.startswith('application/') and not linktype.endswith('xml')):
+ return 1
+ try:
+ path = urlparse.urlparse(attrsD['href'])[2]
+ except ValueError:
+ return 0
+ if path.find('.') == -1:
+ return 0
+ fileext = path.split('.').pop().lower()
+ return fileext in self.known_binary_extensions
+
+ def findTags(self):
+ all = lambda x: 1
+ for elm in self.document(all, {'rel': re.compile(r'\btag\b')}):
+ href = elm.get('href')
+ if not href:
+ continue
+ urlscheme, domain, path, params, query, fragment = \
+ urlparse.urlparse(_urljoin(self.baseuri, href))
+ segments = path.split('/')
+ tag = segments.pop()
+ if not tag:
+ if segments:
+ tag = segments.pop()
+ else:
+ # there are no tags
+ continue
+ tagscheme = urlparse.urlunparse((urlscheme, domain, '/'.join(segments), '', '', ''))
+ if not tagscheme.endswith('/'):
+ tagscheme += '/'
+ self.tags.append(FeedParserDict({"term": tag, "scheme": tagscheme, "label": elm.string or ''}))
+
+ def findEnclosures(self):
+ all = lambda x: 1
+ enclosure_match = re.compile(r'\benclosure\b')
+ for elm in self.document(all, {'href': re.compile(r'.+')}):
+ if not enclosure_match.search(elm.get('rel', u'')) and not self.isProbablyDownloadable(elm):
+ continue
+ if elm.attrMap not in self.enclosures:
+ self.enclosures.append(elm.attrMap)
+ if elm.string and not elm.get('title'):
+ self.enclosures[-1]['title'] = elm.string
+
+ def findXFN(self):
+ all = lambda x: 1
+ for elm in self.document(all, {'rel': re.compile('.+'), 'href': re.compile('.+')}):
+ rels = elm.get('rel', u'').split()
+ xfn_rels = [r for r in rels if r in self.known_xfn_relationships]
+ if xfn_rels:
+ self.xfn.append({"relationships": xfn_rels, "href": elm.get('href', ''), "name": elm.string})
+
+def _parseMicroformats(htmlSource, baseURI, encoding):
+ if not BeautifulSoup:
+ return
+ try:
+ p = _MicroformatsParser(htmlSource, baseURI, encoding)
+ except UnicodeEncodeError:
+ # sgmllib throws this exception when performing lookups of tags
+ # with non-ASCII characters in them.
+ return
+ p.vcard = p.findVCards(p.document)
+ p.findTags()
+ p.findEnclosures()
+ p.findXFN()
+ return {"tags": p.tags, "enclosures": p.enclosures, "xfn": p.xfn, "vcard": p.vcard}
+
+class _RelativeURIResolver(_BaseHTMLProcessor):
+ relative_uris = set([('a', 'href'),
+ ('applet', 'codebase'),
+ ('area', 'href'),
+ ('blockquote', 'cite'),
+ ('body', 'background'),
+ ('del', 'cite'),
+ ('form', 'action'),
+ ('frame', 'longdesc'),
+ ('frame', 'src'),
+ ('iframe', 'longdesc'),
+ ('iframe', 'src'),
+ ('head', 'profile'),
+ ('img', 'longdesc'),
+ ('img', 'src'),
+ ('img', 'usemap'),
+ ('input', 'src'),
+ ('input', 'usemap'),
+ ('ins', 'cite'),
+ ('link', 'href'),
+ ('object', 'classid'),
+ ('object', 'codebase'),
+ ('object', 'data'),
+ ('object', 'usemap'),
+ ('q', 'cite'),
+ ('script', 'src'),
+ ('video', 'poster')])
+
+ def __init__(self, baseuri, encoding, _type):
+ _BaseHTMLProcessor.__init__(self, encoding, _type)
+ self.baseuri = baseuri
+
+ def resolveURI(self, uri):
+ return _makeSafeAbsoluteURI(self.baseuri, uri.strip())
+
+ def unknown_starttag(self, tag, attrs):
+ attrs = self.normalize_attrs(attrs)
+ attrs = [(key, ((tag, key) in self.relative_uris) and self.resolveURI(value) or value) for key, value in attrs]
+ _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
+
+def _resolveRelativeURIs(htmlSource, baseURI, encoding, _type):
+ if not _SGML_AVAILABLE:
+ return htmlSource
+
+ p = _RelativeURIResolver(baseURI, encoding, _type)
+ p.feed(htmlSource)
+ return p.output()
+
+def _makeSafeAbsoluteURI(base, rel=None):
+ # bail if ACCEPTABLE_URI_SCHEMES is empty
+ if not ACCEPTABLE_URI_SCHEMES:
+ try:
+ return _urljoin(base, rel or u'')
+ except ValueError:
+ return u''
+ if not base:
+ return rel or u''
+ if not rel:
+ try:
+ scheme = urlparse.urlparse(base)[0]
+ except ValueError:
+ return u''
+ if not scheme or scheme in ACCEPTABLE_URI_SCHEMES:
+ return base
+ return u''
+ try:
+ uri = _urljoin(base, rel)
+ except ValueError:
+ return u''
+ if uri.strip().split(':', 1)[0] not in ACCEPTABLE_URI_SCHEMES:
+ return u''
+ return uri
+
+class _HTMLSanitizer(_BaseHTMLProcessor):
+ acceptable_elements = set(['a', 'abbr', 'acronym', 'address', 'area',
+ 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
+ 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
+ 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn',
+ 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset',
+ 'figcaption', 'figure', 'footer', 'font', 'form', 'header', 'h1',
+ 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins',
+ 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter',
+ 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option',
+ 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select',
+ 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong',
+ 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
+ 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video', 'noscript'])
+
+ acceptable_attributes = set(['abbr', 'accept', 'accept-charset', 'accesskey',
+ 'action', 'align', 'alt', 'autocomplete', 'autofocus', 'axis',
+ 'background', 'balance', 'bgcolor', 'bgproperties', 'border',
+ 'bordercolor', 'bordercolordark', 'bordercolorlight', 'bottompadding',
+ 'cellpadding', 'cellspacing', 'ch', 'challenge', 'char', 'charoff',
+ 'choff', 'charset', 'checked', 'cite', 'class', 'clear', 'color', 'cols',
+ 'colspan', 'compact', 'contenteditable', 'controls', 'coords', 'data',
+ 'datafld', 'datapagesize', 'datasrc', 'datetime', 'default', 'delay',
+ 'dir', 'disabled', 'draggable', 'dynsrc', 'enctype', 'end', 'face', 'for',
+ 'form', 'frame', 'galleryimg', 'gutter', 'headers', 'height', 'hidefocus',
+ 'hidden', 'high', 'href', 'hreflang', 'hspace', 'icon', 'id', 'inputmode',
+ 'ismap', 'keytype', 'label', 'leftspacing', 'lang', 'list', 'longdesc',
+ 'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max',
+ 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref',
+ 'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size',
+ 'poster', 'pqg', 'preload', 'prompt', 'radiogroup', 'readonly', 'rel',
+ 'repeat-max', 'repeat-min', 'replace', 'required', 'rev', 'rightspacing',
+ 'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span',
+ 'src', 'start', 'step', 'summary', 'suppress', 'tabindex', 'target',
+ 'template', 'title', 'toppadding', 'type', 'unselectable', 'usemap',
+ 'urn', 'valign', 'value', 'variable', 'volume', 'vspace', 'vrml',
+ 'width', 'wrap', 'xml:lang'])
+
+ unacceptable_elements_with_end_tag = set(['script', 'applet', 'style'])
+
+ acceptable_css_properties = set(['azimuth', 'background-color',
+ 'border-bottom-color', 'border-collapse', 'border-color',
+ 'border-left-color', 'border-right-color', 'border-top-color', 'clear',
+ 'color', 'cursor', 'direction', 'display', 'elevation', 'float', 'font',
+ 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight',
+ 'height', 'letter-spacing', 'line-height', 'overflow', 'pause',
+ 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness',
+ 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation',
+ 'speech-rate', 'stress', 'text-align', 'text-decoration', 'text-indent',
+ 'unicode-bidi', 'vertical-align', 'voice-family', 'volume',
+ 'white-space', 'width'])
+
+ # survey of common keywords found in feeds
+ acceptable_css_keywords = set(['auto', 'aqua', 'black', 'block', 'blue',
+ 'bold', 'both', 'bottom', 'brown', 'center', 'collapse', 'dashed',
+ 'dotted', 'fuchsia', 'gray', 'green', '!important', 'italic', 'left',
+ 'lime', 'maroon', 'medium', 'none', 'navy', 'normal', 'nowrap', 'olive',
+ 'pointer', 'purple', 'red', 'right', 'solid', 'silver', 'teal', 'top',
+ 'transparent', 'underline', 'white', 'yellow'])
+
+ valid_css_values = re.compile('^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|' +
+ '\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$')
+
+ mathml_elements = set(['annotation', 'annotation-xml', 'maction', 'math',
+ 'merror', 'mfenced', 'mfrac', 'mi', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded',
+ 'mphantom', 'mprescripts', 'mroot', 'mrow', 'mspace', 'msqrt', 'mstyle',
+ 'msub', 'msubsup', 'msup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder',
+ 'munderover', 'none', 'semantics'])
+
+ mathml_attributes = set(['actiontype', 'align', 'columnalign', 'columnalign',
+ 'columnalign', 'close', 'columnlines', 'columnspacing', 'columnspan', 'depth',
+ 'display', 'displaystyle', 'encoding', 'equalcolumns', 'equalrows',
+ 'fence', 'fontstyle', 'fontweight', 'frame', 'height', 'linethickness',
+ 'lspace', 'mathbackground', 'mathcolor', 'mathvariant', 'mathvariant',
+ 'maxsize', 'minsize', 'open', 'other', 'rowalign', 'rowalign', 'rowalign',
+ 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'scriptlevel', 'selection',
+ 'separator', 'separators', 'stretchy', 'width', 'width', 'xlink:href',
+ 'xlink:show', 'xlink:type', 'xmlns', 'xmlns:xlink'])
+
+ # svgtiny - foreignObject + linearGradient + radialGradient + stop
+ svg_elements = set(['a', 'animate', 'animateColor', 'animateMotion',
+ 'animateTransform', 'circle', 'defs', 'desc', 'ellipse', 'foreignObject',
+ 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern',
+ 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', 'mpath',
+ 'path', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'stop',
+ 'svg', 'switch', 'text', 'title', 'tspan', 'use'])
+
+ # svgtiny + class + opacity + offset + xmlns + xmlns:xlink
+ svg_attributes = set(['accent-height', 'accumulate', 'additive', 'alphabetic',
+ 'arabic-form', 'ascent', 'attributeName', 'attributeType',
+ 'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height',
+ 'class', 'color', 'color-rendering', 'content', 'cx', 'cy', 'd', 'dx',
+ 'dy', 'descent', 'display', 'dur', 'end', 'fill', 'fill-opacity',
+ 'fill-rule', 'font-family', 'font-size', 'font-stretch', 'font-style',
+ 'font-variant', 'font-weight', 'from', 'fx', 'fy', 'g1', 'g2',
+ 'glyph-name', 'gradientUnits', 'hanging', 'height', 'horiz-adv-x',
+ 'horiz-origin-x', 'id', 'ideographic', 'k', 'keyPoints', 'keySplines',
+ 'keyTimes', 'lang', 'mathematical', 'marker-end', 'marker-mid',
+ 'marker-start', 'markerHeight', 'markerUnits', 'markerWidth', 'max',
+ 'min', 'name', 'offset', 'opacity', 'orient', 'origin',
+ 'overline-position', 'overline-thickness', 'panose-1', 'path',
+ 'pathLength', 'points', 'preserveAspectRatio', 'r', 'refX', 'refY',
+ 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures',
+ 'restart', 'rotate', 'rx', 'ry', 'slope', 'stemh', 'stemv',
+ 'stop-color', 'stop-opacity', 'strikethrough-position',
+ 'strikethrough-thickness', 'stroke', 'stroke-dasharray',
+ 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin',
+ 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage',
+ 'target', 'text-anchor', 'to', 'transform', 'type', 'u1', 'u2',
+ 'underline-position', 'underline-thickness', 'unicode', 'unicode-range',
+ 'units-per-em', 'values', 'version', 'viewBox', 'visibility', 'width',
+ 'widths', 'x', 'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole',
+ 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type',
+ 'xml:base', 'xml:lang', 'xml:space', 'xmlns', 'xmlns:xlink', 'y', 'y1',
+ 'y2', 'zoomAndPan'])
+
+ svg_attr_map = None
+ svg_elem_map = None
+
+ acceptable_svg_properties = set([ 'fill', 'fill-opacity', 'fill-rule',
+ 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin',
+ 'stroke-opacity'])
+
+ def reset(self):
+ _BaseHTMLProcessor.reset(self)
+ self.unacceptablestack = 0
+ self.mathmlOK = 0
+ self.svgOK = 0
+
+ def unknown_starttag(self, tag, attrs):
+ acceptable_attributes = self.acceptable_attributes
+ keymap = {}
+ if not tag in self.acceptable_elements or self.svgOK:
+ if tag in self.unacceptable_elements_with_end_tag:
+ self.unacceptablestack += 1
+
+ # add implicit namespaces to html5 inline svg/mathml
+ if self._type.endswith('html'):
+ if not dict(attrs).get('xmlns'):
+ if tag=='svg':
+ attrs.append( ('xmlns','http://www.w3.org/2000/svg') )
+ if tag=='math':
+ attrs.append( ('xmlns','http://www.w3.org/1998/Math/MathML') )
+
+ # not otherwise acceptable, perhaps it is MathML or SVG?
+ if tag=='math' and ('xmlns','http://www.w3.org/1998/Math/MathML') in attrs:
+ self.mathmlOK += 1
+ if tag=='svg' and ('xmlns','http://www.w3.org/2000/svg') in attrs:
+ self.svgOK += 1
+
+ # chose acceptable attributes based on tag class, else bail
+ if self.mathmlOK and tag in self.mathml_elements:
+ acceptable_attributes = self.mathml_attributes
+ elif self.svgOK and tag in self.svg_elements:
+ # for most vocabularies, lowercasing is a good idea. Many
+ # svg elements, however, are camel case
+ if not self.svg_attr_map:
+ lower=[attr.lower() for attr in self.svg_attributes]
+ mix=[a for a in self.svg_attributes if a not in lower]
+ self.svg_attributes = lower
+ self.svg_attr_map = dict([(a.lower(),a) for a in mix])
+
+ lower=[attr.lower() for attr in self.svg_elements]
+ mix=[a for a in self.svg_elements if a not in lower]
+ self.svg_elements = lower
+ self.svg_elem_map = dict([(a.lower(),a) for a in mix])
+ acceptable_attributes = self.svg_attributes
+ tag = self.svg_elem_map.get(tag,tag)
+ keymap = self.svg_attr_map
+ elif not tag in self.acceptable_elements:
+ return
+
+ # declare xlink namespace, if needed
+ if self.mathmlOK or self.svgOK:
+ if filter(lambda (n,v): n.startswith('xlink:'),attrs):
+ if not ('xmlns:xlink','http://www.w3.org/1999/xlink') in attrs:
+ attrs.append(('xmlns:xlink','http://www.w3.org/1999/xlink'))
+
+ clean_attrs = []
+ for key, value in self.normalize_attrs(attrs):
+ if key in acceptable_attributes:
+ key=keymap.get(key,key)
+ # make sure the uri uses an acceptable uri scheme
+ if key == u'href':
+ value = _makeSafeAbsoluteURI(value)
+ clean_attrs.append((key,value))
+ elif key=='style':
+ clean_value = self.sanitize_style(value)
+ if clean_value:
+ clean_attrs.append((key,clean_value))
+ _BaseHTMLProcessor.unknown_starttag(self, tag, clean_attrs)
+
+ def unknown_endtag(self, tag):
+ if not tag in self.acceptable_elements:
+ if tag in self.unacceptable_elements_with_end_tag:
+ self.unacceptablestack -= 1
+ if self.mathmlOK and tag in self.mathml_elements:
+ if tag == 'math' and self.mathmlOK:
+ self.mathmlOK -= 1
+ elif self.svgOK and tag in self.svg_elements:
+ tag = self.svg_elem_map.get(tag,tag)
+ if tag == 'svg' and self.svgOK:
+ self.svgOK -= 1
+ else:
+ return
+ _BaseHTMLProcessor.unknown_endtag(self, tag)
+
+ def handle_pi(self, text):
+ pass
+
+ def handle_decl(self, text):
+ pass
+
+ def handle_data(self, text):
+ if not self.unacceptablestack:
+ _BaseHTMLProcessor.handle_data(self, text)
+
+ def sanitize_style(self, style):
+ # disallow urls
+ style=re.compile('url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ',style)
+
+ # gauntlet
+ if not re.match("""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style):
+ return ''
+ # This replaced a regexp that used re.match and was prone to pathological back-tracking.
+ if re.sub("\s*[-\w]+\s*:\s*[^:;]*;?", '', style).strip():
+ return ''
+
+ clean = []
+ for prop,value in re.findall("([-\w]+)\s*:\s*([^:;]*)",style):
+ if not value:
+ continue
+ if prop.lower() in self.acceptable_css_properties:
+ clean.append(prop + ': ' + value + ';')
+ elif prop.split('-')[0].lower() in ['background','border','margin','padding']:
+ for keyword in value.split():
+ if not keyword in self.acceptable_css_keywords and \
+ not self.valid_css_values.match(keyword):
+ break
+ else:
+ clean.append(prop + ': ' + value + ';')
+ elif self.svgOK and prop.lower() in self.acceptable_svg_properties:
+ clean.append(prop + ': ' + value + ';')
+
+ return ' '.join(clean)
+
+ def parse_comment(self, i, report=1):
+ ret = _BaseHTMLProcessor.parse_comment(self, i, report)
+ if ret >= 0:
+ return ret
+ # if ret == -1, this may be a malicious attempt to circumvent
+ # sanitization, or a page-destroying unclosed comment
+ match = re.compile(r'--[^>]*>').search(self.rawdata, i+4)
+ if match:
+ return match.end()
+ # unclosed comment; deliberately fail to handle_data()
+ return len(self.rawdata)
+
+
+def _sanitizeHTML(htmlSource, encoding, _type):
+ if not _SGML_AVAILABLE:
+ return htmlSource
+ p = _HTMLSanitizer(encoding, _type)
+ htmlSource = htmlSource.replace('<![CDATA[', '&lt;![CDATA[')
+ p.feed(htmlSource)
+ data = p.output()
+ if TIDY_MARKUP:
+ # loop through list of preferred Tidy interfaces looking for one that's installed,
+ # then set up a common _tidy function to wrap the interface-specific API.
+ _tidy = None
+ for tidy_interface in PREFERRED_TIDY_INTERFACES:
+ try:
+ if tidy_interface == "uTidy":
+ from tidy import parseString as _utidy
+ def _tidy(data, **kwargs):
+ return str(_utidy(data, **kwargs))
+ break
+ elif tidy_interface == "mxTidy":
+ from mx.Tidy import Tidy as _mxtidy
+ def _tidy(data, **kwargs):
+ nerrors, nwarnings, data, errordata = _mxtidy.tidy(data, **kwargs)
+ return data
+ break
+ except:
+ pass
+ if _tidy:
+ utf8 = isinstance(data, unicode)
+ if utf8:
+ data = data.encode('utf-8')
+ data = _tidy(data, output_xhtml=1, numeric_entities=1, wrap=0, char_encoding="utf8")
+ if utf8:
+ data = unicode(data, 'utf-8')
+ if data.count('<body'):
+ data = data.split('<body', 1)[1]
+ if data.count('>'):
+ data = data.split('>', 1)[1]
+ if data.count('</body'):
+ data = data.split('</body', 1)[0]
+ data = data.strip().replace('\r\n', '\n')
+ return data
+
+class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
+ def http_error_default(self, req, fp, code, msg, headers):
+ # The default implementation just raises HTTPError.
+ # Forget that.
+ fp.status = code
+ return fp
+
+ def http_error_301(self, req, fp, code, msg, hdrs):
+ result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp,
+ code, msg, hdrs)
+ result.status = code
+ result.newurl = result.geturl()
+ return result
+ # The default implementations in urllib2.HTTPRedirectHandler
+ # are identical, so hardcoding a http_error_301 call above
+ # won't affect anything
+ http_error_300 = http_error_301
+ http_error_302 = http_error_301
+ http_error_303 = http_error_301
+ http_error_307 = http_error_301
+
+ def http_error_401(self, req, fp, code, msg, headers):
+ # Check if
+ # - server requires digest auth, AND
+ # - we tried (unsuccessfully) with basic auth, AND
+ # If all conditions hold, parse authentication information
+ # out of the Authorization header we sent the first time
+ # (for the username and password) and the WWW-Authenticate
+ # header the server sent back (for the realm) and retry
+ # the request with the appropriate digest auth headers instead.
+ # This evil genius hack has been brought to you by Aaron Swartz.
+ host = urlparse.urlparse(req.get_full_url())[1]
+ if base64 is None or 'Authorization' not in req.headers \
+ or 'WWW-Authenticate' not in headers:
+ return self.http_error_default(req, fp, code, msg, headers)
+ auth = _base64decode(req.headers['Authorization'].split(' ')[1])
+ user, passw = auth.split(':')
+ realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
+ self.add_password(realm, host, user, passw)
+ retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
+ self.reset_retry_count()
+ return retry
+
+def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers):
+ """URL, filename, or string --> stream
+
+ This function lets you define parsers that take any input source
+ (URL, pathname to local or network file, or actual data as a string)
+ and deal with it in a uniform manner. Returned object is guaranteed
+ to have all the basic stdio read methods (read, readline, readlines).
+ Just .close() the object when you're done with it.
+
+ If the etag argument is supplied, it will be used as the value of an
+ If-None-Match request header.
+
+ If the modified argument is supplied, it can be a tuple of 9 integers
+ (as returned by gmtime() in the standard Python time module) or a date
+ string in any format supported by feedparser. Regardless, it MUST
+ be in GMT (Greenwich Mean Time). It will be reformatted into an
+ RFC 1123-compliant date and used as the value of an If-Modified-Since
+ request header.
+
+ If the agent argument is supplied, it will be used as the value of a
+ User-Agent request header.
+
+ If the referrer argument is supplied, it will be used as the value of a
+ Referer[sic] request header.
+
+ If handlers is supplied, it is a list of handlers used to build a
+ urllib2 opener.
+
+ if request_headers is supplied it is a dictionary of HTTP request headers
+ that will override the values generated by FeedParser.
+ """
+
+ if hasattr(url_file_stream_or_string, 'read'):
+ return url_file_stream_or_string
+
+ if isinstance(url_file_stream_or_string, basestring) \
+ and urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp', 'file', 'feed'):
+ # Deal with the feed URI scheme
+ if url_file_stream_or_string.startswith('feed:http'):
+ url_file_stream_or_string = url_file_stream_or_string[5:]
+ elif url_file_stream_or_string.startswith('feed:'):
+ url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:]
+ if not agent:
+ agent = USER_AGENT
+ # Test for inline user:password credentials for HTTP basic auth
+ auth = None
+ if base64 and not url_file_stream_or_string.startswith('ftp:'):
+ urltype, rest = urllib.splittype(url_file_stream_or_string)
+ realhost, rest = urllib.splithost(rest)
+ if realhost:
+ user_passwd, realhost = urllib.splituser(realhost)
+ if user_passwd:
+ url_file_stream_or_string = '%s://%s%s' % (urltype, realhost, rest)
+ auth = base64.standard_b64encode(user_passwd).strip()
+
+ # iri support
+ if isinstance(url_file_stream_or_string, unicode):
+ url_file_stream_or_string = _convert_to_idn(url_file_stream_or_string)
+
+ # try to open with urllib2 (to use optional headers)
+ request = _build_urllib2_request(url_file_stream_or_string, agent, etag, modified, referrer, auth, request_headers)
+ opener = urllib2.build_opener(*tuple(handlers + [_FeedURLHandler()]))
+ opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent
+ try:
+ return opener.open(request)
+ finally:
+ opener.close() # JohnD
+
+ # try to open with native open function (if url_file_stream_or_string is a filename)
+ try:
+ return open(url_file_stream_or_string, 'rb')
+ except (IOError, UnicodeEncodeError, TypeError):
+ # if url_file_stream_or_string is a unicode object that
+ # cannot be converted to the encoding returned by
+ # sys.getfilesystemencoding(), a UnicodeEncodeError
+ # will be thrown
+ # If url_file_stream_or_string is a string that contains NULL
+ # (such as an XML document encoded in UTF-32), TypeError will
+ # be thrown.
+ pass
+
+ # treat url_file_stream_or_string as string
+ if isinstance(url_file_stream_or_string, unicode):
+ return _StringIO(url_file_stream_or_string.encode('utf-8'))
+ return _StringIO(url_file_stream_or_string)
+
+def _convert_to_idn(url):
+ """Convert a URL to IDN notation"""
+ # this function should only be called with a unicode string
+ # strategy: if the host cannot be encoded in ascii, then
+ # it'll be necessary to encode it in idn form
+ parts = list(urlparse.urlsplit(url))
+ try:
+ parts[1].encode('ascii')
+ except UnicodeEncodeError:
+ # the url needs to be converted to idn notation
+ host = parts[1].rsplit(':', 1)
+ newhost = []
+ port = u''
+ if len(host) == 2:
+ port = host.pop()
+ for h in host[0].split('.'):
+ newhost.append(h.encode('idna').decode('utf-8'))
+ parts[1] = '.'.join(newhost)
+ if port:
+ parts[1] += ':' + port
+ return urlparse.urlunsplit(parts)
+ else:
+ return url
+
+def _build_urllib2_request(url, agent, etag, modified, referrer, auth, request_headers):
+ request = urllib2.Request(url)
+ request.add_header('User-Agent', agent)
+ if etag:
+ request.add_header('If-None-Match', etag)
+ if isinstance(modified, basestring):
+ modified = _parse_date(modified)
+ elif isinstance(modified, datetime.datetime):
+ modified = modified.utctimetuple()
+ if modified:
+ # format into an RFC 1123-compliant timestamp. We can't use
+ # time.strftime() since the %a and %b directives can be affected
+ # by the current locale, but RFC 2616 states that dates must be
+ # in English.
+ short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+ months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5]))
+ if referrer:
+ request.add_header('Referer', referrer)
+ if gzip and zlib:
+ request.add_header('Accept-encoding', 'gzip, deflate')
+ elif gzip:
+ request.add_header('Accept-encoding', 'gzip')
+ elif zlib:
+ request.add_header('Accept-encoding', 'deflate')
+ else:
+ request.add_header('Accept-encoding', '')
+ if auth:
+ request.add_header('Authorization', 'Basic %s' % auth)
+ if ACCEPT_HEADER:
+ request.add_header('Accept', ACCEPT_HEADER)
+ # use this for whatever -- cookies, special headers, etc
+ # [('Cookie','Something'),('x-special-header','Another Value')]
+ for header_name, header_value in request_headers.items():
+ request.add_header(header_name, header_value)
+ request.add_header('A-IM', 'feed') # RFC 3229 support
+ return request
+
+_date_handlers = []
+def registerDateHandler(func):
+ '''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
+ _date_handlers.insert(0, func)
+
+# ISO-8601 date parsing routines written by Fazal Majid.
+# The ISO 8601 standard is very convoluted and irregular - a full ISO 8601
+# parser is beyond the scope of feedparser and would be a worthwhile addition
+# to the Python library.
+# A single regular expression cannot parse ISO 8601 date formats into groups
+# as the standard is highly irregular (for instance is 030104 2003-01-04 or
+# 0301-04-01), so we use templates instead.
+# Please note the order in templates is significant because we need a
+# greedy match.
+_iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-0MM?-?DD', 'YYYY-MM', 'YYYY-?OOO',
+ 'YY-?MM-?DD', 'YY-?OOO', 'YYYY',
+ '-YY-?MM', '-OOO', '-YY',
+ '--MM-?DD', '--MM',
+ '---DD',
+ 'CC', '']
+_iso8601_re = [
+ tmpl.replace(
+ 'YYYY', r'(?P<year>\d{4})').replace(
+ 'YY', r'(?P<year>\d\d)').replace(
+ 'MM', r'(?P<month>[01]\d)').replace(
+ 'DD', r'(?P<day>[0123]\d)').replace(
+ 'OOO', r'(?P<ordinal>[0123]\d\d)').replace(
+ 'CC', r'(?P<century>\d\d$)')
+ + r'(T?(?P<hour>\d{2}):(?P<minute>\d{2})'
+ + r'(:(?P<second>\d{2}))?'
+ + r'(\.(?P<fracsecond>\d+))?'
+ + r'(?P<tz>[+-](?P<tzhour>\d{2})(:(?P<tzmin>\d{2}))?|Z)?)?'
+ for tmpl in _iso8601_tmpl]
+try:
+ del tmpl
+except NameError:
+ pass
+_iso8601_matches = [re.compile(regex).match for regex in _iso8601_re]
+try:
+ del regex
+except NameError:
+ pass
+def _parse_date_iso8601(dateString):
+ '''Parse a variety of ISO-8601-compatible formats like 20040105'''
+ m = None
+ for _iso8601_match in _iso8601_matches:
+ m = _iso8601_match(dateString)
+ if m:
+ break
+ if not m:
+ return
+ if m.span() == (0, 0):
+ return
+ params = m.groupdict()
+ ordinal = params.get('ordinal', 0)
+ if ordinal:
+ ordinal = int(ordinal)
+ else:
+ ordinal = 0
+ year = params.get('year', '--')
+ if not year or year == '--':
+ year = time.gmtime()[0]
+ elif len(year) == 2:
+ # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993
+ year = 100 * int(time.gmtime()[0] / 100) + int(year)
+ else:
+ year = int(year)
+ month = params.get('month', '-')
+ if not month or month == '-':
+ # ordinals are NOT normalized by mktime, we simulate them
+ # by setting month=1, day=ordinal
+ if ordinal:
+ month = 1
+ else:
+ month = time.gmtime()[1]
+ month = int(month)
+ day = params.get('day', 0)
+ if not day:
+ # see above
+ if ordinal:
+ day = ordinal
+ elif params.get('century', 0) or \
+ params.get('year', 0) or params.get('month', 0):
+ day = 1
+ else:
+ day = time.gmtime()[2]
+ else:
+ day = int(day)
+ # special case of the century - is the first year of the 21st century
+ # 2000 or 2001 ? The debate goes on...
+ if 'century' in params:
+ year = (int(params['century']) - 1) * 100 + 1
+ # in ISO 8601 most fields are optional
+ for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']:
+ if not params.get(field, None):
+ params[field] = 0
+ hour = int(params.get('hour', 0))
+ minute = int(params.get('minute', 0))
+ second = int(float(params.get('second', 0)))
+ # weekday is normalized by mktime(), we can ignore it
+ weekday = 0
+ daylight_savings_flag = -1
+ tm = [year, month, day, hour, minute, second, weekday,
+ ordinal, daylight_savings_flag]
+ # ISO 8601 time zone adjustments
+ tz = params.get('tz')
+ if tz and tz != 'Z':
+ if tz[0] == '-':
+ tm[3] += int(params.get('tzhour', 0))
+ tm[4] += int(params.get('tzmin', 0))
+ elif tz[0] == '+':
+ tm[3] -= int(params.get('tzhour', 0))
+ tm[4] -= int(params.get('tzmin', 0))
+ else:
+ return None
+ # Python's time.mktime() is a wrapper around the ANSI C mktime(3c)
+ # which is guaranteed to normalize d/m/y/h/m/s.
+ # Many implementations have bugs, but we'll pretend they don't.
+ return time.localtime(time.mktime(tuple(tm)))
+registerDateHandler(_parse_date_iso8601)
+
+# 8-bit date handling routines written by ytrewq1.
+_korean_year = u'\ub144' # b3e2 in euc-kr
+_korean_month = u'\uc6d4' # bff9 in euc-kr
+_korean_day = u'\uc77c' # c0cf in euc-kr
+_korean_am = u'\uc624\uc804' # bfc0 c0fc in euc-kr
+_korean_pm = u'\uc624\ud6c4' # bfc0 c8c4 in euc-kr
+
+_korean_onblog_date_re = \
+ re.compile('(\d{4})%s\s+(\d{2})%s\s+(\d{2})%s\s+(\d{2}):(\d{2}):(\d{2})' % \
+ (_korean_year, _korean_month, _korean_day))
+_korean_nate_date_re = \
+ re.compile(u'(\d{4})-(\d{2})-(\d{2})\s+(%s|%s)\s+(\d{,2}):(\d{,2}):(\d{,2})' % \
+ (_korean_am, _korean_pm))
+def _parse_date_onblog(dateString):
+ '''Parse a string according to the OnBlog 8-bit date format'''
+ m = _korean_onblog_date_re.match(dateString)
+ if not m:
+ return
+ w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \
+ {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\
+ 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\
+ 'zonediff': '+09:00'}
+ return _parse_date_w3dtf(w3dtfdate)
+registerDateHandler(_parse_date_onblog)
+
+def _parse_date_nate(dateString):
+ '''Parse a string according to the Nate 8-bit date format'''
+ m = _korean_nate_date_re.match(dateString)
+ if not m:
+ return
+ hour = int(m.group(5))
+ ampm = m.group(4)
+ if (ampm == _korean_pm):
+ hour += 12
+ hour = str(hour)
+ if len(hour) == 1:
+ hour = '0' + hour
+ w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \
+ {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\
+ 'hour': hour, 'minute': m.group(6), 'second': m.group(7),\
+ 'zonediff': '+09:00'}
+ return _parse_date_w3dtf(w3dtfdate)
+registerDateHandler(_parse_date_nate)
+
+# Unicode strings for Greek date strings
+_greek_months = \
+ { \
+ u'\u0399\u03b1\u03bd': u'Jan', # c9e1ed in iso-8859-7
+ u'\u03a6\u03b5\u03b2': u'Feb', # d6e5e2 in iso-8859-7
+ u'\u039c\u03ac\u03ce': u'Mar', # ccdcfe in iso-8859-7
+ u'\u039c\u03b1\u03ce': u'Mar', # cce1fe in iso-8859-7
+ u'\u0391\u03c0\u03c1': u'Apr', # c1f0f1 in iso-8859-7
+ u'\u039c\u03ac\u03b9': u'May', # ccdce9 in iso-8859-7
+ u'\u039c\u03b1\u03ca': u'May', # cce1fa in iso-8859-7
+ u'\u039c\u03b1\u03b9': u'May', # cce1e9 in iso-8859-7
+ u'\u0399\u03bf\u03cd\u03bd': u'Jun', # c9effded in iso-8859-7
+ u'\u0399\u03bf\u03bd': u'Jun', # c9efed in iso-8859-7
+ u'\u0399\u03bf\u03cd\u03bb': u'Jul', # c9effdeb in iso-8859-7
+ u'\u0399\u03bf\u03bb': u'Jul', # c9f9eb in iso-8859-7
+ u'\u0391\u03cd\u03b3': u'Aug', # c1fde3 in iso-8859-7
+ u'\u0391\u03c5\u03b3': u'Aug', # c1f5e3 in iso-8859-7
+ u'\u03a3\u03b5\u03c0': u'Sep', # d3e5f0 in iso-8859-7
+ u'\u039f\u03ba\u03c4': u'Oct', # cfeaf4 in iso-8859-7
+ u'\u039d\u03bf\u03ad': u'Nov', # cdefdd in iso-8859-7
+ u'\u039d\u03bf\u03b5': u'Nov', # cdefe5 in iso-8859-7
+ u'\u0394\u03b5\u03ba': u'Dec', # c4e5ea in iso-8859-7
+ }
+
+_greek_wdays = \
+ { \
+ u'\u039a\u03c5\u03c1': u'Sun', # caf5f1 in iso-8859-7
+ u'\u0394\u03b5\u03c5': u'Mon', # c4e5f5 in iso-8859-7
+ u'\u03a4\u03c1\u03b9': u'Tue', # d4f1e9 in iso-8859-7
+ u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7
+ u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7
+ u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7
+ u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7
+ }
+
+_greek_date_format_re = \
+ re.compile(u'([^,]+),\s+(\d{2})\s+([^\s]+)\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+([^\s]+)')
+
+def _parse_date_greek(dateString):
+ '''Parse a string according to a Greek 8-bit date format.'''
+ m = _greek_date_format_re.match(dateString)
+ if not m:
+ return
+ wday = _greek_wdays[m.group(1)]
+ month = _greek_months[m.group(3)]
+ rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % \
+ {'wday': wday, 'day': m.group(2), 'month': month, 'year': m.group(4),\
+ 'hour': m.group(5), 'minute': m.group(6), 'second': m.group(7),\
+ 'zonediff': m.group(8)}
+ return _parse_date_rfc822(rfc822date)
+registerDateHandler(_parse_date_greek)
+
+# Unicode strings for Hungarian date strings
+_hungarian_months = \
+ { \
+ u'janu\u00e1r': u'01', # e1 in iso-8859-2
+ u'febru\u00e1ri': u'02', # e1 in iso-8859-2
+ u'm\u00e1rcius': u'03', # e1 in iso-8859-2
+ u'\u00e1prilis': u'04', # e1 in iso-8859-2
+ u'm\u00e1ujus': u'05', # e1 in iso-8859-2
+ u'j\u00fanius': u'06', # fa in iso-8859-2
+ u'j\u00falius': u'07', # fa in iso-8859-2
+ u'augusztus': u'08',
+ u'szeptember': u'09',
+ u'okt\u00f3ber': u'10', # f3 in iso-8859-2
+ u'november': u'11',
+ u'december': u'12',
+ }
+
+_hungarian_date_format_re = \
+ re.compile(u'(\d{4})-([^-]+)-(\d{,2})T(\d{,2}):(\d{2})((\+|-)(\d{,2}:\d{2}))')
+
+def _parse_date_hungarian(dateString):
+ '''Parse a string according to a Hungarian 8-bit date format.'''
+ m = _hungarian_date_format_re.match(dateString)
+ if not m or m.group(2) not in _hungarian_months:
+ return None
+ month = _hungarian_months[m.group(2)]
+ day = m.group(3)
+ if len(day) == 1:
+ day = '0' + day
+ hour = m.group(4)
+ if len(hour) == 1:
+ hour = '0' + hour
+ w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % \
+ {'year': m.group(1), 'month': month, 'day': day,\
+ 'hour': hour, 'minute': m.group(5),\
+ 'zonediff': m.group(6)}
+ return _parse_date_w3dtf(w3dtfdate)
+registerDateHandler(_parse_date_hungarian)
+
+# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by
+# Drake and licensed under the Python license. Removed all range checking
+# for month, day, hour, minute, and second, since mktime will normalize
+# these later
+# Modified to also support MSSQL-style datetimes as defined at:
+# http://msdn.microsoft.com/en-us/library/ms186724.aspx
+# (which basically means allowing a space as a date/time/timezone separator)
+def _parse_date_w3dtf(dateString):
+ def __extract_date(m):
+ year = int(m.group('year'))
+ if year < 100:
+ year = 100 * int(time.gmtime()[0] / 100) + int(year)
+ if year < 1000:
+ return 0, 0, 0
+ julian = m.group('julian')
+ if julian:
+ julian = int(julian)
+ month = julian / 30 + 1
+ day = julian % 30 + 1
+ jday = None
+ while jday != julian:
+ t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
+ jday = time.gmtime(t)[-2]
+ diff = abs(jday - julian)
+ if jday > julian:
+ if diff < day:
+ day = day - diff
+ else:
+ month = month - 1
+ day = 31
+ elif jday < julian:
+ if day + diff < 28:
+ day = day + diff
+ else:
+ month = month + 1
+ return year, month, day
+ month = m.group('month')
+ day = 1
+ if month is None:
+ month = 1
+ else:
+ month = int(month)
+ day = m.group('day')
+ if day:
+ day = int(day)
+ else:
+ day = 1
+ return year, month, day
+
+ def __extract_time(m):
+ if not m:
+ return 0, 0, 0
+ hours = m.group('hours')
+ if not hours:
+ return 0, 0, 0
+ hours = int(hours)
+ minutes = int(m.group('minutes'))
+ seconds = m.group('seconds')
+ if seconds:
+ seconds = int(seconds)
+ else:
+ seconds = 0
+ return hours, minutes, seconds
+
+ def __extract_tzd(m):
+ '''Return the Time Zone Designator as an offset in seconds from UTC.'''
+ if not m:
+ return 0
+ tzd = m.group('tzd')
+ if not tzd:
+ return 0
+ if tzd == 'Z':
+ return 0
+ hours = int(m.group('tzdhours'))
+ minutes = m.group('tzdminutes')
+ if minutes:
+ minutes = int(minutes)
+ else:
+ minutes = 0
+ offset = (hours*60 + minutes) * 60
+ if tzd[0] == '+':
+ return -offset
+ return offset
+
+ __date_re = ('(?P<year>\d\d\d\d)'
+ '(?:(?P<dsep>-|)'
+ '(?:(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?'
+ '|(?P<julian>\d\d\d)))?')
+ __tzd_re = ' ?(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)?'
+ __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)'
+ '(?:(?P=tsep)(?P<seconds>\d\d)(?:[.,]\d+)?)?'
+ + __tzd_re)
+ __datetime_re = '%s(?:[T ]%s)?' % (__date_re, __time_re)
+ __datetime_rx = re.compile(__datetime_re)
+ m = __datetime_rx.match(dateString)
+ if (m is None) or (m.group() != dateString):
+ return
+ gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
+ if gmt[0] == 0:
+ return
+ return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
+registerDateHandler(_parse_date_w3dtf)
+
+# Define the strings used by the RFC822 datetime parser
+_rfc822_months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
+ 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']
+_rfc822_daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
+
+# Only the first three letters of the month name matter
+_rfc822_month = "(?P<month>%s)(?:[a-z]*,?)" % ('|'.join(_rfc822_months))
+# The year may be 2 or 4 digits; capture the century if it exists
+_rfc822_year = "(?P<year>(?:\d{2})?\d{2})"
+_rfc822_day = "(?P<day> *\d{1,2})"
+_rfc822_date = "%s %s %s" % (_rfc822_day, _rfc822_month, _rfc822_year)
+
+_rfc822_hour = "(?P<hour>\d{2}):(?P<minute>\d{2})(?::(?P<second>\d{2}))?"
+_rfc822_tz = "(?P<tz>ut|gmt(?:[+-]\d{2}:\d{2})?|[aecmp][sd]?t|[zamny]|[+-]\d{4})"
+_rfc822_tznames = {
+ 'ut': 0, 'gmt': 0, 'z': 0,
+ 'adt': -3, 'ast': -4, 'at': -4,
+ 'edt': -4, 'est': -5, 'et': -5,
+ 'cdt': -5, 'cst': -6, 'ct': -6,
+ 'mdt': -6, 'mst': -7, 'mt': -7,
+ 'pdt': -7, 'pst': -8, 'pt': -8,
+ 'a': -1, 'n': 1,
+ 'm': -12, 'y': 12,
+ }
+# The timezone may be prefixed by 'Etc/'
+_rfc822_time = "%s (?:etc/)?%s" % (_rfc822_hour, _rfc822_tz)
+
+_rfc822_dayname = "(?P<dayname>%s)" % ('|'.join(_rfc822_daynames))
+_rfc822_match = re.compile(
+ "(?:%s, )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date, _rfc822_time)
+).match
+
+def _parse_date_group_rfc822(m):
+ # Calculate a date and timestamp
+ for k in ('year', 'day', 'hour', 'minute', 'second'):
+ m[k] = int(m[k])
+ m['month'] = _rfc822_months.index(m['month']) + 1
+ # If the year is 2 digits, assume everything in the 90's is the 1990's
+ if m['year'] < 100:
+ m['year'] += (1900, 2000)[m['year'] < 90]
+ stamp = datetime.datetime(*[m[i] for i in
+ ('year', 'month', 'day', 'hour', 'minute', 'second')])
+
+ # Use the timezone information to calculate the difference between
+ # the given date and timestamp and Universal Coordinated Time
+ tzhour = 0
+ tzmin = 0
+ if m['tz'] and m['tz'].startswith('gmt'):
+ # Handle GMT and GMT+hh:mm timezone syntax (the trailing
+ # timezone info will be handled by the next `if` block)
+ m['tz'] = ''.join(m['tz'][3:].split(':')) or 'gmt'
+ if not m['tz']:
+ pass
+ elif m['tz'].startswith('+'):
+ tzhour = int(m['tz'][1:3])
+ tzmin = int(m['tz'][3:])
+ elif m['tz'].startswith('-'):
+ tzhour = int(m['tz'][1:3]) * -1
+ tzmin = int(m['tz'][3:]) * -1
+ else:
+ tzhour = _rfc822_tznames[m['tz']]
+ delta = datetime.timedelta(0, 0, 0, 0, tzmin, tzhour)
+
+ # Return the date and timestamp in UTC
+ return (stamp - delta).utctimetuple()
+
+def _parse_date_rfc822(dt):
+ """Parse RFC 822 dates and times, with one minor
+ difference: years may be 4DIGIT or 2DIGIT.
+ http://tools.ietf.org/html/rfc822#section-5"""
+ try:
+ m = _rfc822_match(dt.lower()).groupdict(0)
+ except AttributeError:
+ return None
+
+ return _parse_date_group_rfc822(m)
+registerDateHandler(_parse_date_rfc822)
+
+def _parse_date_rfc822_grubby(dt):
+ """Parse date format similar to RFC 822, but
+ the comma after the dayname is optional and
+ month/day are inverted"""
+ _rfc822_date_grubby = "%s %s %s" % (_rfc822_month, _rfc822_day, _rfc822_year)
+ _rfc822_match_grubby = re.compile(
+ "(?:%s[,]? )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date_grubby, _rfc822_time)
+ ).match
+
+ try:
+ m = _rfc822_match_grubby(dt.lower()).groupdict(0)
+ except AttributeError:
+ return None
+
+ return _parse_date_group_rfc822(m)
+registerDateHandler(_parse_date_rfc822_grubby)
+
+def _parse_date_asctime(dt):
+ """Parse asctime-style dates"""
+ dayname, month, day, remainder = dt.split(None, 3)
+ # Convert month and day into zero-padded integers
+ month = '%02i ' % (_rfc822_months.index(month.lower()) + 1)
+ day = '%02i ' % (int(day),)
+ dt = month + day + remainder
+ return time.strptime(dt, '%m %d %H:%M:%S %Y')[:-1] + (0, )
+registerDateHandler(_parse_date_asctime)
+
+def _parse_date_perforce(aDateString):
+ """parse a date in yyyy/mm/dd hh:mm:ss TTT format"""
+ # Fri, 2006/09/15 08:19:53 EDT
+ _my_date_pattern = re.compile( \
+ r'(\w{,3}), (\d{,4})/(\d{,2})/(\d{2}) (\d{,2}):(\d{2}):(\d{2}) (\w{,3})')
+
+ m = _my_date_pattern.search(aDateString)
+ if m is None:
+ return None
+ dow, year, month, day, hour, minute, second, tz = m.groups()
+ months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ dateString = "%s, %s %s %s %s:%s:%s %s" % (dow, day, months[int(month) - 1], year, hour, minute, second, tz)
+ tm = rfc822.parsedate_tz(dateString)
+ if tm:
+ return time.gmtime(rfc822.mktime_tz(tm))
+registerDateHandler(_parse_date_perforce)
+
+def _parse_date(dateString):
+ '''Parses a variety of date formats into a 9-tuple in GMT'''
+ if not dateString:
+ return None
+ for handler in _date_handlers:
+ try:
+ date9tuple = handler(dateString)
+ except (KeyError, OverflowError, ValueError):
+ continue
+ if not date9tuple:
+ continue
+ if len(date9tuple) != 9:
+ continue
+ return date9tuple
+ return None
+
+# Each marker represents some of the characters of the opening XML
+# processing instruction ('<?xm') in the specified encoding.
+EBCDIC_MARKER = _l2bytes([0x4C, 0x6F, 0xA7, 0x94])
+UTF16BE_MARKER = _l2bytes([0x00, 0x3C, 0x00, 0x3F])
+UTF16LE_MARKER = _l2bytes([0x3C, 0x00, 0x3F, 0x00])
+UTF32BE_MARKER = _l2bytes([0x00, 0x00, 0x00, 0x3C])
+UTF32LE_MARKER = _l2bytes([0x3C, 0x00, 0x00, 0x00])
+
+ZERO_BYTES = _l2bytes([0x00, 0x00])
+
+# Match the opening XML declaration.
+# Example: <?xml version="1.0" encoding="utf-8"?>
+RE_XML_DECLARATION = re.compile('^<\?xml[^>]*?>')
+
+# Capture the value of the XML processing instruction's encoding attribute.
+# Example: <?xml version="1.0" encoding="utf-8"?>
+RE_XML_PI_ENCODING = re.compile(_s2bytes('^<\?.*encoding=[\'"](.*?)[\'"].*\?>'))
+
+def convert_to_utf8(http_headers, data):
+ '''Detect and convert the character encoding to UTF-8.
+
+ http_headers is a dictionary
+ data is a raw string (not Unicode)'''
+
+ # This is so much trickier than it sounds, it's not even funny.
+ # According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
+ # is application/xml, application/*+xml,
+ # application/xml-external-parsed-entity, or application/xml-dtd,
+ # the encoding given in the charset parameter of the HTTP Content-Type
+ # takes precedence over the encoding given in the XML prefix within the
+ # document, and defaults to 'utf-8' if neither are specified. But, if
+ # the HTTP Content-Type is text/xml, text/*+xml, or
+ # text/xml-external-parsed-entity, the encoding given in the XML prefix
+ # within the document is ALWAYS IGNORED and only the encoding given in
+ # the charset parameter of the HTTP Content-Type header should be
+ # respected, and it defaults to 'us-ascii' if not specified.
+
+ # Furthermore, discussion on the atom-syntax mailing list with the
+ # author of RFC 3023 leads me to the conclusion that any document
+ # served with a Content-Type of text/* and no charset parameter
+ # must be treated as us-ascii. (We now do this.) And also that it
+ # must always be flagged as non-well-formed. (We now do this too.)
+
+ # If Content-Type is unspecified (input was local file or non-HTTP source)
+ # or unrecognized (server just got it totally wrong), then go by the
+ # encoding given in the XML prefix of the document and default to
+ # 'iso-8859-1' as per the HTTP specification (RFC 2616).
+
+ # Then, assuming we didn't find a character encoding in the HTTP headers
+ # (and the HTTP Content-type allowed us to look in the body), we need
+ # to sniff the first few bytes of the XML data and try to determine
+ # whether the encoding is ASCII-compatible. Section F of the XML
+ # specification shows the way here:
+ # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
+
+ # If the sniffed encoding is not ASCII-compatible, we need to make it
+ # ASCII compatible so that we can sniff further into the XML declaration
+ # to find the encoding attribute, which will tell us the true encoding.
+
+ # Of course, none of this guarantees that we will be able to parse the
+ # feed in the declared character encoding (assuming it was declared
+ # correctly, which many are not). iconv_codec can help a lot;
+ # you should definitely install it if you can.
+ # http://cjkpython.i18n.org/
+
+ bom_encoding = u''
+ xml_encoding = u''
+ rfc3023_encoding = u''
+
+ # Look at the first few bytes of the document to guess what
+ # its encoding may be. We only need to decode enough of the
+ # document that we can use an ASCII-compatible regular
+ # expression to search for an XML encoding declaration.
+ # The heuristic follows the XML specification, section F:
+ # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
+ # Check for BOMs first.
+ if data[:4] == codecs.BOM_UTF32_BE:
+ bom_encoding = u'utf-32be'
+ data = data[4:]
+ elif data[:4] == codecs.BOM_UTF32_LE:
+ bom_encoding = u'utf-32le'
+ data = data[4:]
+ elif data[:2] == codecs.BOM_UTF16_BE and data[2:4] != ZERO_BYTES:
+ bom_encoding = u'utf-16be'
+ data = data[2:]
+ elif data[:2] == codecs.BOM_UTF16_LE and data[2:4] != ZERO_BYTES:
+ bom_encoding = u'utf-16le'
+ data = data[2:]
+ elif data[:3] == codecs.BOM_UTF8:
+ bom_encoding = u'utf-8'
+ data = data[3:]
+ # Check for the characters '<?xm' in several encodings.
+ elif data[:4] == EBCDIC_MARKER:
+ bom_encoding = u'cp037'
+ elif data[:4] == UTF16BE_MARKER:
+ bom_encoding = u'utf-16be'
+ elif data[:4] == UTF16LE_MARKER:
+ bom_encoding = u'utf-16le'
+ elif data[:4] == UTF32BE_MARKER:
+ bom_encoding = u'utf-32be'
+ elif data[:4] == UTF32LE_MARKER:
+ bom_encoding = u'utf-32le'
+
+ tempdata = data
+ try:
+ if bom_encoding:
+ tempdata = data.decode(bom_encoding).encode('utf-8')
+ except (UnicodeDecodeError, LookupError):
+ # feedparser recognizes UTF-32 encodings that aren't
+ # available in Python 2.4 and 2.5, so it's possible to
+ # encounter a LookupError during decoding.
+ xml_encoding_match = None
+ else:
+ xml_encoding_match = RE_XML_PI_ENCODING.match(tempdata)
+
+ if xml_encoding_match:
+ xml_encoding = xml_encoding_match.groups()[0].decode('utf-8').lower()
+ # Normalize the xml_encoding if necessary.
+ if bom_encoding and (xml_encoding in (
+ u'u16', u'utf-16', u'utf16', u'utf_16',
+ u'u32', u'utf-32', u'utf32', u'utf_32',
+ u'iso-10646-ucs-2', u'iso-10646-ucs-4',
+ u'csucs4', u'csunicode', u'ucs-2', u'ucs-4'
+ )):
+ xml_encoding = bom_encoding
+
+ # Find the HTTP Content-Type and, hopefully, a character
+ # encoding provided by the server. The Content-Type is used
+ # to choose the "correct" encoding among the BOM encoding,
+ # XML declaration encoding, and HTTP encoding, following the
+ # heuristic defined in RFC 3023.
+ http_content_type = http_headers.get('content-type') or ''
+ http_content_type, params = cgi.parse_header(http_content_type)
+ http_encoding = params.get('charset', '').replace("'", "")
+ if not isinstance(http_encoding, unicode):
+ http_encoding = http_encoding.decode('utf-8', 'ignore')
+
+ acceptable_content_type = 0
+ application_content_types = (u'application/xml', u'application/xml-dtd',
+ u'application/xml-external-parsed-entity')
+ text_content_types = (u'text/xml', u'text/xml-external-parsed-entity')
+ if (http_content_type in application_content_types) or \
+ (http_content_type.startswith(u'application/') and
+ http_content_type.endswith(u'+xml')):
+ acceptable_content_type = 1
+ rfc3023_encoding = http_encoding or xml_encoding or u'utf-8'
+ elif (http_content_type in text_content_types) or \
+ (http_content_type.startswith(u'text/') and
+ http_content_type.endswith(u'+xml')):
+ acceptable_content_type = 1
+ rfc3023_encoding = http_encoding or u'us-ascii'
+ elif http_content_type.startswith(u'text/'):
+ rfc3023_encoding = http_encoding or u'us-ascii'
+ elif http_headers and 'content-type' not in http_headers:
+ rfc3023_encoding = xml_encoding or u'iso-8859-1'
+ else:
+ rfc3023_encoding = xml_encoding or u'utf-8'
+ # gb18030 is a superset of gb2312, so always replace gb2312
+ # with gb18030 for greater compatibility.
+ if rfc3023_encoding.lower() == u'gb2312':
+ rfc3023_encoding = u'gb18030'
+ if xml_encoding.lower() == u'gb2312':
+ xml_encoding = u'gb18030'
+
+ # there are four encodings to keep track of:
+ # - http_encoding is the encoding declared in the Content-Type HTTP header
+ # - xml_encoding is the encoding declared in the <?xml declaration
+ # - bom_encoding is the encoding sniffed from the first 4 bytes of the XML data
+ # - rfc3023_encoding is the actual encoding, as per RFC 3023 and a variety of other conflicting specifications
+ error = None
+
+ if http_headers and (not acceptable_content_type):
+ if 'content-type' in http_headers:
+ msg = '%s is not an XML media type' % http_headers['content-type']
+ else:
+ msg = 'no Content-type specified'
+ error = NonXMLContentType(msg)
+
+ # determine character encoding
+ known_encoding = 0
+ chardet_encoding = None
+ tried_encodings = []
+ if chardet:
+ chardet_encoding = unicode(chardet.detect(data)['encoding'] or '', 'ascii', 'ignore')
+ # try: HTTP encoding, declared XML encoding, encoding sniffed from BOM
+ for proposed_encoding in (rfc3023_encoding, xml_encoding, bom_encoding,
+ chardet_encoding, u'utf-8', u'windows-1252', u'iso-8859-2'):
+ if not proposed_encoding:
+ continue
+ if proposed_encoding in tried_encodings:
+ continue
+ tried_encodings.append(proposed_encoding)
+ try:
+ data = data.decode(proposed_encoding)
+ except (UnicodeDecodeError, LookupError):
+ pass
+ else:
+ known_encoding = 1
+ # Update the encoding in the opening XML processing instruction.
+ new_declaration = '''<?xml version='1.0' encoding='utf-8'?>'''
+ if RE_XML_DECLARATION.search(data):
+ data = RE_XML_DECLARATION.sub(new_declaration, data)
+ else:
+ data = new_declaration + u'\n' + data
+ data = data.encode('utf-8')
+ break
+ # if still no luck, give up
+ if not known_encoding:
+ error = CharacterEncodingUnknown(
+ 'document encoding unknown, I tried ' +
+ '%s, %s, utf-8, windows-1252, and iso-8859-2 but nothing worked' %
+ (rfc3023_encoding, xml_encoding))
+ rfc3023_encoding = u''
+ elif proposed_encoding != rfc3023_encoding:
+ error = CharacterEncodingOverride(
+ 'document declared as %s, but parsed as %s' %
+ (rfc3023_encoding, proposed_encoding))
+ rfc3023_encoding = proposed_encoding
+
+ return data, rfc3023_encoding, error
+
+# Match XML entity declarations.
+# Example: <!ENTITY copyright "(C)">
+RE_ENTITY_PATTERN = re.compile(_s2bytes(r'^\s*<!ENTITY([^>]*?)>'), re.MULTILINE)
+
+# Match XML DOCTYPE declarations.
+# Example: <!DOCTYPE feed [ ]>
+RE_DOCTYPE_PATTERN = re.compile(_s2bytes(r'^\s*<!DOCTYPE([^>]*?)>'), re.MULTILINE)
+
+# Match safe entity declarations.
+# This will allow hexadecimal character references through,
+# as well as text, but not arbitrary nested entities.
+# Example: cubed "&#179;"
+# Example: copyright "(C)"
+# Forbidden: explode1 "&explode2;&explode2;"
+RE_SAFE_ENTITY_PATTERN = re.compile(_s2bytes('\s+(\w+)\s+"(&#\w+;|[^&"]*)"'))
+
+def replace_doctype(data):
+ '''Strips and replaces the DOCTYPE, returns (rss_version, stripped_data)
+
+ rss_version may be 'rss091n' or None
+ stripped_data is the same XML document with a replaced DOCTYPE
+ '''
+
+ # Divide the document into two groups by finding the location
+ # of the first element that doesn't begin with '<?' or '<!'.
+ start = re.search(_s2bytes('<\w'), data)
+ start = start and start.start() or -1
+ head, data = data[:start+1], data[start+1:]
+
+ # Save and then remove all of the ENTITY declarations.
+ entity_results = RE_ENTITY_PATTERN.findall(head)
+ head = RE_ENTITY_PATTERN.sub(_s2bytes(''), head)
+
+ # Find the DOCTYPE declaration and check the feed type.
+ doctype_results = RE_DOCTYPE_PATTERN.findall(head)
+ doctype = doctype_results and doctype_results[0] or _s2bytes('')
+ if _s2bytes('netscape') in doctype.lower():
+ version = u'rss091n'
+ else:
+ version = None
+
+ # Re-insert the safe ENTITY declarations if a DOCTYPE was found.
+ replacement = _s2bytes('')
+ if len(doctype_results) == 1 and entity_results:
+ match_safe_entities = lambda e: RE_SAFE_ENTITY_PATTERN.match(e)
+ safe_entities = filter(match_safe_entities, entity_results)
+ if safe_entities:
+ replacement = _s2bytes('<!DOCTYPE feed [\n<!ENTITY') \
+ + _s2bytes('>\n<!ENTITY ').join(safe_entities) \
+ + _s2bytes('>\n]>')
+ data = RE_DOCTYPE_PATTERN.sub(replacement, head) + data
+
+ # Precompute the safe entities for the loose parser.
+ safe_entities = dict((k.decode('utf-8'), v.decode('utf-8'))
+ for k, v in RE_SAFE_ENTITY_PATTERN.findall(replacement))
+ return version, data, safe_entities
+
+def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=None, request_headers=None, response_headers=None):
+ '''Parse a feed from a URL, file, stream, or string.
+
+ request_headers, if given, is a dict from http header name to value to add
+ to the request; this overrides internally generated values.
+ '''
+
+ if handlers is None:
+ handlers = []
+ if request_headers is None:
+ request_headers = {}
+ if response_headers is None:
+ response_headers = {}
+
+ result = FeedParserDict()
+ result['feed'] = FeedParserDict()
+ result['entries'] = []
+ result['bozo'] = 0
+ if not isinstance(handlers, list):
+ handlers = [handlers]
+ try:
+ f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers, request_headers)
+ data = f.read()
+ except Exception, e:
+ result['bozo'] = 1
+ result['bozo_exception'] = e
+ data = None
+ f = None
+
+ if hasattr(f, 'headers'):
+ result['headers'] = dict(f.headers)
+ # overwrite existing headers using response_headers
+ if 'headers' in result:
+ result['headers'].update(response_headers)
+ elif response_headers:
+ result['headers'] = copy.deepcopy(response_headers)
+
+ # lowercase all of the HTTP headers for comparisons per RFC 2616
+ if 'headers' in result:
+ http_headers = dict((k.lower(), v) for k, v in result['headers'].items())
+ else:
+ http_headers = {}
+
+ # if feed is gzip-compressed, decompress it
+ if f and data and http_headers:
+ if gzip and 'gzip' in http_headers.get('content-encoding', ''):
+ try:
+ data = gzip.GzipFile(fileobj=_StringIO(data)).read()
+ except (IOError, struct.error), e:
+ # IOError can occur if the gzip header is bad.
+ # struct.error can occur if the data is damaged.
+ result['bozo'] = 1
+ result['bozo_exception'] = e
+ if isinstance(e, struct.error):
+ # A gzip header was found but the data is corrupt.
+ # Ideally, we should re-request the feed without the
+ # 'Accept-encoding: gzip' header, but we don't.
+ data = None
+ elif zlib and 'deflate' in http_headers.get('content-encoding', ''):
+ try:
+ data = zlib.decompress(data)
+ except zlib.error, e:
+ try:
+ # The data may have no headers and no checksum.
+ data = zlib.decompress(data, -15)
+ except zlib.error, e:
+ result['bozo'] = 1
+ result['bozo_exception'] = e
+
+ # save HTTP headers
+ if http_headers:
+ if 'etag' in http_headers:
+ etag = http_headers.get('etag', u'')
+ if not isinstance(etag, unicode):
+ etag = etag.decode('utf-8', 'ignore')
+ if etag:
+ result['etag'] = etag
+ if 'last-modified' in http_headers:
+ modified = http_headers.get('last-modified', u'')
+ if modified:
+ result['modified'] = modified
+ result['modified_parsed'] = _parse_date(modified)
+ if hasattr(f, 'url'):
+ if not isinstance(f.url, unicode):
+ result['href'] = f.url.decode('utf-8', 'ignore')
+ else:
+ result['href'] = f.url
+ result['status'] = 200
+ if hasattr(f, 'status'):
+ result['status'] = f.status
+ if hasattr(f, 'close'):
+ f.close()
+
+ if data is None:
+ return result
+
+ # Stop processing if the server sent HTTP 304 Not Modified.
+ if getattr(f, 'code', 0) == 304:
+ result['version'] = u''
+ result['debug_message'] = 'The feed has not changed since you last checked, ' + \
+ 'so the server sent no data. This is a feature, not a bug!'
+ return result
+
+ data, result['encoding'], error = convert_to_utf8(http_headers, data)
+ use_strict_parser = result['encoding'] and True or False
+ if error is not None:
+ result['bozo'] = 1
+ result['bozo_exception'] = error
+
+ result['version'], data, entities = replace_doctype(data)
+
+ # Ensure that baseuri is an absolute URI using an acceptable URI scheme.
+ contentloc = http_headers.get('content-location', u'')
+ href = result.get('href', u'')
+ baseuri = _makeSafeAbsoluteURI(href, contentloc) or _makeSafeAbsoluteURI(contentloc) or href
+
+ baselang = http_headers.get('content-language', None)
+ if not isinstance(baselang, unicode) and baselang is not None:
+ baselang = baselang.decode('utf-8', 'ignore')
+
+ if not _XML_AVAILABLE:
+ use_strict_parser = 0
+ if use_strict_parser:
+ # initialize the SAX parser
+ feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
+ saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
+ saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
+ try:
+ # disable downloading external doctype references, if possible
+ saxparser.setFeature(xml.sax.handler.feature_external_ges, 0)
+ except xml.sax.SAXNotSupportedException:
+ pass
+ saxparser.setContentHandler(feedparser)
+ saxparser.setErrorHandler(feedparser)
+ source = xml.sax.xmlreader.InputSource()
+ source.setByteStream(_StringIO(data))
+ try:
+ saxparser.parse(source)
+ except xml.sax.SAXException, e:
+ result['bozo'] = 1
+ result['bozo_exception'] = feedparser.exc or e
+ use_strict_parser = 0
+ if not use_strict_parser and _SGML_AVAILABLE:
+ feedparser = _LooseFeedParser(baseuri, baselang, 'utf-8', entities)
+ feedparser.feed(data.decode('utf-8', 'replace'))
+ result['feed'] = feedparser.feeddata
+ result['entries'] = feedparser.entries
+ result['version'] = result['version'] or feedparser.version
+ result['namespaces'] = feedparser.namespacesInUse
+ return result
diff --git a/pyload/lib/jinja2/__init__.py b/pyload/lib/jinja2/__init__.py
new file mode 100644
index 000000000..a4f7e9c4e
--- /dev/null
+++ b/pyload/lib/jinja2/__init__.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2
+ ~~~~~~
+
+ Jinja2 is a template engine written in pure Python. It provides a
+ Django inspired non-XML syntax but supports inline expressions and
+ an optional sandboxed environment.
+
+ Nutshell
+ --------
+
+ Here a small example of a Jinja2 template::
+
+ {% extends 'base.html' %}
+ {% block title %}Memberlist{% endblock %}
+ {% block content %}
+ <ul>
+ {% for user in users %}
+ <li><a href="{{ user.url }}">{{ user.username }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
+
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+__docformat__ = 'restructuredtext en'
+__version__ = '2.7.3'
+
+# high level interface
+from jinja2.environment import Environment, Template
+
+# loaders
+from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
+ DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
+ ModuleLoader
+
+# bytecode caches
+from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
+ MemcachedBytecodeCache
+
+# undefined types
+from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+
+# exceptions
+from jinja2.exceptions import TemplateError, UndefinedError, \
+ TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
+ TemplateAssertionError
+
+# decorators and public utilities
+from jinja2.filters import environmentfilter, contextfilter, \
+ evalcontextfilter
+from jinja2.utils import Markup, escape, clear_caches, \
+ environmentfunction, evalcontextfunction, contextfunction, \
+ is_undefined
+
+__all__ = [
+ 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
+ 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
+ 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
+ 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
+ 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
+ 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
+ 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
+ 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
+ 'evalcontextfilter', 'evalcontextfunction'
+]
diff --git a/pyload/lib/jinja2/_compat.py b/pyload/lib/jinja2/_compat.py
new file mode 100644
index 000000000..8fa8a49a0
--- /dev/null
+++ b/pyload/lib/jinja2/_compat.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2._compat
+ ~~~~~~~~~~~~~~
+
+ Some py2/py3 compatibility support based on a stripped down
+ version of six so we don't have to depend on a specific version
+ of it.
+
+ :copyright: Copyright 2013 by the Jinja team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+import sys
+
+PY2 = sys.version_info[0] == 2
+PYPY = hasattr(sys, 'pypy_translation_info')
+_identity = lambda x: x
+
+
+if not PY2:
+ unichr = chr
+ range_type = range
+ text_type = str
+ string_types = (str,)
+
+ iterkeys = lambda d: iter(d.keys())
+ itervalues = lambda d: iter(d.values())
+ iteritems = lambda d: iter(d.items())
+
+ import pickle
+ from io import BytesIO, StringIO
+ NativeStringIO = StringIO
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+
+ ifilter = filter
+ imap = map
+ izip = zip
+ intern = sys.intern
+
+ implements_iterator = _identity
+ implements_to_string = _identity
+ encode_filename = _identity
+ get_next = lambda x: x.__next__
+
+else:
+ unichr = unichr
+ text_type = unicode
+ range_type = xrange
+ string_types = (str, unicode)
+
+ iterkeys = lambda d: d.iterkeys()
+ itervalues = lambda d: d.itervalues()
+ iteritems = lambda d: d.iteritems()
+
+ import cPickle as pickle
+ from cStringIO import StringIO as BytesIO, StringIO
+ NativeStringIO = BytesIO
+
+ exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
+
+ from itertools import imap, izip, ifilter
+ intern = intern
+
+ def implements_iterator(cls):
+ cls.next = cls.__next__
+ del cls.__next__
+ return cls
+
+ def implements_to_string(cls):
+ cls.__unicode__ = cls.__str__
+ cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
+ return cls
+
+ get_next = lambda x: x.next
+
+ def encode_filename(filename):
+ if isinstance(filename, unicode):
+ return filename.encode('utf-8')
+ return filename
+
+try:
+ next = next
+except NameError:
+ def next(it):
+ return it.next()
+
+
+def with_metaclass(meta, *bases):
+ # This requires a bit of explanation: the basic idea is to make a
+ # dummy metaclass for one level of class instanciation that replaces
+ # itself with the actual metaclass. Because of internal type checks
+ # we also need to make sure that we downgrade the custom metaclass
+ # for one level to something closer to type (that's why __call__ and
+ # __init__ comes back from type etc.).
+ #
+ # This has the advantage over six.with_metaclass in that it does not
+ # introduce dummy classes into the final MRO.
+ class metaclass(meta):
+ __call__ = type.__call__
+ __init__ = type.__init__
+ def __new__(cls, name, this_bases, d):
+ if this_bases is None:
+ return type.__new__(cls, name, (), d)
+ return meta(name, bases, d)
+ return metaclass('temporary_class', None, {})
+
+
+try:
+ from collections import Mapping as mapping_types
+except ImportError:
+ import UserDict
+ mapping_types = (UserDict.UserDict, UserDict.DictMixin, dict)
+
+
+# common types. These do exist in the special types module too which however
+# does not exist in IronPython out of the box. Also that way we don't have
+# to deal with implementation specific stuff here
+class _C(object):
+ def method(self): pass
+def _func():
+ yield None
+function_type = type(_func)
+generator_type = type(_func())
+method_type = type(_C().method)
+code_type = type(_C.method.__code__)
+try:
+ raise TypeError()
+except TypeError:
+ _tb = sys.exc_info()[2]
+ traceback_type = type(_tb)
+ frame_type = type(_tb.tb_frame)
+
+
+try:
+ from urllib.parse import quote_from_bytes as url_quote
+except ImportError:
+ from urllib import quote as url_quote
+
+
+try:
+ from thread import allocate_lock
+except ImportError:
+ try:
+ from threading import Lock as allocate_lock
+ except ImportError:
+ from dummy_thread import allocate_lock
diff --git a/pyload/lib/jinja2/_stringdefs.py b/pyload/lib/jinja2/_stringdefs.py
new file mode 100644
index 000000000..da5830e9f
--- /dev/null
+++ b/pyload/lib/jinja2/_stringdefs.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2._stringdefs
+ ~~~~~~~~~~~~~~~~~~
+
+ Strings of all Unicode characters of a certain category.
+ Used for matching in Unicode-aware languages. Run to regenerate.
+
+ Inspired by chartypes_create.py from the MoinMoin project, original
+ implementation from Pygments.
+
+ :copyright: Copyright 2006-2009 by the Jinja team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+from jinja2._compat import unichr
+
+Cc = u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
+
+Cf = u'\xad\u0600\u0601\u0602\u0603\u06dd\u070f\u17b4\u17b5\u200b\u200c\u200d\u200e\u200f\u202a\u202b\u202c\u202d\u202e\u2060\u2061\u2062\u2063\u206a\u206b\u206c\u206d\u206e\u206f\ufeff\ufff9\ufffa\ufffb'
+
+Cn = u'\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u0370\u0371\u0372\u0373\u0376\u0377\u0378\u0379\u037b\u037c\u037d\u037f\u0380\u0381\u0382\u0383\u038b\u038d\u03a2\u03cf\u0487\u04cf\u04fa\u04fb\u04fc\u04fd\u04fe\u04ff\u0510\u0511\u0512\u0513\u0514\u0515\u0516\u0517\u0518\u0519\u051a\u051b\u051c\u051d\u051e\u051f\u0520\u0521\u0522\u0523\u0524\u0525\u0526\u0527\u0528\u0529\u052a\u052b\u052c\u052d\u052e\u052f\u0530\u0557\u0558\u0560\u0588\u058b\u058c\u058d\u058e\u058f\u0590\u05ba\u05c8\u05c9\u05ca\u05cb\u05cc\u05cd\u05ce\u05cf\u05eb\u05ec\u05ed\u05ee\u05ef\u05f5\u05f6\u05f7\u05f8\u05f9\u05fa\u05fb\u05fc\u05fd\u05fe\u05ff\u0604\u0605\u0606\u0607\u0608\u0609\u060a\u0616\u0617\u0618\u0619\u061a\u061c\u061d\u0620\u063b\u063c\u063d\u063e\u063f\u065f\u070e\u074b\u074c\u076e\u076f\u0770\u0771\u0772\u0773\u0774\u0775\u0776\u0777\u0778\u0779\u077a\u077b\u077c\u077d\u077e\u077f\u07b2\u07b3\u07b4\u07b5\u07b6\u07b7\u07b8\u07b9\u07ba\u07bb\u07bc\u07bd\u07be\u07bf\u07c0\u07c1\u07c2\u07c3\u07c4\u07c5\u07c6\u07c7\u07c8\u07c9\u07ca\u07cb\u07cc\u07cd\u07ce\u07cf\u07d0\u07d1\u07d2\u07d3\u07d4\u07d5\u07d6\u07d7\u07d8\u07d9\u07da\u07db\u07dc\u07dd\u07de\u07df\u07e0\u07e1\u07e2\u07e3\u07e4\u07e5\u07e6\u07e7\u07e8\u07e9\u07ea\u07eb\u07ec\u07ed\u07ee\u07ef\u07f0\u07f1\u07f2\u07f3\u07f4\u07f5\u07f6\u07f7\u07f8\u07f9\u07fa\u07fb\u07fc\u07fd\u07fe\u07ff\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807\u0808\u0809\u080a\u080b\u080c\u080d\u080e\u080f\u0810\u0811\u0812\u0813\u0814\u0815\u0816\u0817\u0818\u0819\u081a\u081b\u081c\u081d\u081e\u081f\u0820\u0821\u0822\u0823\u0824\u0825\u0826\u0827\u0828\u0829\u082a\u082b\u082c\u082d\u082e\u082f\u0830\u0831\u0832\u0833\u0834\u0835\u0836\u0837\u0838\u0839\u083a\u083b\u083c\u083d\u083e\u083f\u0840\u0841\u0842\u0843\u0844\u0845\u0846\u0847\u0848\u0849\u084a\u084b\u084c\u084d\u084e\u084f\u0850\u0851\u0852\u0853\u0854\u0855\u0856\u0857\u0858\u0859\u085a\u085b\u085c\u085d\u085e\u085f\u0860\u0861\u0862\u0863\u0864\u0865\u0866\u0867\u0868\u0869\u086a\u086b\u086c\u086d\u086e\u086f\u0870\u0871\u0872\u0873\u0874\u0875\u0876\u0877\u0878\u0879\u087a\u087b\u087c\u087d\u087e\u087f\u0880\u0881\u0882\u0883\u0884\u0885\u0886\u0887\u0888\u0889\u088a\u088b\u088c\u088d\u088e\u088f\u0890\u0891\u0892\u0893\u0894\u0895\u0896\u0897\u0898\u0899\u089a\u089b\u089c\u089d\u089e\u089f\u08a0\u08a1\u08a2\u08a3\u08a4\u08a5\u08a6\u08a7\u08a8\u08a9\u08aa\u08ab\u08ac\u08ad\u08ae\u08af\u08b0\u08b1\u08b2\u08b3\u08b4\u08b5\u08b6\u08b7\u08b8\u08b9\u08ba\u08bb\u08bc\u08bd\u08be\u08bf\u08c0\u08c1\u08c2\u08c3\u08c4\u08c5\u08c6\u08c7\u08c8\u08c9\u08ca\u08cb\u08cc\u08cd\u08ce\u08cf\u08d0\u08d1\u08d2\u08d3\u08d4\u08d5\u08d6\u08d7\u08d8\u08d9\u08da\u08db\u08dc\u08dd\u08de\u08df\u08e0\u08e1\u08e2\u08e3\u08e4\u08e5\u08e6\u08e7\u08e8\u08e9\u08ea\u08eb\u08ec\u08ed\u08ee\u08ef\u08f0\u08f1\u08f2\u08f3\u08f4\u08f5\u08f6\u08f7\u08f8\u08f9\u08fa\u08fb\u08fc\u08fd\u08fe\u08ff\u0900\u093a\u093b\u094e\u094f\u0955\u0956\u0957\u0971\u0972\u0973\u0974\u0975\u0976\u0977\u0978\u0979\u097a\u097b\u097c\u097e\u097f\u0980\u0984\u098d\u098e\u0991\u0992\u09a9\u09b1\u09b3\u09b4\u09b5\u09ba\u09bb\u09c5\u09c6\u09c9\u09ca\u09cf\u09d0\u09d1\u09d2\u09d3\u09d4\u09d5\u09d6\u09d8\u09d9\u09da\u09db\u09de\u09e4\u09e5\u09fb\u09fc\u09fd\u09fe\u09ff\u0a00\u0a04\u0a0b\u0a0c\u0a0d\u0a0e\u0a11\u0a12\u0a29\u0a31\u0a34\u0a37\u0a3a\u0a3b\u0a3d\u0a43\u0a44\u0a45\u0a46\u0a49\u0a4a\u0a4e\u0a4f\u0a50\u0a51\u0a52\u0a53\u0a54\u0a55\u0a56\u0a57\u0a58\u0a5d\u0a5f\u0a60\u0a61\u0a62\u0a63\u0a64\u0a65\u0a75\u0a76\u0a77\u0a78\u0a79\u0a7a\u0a7b\u0a7c\u0a7d\u0a7e\u0a7f\u0a80\u0a84\u0a8e\u0a92\u0aa9\u0ab1\u0ab4\u0aba\u0abb\u0ac6\u0aca\u0ace\u0acf\u0ad1\u0ad2\u0ad3\u0ad4\u0ad5\u0ad6\u0ad7\u0ad8\u0ad9\u0ada\u0adb\u0adc\u0add\u0ade\u0adf\u0ae4\u0ae5\u0af0\u0af2\u0af3\u0af4\u0af5\u0af6\u0af7\u0af8\u0af9\u0afa\u0afb\u0afc\u0afd\u0afe\u0aff\u0b00\u0b04\u0b0d\u0b0e\u0b11\u0b12\u0b29\u0b31\u0b34\u0b3a\u0b3b\u0b44\u0b45\u0b46\u0b49\u0b4a\u0b4e\u0b4f\u0b50\u0b51\u0b52\u0b53\u0b54\u0b55\u0b58\u0b59\u0b5a\u0b5b\u0b5e\u0b62\u0b63\u0b64\u0b65\u0b72\u0b73\u0b74\u0b75\u0b76\u0b77\u0b78\u0b79\u0b7a\u0b7b\u0b7c\u0b7d\u0b7e\u0b7f\u0b80\u0b81\u0b84\u0b8b\u0b8c\u0b8d\u0b91\u0b96\u0b97\u0b98\u0b9b\u0b9d\u0ba0\u0ba1\u0ba2\u0ba5\u0ba6\u0ba7\u0bab\u0bac\u0bad\u0bba\u0bbb\u0bbc\u0bbd\u0bc3\u0bc4\u0bc5\u0bc9\u0bce\u0bcf\u0bd0\u0bd1\u0bd2\u0bd3\u0bd4\u0bd5\u0bd6\u0bd8\u0bd9\u0bda\u0bdb\u0bdc\u0bdd\u0bde\u0bdf\u0be0\u0be1\u0be2\u0be3\u0be4\u0be5\u0bfb\u0bfc\u0bfd\u0bfe\u0bff\u0c00\u0c04\u0c0d\u0c11\u0c29\u0c34\u0c3a\u0c3b\u0c3c\u0c3d\u0c45\u0c49\u0c4e\u0c4f\u0c50\u0c51\u0c52\u0c53\u0c54\u0c57\u0c58\u0c59\u0c5a\u0c5b\u0c5c\u0c5d\u0c5e\u0c5f\u0c62\u0c63\u0c64\u0c65\u0c70\u0c71\u0c72\u0c73\u0c74\u0c75\u0c76\u0c77\u0c78\u0c79\u0c7a\u0c7b\u0c7c\u0c7d\u0c7e\u0c7f\u0c80\u0c81\u0c84\u0c8d\u0c91\u0ca9\u0cb4\u0cba\u0cbb\u0cc5\u0cc9\u0cce\u0ccf\u0cd0\u0cd1\u0cd2\u0cd3\u0cd4\u0cd7\u0cd8\u0cd9\u0cda\u0cdb\u0cdc\u0cdd\u0cdf\u0ce2\u0ce3\u0ce4\u0ce5\u0cf0\u0cf1\u0cf2\u0cf3\u0cf4\u0cf5\u0cf6\u0cf7\u0cf8\u0cf9\u0cfa\u0cfb\u0cfc\u0cfd\u0cfe\u0cff\u0d00\u0d01\u0d04\u0d0d\u0d11\u0d29\u0d3a\u0d3b\u0d3c\u0d3d\u0d44\u0d45\u0d49\u0d4e\u0d4f\u0d50\u0d51\u0d52\u0d53\u0d54\u0d55\u0d56\u0d58\u0d59\u0d5a\u0d5b\u0d5c\u0d5d\u0d5e\u0d5f\u0d62\u0d63\u0d64\u0d65\u0d70\u0d71\u0d72\u0d73\u0d74\u0d75\u0d76\u0d77\u0d78\u0d79\u0d7a\u0d7b\u0d7c\u0d7d\u0d7e\u0d7f\u0d80\u0d81\u0d84\u0d97\u0d98\u0d99\u0db2\u0dbc\u0dbe\u0dbf\u0dc7\u0dc8\u0dc9\u0dcb\u0dcc\u0dcd\u0dce\u0dd5\u0dd7\u0de0\u0de1\u0de2\u0de3\u0de4\u0de5\u0de6\u0de7\u0de8\u0de9\u0dea\u0deb\u0dec\u0ded\u0dee\u0def\u0df0\u0df1\u0df5\u0df6\u0df7\u0df8\u0df9\u0dfa\u0dfb\u0dfc\u0dfd\u0dfe\u0dff\u0e00\u0e3b\u0e3c\u0e3d\u0e3e\u0e5c\u0e5d\u0e5e\u0e5f\u0e60\u0e61\u0e62\u0e63\u0e64\u0e65\u0e66\u0e67\u0e68\u0e69\u0e6a\u0e6b\u0e6c\u0e6d\u0e6e\u0e6f\u0e70\u0e71\u0e72\u0e73\u0e74\u0e75\u0e76\u0e77\u0e78\u0e79\u0e7a\u0e7b\u0e7c\u0e7d\u0e7e\u0e7f\u0e80\u0e83\u0e85\u0e86\u0e89\u0e8b\u0e8c\u0e8e\u0e8f\u0e90\u0e91\u0e92\u0e93\u0e98\u0ea0\u0ea4\u0ea6\u0ea8\u0ea9\u0eac\u0eba\u0ebe\u0ebf\u0ec5\u0ec7\u0ece\u0ecf\u0eda\u0edb\u0ede\u0edf\u0ee0\u0ee1\u0ee2\u0ee3\u0ee4\u0ee5\u0ee6\u0ee7\u0ee8\u0ee9\u0eea\u0eeb\u0eec\u0eed\u0eee\u0eef\u0ef0\u0ef1\u0ef2\u0ef3\u0ef4\u0ef5\u0ef6\u0ef7\u0ef8\u0ef9\u0efa\u0efb\u0efc\u0efd\u0efe\u0eff\u0f48\u0f6b\u0f6c\u0f6d\u0f6e\u0f6f\u0f70\u0f8c\u0f8d\u0f8e\u0f8f\u0f98\u0fbd\u0fcd\u0fce\u0fd2\u0fd3\u0fd4\u0fd5\u0fd6\u0fd7\u0fd8\u0fd9\u0fda\u0fdb\u0fdc\u0fdd\u0fde\u0fdf\u0fe0\u0fe1\u0fe2\u0fe3\u0fe4\u0fe5\u0fe6\u0fe7\u0fe8\u0fe9\u0fea\u0feb\u0fec\u0fed\u0fee\u0fef\u0ff0\u0ff1\u0ff2\u0ff3\u0ff4\u0ff5\u0ff6\u0ff7\u0ff8\u0ff9\u0ffa\u0ffb\u0ffc\u0ffd\u0ffe\u0fff\u1022\u1028\u102b\u1033\u1034\u1035\u103a\u103b\u103c\u103d\u103e\u103f\u105a\u105b\u105c\u105d\u105e\u105f\u1060\u1061\u1062\u1063\u1064\u1065\u1066\u1067\u1068\u1069\u106a\u106b\u106c\u106d\u106e\u106f\u1070\u1071\u1072\u1073\u1074\u1075\u1076\u1077\u1078\u1079\u107a\u107b\u107c\u107d\u107e\u107f\u1080\u1081\u1082\u1083\u1084\u1085\u1086\u1087\u1088\u1089\u108a\u108b\u108c\u108d\u108e\u108f\u1090\u1091\u1092\u1093\u1094\u1095\u1096\u1097\u1098\u1099\u109a\u109b\u109c\u109d\u109e\u109f\u10c6\u10c7\u10c8\u10c9\u10ca\u10cb\u10cc\u10cd\u10ce\u10cf\u10fd\u10fe\u10ff\u115a\u115b\u115c\u115d\u115e\u11a3\u11a4\u11a5\u11a6\u11a7\u11fa\u11fb\u11fc\u11fd\u11fe\u11ff\u1249\u124e\u124f\u1257\u1259\u125e\u125f\u1289\u128e\u128f\u12b1\u12b6\u12b7\u12bf\u12c1\u12c6\u12c7\u12d7\u1311\u1316\u1317\u135b\u135c\u135d\u135e\u137d\u137e\u137f\u139a\u139b\u139c\u139d\u139e\u139f\u13f5\u13f6\u13f7\u13f8\u13f9\u13fa\u13fb\u13fc\u13fd\u13fe\u13ff\u1400\u1677\u1678\u1679\u167a\u167b\u167c\u167d\u167e\u167f\u169d\u169e\u169f\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u16f9\u16fa\u16fb\u16fc\u16fd\u16fe\u16ff\u170d\u1715\u1716\u1717\u1718\u1719\u171a\u171b\u171c\u171d\u171e\u171f\u1737\u1738\u1739\u173a\u173b\u173c\u173d\u173e\u173f\u1754\u1755\u1756\u1757\u1758\u1759\u175a\u175b\u175c\u175d\u175e\u175f\u176d\u1771\u1774\u1775\u1776\u1777\u1778\u1779\u177a\u177b\u177c\u177d\u177e\u177f\u17de\u17df\u17ea\u17eb\u17ec\u17ed\u17ee\u17ef\u17fa\u17fb\u17fc\u17fd\u17fe\u17ff\u180f\u181a\u181b\u181c\u181d\u181e\u181f\u1878\u1879\u187a\u187b\u187c\u187d\u187e\u187f\u18aa\u18ab\u18ac\u18ad\u18ae\u18af\u18b0\u18b1\u18b2\u18b3\u18b4\u18b5\u18b6\u18b7\u18b8\u18b9\u18ba\u18bb\u18bc\u18bd\u18be\u18bf\u18c0\u18c1\u18c2\u18c3\u18c4\u18c5\u18c6\u18c7\u18c8\u18c9\u18ca\u18cb\u18cc\u18cd\u18ce\u18cf\u18d0\u18d1\u18d2\u18d3\u18d4\u18d5\u18d6\u18d7\u18d8\u18d9\u18da\u18db\u18dc\u18dd\u18de\u18df\u18e0\u18e1\u18e2\u18e3\u18e4\u18e5\u18e6\u18e7\u18e8\u18e9\u18ea\u18eb\u18ec\u18ed\u18ee\u18ef\u18f0\u18f1\u18f2\u18f3\u18f4\u18f5\u18f6\u18f7\u18f8\u18f9\u18fa\u18fb\u18fc\u18fd\u18fe\u18ff\u191d\u191e\u191f\u192c\u192d\u192e\u192f\u193c\u193d\u193e\u193f\u1941\u1942\u1943\u196e\u196f\u1975\u1976\u1977\u1978\u1979\u197a\u197b\u197c\u197d\u197e\u197f\u19aa\u19ab\u19ac\u19ad\u19ae\u19af\u19ca\u19cb\u19cc\u19cd\u19ce\u19cf\u19da\u19db\u19dc\u19dd\u1a1c\u1a1d\u1a20\u1a21\u1a22\u1a23\u1a24\u1a25\u1a26\u1a27\u1a28\u1a29\u1a2a\u1a2b\u1a2c\u1a2d\u1a2e\u1a2f\u1a30\u1a31\u1a32\u1a33\u1a34\u1a35\u1a36\u1a37\u1a38\u1a39\u1a3a\u1a3b\u1a3c\u1a3d\u1a3e\u1a3f\u1a40\u1a41\u1a42\u1a43\u1a44\u1a45\u1a46\u1a47\u1a48\u1a49\u1a4a\u1a4b\u1a4c\u1a4d\u1a4e\u1a4f\u1a50\u1a51\u1a52\u1a53\u1a54\u1a55\u1a56\u1a57\u1a58\u1a59\u1a5a\u1a5b\u1a5c\u1a5d\u1a5e\u1a5f\u1a60\u1a61\u1a62\u1a63\u1a64\u1a65\u1a66\u1a67\u1a68\u1a69\u1a6a\u1a6b\u1a6c\u1a6d\u1a6e\u1a6f\u1a70\u1a71\u1a72\u1a73\u1a74\u1a75\u1a76\u1a77\u1a78\u1a79\u1a7a\u1a7b\u1a7c\u1a7d\u1a7e\u1a7f\u1a80\u1a81\u1a82\u1a83\u1a84\u1a85\u1a86\u1a87\u1a88\u1a89\u1a8a\u1a8b\u1a8c\u1a8d\u1a8e\u1a8f\u1a90\u1a91\u1a92\u1a93\u1a94\u1a95\u1a96\u1a97\u1a98\u1a99\u1a9a\u1a9b\u1a9c\u1a9d\u1a9e\u1a9f\u1aa0\u1aa1\u1aa2\u1aa3\u1aa4\u1aa5\u1aa6\u1aa7\u1aa8\u1aa9\u1aaa\u1aab\u1aac\u1aad\u1aae\u1aaf\u1ab0\u1ab1\u1ab2\u1ab3\u1ab4\u1ab5\u1ab6\u1ab7\u1ab8\u1ab9\u1aba\u1abb\u1abc\u1abd\u1abe\u1abf\u1ac0\u1ac1\u1ac2\u1ac3\u1ac4\u1ac5\u1ac6\u1ac7\u1ac8\u1ac9\u1aca\u1acb\u1acc\u1acd\u1ace\u1acf\u1ad0\u1ad1\u1ad2\u1ad3\u1ad4\u1ad5\u1ad6\u1ad7\u1ad8\u1ad9\u1ada\u1adb\u1adc\u1add\u1ade\u1adf\u1ae0\u1ae1\u1ae2\u1ae3\u1ae4\u1ae5\u1ae6\u1ae7\u1ae8\u1ae9\u1aea\u1aeb\u1aec\u1aed\u1aee\u1aef\u1af0\u1af1\u1af2\u1af3\u1af4\u1af5\u1af6\u1af7\u1af8\u1af9\u1afa\u1afb\u1afc\u1afd\u1afe\u1aff\u1b00\u1b01\u1b02\u1b03\u1b04\u1b05\u1b06\u1b07\u1b08\u1b09\u1b0a\u1b0b\u1b0c\u1b0d\u1b0e\u1b0f\u1b10\u1b11\u1b12\u1b13\u1b14\u1b15\u1b16\u1b17\u1b18\u1b19\u1b1a\u1b1b\u1b1c\u1b1d\u1b1e\u1b1f\u1b20\u1b21\u1b22\u1b23\u1b24\u1b25\u1b26\u1b27\u1b28\u1b29\u1b2a\u1b2b\u1b2c\u1b2d\u1b2e\u1b2f\u1b30\u1b31\u1b32\u1b33\u1b34\u1b35\u1b36\u1b37\u1b38\u1b39\u1b3a\u1b3b\u1b3c\u1b3d\u1b3e\u1b3f\u1b40\u1b41\u1b42\u1b43\u1b44\u1b45\u1b46\u1b47\u1b48\u1b49\u1b4a\u1b4b\u1b4c\u1b4d\u1b4e\u1b4f\u1b50\u1b51\u1b52\u1b53\u1b54\u1b55\u1b56\u1b57\u1b58\u1b59\u1b5a\u1b5b\u1b5c\u1b5d\u1b5e\u1b5f\u1b60\u1b61\u1b62\u1b63\u1b64\u1b65\u1b66\u1b67\u1b68\u1b69\u1b6a\u1b6b\u1b6c\u1b6d\u1b6e\u1b6f\u1b70\u1b71\u1b72\u1b73\u1b74\u1b75\u1b76\u1b77\u1b78\u1b79\u1b7a\u1b7b\u1b7c\u1b7d\u1b7e\u1b7f\u1b80\u1b81\u1b82\u1b83\u1b84\u1b85\u1b86\u1b87\u1b88\u1b89\u1b8a\u1b8b\u1b8c\u1b8d\u1b8e\u1b8f\u1b90\u1b91\u1b92\u1b93\u1b94\u1b95\u1b96\u1b97\u1b98\u1b99\u1b9a\u1b9b\u1b9c\u1b9d\u1b9e\u1b9f\u1ba0\u1ba1\u1ba2\u1ba3\u1ba4\u1ba5\u1ba6\u1ba7\u1ba8\u1ba9\u1baa\u1bab\u1bac\u1bad\u1bae\u1baf\u1bb0\u1bb1\u1bb2\u1bb3\u1bb4\u1bb5\u1bb6\u1bb7\u1bb8\u1bb9\u1bba\u1bbb\u1bbc\u1bbd\u1bbe\u1bbf\u1bc0\u1bc1\u1bc2\u1bc3\u1bc4\u1bc5\u1bc6\u1bc7\u1bc8\u1bc9\u1bca\u1bcb\u1bcc\u1bcd\u1bce\u1bcf\u1bd0\u1bd1\u1bd2\u1bd3\u1bd4\u1bd5\u1bd6\u1bd7\u1bd8\u1bd9\u1bda\u1bdb\u1bdc\u1bdd\u1bde\u1bdf\u1be0\u1be1\u1be2\u1be3\u1be4\u1be5\u1be6\u1be7\u1be8\u1be9\u1bea\u1beb\u1bec\u1bed\u1bee\u1bef\u1bf0\u1bf1\u1bf2\u1bf3\u1bf4\u1bf5\u1bf6\u1bf7\u1bf8\u1bf9\u1bfa\u1bfb\u1bfc\u1bfd\u1bfe\u1bff\u1c00\u1c01\u1c02\u1c03\u1c04\u1c05\u1c06\u1c07\u1c08\u1c09\u1c0a\u1c0b\u1c0c\u1c0d\u1c0e\u1c0f\u1c10\u1c11\u1c12\u1c13\u1c14\u1c15\u1c16\u1c17\u1c18\u1c19\u1c1a\u1c1b\u1c1c\u1c1d\u1c1e\u1c1f\u1c20\u1c21\u1c22\u1c23\u1c24\u1c25\u1c26\u1c27\u1c28\u1c29\u1c2a\u1c2b\u1c2c\u1c2d\u1c2e\u1c2f\u1c30\u1c31\u1c32\u1c33\u1c34\u1c35\u1c36\u1c37\u1c38\u1c39\u1c3a\u1c3b\u1c3c\u1c3d\u1c3e\u1c3f\u1c40\u1c41\u1c42\u1c43\u1c44\u1c45\u1c46\u1c47\u1c48\u1c49\u1c4a\u1c4b\u1c4c\u1c4d\u1c4e\u1c4f\u1c50\u1c51\u1c52\u1c53\u1c54\u1c55\u1c56\u1c57\u1c58\u1c59\u1c5a\u1c5b\u1c5c\u1c5d\u1c5e\u1c5f\u1c60\u1c61\u1c62\u1c63\u1c64\u1c65\u1c66\u1c67\u1c68\u1c69\u1c6a\u1c6b\u1c6c\u1c6d\u1c6e\u1c6f\u1c70\u1c71\u1c72\u1c73\u1c74\u1c75\u1c76\u1c77\u1c78\u1c79\u1c7a\u1c7b\u1c7c\u1c7d\u1c7e\u1c7f\u1c80\u1c81\u1c82\u1c83\u1c84\u1c85\u1c86\u1c87\u1c88\u1c89\u1c8a\u1c8b\u1c8c\u1c8d\u1c8e\u1c8f\u1c90\u1c91\u1c92\u1c93\u1c94\u1c95\u1c96\u1c97\u1c98\u1c99\u1c9a\u1c9b\u1c9c\u1c9d\u1c9e\u1c9f\u1ca0\u1ca1\u1ca2\u1ca3\u1ca4\u1ca5\u1ca6\u1ca7\u1ca8\u1ca9\u1caa\u1cab\u1cac\u1cad\u1cae\u1caf\u1cb0\u1cb1\u1cb2\u1cb3\u1cb4\u1cb5\u1cb6\u1cb7\u1cb8\u1cb9\u1cba\u1cbb\u1cbc\u1cbd\u1cbe\u1cbf\u1cc0\u1cc1\u1cc2\u1cc3\u1cc4\u1cc5\u1cc6\u1cc7\u1cc8\u1cc9\u1cca\u1ccb\u1ccc\u1ccd\u1cce\u1ccf\u1cd0\u1cd1\u1cd2\u1cd3\u1cd4\u1cd5\u1cd6\u1cd7\u1cd8\u1cd9\u1cda\u1cdb\u1cdc\u1cdd\u1cde\u1cdf\u1ce0\u1ce1\u1ce2\u1ce3\u1ce4\u1ce5\u1ce6\u1ce7\u1ce8\u1ce9\u1cea\u1ceb\u1cec\u1ced\u1cee\u1cef\u1cf0\u1cf1\u1cf2\u1cf3\u1cf4\u1cf5\u1cf6\u1cf7\u1cf8\u1cf9\u1cfa\u1cfb\u1cfc\u1cfd\u1cfe\u1cff\u1dc4\u1dc5\u1dc6\u1dc7\u1dc8\u1dc9\u1dca\u1dcb\u1dcc\u1dcd\u1dce\u1dcf\u1dd0\u1dd1\u1dd2\u1dd3\u1dd4\u1dd5\u1dd6\u1dd7\u1dd8\u1dd9\u1dda\u1ddb\u1ddc\u1ddd\u1dde\u1ddf\u1de0\u1de1\u1de2\u1de3\u1de4\u1de5\u1de6\u1de7\u1de8\u1de9\u1dea\u1deb\u1dec\u1ded\u1dee\u1def\u1df0\u1df1\u1df2\u1df3\u1df4\u1df5\u1df6\u1df7\u1df8\u1df9\u1dfa\u1dfb\u1dfc\u1dfd\u1dfe\u1dff\u1e9c\u1e9d\u1e9e\u1e9f\u1efa\u1efb\u1efc\u1efd\u1efe\u1eff\u1f16\u1f17\u1f1e\u1f1f\u1f46\u1f47\u1f4e\u1f4f\u1f58\u1f5a\u1f5c\u1f5e\u1f7e\u1f7f\u1fb5\u1fc5\u1fd4\u1fd5\u1fdc\u1ff0\u1ff1\u1ff5\u1fff\u2064\u2065\u2066\u2067\u2068\u2069\u2072\u2073\u208f\u2095\u2096\u2097\u2098\u2099\u209a\u209b\u209c\u209d\u209e\u209f\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb\u20bc\u20bd\u20be\u20bf\u20c0\u20c1\u20c2\u20c3\u20c4\u20c5\u20c6\u20c7\u20c8\u20c9\u20ca\u20cb\u20cc\u20cd\u20ce\u20cf\u20ec\u20ed\u20ee\u20ef\u20f0\u20f1\u20f2\u20f3\u20f4\u20f5\u20f6\u20f7\u20f8\u20f9\u20fa\u20fb\u20fc\u20fd\u20fe\u20ff\u214d\u214e\u214f\u2150\u2151\u2152\u2184\u2185\u2186\u2187\u2188\u2189\u218a\u218b\u218c\u218d\u218e\u218f\u23dc\u23dd\u23de\u23df\u23e0\u23e1\u23e2\u23e3\u23e4\u23e5\u23e6\u23e7\u23e8\u23e9\u23ea\u23eb\u23ec\u23ed\u23ee\u23ef\u23f0\u23f1\u23f2\u23f3\u23f4\u23f5\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u23fe\u23ff\u2427\u2428\u2429\u242a\u242b\u242c\u242d\u242e\u242f\u2430\u2431\u2432\u2433\u2434\u2435\u2436\u2437\u2438\u2439\u243a\u243b\u243c\u243d\u243e\u243f\u244b\u244c\u244d\u244e\u244f\u2450\u2451\u2452\u2453\u2454\u2455\u2456\u2457\u2458\u2459\u245a\u245b\u245c\u245d\u245e\u245f\u269d\u269e\u269f\u26b2\u26b3\u26b4\u26b5\u26b6\u26b7\u26b8\u26b9\u26ba\u26bb\u26bc\u26bd\u26be\u26bf\u26c0\u26c1\u26c2\u26c3\u26c4\u26c5\u26c6\u26c7\u26c8\u26c9\u26ca\u26cb\u26cc\u26cd\u26ce\u26cf\u26d0\u26d1\u26d2\u26d3\u26d4\u26d5\u26d6\u26d7\u26d8\u26d9\u26da\u26db\u26dc\u26dd\u26de\u26df\u26e0\u26e1\u26e2\u26e3\u26e4\u26e5\u26e6\u26e7\u26e8\u26e9\u26ea\u26eb\u26ec\u26ed\u26ee\u26ef\u26f0\u26f1\u26f2\u26f3\u26f4\u26f5\u26f6\u26f7\u26f8\u26f9\u26fa\u26fb\u26fc\u26fd\u26fe\u26ff\u2700\u2705\u270a\u270b\u2728\u274c\u274e\u2753\u2754\u2755\u2757\u275f\u2760\u2795\u2796\u2797\u27b0\u27bf\u27c7\u27c8\u27c9\u27ca\u27cb\u27cc\u27cd\u27ce\u27cf\u27ec\u27ed\u27ee\u27ef\u2b14\u2b15\u2b16\u2b17\u2b18\u2b19\u2b1a\u2b1b\u2b1c\u2b1d\u2b1e\u2b1f\u2b20\u2b21\u2b22\u2b23\u2b24\u2b25\u2b26\u2b27\u2b28\u2b29\u2b2a\u2b2b\u2b2c\u2b2d\u2b2e\u2b2f\u2b30\u2b31\u2b32\u2b33\u2b34\u2b35\u2b36\u2b37\u2b38\u2b39\u2b3a\u2b3b\u2b3c\u2b3d\u2b3e\u2b3f\u2b40\u2b41\u2b42\u2b43\u2b44\u2b45\u2b46\u2b47\u2b48\u2b49\u2b4a\u2b4b\u2b4c\u2b4d\u2b4e\u2b4f\u2b50\u2b51\u2b52\u2b53\u2b54\u2b55\u2b56\u2b57\u2b58\u2b59\u2b5a\u2b5b\u2b5c\u2b5d\u2b5e\u2b5f\u2b60\u2b61\u2b62\u2b63\u2b64\u2b65\u2b66\u2b67\u2b68\u2b69\u2b6a\u2b6b\u2b6c\u2b6d\u2b6e\u2b6f\u2b70\u2b71\u2b72\u2b73\u2b74\u2b75\u2b76\u2b77\u2b78\u2b79\u2b7a\u2b7b\u2b7c\u2b7d\u2b7e\u2b7f\u2b80\u2b81\u2b82\u2b83\u2b84\u2b85\u2b86\u2b87\u2b88\u2b89\u2b8a\u2b8b\u2b8c\u2b8d\u2b8e\u2b8f\u2b90\u2b91\u2b92\u2b93\u2b94\u2b95\u2b96\u2b97\u2b98\u2b99\u2b9a\u2b9b\u2b9c\u2b9d\u2b9e\u2b9f\u2ba0\u2ba1\u2ba2\u2ba3\u2ba4\u2ba5\u2ba6\u2ba7\u2ba8\u2ba9\u2baa\u2bab\u2bac\u2bad\u2bae\u2baf\u2bb0\u2bb1\u2bb2\u2bb3\u2bb4\u2bb5\u2bb6\u2bb7\u2bb8\u2bb9\u2bba\u2bbb\u2bbc\u2bbd\u2bbe\u2bbf\u2bc0\u2bc1\u2bc2\u2bc3\u2bc4\u2bc5\u2bc6\u2bc7\u2bc8\u2bc9\u2bca\u2bcb\u2bcc\u2bcd\u2bce\u2bcf\u2bd0\u2bd1\u2bd2\u2bd3\u2bd4\u2bd5\u2bd6\u2bd7\u2bd8\u2bd9\u2bda\u2bdb\u2bdc\u2bdd\u2bde\u2bdf\u2be0\u2be1\u2be2\u2be3\u2be4\u2be5\u2be6\u2be7\u2be8\u2be9\u2bea\u2beb\u2bec\u2bed\u2bee\u2bef\u2bf0\u2bf1\u2bf2\u2bf3\u2bf4\u2bf5\u2bf6\u2bf7\u2bf8\u2bf9\u2bfa\u2bfb\u2bfc\u2bfd\u2bfe\u2bff\u2c2f\u2c5f\u2c60\u2c61\u2c62\u2c63\u2c64\u2c65\u2c66\u2c67\u2c68\u2c69\u2c6a\u2c6b\u2c6c\u2c6d\u2c6e\u2c6f\u2c70\u2c71\u2c72\u2c73\u2c74\u2c75\u2c76\u2c77\u2c78\u2c79\u2c7a\u2c7b\u2c7c\u2c7d\u2c7e\u2c7f\u2ceb\u2cec\u2ced\u2cee\u2cef\u2cf0\u2cf1\u2cf2\u2cf3\u2cf4\u2cf5\u2cf6\u2cf7\u2cf8\u2d26\u2d27\u2d28\u2d29\u2d2a\u2d2b\u2d2c\u2d2d\u2d2e\u2d2f\u2d66\u2d67\u2d68\u2d69\u2d6a\u2d6b\u2d6c\u2d6d\u2d6e\u2d70\u2d71\u2d72\u2d73\u2d74\u2d75\u2d76\u2d77\u2d78\u2d79\u2d7a\u2d7b\u2d7c\u2d7d\u2d7e\u2d7f\u2d97\u2d98\u2d99\u2d9a\u2d9b\u2d9c\u2d9d\u2d9e\u2d9f\u2da7\u2daf\u2db7\u2dbf\u2dc7\u2dcf\u2dd7\u2ddf\u2de0\u2de1\u2de2\u2de3\u2de4\u2de5\u2de6\u2de7\u2de8\u2de9\u2dea\u2deb\u2dec\u2ded\u2dee\u2def\u2df0\u2df1\u2df2\u2df3\u2df4\u2df5\u2df6\u2df7\u2df8\u2df9\u2dfa\u2dfb\u2dfc\u2dfd\u2dfe\u2dff\u2e18\u2e19\u2e1a\u2e1b\u2e1e\u2e1f\u2e20\u2e21\u2e22\u2e23\u2e24\u2e25\u2e26\u2e27\u2e28\u2e29\u2e2a\u2e2b\u2e2c\u2e2d\u2e2e\u2e2f\u2e30\u2e31\u2e32\u2e33\u2e34\u2e35\u2e36\u2e37\u2e38\u2e39\u2e3a\u2e3b\u2e3c\u2e3d\u2e3e\u2e3f\u2e40\u2e41\u2e42\u2e43\u2e44\u2e45\u2e46\u2e47\u2e48\u2e49\u2e4a\u2e4b\u2e4c\u2e4d\u2e4e\u2e4f\u2e50\u2e51\u2e52\u2e53\u2e54\u2e55\u2e56\u2e57\u2e58\u2e59\u2e5a\u2e5b\u2e5c\u2e5d\u2e5e\u2e5f\u2e60\u2e61\u2e62\u2e63\u2e64\u2e65\u2e66\u2e67\u2e68\u2e69\u2e6a\u2e6b\u2e6c\u2e6d\u2e6e\u2e6f\u2e70\u2e71\u2e72\u2e73\u2e74\u2e75\u2e76\u2e77\u2e78\u2e79\u2e7a\u2e7b\u2e7c\u2e7d\u2e7e\u2e7f\u2e9a\u2ef4\u2ef5\u2ef6\u2ef7\u2ef8\u2ef9\u2efa\u2efb\u2efc\u2efd\u2efe\u2eff\u2fd6\u2fd7\u2fd8\u2fd9\u2fda\u2fdb\u2fdc\u2fdd\u2fde\u2fdf\u2fe0\u2fe1\u2fe2\u2fe3\u2fe4\u2fe5\u2fe6\u2fe7\u2fe8\u2fe9\u2fea\u2feb\u2fec\u2fed\u2fee\u2fef\u2ffc\u2ffd\u2ffe\u2fff\u3040\u3097\u3098\u3100\u3101\u3102\u3103\u3104\u312d\u312e\u312f\u3130\u318f\u31b8\u31b9\u31ba\u31bb\u31bc\u31bd\u31be\u31bf\u31d0\u31d1\u31d2\u31d3\u31d4\u31d5\u31d6\u31d7\u31d8\u31d9\u31da\u31db\u31dc\u31dd\u31de\u31df\u31e0\u31e1\u31e2\u31e3\u31e4\u31e5\u31e6\u31e7\u31e8\u31e9\u31ea\u31eb\u31ec\u31ed\u31ee\u31ef\u321f\u3244\u3245\u3246\u3247\u3248\u3249\u324a\u324b\u324c\u324d\u324e\u324f\u32ff\u4db6\u4db7\u4db8\u4db9\u4dba\u4dbb\u4dbc\u4dbd\u4dbe\u4dbf\u9fbc\u9fbd\u9fbe\u9fbf\u9fc0\u9fc1\u9fc2\u9fc3\u9fc4\u9fc5\u9fc6\u9fc7\u9fc8\u9fc9\u9fca\u9fcb\u9fcc\u9fcd\u9fce\u9fcf\u9fd0\u9fd1\u9fd2\u9fd3\u9fd4\u9fd5\u9fd6\u9fd7\u9fd8\u9fd9\u9fda\u9fdb\u9fdc\u9fdd\u9fde\u9fdf\u9fe0\u9fe1\u9fe2\u9fe3\u9fe4\u9fe5\u9fe6\u9fe7\u9fe8\u9fe9\u9fea\u9feb\u9fec\u9fed\u9fee\u9fef\u9ff0\u9ff1\u9ff2\u9ff3\u9ff4\u9ff5\u9ff6\u9ff7\u9ff8\u9ff9\u9ffa\u9ffb\u9ffc\u9ffd\u9ffe\u9fff\ua48d\ua48e\ua48f\ua4c7\ua4c8\ua4c9\ua4ca\ua4cb\ua4cc\ua4cd\ua4ce\ua4cf\ua4d0\ua4d1\ua4d2\ua4d3\ua4d4\ua4d5\ua4d6\ua4d7\ua4d8\ua4d9\ua4da\ua4db\ua4dc\ua4dd\ua4de\ua4df\ua4e0\ua4e1\ua4e2\ua4e3\ua4e4\ua4e5\ua4e6\ua4e7\ua4e8\ua4e9\ua4ea\ua4eb\ua4ec\ua4ed\ua4ee\ua4ef\ua4f0\ua4f1\ua4f2\ua4f3\ua4f4\ua4f5\ua4f6\ua4f7\ua4f8\ua4f9\ua4fa\ua4fb\ua4fc\ua4fd\ua4fe\ua4ff\ua500\ua501\ua502\ua503\ua504\ua505\ua506\ua507\ua508\ua509\ua50a\ua50b\ua50c\ua50d\ua50e\ua50f\ua510\ua511\ua512\ua513\ua514\ua515\ua516\ua517\ua518\ua519\ua51a\ua51b\ua51c\ua51d\ua51e\ua51f\ua520\ua521\ua522\ua523\ua524\ua525\ua526\ua527\ua528\ua529\ua52a\ua52b\ua52c\ua52d\ua52e\ua52f\ua530\ua531\ua532\ua533\ua534\ua535\ua536\ua537\ua538\ua539\ua53a\ua53b\ua53c\ua53d\ua53e\ua53f\ua540\ua541\ua542\ua543\ua544\ua545\ua546\ua547\ua548\ua549\ua54a\ua54b\ua54c\ua54d\ua54e\ua54f\ua550\ua551\ua552\ua553\ua554\ua555\ua556\ua557\ua558\ua559\ua55a\ua55b\ua55c\ua55d\ua55e\ua55f\ua560\ua561\ua562\ua563\ua564\ua565\ua566\ua567\ua568\ua569\ua56a\ua56b\ua56c\ua56d\ua56e\ua56f\ua570\ua571\ua572\ua573\ua574\ua575\ua576\ua577\ua578\ua579\ua57a\ua57b\ua57c\ua57d\ua57e\ua57f\ua580\ua581\ua582\ua583\ua584\ua585\ua586\ua587\ua588\ua589\ua58a\ua58b\ua58c\ua58d\ua58e\ua58f\ua590\ua591\ua592\ua593\ua594\ua595\ua596\ua597\ua598\ua599\ua59a\ua59b\ua59c\ua59d\ua59e\ua59f\ua5a0\ua5a1\ua5a2\ua5a3\ua5a4\ua5a5\ua5a6\ua5a7\ua5a8\ua5a9\ua5aa\ua5ab\ua5ac\ua5ad\ua5ae\ua5af\ua5b0\ua5b1\ua5b2\ua5b3\ua5b4\ua5b5\ua5b6\ua5b7\ua5b8\ua5b9\ua5ba\ua5bb\ua5bc\ua5bd\ua5be\ua5bf\ua5c0\ua5c1\ua5c2\ua5c3\ua5c4\ua5c5\ua5c6\ua5c7\ua5c8\ua5c9\ua5ca\ua5cb\ua5cc\ua5cd\ua5ce\ua5cf\ua5d0\ua5d1\ua5d2\ua5d3\ua5d4\ua5d5\ua5d6\ua5d7\ua5d8\ua5d9\ua5da\ua5db\ua5dc\ua5dd\ua5de\ua5df\ua5e0\ua5e1\ua5e2\ua5e3\ua5e4\ua5e5\ua5e6\ua5e7\ua5e8\ua5e9\ua5ea\ua5eb\ua5ec\ua5ed\ua5ee\ua5ef\ua5f0\ua5f1\ua5f2\ua5f3\ua5f4\ua5f5\ua5f6\ua5f7\ua5f8\ua5f9\ua5fa\ua5fb\ua5fc\ua5fd\ua5fe\ua5ff\ua600\ua601\ua602\ua603\ua604\ua605\ua606\ua607\ua608\ua609\ua60a\ua60b\ua60c\ua60d\ua60e\ua60f\ua610\ua611\ua612\ua613\ua614\ua615\ua616\ua617\ua618\ua619\ua61a\ua61b\ua61c\ua61d\ua61e\ua61f\ua620\ua621\ua622\ua623\ua624\ua625\ua626\ua627\ua628\ua629\ua62a\ua62b\ua62c\ua62d\ua62e\ua62f\ua630\ua631\ua632\ua633\ua634\ua635\ua636\ua637\ua638\ua639\ua63a\ua63b\ua63c\ua63d\ua63e\ua63f\ua640\ua641\ua642\ua643\ua644\ua645\ua646\ua647\ua648\ua649\ua64a\ua64b\ua64c\ua64d\ua64e\ua64f\ua650\ua651\ua652\ua653\ua654\ua655\ua656\ua657\ua658\ua659\ua65a\ua65b\ua65c\ua65d\ua65e\ua65f\ua660\ua661\ua662\ua663\ua664\ua665\ua666\ua667\ua668\ua669\ua66a\ua66b\ua66c\ua66d\ua66e\ua66f\ua670\ua671\ua672\ua673\ua674\ua675\ua676\ua677\ua678\ua679\ua67a\ua67b\ua67c\ua67d\ua67e\ua67f\ua680\ua681\ua682\ua683\ua684\ua685\ua686\ua687\ua688\ua689\ua68a\ua68b\ua68c\ua68d\ua68e\ua68f\ua690\ua691\ua692\ua693\ua694\ua695\ua696\ua697\ua698\ua699\ua69a\ua69b\ua69c\ua69d\ua69e\ua69f\ua6a0\ua6a1\ua6a2\ua6a3\ua6a4\ua6a5\ua6a6\ua6a7\ua6a8\ua6a9\ua6aa\ua6ab\ua6ac\ua6ad\ua6ae\ua6af\ua6b0\ua6b1\ua6b2\ua6b3\ua6b4\ua6b5\ua6b6\ua6b7\ua6b8\ua6b9\ua6ba\ua6bb\ua6bc\ua6bd\ua6be\ua6bf\ua6c0\ua6c1\ua6c2\ua6c3\ua6c4\ua6c5\ua6c6\ua6c7\ua6c8\ua6c9\ua6ca\ua6cb\ua6cc\ua6cd\ua6ce\ua6cf\ua6d0\ua6d1\ua6d2\ua6d3\ua6d4\ua6d5\ua6d6\ua6d7\ua6d8\ua6d9\ua6da\ua6db\ua6dc\ua6dd\ua6de\ua6df\ua6e0\ua6e1\ua6e2\ua6e3\ua6e4\ua6e5\ua6e6\ua6e7\ua6e8\ua6e9\ua6ea\ua6eb\ua6ec\ua6ed\ua6ee\ua6ef\ua6f0\ua6f1\ua6f2\ua6f3\ua6f4\ua6f5\ua6f6\ua6f7\ua6f8\ua6f9\ua6fa\ua6fb\ua6fc\ua6fd\ua6fe\ua6ff\ua717\ua718\ua719\ua71a\ua71b\ua71c\ua71d\ua71e\ua71f\ua720\ua721\ua722\ua723\ua724\ua725\ua726\ua727\ua728\ua729\ua72a\ua72b\ua72c\ua72d\ua72e\ua72f\ua730\ua731\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua739\ua73a\ua73b\ua73c\ua73d\ua73e\ua73f\ua740\ua741\ua742\ua743\ua744\ua745\ua746\ua747\ua748\ua749\ua74a\ua74b\ua74c\ua74d\ua74e\ua74f\ua750\ua751\ua752\ua753\ua754\ua755\ua756\ua757\ua758\ua759\ua75a\ua75b\ua75c\ua75d\ua75e\ua75f\ua760\ua761\ua762\ua763\ua764\ua765\ua766\ua767\ua768\ua769\ua76a\ua76b\ua76c\ua76d\ua76e\ua76f\ua770\ua771\ua772\ua773\ua774\ua775\ua776\ua777\ua778\ua779\ua77a\ua77b\ua77c\ua77d\ua77e\ua77f\ua780\ua781\ua782\ua783\ua784\ua785\ua786\ua787\ua788\ua789\ua78a\ua78b\ua78c\ua78d\ua78e\ua78f\ua790\ua791\ua792\ua793\ua794\ua795\ua796\ua797\ua798\ua799\ua79a\ua79b\ua79c\ua79d\ua79e\ua79f\ua7a0\ua7a1\ua7a2\ua7a3\ua7a4\ua7a5\ua7a6\ua7a7\ua7a8\ua7a9\ua7aa\ua7ab\ua7ac\ua7ad\ua7ae\ua7af\ua7b0\ua7b1\ua7b2\ua7b3\ua7b4\ua7b5\ua7b6\ua7b7\ua7b8\ua7b9\ua7ba\ua7bb\ua7bc\ua7bd\ua7be\ua7bf\ua7c0\ua7c1\ua7c2\ua7c3\ua7c4\ua7c5\ua7c6\ua7c7\ua7c8\ua7c9\ua7ca\ua7cb\ua7cc\ua7cd\ua7ce\ua7cf\ua7d0\ua7d1\ua7d2\ua7d3\ua7d4\ua7d5\ua7d6\ua7d7\ua7d8\ua7d9\ua7da\ua7db\ua7dc\ua7dd\ua7de\ua7df\ua7e0\ua7e1\ua7e2\ua7e3\ua7e4\ua7e5\ua7e6\ua7e7\ua7e8\ua7e9\ua7ea\ua7eb\ua7ec\ua7ed\ua7ee\ua7ef\ua7f0\ua7f1\ua7f2\ua7f3\ua7f4\ua7f5\ua7f6\ua7f7\ua7f8\ua7f9\ua7fa\ua7fb\ua7fc\ua7fd\ua7fe\ua7ff\ua82c\ua82d\ua82e\ua82f\ua830\ua831\ua832\ua833\ua834\ua835\ua836\ua837\ua838\ua839\ua83a\ua83b\ua83c\ua83d\ua83e\ua83f\ua840\ua841\ua842\ua843\ua844\ua845\ua846\ua847\ua848\ua849\ua84a\ua84b\ua84c\ua84d\ua84e\ua84f\ua850\ua851\ua852\ua853\ua854\ua855\ua856\ua857\ua858\ua859\ua85a\ua85b\ua85c\ua85d\ua85e\ua85f\ua860\ua861\ua862\ua863\ua864\ua865\ua866\ua867\ua868\ua869\ua86a\ua86b\ua86c\ua86d\ua86e\ua86f\ua870\ua871\ua872\ua873\ua874\ua875\ua876\ua877\ua878\ua879\ua87a\ua87b\ua87c\ua87d\ua87e\ua87f\ua880\ua881\ua882\ua883\ua884\ua885\ua886\ua887\ua888\ua889\ua88a\ua88b\ua88c\ua88d\ua88e\ua88f\ua890\ua891\ua892\ua893\ua894\ua895\ua896\ua897\ua898\ua899\ua89a\ua89b\ua89c\ua89d\ua89e\ua89f\ua8a0\ua8a1\ua8a2\ua8a3\ua8a4\ua8a5\ua8a6\ua8a7\ua8a8\ua8a9\ua8aa\ua8ab\ua8ac\ua8ad\ua8ae\ua8af\ua8b0\ua8b1\ua8b2\ua8b3\ua8b4\ua8b5\ua8b6\ua8b7\ua8b8\ua8b9\ua8ba\ua8bb\ua8bc\ua8bd\ua8be\ua8bf\ua8c0\ua8c1\ua8c2\ua8c3\ua8c4\ua8c5\ua8c6\ua8c7\ua8c8\ua8c9\ua8ca\ua8cb\ua8cc\ua8cd\ua8ce\ua8cf\ua8d0\ua8d1\ua8d2\ua8d3\ua8d4\ua8d5\ua8d6\ua8d7\ua8d8\ua8d9\ua8da\ua8db\ua8dc\ua8dd\ua8de\ua8df\ua8e0\ua8e1\ua8e2\ua8e3\ua8e4\ua8e5\ua8e6\ua8e7\ua8e8\ua8e9\ua8ea\ua8eb\ua8ec\ua8ed\ua8ee\ua8ef\ua8f0\ua8f1\ua8f2\ua8f3\ua8f4\ua8f5\ua8f6\ua8f7\ua8f8\ua8f9\ua8fa\ua8fb\ua8fc\ua8fd\ua8fe\ua8ff\ua900\ua901\ua902\ua903\ua904\ua905\ua906\ua907\ua908\ua909\ua90a\ua90b\ua90c\ua90d\ua90e\ua90f\ua910\ua911\ua912\ua913\ua914\ua915\ua916\ua917\ua918\ua919\ua91a\ua91b\ua91c\ua91d\ua91e\ua91f\ua920\ua921\ua922\ua923\ua924\ua925\ua926\ua927\ua928\ua929\ua92a\ua92b\ua92c\ua92d\ua92e\ua92f\ua930\ua931\ua932\ua933\ua934\ua935\ua936\ua937\ua938\ua939\ua93a\ua93b\ua93c\ua93d\ua93e\ua93f\ua940\ua941\ua942\ua943\ua944\ua945\ua946\ua947\ua948\ua949\ua94a\ua94b\ua94c\ua94d\ua94e\ua94f\ua950\ua951\ua952\ua953\ua954\ua955\ua956\ua957\ua958\ua959\ua95a\ua95b\ua95c\ua95d\ua95e\ua95f\ua960\ua961\ua962\ua963\ua964\ua965\ua966\ua967\ua968\ua969\ua96a\ua96b\ua96c\ua96d\ua96e\ua96f\ua970\ua971\ua972\ua973\ua974\ua975\ua976\ua977\ua978\ua979\ua97a\ua97b\ua97c\ua97d\ua97e\ua97f\ua980\ua981\ua982\ua983\ua984\ua985\ua986\ua987\ua988\ua989\ua98a\ua98b\ua98c\ua98d\ua98e\ua98f\ua990\ua991\ua992\ua993\ua994\ua995\ua996\ua997\ua998\ua999\ua99a\ua99b\ua99c\ua99d\ua99e\ua99f\ua9a0\ua9a1\ua9a2\ua9a3\ua9a4\ua9a5\ua9a6\ua9a7\ua9a8\ua9a9\ua9aa\ua9ab\ua9ac\ua9ad\ua9ae\ua9af\ua9b0\ua9b1\ua9b2\ua9b3\ua9b4\ua9b5\ua9b6\ua9b7\ua9b8\ua9b9\ua9ba\ua9bb\ua9bc\ua9bd\ua9be\ua9bf\ua9c0\ua9c1\ua9c2\ua9c3\ua9c4\ua9c5\ua9c6\ua9c7\ua9c8\ua9c9\ua9ca\ua9cb\ua9cc\ua9cd\ua9ce\ua9cf\ua9d0\ua9d1\ua9d2\ua9d3\ua9d4\ua9d5\ua9d6\ua9d7\ua9d8\ua9d9\ua9da\ua9db\ua9dc\ua9dd\ua9de\ua9df\ua9e0\ua9e1\ua9e2\ua9e3\ua9e4\ua9e5\ua9e6\ua9e7\ua9e8\ua9e9\ua9ea\ua9eb\ua9ec\ua9ed\ua9ee\ua9ef\ua9f0\ua9f1\ua9f2\ua9f3\ua9f4\ua9f5\ua9f6\ua9f7\ua9f8\ua9f9\ua9fa\ua9fb\ua9fc\ua9fd\ua9fe\ua9ff\uaa00\uaa01\uaa02\uaa03\uaa04\uaa05\uaa06\uaa07\uaa08\uaa09\uaa0a\uaa0b\uaa0c\uaa0d\uaa0e\uaa0f\uaa10\uaa11\uaa12\uaa13\uaa14\uaa15\uaa16\uaa17\uaa18\uaa19\uaa1a\uaa1b\uaa1c\uaa1d\uaa1e\uaa1f\uaa20\uaa21\uaa22\uaa23\uaa24\uaa25\uaa26\uaa27\uaa28\uaa29\uaa2a\uaa2b\uaa2c\uaa2d\uaa2e\uaa2f\uaa30\uaa31\uaa32\uaa33\uaa34\uaa35\uaa36\uaa37\uaa38\uaa39\uaa3a\uaa3b\uaa3c\uaa3d\uaa3e\uaa3f\uaa40\uaa41\uaa42\uaa43\uaa44\uaa45\uaa46\uaa47\uaa48\uaa49\uaa4a\uaa4b\uaa4c\uaa4d\uaa4e\uaa4f\uaa50\uaa51\uaa52\uaa53\uaa54\uaa55\uaa56\uaa57\uaa58\uaa59\uaa5a\uaa5b\uaa5c\uaa5d\uaa5e\uaa5f\uaa60\uaa61\uaa62\uaa63\uaa64\uaa65\uaa66\uaa67\uaa68\uaa69\uaa6a\uaa6b\uaa6c\uaa6d\uaa6e\uaa6f\uaa70\uaa71\uaa72\uaa73\uaa74\uaa75\uaa76\uaa77\uaa78\uaa79\uaa7a\uaa7b\uaa7c\uaa7d\uaa7e\uaa7f\uaa80\uaa81\uaa82\uaa83\uaa84\uaa85\uaa86\uaa87\uaa88\uaa89\uaa8a\uaa8b\uaa8c\uaa8d\uaa8e\uaa8f\uaa90\uaa91\uaa92\uaa93\uaa94\uaa95\uaa96\uaa97\uaa98\uaa99\uaa9a\uaa9b\uaa9c\uaa9d\uaa9e\uaa9f\uaaa0\uaaa1\uaaa2\uaaa3\uaaa4\uaaa5\uaaa6\uaaa7\uaaa8\uaaa9\uaaaa\uaaab\uaaac\uaaad\uaaae\uaaaf\uaab0\uaab1\uaab2\uaab3\uaab4\uaab5\uaab6\uaab7\uaab8\uaab9\uaaba\uaabb\uaabc\uaabd\uaabe\uaabf\uaac0\uaac1\uaac2\uaac3\uaac4\uaac5\uaac6\uaac7\uaac8\uaac9\uaaca\uaacb\uaacc\uaacd\uaace\uaacf\uaad0\uaad1\uaad2\uaad3\uaad4\uaad5\uaad6\uaad7\uaad8\uaad9\uaada\uaadb\uaadc\uaadd\uaade\uaadf\uaae0\uaae1\uaae2\uaae3\uaae4\uaae5\uaae6\uaae7\uaae8\uaae9\uaaea\uaaeb\uaaec\uaaed\uaaee\uaaef\uaaf0\uaaf1\uaaf2\uaaf3\uaaf4\uaaf5\uaaf6\uaaf7\uaaf8\uaaf9\uaafa\uaafb\uaafc\uaafd\uaafe\uaaff\uab00\uab01\uab02\uab03\uab04\uab05\uab06\uab07\uab08\uab09\uab0a\uab0b\uab0c\uab0d\uab0e\uab0f\uab10\uab11\uab12\uab13\uab14\uab15\uab16\uab17\uab18\uab19\uab1a\uab1b\uab1c\uab1d\uab1e\uab1f\uab20\uab21\uab22\uab23\uab24\uab25\uab26\uab27\uab28\uab29\uab2a\uab2b\uab2c\uab2d\uab2e\uab2f\uab30\uab31\uab32\uab33\uab34\uab35\uab36\uab37\uab38\uab39\uab3a\uab3b\uab3c\uab3d\uab3e\uab3f\uab40\uab41\uab42\uab43\uab44\uab45\uab46\uab47\uab48\uab49\uab4a\uab4b\uab4c\uab4d\uab4e\uab4f\uab50\uab51\uab52\uab53\uab54\uab55\uab56\uab57\uab58\uab59\uab5a\uab5b\uab5c\uab5d\uab5e\uab5f\uab60\uab61\uab62\uab63\uab64\uab65\uab66\uab67\uab68\uab69\uab6a\uab6b\uab6c\uab6d\uab6e\uab6f\uab70\uab71\uab72\uab73\uab74\uab75\uab76\uab77\uab78\uab79\uab7a\uab7b\uab7c\uab7d\uab7e\uab7f\uab80\uab81\uab82\uab83\uab84\uab85\uab86\uab87\uab88\uab89\uab8a\uab8b\uab8c\uab8d\uab8e\uab8f\uab90\uab91\uab92\uab93\uab94\uab95\uab96\uab97\uab98\uab99\uab9a\uab9b\uab9c\uab9d\uab9e\uab9f\uaba0\uaba1\uaba2\uaba3\uaba4\uaba5\uaba6\uaba7\uaba8\uaba9\uabaa\uabab\uabac\uabad\uabae\uabaf\uabb0\uabb1\uabb2\uabb3\uabb4\uabb5\uabb6\uabb7\uabb8\uabb9\uabba\uabbb\uabbc\uabbd\uabbe\uabbf\uabc0\uabc1\uabc2\uabc3\uabc4\uabc5\uabc6\uabc7\uabc8\uabc9\uabca\uabcb\uabcc\uabcd\uabce\uabcf\uabd0\uabd1\uabd2\uabd3\uabd4\uabd5\uabd6\uabd7\uabd8\uabd9\uabda\uabdb\uabdc\uabdd\uabde\uabdf\uabe0\uabe1\uabe2\uabe3\uabe4\uabe5\uabe6\uabe7\uabe8\uabe9\uabea\uabeb\uabec\uabed\uabee\uabef\uabf0\uabf1\uabf2\uabf3\uabf4\uabf5\uabf6\uabf7\uabf8\uabf9\uabfa\uabfb\uabfc\uabfd\uabfe\uabff\ud7a4\ud7a5\ud7a6\ud7a7\ud7a8\ud7a9\ud7aa\ud7ab\ud7ac\ud7ad\ud7ae\ud7af\ud7b0\ud7b1\ud7b2\ud7b3\ud7b4\ud7b5\ud7b6\ud7b7\ud7b8\ud7b9\ud7ba\ud7bb\ud7bc\ud7bd\ud7be\ud7bf\ud7c0\ud7c1\ud7c2\ud7c3\ud7c4\ud7c5\ud7c6\ud7c7\ud7c8\ud7c9\ud7ca\ud7cb\ud7cc\ud7cd\ud7ce\ud7cf\ud7d0\ud7d1\ud7d2\ud7d3\ud7d4\ud7d5\ud7d6\ud7d7\ud7d8\ud7d9\ud7da\ud7db\ud7dc\ud7dd\ud7de\ud7df\ud7e0\ud7e1\ud7e2\ud7e3\ud7e4\ud7e5\ud7e6\ud7e7\ud7e8\ud7e9\ud7ea\ud7eb\ud7ec\ud7ed\ud7ee\ud7ef\ud7f0\ud7f1\ud7f2\ud7f3\ud7f4\ud7f5\ud7f6\ud7f7\ud7f8\ud7f9\ud7fa\ud7fb\ud7fc\ud7fd\ud7fe\ud7ff\ufa2e\ufa2f\ufa6b\ufa6c\ufa6d\ufa6e\ufa6f\ufada\ufadb\ufadc\ufadd\ufade\ufadf\ufae0\ufae1\ufae2\ufae3\ufae4\ufae5\ufae6\ufae7\ufae8\ufae9\ufaea\ufaeb\ufaec\ufaed\ufaee\ufaef\ufaf0\ufaf1\ufaf2\ufaf3\ufaf4\ufaf5\ufaf6\ufaf7\ufaf8\ufaf9\ufafa\ufafb\ufafc\ufafd\ufafe\ufaff\ufb07\ufb08\ufb09\ufb0a\ufb0b\ufb0c\ufb0d\ufb0e\ufb0f\ufb10\ufb11\ufb12\ufb18\ufb19\ufb1a\ufb1b\ufb1c\ufb37\ufb3d\ufb3f\ufb42\ufb45\ufbb2\ufbb3\ufbb4\ufbb5\ufbb6\ufbb7\ufbb8\ufbb9\ufbba\ufbbb\ufbbc\ufbbd\ufbbe\ufbbf\ufbc0\ufbc1\ufbc2\ufbc3\ufbc4\ufbc5\ufbc6\ufbc7\ufbc8\ufbc9\ufbca\ufbcb\ufbcc\ufbcd\ufbce\ufbcf\ufbd0\ufbd1\ufbd2\ufd40\ufd41\ufd42\ufd43\ufd44\ufd45\ufd46\ufd47\ufd48\ufd49\ufd4a\ufd4b\ufd4c\ufd4d\ufd4e\ufd4f\ufd90\ufd91\ufdc8\ufdc9\ufdca\ufdcb\ufdcc\ufdcd\ufdce\ufdcf\ufdd0\ufdd1\ufdd2\ufdd3\ufdd4\ufdd5\ufdd6\ufdd7\ufdd8\ufdd9\ufdda\ufddb\ufddc\ufddd\ufdde\ufddf\ufde0\ufde1\ufde2\ufde3\ufde4\ufde5\ufde6\ufde7\ufde8\ufde9\ufdea\ufdeb\ufdec\ufded\ufdee\ufdef\ufdfe\ufdff\ufe1a\ufe1b\ufe1c\ufe1d\ufe1e\ufe1f\ufe24\ufe25\ufe26\ufe27\ufe28\ufe29\ufe2a\ufe2b\ufe2c\ufe2d\ufe2e\ufe2f\ufe53\ufe67\ufe6c\ufe6d\ufe6e\ufe6f\ufe75\ufefd\ufefe\uff00\uffbf\uffc0\uffc1\uffc8\uffc9\uffd0\uffd1\uffd8\uffd9\uffdd\uffde\uffdf\uffe7\uffef\ufff0\ufff1\ufff2\ufff3\ufff4\ufff5\ufff6\ufff7\ufff8\ufffe'
+
+Co = u'\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009\ue00a\ue00b\ue00c\ue00d\ue00e\ue00f\ue010\ue011\ue012\ue013\ue014\ue015\ue016\ue017\ue018\ue019\ue01a\ue01b\ue01c\ue01d\ue01e\ue01f\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029\ue02a\ue02b\ue02c\ue02d\ue02e\ue02f\ue030\ue031\ue032\ue033\ue034\ue035\ue036\ue037\ue038\ue039\ue03a\ue03b\ue03c\ue03d\ue03e\ue03f\ue040\ue041\ue042\ue043\ue044\ue045\ue046\ue047\ue048\ue049\ue04a\ue04b\ue04c\ue04d\ue04e\ue04f\ue050\ue051\ue052\ue053\ue054\ue055\ue056\ue057\ue058\ue059\ue05a\ue05b\ue05c\ue05d\ue05e\ue05f\ue060\ue061\ue062\ue063\ue064\ue065\ue066\ue067\ue068\ue069\ue06a\ue06b\ue06c\ue06d\ue06e\ue06f\ue070\ue071\ue072\ue073\ue074\ue075\ue076\ue077\ue078\ue079\ue07a\ue07b\ue07c\ue07d\ue07e\ue07f\ue080\ue081\ue082\ue083\ue084\ue085\ue086\ue087\ue088\ue089\ue08a\ue08b\ue08c\ue08d\ue08e\ue08f\ue090\ue091\ue092\ue093\ue094\ue095\ue096\ue097\ue098\ue099\ue09a\ue09b\ue09c\ue09d\ue09e\ue09f\ue0a0\ue0a1\ue0a2\ue0a3\ue0a4\ue0a5\ue0a6\ue0a7\ue0a8\ue0a9\ue0aa\ue0ab\ue0ac\ue0ad\ue0ae\ue0af\ue0b0\ue0b1\ue0b2\ue0b3\ue0b4\ue0b5\ue0b6\ue0b7\ue0b8\ue0b9\ue0ba\ue0bb\ue0bc\ue0bd\ue0be\ue0bf\ue0c0\ue0c1\ue0c2\ue0c3\ue0c4\ue0c5\ue0c6\ue0c7\ue0c8\ue0c9\ue0ca\ue0cb\ue0cc\ue0cd\ue0ce\ue0cf\ue0d0\ue0d1\ue0d2\ue0d3\ue0d4\ue0d5\ue0d6\ue0d7\ue0d8\ue0d9\ue0da\ue0db\ue0dc\ue0dd\ue0de\ue0df\ue0e0\ue0e1\ue0e2\ue0e3\ue0e4\ue0e5\ue0e6\ue0e7\ue0e8\ue0e9\ue0ea\ue0eb\ue0ec\ue0ed\ue0ee\ue0ef\ue0f0\ue0f1\ue0f2\ue0f3\ue0f4\ue0f5\ue0f6\ue0f7\ue0f8\ue0f9\ue0fa\ue0fb\ue0fc\ue0fd\ue0fe\ue0ff\ue100\ue101\ue102\ue103\ue104\ue105\ue106\ue107\ue108\ue109\ue10a\ue10b\ue10c\ue10d\ue10e\ue10f\ue110\ue111\ue112\ue113\ue114\ue115\ue116\ue117\ue118\ue119\ue11a\ue11b\ue11c\ue11d\ue11e\ue11f\ue120\ue121\ue122\ue123\ue124\ue125\ue126\ue127\ue128\ue129\ue12a\ue12b\ue12c\ue12d\ue12e\ue12f\ue130\ue131\ue132\ue133\ue134\ue135\ue136\ue137\ue138\ue139\ue13a\ue13b\ue13c\ue13d\ue13e\ue13f\ue140\ue141\ue142\ue143\ue144\ue145\ue146\ue147\ue148\ue149\ue14a\ue14b\ue14c\ue14d\ue14e\ue14f\ue150\ue151\ue152\ue153\ue154\ue155\ue156\ue157\ue158\ue159\ue15a\ue15b\ue15c\ue15d\ue15e\ue15f\ue160\ue161\ue162\ue163\ue164\ue165\ue166\ue167\ue168\ue169\ue16a\ue16b\ue16c\ue16d\ue16e\ue16f\ue170\ue171\ue172\ue173\ue174\ue175\ue176\ue177\ue178\ue179\ue17a\ue17b\ue17c\ue17d\ue17e\ue17f\ue180\ue181\ue182\ue183\ue184\ue185\ue186\ue187\ue188\ue189\ue18a\ue18b\ue18c\ue18d\ue18e\ue18f\ue190\ue191\ue192\ue193\ue194\ue195\ue196\ue197\ue198\ue199\ue19a\ue19b\ue19c\ue19d\ue19e\ue19f\ue1a0\ue1a1\ue1a2\ue1a3\ue1a4\ue1a5\ue1a6\ue1a7\ue1a8\ue1a9\ue1aa\ue1ab\ue1ac\ue1ad\ue1ae\ue1af\ue1b0\ue1b1\ue1b2\ue1b3\ue1b4\ue1b5\ue1b6\ue1b7\ue1b8\ue1b9\ue1ba\ue1bb\ue1bc\ue1bd\ue1be\ue1bf\ue1c0\ue1c1\ue1c2\ue1c3\ue1c4\ue1c5\ue1c6\ue1c7\ue1c8\ue1c9\ue1ca\ue1cb\ue1cc\ue1cd\ue1ce\ue1cf\ue1d0\ue1d1\ue1d2\ue1d3\ue1d4\ue1d5\ue1d6\ue1d7\ue1d8\ue1d9\ue1da\ue1db\ue1dc\ue1dd\ue1de\ue1df\ue1e0\ue1e1\ue1e2\ue1e3\ue1e4\ue1e5\ue1e6\ue1e7\ue1e8\ue1e9\ue1ea\ue1eb\ue1ec\ue1ed\ue1ee\ue1ef\ue1f0\ue1f1\ue1f2\ue1f3\ue1f4\ue1f5\ue1f6\ue1f7\ue1f8\ue1f9\ue1fa\ue1fb\ue1fc\ue1fd\ue1fe\ue1ff\ue200\ue201\ue202\ue203\ue204\ue205\ue206\ue207\ue208\ue209\ue20a\ue20b\ue20c\ue20d\ue20e\ue20f\ue210\ue211\ue212\ue213\ue214\ue215\ue216\ue217\ue218\ue219\ue21a\ue21b\ue21c\ue21d\ue21e\ue21f\ue220\ue221\ue222\ue223\ue224\ue225\ue226\ue227\ue228\ue229\ue22a\ue22b\ue22c\ue22d\ue22e\ue22f\ue230\ue231\ue232\ue233\ue234\ue235\ue236\ue237\ue238\ue239\ue23a\ue23b\ue23c\ue23d\ue23e\ue23f\ue240\ue241\ue242\ue243\ue244\ue245\ue246\ue247\ue248\ue249\ue24a\ue24b\ue24c\ue24d\ue24e\ue24f\ue250\ue251\ue252\ue253\ue254\ue255\ue256\ue257\ue258\ue259\ue25a\ue25b\ue25c\ue25d\ue25e\ue25f\ue260\ue261\ue262\ue263\ue264\ue265\ue266\ue267\ue268\ue269\ue26a\ue26b\ue26c\ue26d\ue26e\ue26f\ue270\ue271\ue272\ue273\ue274\ue275\ue276\ue277\ue278\ue279\ue27a\ue27b\ue27c\ue27d\ue27e\ue27f\ue280\ue281\ue282\ue283\ue284\ue285\ue286\ue287\ue288\ue289\ue28a\ue28b\ue28c\ue28d\ue28e\ue28f\ue290\ue291\ue292\ue293\ue294\ue295\ue296\ue297\ue298\ue299\ue29a\ue29b\ue29c\ue29d\ue29e\ue29f\ue2a0\ue2a1\ue2a2\ue2a3\ue2a4\ue2a5\ue2a6\ue2a7\ue2a8\ue2a9\ue2aa\ue2ab\ue2ac\ue2ad\ue2ae\ue2af\ue2b0\ue2b1\ue2b2\ue2b3\ue2b4\ue2b5\ue2b6\ue2b7\ue2b8\ue2b9\ue2ba\ue2bb\ue2bc\ue2bd\ue2be\ue2bf\ue2c0\ue2c1\ue2c2\ue2c3\ue2c4\ue2c5\ue2c6\ue2c7\ue2c8\ue2c9\ue2ca\ue2cb\ue2cc\ue2cd\ue2ce\ue2cf\ue2d0\ue2d1\ue2d2\ue2d3\ue2d4\ue2d5\ue2d6\ue2d7\ue2d8\ue2d9\ue2da\ue2db\ue2dc\ue2dd\ue2de\ue2df\ue2e0\ue2e1\ue2e2\ue2e3\ue2e4\ue2e5\ue2e6\ue2e7\ue2e8\ue2e9\ue2ea\ue2eb\ue2ec\ue2ed\ue2ee\ue2ef\ue2f0\ue2f1\ue2f2\ue2f3\ue2f4\ue2f5\ue2f6\ue2f7\ue2f8\ue2f9\ue2fa\ue2fb\ue2fc\ue2fd\ue2fe\ue2ff\ue300\ue301\ue302\ue303\ue304\ue305\ue306\ue307\ue308\ue309\ue30a\ue30b\ue30c\ue30d\ue30e\ue30f\ue310\ue311\ue312\ue313\ue314\ue315\ue316\ue317\ue318\ue319\ue31a\ue31b\ue31c\ue31d\ue31e\ue31f\ue320\ue321\ue322\ue323\ue324\ue325\ue326\ue327\ue328\ue329\ue32a\ue32b\ue32c\ue32d\ue32e\ue32f\ue330\ue331\ue332\ue333\ue334\ue335\ue336\ue337\ue338\ue339\ue33a\ue33b\ue33c\ue33d\ue33e\ue33f\ue340\ue341\ue342\ue343\ue344\ue345\ue346\ue347\ue348\ue349\ue34a\ue34b\ue34c\ue34d\ue34e\ue34f\ue350\ue351\ue352\ue353\ue354\ue355\ue356\ue357\ue358\ue359\ue35a\ue35b\ue35c\ue35d\ue35e\ue35f\ue360\ue361\ue362\ue363\ue364\ue365\ue366\ue367\ue368\ue369\ue36a\ue36b\ue36c\ue36d\ue36e\ue36f\ue370\ue371\ue372\ue373\ue374\ue375\ue376\ue377\ue378\ue379\ue37a\ue37b\ue37c\ue37d\ue37e\ue37f\ue380\ue381\ue382\ue383\ue384\ue385\ue386\ue387\ue388\ue389\ue38a\ue38b\ue38c\ue38d\ue38e\ue38f\ue390\ue391\ue392\ue393\ue394\ue395\ue396\ue397\ue398\ue399\ue39a\ue39b\ue39c\ue39d\ue39e\ue39f\ue3a0\ue3a1\ue3a2\ue3a3\ue3a4\ue3a5\ue3a6\ue3a7\ue3a8\ue3a9\ue3aa\ue3ab\ue3ac\ue3ad\ue3ae\ue3af\ue3b0\ue3b1\ue3b2\ue3b3\ue3b4\ue3b5\ue3b6\ue3b7\ue3b8\ue3b9\ue3ba\ue3bb\ue3bc\ue3bd\ue3be\ue3bf\ue3c0\ue3c1\ue3c2\ue3c3\ue3c4\ue3c5\ue3c6\ue3c7\ue3c8\ue3c9\ue3ca\ue3cb\ue3cc\ue3cd\ue3ce\ue3cf\ue3d0\ue3d1\ue3d2\ue3d3\ue3d4\ue3d5\ue3d6\ue3d7\ue3d8\ue3d9\ue3da\ue3db\ue3dc\ue3dd\ue3de\ue3df\ue3e0\ue3e1\ue3e2\ue3e3\ue3e4\ue3e5\ue3e6\ue3e7\ue3e8\ue3e9\ue3ea\ue3eb\ue3ec\ue3ed\ue3ee\ue3ef\ue3f0\ue3f1\ue3f2\ue3f3\ue3f4\ue3f5\ue3f6\ue3f7\ue3f8\ue3f9\ue3fa\ue3fb\ue3fc\ue3fd\ue3fe\ue3ff\ue400\ue401\ue402\ue403\ue404\ue405\ue406\ue407\ue408\ue409\ue40a\ue40b\ue40c\ue40d\ue40e\ue40f\ue410\ue411\ue412\ue413\ue414\ue415\ue416\ue417\ue418\ue419\ue41a\ue41b\ue41c\ue41d\ue41e\ue41f\ue420\ue421\ue422\ue423\ue424\ue425\ue426\ue427\ue428\ue429\ue42a\ue42b\ue42c\ue42d\ue42e\ue42f\ue430\ue431\ue432\ue433\ue434\ue435\ue436\ue437\ue438\ue439\ue43a\ue43b\ue43c\ue43d\ue43e\ue43f\ue440\ue441\ue442\ue443\ue444\ue445\ue446\ue447\ue448\ue449\ue44a\ue44b\ue44c\ue44d\ue44e\ue44f\ue450\ue451\ue452\ue453\ue454\ue455\ue456\ue457\ue458\ue459\ue45a\ue45b\ue45c\ue45d\ue45e\ue45f\ue460\ue461\ue462\ue463\ue464\ue465\ue466\ue467\ue468\ue469\ue46a\ue46b\ue46c\ue46d\ue46e\ue46f\ue470\ue471\ue472\ue473\ue474\ue475\ue476\ue477\ue478\ue479\ue47a\ue47b\ue47c\ue47d\ue47e\ue47f\ue480\ue481\ue482\ue483\ue484\ue485\ue486\ue487\ue488\ue489\ue48a\ue48b\ue48c\ue48d\ue48e\ue48f\ue490\ue491\ue492\ue493\ue494\ue495\ue496\ue497\ue498\ue499\ue49a\ue49b\ue49c\ue49d\ue49e\ue49f\ue4a0\ue4a1\ue4a2\ue4a3\ue4a4\ue4a5\ue4a6\ue4a7\ue4a8\ue4a9\ue4aa\ue4ab\ue4ac\ue4ad\ue4ae\ue4af\ue4b0\ue4b1\ue4b2\ue4b3\ue4b4\ue4b5\ue4b6\ue4b7\ue4b8\ue4b9\ue4ba\ue4bb\ue4bc\ue4bd\ue4be\ue4bf\ue4c0\ue4c1\ue4c2\ue4c3\ue4c4\ue4c5\ue4c6\ue4c7\ue4c8\ue4c9\ue4ca\ue4cb\ue4cc\ue4cd\ue4ce\ue4cf\ue4d0\ue4d1\ue4d2\ue4d3\ue4d4\ue4d5\ue4d6\ue4d7\ue4d8\ue4d9\ue4da\ue4db\ue4dc\ue4dd\ue4de\ue4df\ue4e0\ue4e1\ue4e2\ue4e3\ue4e4\ue4e5\ue4e6\ue4e7\ue4e8\ue4e9\ue4ea\ue4eb\ue4ec\ue4ed\ue4ee\ue4ef\ue4f0\ue4f1\ue4f2\ue4f3\ue4f4\ue4f5\ue4f6\ue4f7\ue4f8\ue4f9\ue4fa\ue4fb\ue4fc\ue4fd\ue4fe\ue4ff\ue500\ue501\ue502\ue503\ue504\ue505\ue506\ue507\ue508\ue509\ue50a\ue50b\ue50c\ue50d\ue50e\ue50f\ue510\ue511\ue512\ue513\ue514\ue515\ue516\ue517\ue518\ue519\ue51a\ue51b\ue51c\ue51d\ue51e\ue51f\ue520\ue521\ue522\ue523\ue524\ue525\ue526\ue527\ue528\ue529\ue52a\ue52b\ue52c\ue52d\ue52e\ue52f\ue530\ue531\ue532\ue533\ue534\ue535\ue536\ue537\ue538\ue539\ue53a\ue53b\ue53c\ue53d\ue53e\ue53f\ue540\ue541\ue542\ue543\ue544\ue545\ue546\ue547\ue548\ue549\ue54a\ue54b\ue54c\ue54d\ue54e\ue54f\ue550\ue551\ue552\ue553\ue554\ue555\ue556\ue557\ue558\ue559\ue55a\ue55b\ue55c\ue55d\ue55e\ue55f\ue560\ue561\ue562\ue563\ue564\ue565\ue566\ue567\ue568\ue569\ue56a\ue56b\ue56c\ue56d\ue56e\ue56f\ue570\ue571\ue572\ue573\ue574\ue575\ue576\ue577\ue578\ue579\ue57a\ue57b\ue57c\ue57d\ue57e\ue57f\ue580\ue581\ue582\ue583\ue584\ue585\ue586\ue587\ue588\ue589\ue58a\ue58b\ue58c\ue58d\ue58e\ue58f\ue590\ue591\ue592\ue593\ue594\ue595\ue596\ue597\ue598\ue599\ue59a\ue59b\ue59c\ue59d\ue59e\ue59f\ue5a0\ue5a1\ue5a2\ue5a3\ue5a4\ue5a5\ue5a6\ue5a7\ue5a8\ue5a9\ue5aa\ue5ab\ue5ac\ue5ad\ue5ae\ue5af\ue5b0\ue5b1\ue5b2\ue5b3\ue5b4\ue5b5\ue5b6\ue5b7\ue5b8\ue5b9\ue5ba\ue5bb\ue5bc\ue5bd\ue5be\ue5bf\ue5c0\ue5c1\ue5c2\ue5c3\ue5c4\ue5c5\ue5c6\ue5c7\ue5c8\ue5c9\ue5ca\ue5cb\ue5cc\ue5cd\ue5ce\ue5cf\ue5d0\ue5d1\ue5d2\ue5d3\ue5d4\ue5d5\ue5d6\ue5d7\ue5d8\ue5d9\ue5da\ue5db\ue5dc\ue5dd\ue5de\ue5df\ue5e0\ue5e1\ue5e2\ue5e3\ue5e4\ue5e5\ue5e6\ue5e7\ue5e8\ue5e9\ue5ea\ue5eb\ue5ec\ue5ed\ue5ee\ue5ef\ue5f0\ue5f1\ue5f2\ue5f3\ue5f4\ue5f5\ue5f6\ue5f7\ue5f8\ue5f9\ue5fa\ue5fb\ue5fc\ue5fd\ue5fe\ue5ff\ue600\ue601\ue602\ue603\ue604\ue605\ue606\ue607\ue608\ue609\ue60a\ue60b\ue60c\ue60d\ue60e\ue60f\ue610\ue611\ue612\ue613\ue614\ue615\ue616\ue617\ue618\ue619\ue61a\ue61b\ue61c\ue61d\ue61e\ue61f\ue620\ue621\ue622\ue623\ue624\ue625\ue626\ue627\ue628\ue629\ue62a\ue62b\ue62c\ue62d\ue62e\ue62f\ue630\ue631\ue632\ue633\ue634\ue635\ue636\ue637\ue638\ue639\ue63a\ue63b\ue63c\ue63d\ue63e\ue63f\ue640\ue641\ue642\ue643\ue644\ue645\ue646\ue647\ue648\ue649\ue64a\ue64b\ue64c\ue64d\ue64e\ue64f\ue650\ue651\ue652\ue653\ue654\ue655\ue656\ue657\ue658\ue659\ue65a\ue65b\ue65c\ue65d\ue65e\ue65f\ue660\ue661\ue662\ue663\ue664\ue665\ue666\ue667\ue668\ue669\ue66a\ue66b\ue66c\ue66d\ue66e\ue66f\ue670\ue671\ue672\ue673\ue674\ue675\ue676\ue677\ue678\ue679\ue67a\ue67b\ue67c\ue67d\ue67e\ue67f\ue680\ue681\ue682\ue683\ue684\ue685\ue686\ue687\ue688\ue689\ue68a\ue68b\ue68c\ue68d\ue68e\ue68f\ue690\ue691\ue692\ue693\ue694\ue695\ue696\ue697\ue698\ue699\ue69a\ue69b\ue69c\ue69d\ue69e\ue69f\ue6a0\ue6a1\ue6a2\ue6a3\ue6a4\ue6a5\ue6a6\ue6a7\ue6a8\ue6a9\ue6aa\ue6ab\ue6ac\ue6ad\ue6ae\ue6af\ue6b0\ue6b1\ue6b2\ue6b3\ue6b4\ue6b5\ue6b6\ue6b7\ue6b8\ue6b9\ue6ba\ue6bb\ue6bc\ue6bd\ue6be\ue6bf\ue6c0\ue6c1\ue6c2\ue6c3\ue6c4\ue6c5\ue6c6\ue6c7\ue6c8\ue6c9\ue6ca\ue6cb\ue6cc\ue6cd\ue6ce\ue6cf\ue6d0\ue6d1\ue6d2\ue6d3\ue6d4\ue6d5\ue6d6\ue6d7\ue6d8\ue6d9\ue6da\ue6db\ue6dc\ue6dd\ue6de\ue6df\ue6e0\ue6e1\ue6e2\ue6e3\ue6e4\ue6e5\ue6e6\ue6e7\ue6e8\ue6e9\ue6ea\ue6eb\ue6ec\ue6ed\ue6ee\ue6ef\ue6f0\ue6f1\ue6f2\ue6f3\ue6f4\ue6f5\ue6f6\ue6f7\ue6f8\ue6f9\ue6fa\ue6fb\ue6fc\ue6fd\ue6fe\ue6ff\ue700\ue701\ue702\ue703\ue704\ue705\ue706\ue707\ue708\ue709\ue70a\ue70b\ue70c\ue70d\ue70e\ue70f\ue710\ue711\ue712\ue713\ue714\ue715\ue716\ue717\ue718\ue719\ue71a\ue71b\ue71c\ue71d\ue71e\ue71f\ue720\ue721\ue722\ue723\ue724\ue725\ue726\ue727\ue728\ue729\ue72a\ue72b\ue72c\ue72d\ue72e\ue72f\ue730\ue731\ue732\ue733\ue734\ue735\ue736\ue737\ue738\ue739\ue73a\ue73b\ue73c\ue73d\ue73e\ue73f\ue740\ue741\ue742\ue743\ue744\ue745\ue746\ue747\ue748\ue749\ue74a\ue74b\ue74c\ue74d\ue74e\ue74f\ue750\ue751\ue752\ue753\ue754\ue755\ue756\ue757\ue758\ue759\ue75a\ue75b\ue75c\ue75d\ue75e\ue75f\ue760\ue761\ue762\ue763\ue764\ue765\ue766\ue767\ue768\ue769\ue76a\ue76b\ue76c\ue76d\ue76e\ue76f\ue770\ue771\ue772\ue773\ue774\ue775\ue776\ue777\ue778\ue779\ue77a\ue77b\ue77c\ue77d\ue77e\ue77f\ue780\ue781\ue782\ue783\ue784\ue785\ue786\ue787\ue788\ue789\ue78a\ue78b\ue78c\ue78d\ue78e\ue78f\ue790\ue791\ue792\ue793\ue794\ue795\ue796\ue797\ue798\ue799\ue79a\ue79b\ue79c\ue79d\ue79e\ue79f\ue7a0\ue7a1\ue7a2\ue7a3\ue7a4\ue7a5\ue7a6\ue7a7\ue7a8\ue7a9\ue7aa\ue7ab\ue7ac\ue7ad\ue7ae\ue7af\ue7b0\ue7b1\ue7b2\ue7b3\ue7b4\ue7b5\ue7b6\ue7b7\ue7b8\ue7b9\ue7ba\ue7bb\ue7bc\ue7bd\ue7be\ue7bf\ue7c0\ue7c1\ue7c2\ue7c3\ue7c4\ue7c5\ue7c6\ue7c7\ue7c8\ue7c9\ue7ca\ue7cb\ue7cc\ue7cd\ue7ce\ue7cf\ue7d0\ue7d1\ue7d2\ue7d3\ue7d4\ue7d5\ue7d6\ue7d7\ue7d8\ue7d9\ue7da\ue7db\ue7dc\ue7dd\ue7de\ue7df\ue7e0\ue7e1\ue7e2\ue7e3\ue7e4\ue7e5\ue7e6\ue7e7\ue7e8\ue7e9\ue7ea\ue7eb\ue7ec\ue7ed\ue7ee\ue7ef\ue7f0\ue7f1\ue7f2\ue7f3\ue7f4\ue7f5\ue7f6\ue7f7\ue7f8\ue7f9\ue7fa\ue7fb\ue7fc\ue7fd\ue7fe\ue7ff\ue800\ue801\ue802\ue803\ue804\ue805\ue806\ue807\ue808\ue809\ue80a\ue80b\ue80c\ue80d\ue80e\ue80f\ue810\ue811\ue812\ue813\ue814\ue815\ue816\ue817\ue818\ue819\ue81a\ue81b\ue81c\ue81d\ue81e\ue81f\ue820\ue821\ue822\ue823\ue824\ue825\ue826\ue827\ue828\ue829\ue82a\ue82b\ue82c\ue82d\ue82e\ue82f\ue830\ue831\ue832\ue833\ue834\ue835\ue836\ue837\ue838\ue839\ue83a\ue83b\ue83c\ue83d\ue83e\ue83f\ue840\ue841\ue842\ue843\ue844\ue845\ue846\ue847\ue848\ue849\ue84a\ue84b\ue84c\ue84d\ue84e\ue84f\ue850\ue851\ue852\ue853\ue854\ue855\ue856\ue857\ue858\ue859\ue85a\ue85b\ue85c\ue85d\ue85e\ue85f\ue860\ue861\ue862\ue863\ue864\ue865\ue866\ue867\ue868\ue869\ue86a\ue86b\ue86c\ue86d\ue86e\ue86f\ue870\ue871\ue872\ue873\ue874\ue875\ue876\ue877\ue878\ue879\ue87a\ue87b\ue87c\ue87d\ue87e\ue87f\ue880\ue881\ue882\ue883\ue884\ue885\ue886\ue887\ue888\ue889\ue88a\ue88b\ue88c\ue88d\ue88e\ue88f\ue890\ue891\ue892\ue893\ue894\ue895\ue896\ue897\ue898\ue899\ue89a\ue89b\ue89c\ue89d\ue89e\ue89f\ue8a0\ue8a1\ue8a2\ue8a3\ue8a4\ue8a5\ue8a6\ue8a7\ue8a8\ue8a9\ue8aa\ue8ab\ue8ac\ue8ad\ue8ae\ue8af\ue8b0\ue8b1\ue8b2\ue8b3\ue8b4\ue8b5\ue8b6\ue8b7\ue8b8\ue8b9\ue8ba\ue8bb\ue8bc\ue8bd\ue8be\ue8bf\ue8c0\ue8c1\ue8c2\ue8c3\ue8c4\ue8c5\ue8c6\ue8c7\ue8c8\ue8c9\ue8ca\ue8cb\ue8cc\ue8cd\ue8ce\ue8cf\ue8d0\ue8d1\ue8d2\ue8d3\ue8d4\ue8d5\ue8d6\ue8d7\ue8d8\ue8d9\ue8da\ue8db\ue8dc\ue8dd\ue8de\ue8df\ue8e0\ue8e1\ue8e2\ue8e3\ue8e4\ue8e5\ue8e6\ue8e7\ue8e8\ue8e9\ue8ea\ue8eb\ue8ec\ue8ed\ue8ee\ue8ef\ue8f0\ue8f1\ue8f2\ue8f3\ue8f4\ue8f5\ue8f6\ue8f7\ue8f8\ue8f9\ue8fa\ue8fb\ue8fc\ue8fd\ue8fe\ue8ff\ue900\ue901\ue902\ue903\ue904\ue905\ue906\ue907\ue908\ue909\ue90a\ue90b\ue90c\ue90d\ue90e\ue90f\ue910\ue911\ue912\ue913\ue914\ue915\ue916\ue917\ue918\ue919\ue91a\ue91b\ue91c\ue91d\ue91e\ue91f\ue920\ue921\ue922\ue923\ue924\ue925\ue926\ue927\ue928\ue929\ue92a\ue92b\ue92c\ue92d\ue92e\ue92f\ue930\ue931\ue932\ue933\ue934\ue935\ue936\ue937\ue938\ue939\ue93a\ue93b\ue93c\ue93d\ue93e\ue93f\ue940\ue941\ue942\ue943\ue944\ue945\ue946\ue947\ue948\ue949\ue94a\ue94b\ue94c\ue94d\ue94e\ue94f\ue950\ue951\ue952\ue953\ue954\ue955\ue956\ue957\ue958\ue959\ue95a\ue95b\ue95c\ue95d\ue95e\ue95f\ue960\ue961\ue962\ue963\ue964\ue965\ue966\ue967\ue968\ue969\ue96a\ue96b\ue96c\ue96d\ue96e\ue96f\ue970\ue971\ue972\ue973\ue974\ue975\ue976\ue977\ue978\ue979\ue97a\ue97b\ue97c\ue97d\ue97e\ue97f\ue980\ue981\ue982\ue983\ue984\ue985\ue986\ue987\ue988\ue989\ue98a\ue98b\ue98c\ue98d\ue98e\ue98f\ue990\ue991\ue992\ue993\ue994\ue995\ue996\ue997\ue998\ue999\ue99a\ue99b\ue99c\ue99d\ue99e\ue99f\ue9a0\ue9a1\ue9a2\ue9a3\ue9a4\ue9a5\ue9a6\ue9a7\ue9a8\ue9a9\ue9aa\ue9ab\ue9ac\ue9ad\ue9ae\ue9af\ue9b0\ue9b1\ue9b2\ue9b3\ue9b4\ue9b5\ue9b6\ue9b7\ue9b8\ue9b9\ue9ba\ue9bb\ue9bc\ue9bd\ue9be\ue9bf\ue9c0\ue9c1\ue9c2\ue9c3\ue9c4\ue9c5\ue9c6\ue9c7\ue9c8\ue9c9\ue9ca\ue9cb\ue9cc\ue9cd\ue9ce\ue9cf\ue9d0\ue9d1\ue9d2\ue9d3\ue9d4\ue9d5\ue9d6\ue9d7\ue9d8\ue9d9\ue9da\ue9db\ue9dc\ue9dd\ue9de\ue9df\ue9e0\ue9e1\ue9e2\ue9e3\ue9e4\ue9e5\ue9e6\ue9e7\ue9e8\ue9e9\ue9ea\ue9eb\ue9ec\ue9ed\ue9ee\ue9ef\ue9f0\ue9f1\ue9f2\ue9f3\ue9f4\ue9f5\ue9f6\ue9f7\ue9f8\ue9f9\ue9fa\ue9fb\ue9fc\ue9fd\ue9fe\ue9ff\uea00\uea01\uea02\uea03\uea04\uea05\uea06\uea07\uea08\uea09\uea0a\uea0b\uea0c\uea0d\uea0e\uea0f\uea10\uea11\uea12\uea13\uea14\uea15\uea16\uea17\uea18\uea19\uea1a\uea1b\uea1c\uea1d\uea1e\uea1f\uea20\uea21\uea22\uea23\uea24\uea25\uea26\uea27\uea28\uea29\uea2a\uea2b\uea2c\uea2d\uea2e\uea2f\uea30\uea31\uea32\uea33\uea34\uea35\uea36\uea37\uea38\uea39\uea3a\uea3b\uea3c\uea3d\uea3e\uea3f\uea40\uea41\uea42\uea43\uea44\uea45\uea46\uea47\uea48\uea49\uea4a\uea4b\uea4c\uea4d\uea4e\uea4f\uea50\uea51\uea52\uea53\uea54\uea55\uea56\uea57\uea58\uea59\uea5a\uea5b\uea5c\uea5d\uea5e\uea5f\uea60\uea61\uea62\uea63\uea64\uea65\uea66\uea67\uea68\uea69\uea6a\uea6b\uea6c\uea6d\uea6e\uea6f\uea70\uea71\uea72\uea73\uea74\uea75\uea76\uea77\uea78\uea79\uea7a\uea7b\uea7c\uea7d\uea7e\uea7f\uea80\uea81\uea82\uea83\uea84\uea85\uea86\uea87\uea88\uea89\uea8a\uea8b\uea8c\uea8d\uea8e\uea8f\uea90\uea91\uea92\uea93\uea94\uea95\uea96\uea97\uea98\uea99\uea9a\uea9b\uea9c\uea9d\uea9e\uea9f\ueaa0\ueaa1\ueaa2\ueaa3\ueaa4\ueaa5\ueaa6\ueaa7\ueaa8\ueaa9\ueaaa\ueaab\ueaac\ueaad\ueaae\ueaaf\ueab0\ueab1\ueab2\ueab3\ueab4\ueab5\ueab6\ueab7\ueab8\ueab9\ueaba\ueabb\ueabc\ueabd\ueabe\ueabf\ueac0\ueac1\ueac2\ueac3\ueac4\ueac5\ueac6\ueac7\ueac8\ueac9\ueaca\ueacb\ueacc\ueacd\ueace\ueacf\uead0\uead1\uead2\uead3\uead4\uead5\uead6\uead7\uead8\uead9\ueada\ueadb\ueadc\ueadd\ueade\ueadf\ueae0\ueae1\ueae2\ueae3\ueae4\ueae5\ueae6\ueae7\ueae8\ueae9\ueaea\ueaeb\ueaec\ueaed\ueaee\ueaef\ueaf0\ueaf1\ueaf2\ueaf3\ueaf4\ueaf5\ueaf6\ueaf7\ueaf8\ueaf9\ueafa\ueafb\ueafc\ueafd\ueafe\ueaff\ueb00\ueb01\ueb02\ueb03\ueb04\ueb05\ueb06\ueb07\ueb08\ueb09\ueb0a\ueb0b\ueb0c\ueb0d\ueb0e\ueb0f\ueb10\ueb11\ueb12\ueb13\ueb14\ueb15\ueb16\ueb17\ueb18\ueb19\ueb1a\ueb1b\ueb1c\ueb1d\ueb1e\ueb1f\ueb20\ueb21\ueb22\ueb23\ueb24\ueb25\ueb26\ueb27\ueb28\ueb29\ueb2a\ueb2b\ueb2c\ueb2d\ueb2e\ueb2f\ueb30\ueb31\ueb32\ueb33\ueb34\ueb35\ueb36\ueb37\ueb38\ueb39\ueb3a\ueb3b\ueb3c\ueb3d\ueb3e\ueb3f\ueb40\ueb41\ueb42\ueb43\ueb44\ueb45\ueb46\ueb47\ueb48\ueb49\ueb4a\ueb4b\ueb4c\ueb4d\ueb4e\ueb4f\ueb50\ueb51\ueb52\ueb53\ueb54\ueb55\ueb56\ueb57\ueb58\ueb59\ueb5a\ueb5b\ueb5c\ueb5d\ueb5e\ueb5f\ueb60\ueb61\ueb62\ueb63\ueb64\ueb65\ueb66\ueb67\ueb68\ueb69\ueb6a\ueb6b\ueb6c\ueb6d\ueb6e\ueb6f\ueb70\ueb71\ueb72\ueb73\ueb74\ueb75\ueb76\ueb77\ueb78\ueb79\ueb7a\ueb7b\ueb7c\ueb7d\ueb7e\ueb7f\ueb80\ueb81\ueb82\ueb83\ueb84\ueb85\ueb86\ueb87\ueb88\ueb89\ueb8a\ueb8b\ueb8c\ueb8d\ueb8e\ueb8f\ueb90\ueb91\ueb92\ueb93\ueb94\ueb95\ueb96\ueb97\ueb98\ueb99\ueb9a\ueb9b\ueb9c\ueb9d\ueb9e\ueb9f\ueba0\ueba1\ueba2\ueba3\ueba4\ueba5\ueba6\ueba7\ueba8\ueba9\uebaa\uebab\uebac\uebad\uebae\uebaf\uebb0\uebb1\uebb2\uebb3\uebb4\uebb5\uebb6\uebb7\uebb8\uebb9\uebba\uebbb\uebbc\uebbd\uebbe\uebbf\uebc0\uebc1\uebc2\uebc3\uebc4\uebc5\uebc6\uebc7\uebc8\uebc9\uebca\uebcb\uebcc\uebcd\uebce\uebcf\uebd0\uebd1\uebd2\uebd3\uebd4\uebd5\uebd6\uebd7\uebd8\uebd9\uebda\uebdb\uebdc\uebdd\uebde\uebdf\uebe0\uebe1\uebe2\uebe3\uebe4\uebe5\uebe6\uebe7\uebe8\uebe9\uebea\uebeb\uebec\uebed\uebee\uebef\uebf0\uebf1\uebf2\uebf3\uebf4\uebf5\uebf6\uebf7\uebf8\uebf9\uebfa\uebfb\uebfc\uebfd\uebfe\uebff\uec00\uec01\uec02\uec03\uec04\uec05\uec06\uec07\uec08\uec09\uec0a\uec0b\uec0c\uec0d\uec0e\uec0f\uec10\uec11\uec12\uec13\uec14\uec15\uec16\uec17\uec18\uec19\uec1a\uec1b\uec1c\uec1d\uec1e\uec1f\uec20\uec21\uec22\uec23\uec24\uec25\uec26\uec27\uec28\uec29\uec2a\uec2b\uec2c\uec2d\uec2e\uec2f\uec30\uec31\uec32\uec33\uec34\uec35\uec36\uec37\uec38\uec39\uec3a\uec3b\uec3c\uec3d\uec3e\uec3f\uec40\uec41\uec42\uec43\uec44\uec45\uec46\uec47\uec48\uec49\uec4a\uec4b\uec4c\uec4d\uec4e\uec4f\uec50\uec51\uec52\uec53\uec54\uec55\uec56\uec57\uec58\uec59\uec5a\uec5b\uec5c\uec5d\uec5e\uec5f\uec60\uec61\uec62\uec63\uec64\uec65\uec66\uec67\uec68\uec69\uec6a\uec6b\uec6c\uec6d\uec6e\uec6f\uec70\uec71\uec72\uec73\uec74\uec75\uec76\uec77\uec78\uec79\uec7a\uec7b\uec7c\uec7d\uec7e\uec7f\uec80\uec81\uec82\uec83\uec84\uec85\uec86\uec87\uec88\uec89\uec8a\uec8b\uec8c\uec8d\uec8e\uec8f\uec90\uec91\uec92\uec93\uec94\uec95\uec96\uec97\uec98\uec99\uec9a\uec9b\uec9c\uec9d\uec9e\uec9f\ueca0\ueca1\ueca2\ueca3\ueca4\ueca5\ueca6\ueca7\ueca8\ueca9\uecaa\uecab\uecac\uecad\uecae\uecaf\uecb0\uecb1\uecb2\uecb3\uecb4\uecb5\uecb6\uecb7\uecb8\uecb9\uecba\uecbb\uecbc\uecbd\uecbe\uecbf\uecc0\uecc1\uecc2\uecc3\uecc4\uecc5\uecc6\uecc7\uecc8\uecc9\uecca\ueccb\ueccc\ueccd\uecce\ueccf\uecd0\uecd1\uecd2\uecd3\uecd4\uecd5\uecd6\uecd7\uecd8\uecd9\uecda\uecdb\uecdc\uecdd\uecde\uecdf\uece0\uece1\uece2\uece3\uece4\uece5\uece6\uece7\uece8\uece9\uecea\ueceb\uecec\ueced\uecee\uecef\uecf0\uecf1\uecf2\uecf3\uecf4\uecf5\uecf6\uecf7\uecf8\uecf9\uecfa\uecfb\uecfc\uecfd\uecfe\uecff\ued00\ued01\ued02\ued03\ued04\ued05\ued06\ued07\ued08\ued09\ued0a\ued0b\ued0c\ued0d\ued0e\ued0f\ued10\ued11\ued12\ued13\ued14\ued15\ued16\ued17\ued18\ued19\ued1a\ued1b\ued1c\ued1d\ued1e\ued1f\ued20\ued21\ued22\ued23\ued24\ued25\ued26\ued27\ued28\ued29\ued2a\ued2b\ued2c\ued2d\ued2e\ued2f\ued30\ued31\ued32\ued33\ued34\ued35\ued36\ued37\ued38\ued39\ued3a\ued3b\ued3c\ued3d\ued3e\ued3f\ued40\ued41\ued42\ued43\ued44\ued45\ued46\ued47\ued48\ued49\ued4a\ued4b\ued4c\ued4d\ued4e\ued4f\ued50\ued51\ued52\ued53\ued54\ued55\ued56\ued57\ued58\ued59\ued5a\ued5b\ued5c\ued5d\ued5e\ued5f\ued60\ued61\ued62\ued63\ued64\ued65\ued66\ued67\ued68\ued69\ued6a\ued6b\ued6c\ued6d\ued6e\ued6f\ued70\ued71\ued72\ued73\ued74\ued75\ued76\ued77\ued78\ued79\ued7a\ued7b\ued7c\ued7d\ued7e\ued7f\ued80\ued81\ued82\ued83\ued84\ued85\ued86\ued87\ued88\ued89\ued8a\ued8b\ued8c\ued8d\ued8e\ued8f\ued90\ued91\ued92\ued93\ued94\ued95\ued96\ued97\ued98\ued99\ued9a\ued9b\ued9c\ued9d\ued9e\ued9f\ueda0\ueda1\ueda2\ueda3\ueda4\ueda5\ueda6\ueda7\ueda8\ueda9\uedaa\uedab\uedac\uedad\uedae\uedaf\uedb0\uedb1\uedb2\uedb3\uedb4\uedb5\uedb6\uedb7\uedb8\uedb9\uedba\uedbb\uedbc\uedbd\uedbe\uedbf\uedc0\uedc1\uedc2\uedc3\uedc4\uedc5\uedc6\uedc7\uedc8\uedc9\uedca\uedcb\uedcc\uedcd\uedce\uedcf\uedd0\uedd1\uedd2\uedd3\uedd4\uedd5\uedd6\uedd7\uedd8\uedd9\uedda\ueddb\ueddc\ueddd\uedde\ueddf\uede0\uede1\uede2\uede3\uede4\uede5\uede6\uede7\uede8\uede9\uedea\uedeb\uedec\ueded\uedee\uedef\uedf0\uedf1\uedf2\uedf3\uedf4\uedf5\uedf6\uedf7\uedf8\uedf9\uedfa\uedfb\uedfc\uedfd\uedfe\uedff\uee00\uee01\uee02\uee03\uee04\uee05\uee06\uee07\uee08\uee09\uee0a\uee0b\uee0c\uee0d\uee0e\uee0f\uee10\uee11\uee12\uee13\uee14\uee15\uee16\uee17\uee18\uee19\uee1a\uee1b\uee1c\uee1d\uee1e\uee1f\uee20\uee21\uee22\uee23\uee24\uee25\uee26\uee27\uee28\uee29\uee2a\uee2b\uee2c\uee2d\uee2e\uee2f\uee30\uee31\uee32\uee33\uee34\uee35\uee36\uee37\uee38\uee39\uee3a\uee3b\uee3c\uee3d\uee3e\uee3f\uee40\uee41\uee42\uee43\uee44\uee45\uee46\uee47\uee48\uee49\uee4a\uee4b\uee4c\uee4d\uee4e\uee4f\uee50\uee51\uee52\uee53\uee54\uee55\uee56\uee57\uee58\uee59\uee5a\uee5b\uee5c\uee5d\uee5e\uee5f\uee60\uee61\uee62\uee63\uee64\uee65\uee66\uee67\uee68\uee69\uee6a\uee6b\uee6c\uee6d\uee6e\uee6f\uee70\uee71\uee72\uee73\uee74\uee75\uee76\uee77\uee78\uee79\uee7a\uee7b\uee7c\uee7d\uee7e\uee7f\uee80\uee81\uee82\uee83\uee84\uee85\uee86\uee87\uee88\uee89\uee8a\uee8b\uee8c\uee8d\uee8e\uee8f\uee90\uee91\uee92\uee93\uee94\uee95\uee96\uee97\uee98\uee99\uee9a\uee9b\uee9c\uee9d\uee9e\uee9f\ueea0\ueea1\ueea2\ueea3\ueea4\ueea5\ueea6\ueea7\ueea8\ueea9\ueeaa\ueeab\ueeac\ueead\ueeae\ueeaf\ueeb0\ueeb1\ueeb2\ueeb3\ueeb4\ueeb5\ueeb6\ueeb7\ueeb8\ueeb9\ueeba\ueebb\ueebc\ueebd\ueebe\ueebf\ueec0\ueec1\ueec2\ueec3\ueec4\ueec5\ueec6\ueec7\ueec8\ueec9\ueeca\ueecb\ueecc\ueecd\ueece\ueecf\ueed0\ueed1\ueed2\ueed3\ueed4\ueed5\ueed6\ueed7\ueed8\ueed9\ueeda\ueedb\ueedc\ueedd\ueede\ueedf\ueee0\ueee1\ueee2\ueee3\ueee4\ueee5\ueee6\ueee7\ueee8\ueee9\ueeea\ueeeb\ueeec\ueeed\ueeee\ueeef\ueef0\ueef1\ueef2\ueef3\ueef4\ueef5\ueef6\ueef7\ueef8\ueef9\ueefa\ueefb\ueefc\ueefd\ueefe\ueeff\uef00\uef01\uef02\uef03\uef04\uef05\uef06\uef07\uef08\uef09\uef0a\uef0b\uef0c\uef0d\uef0e\uef0f\uef10\uef11\uef12\uef13\uef14\uef15\uef16\uef17\uef18\uef19\uef1a\uef1b\uef1c\uef1d\uef1e\uef1f\uef20\uef21\uef22\uef23\uef24\uef25\uef26\uef27\uef28\uef29\uef2a\uef2b\uef2c\uef2d\uef2e\uef2f\uef30\uef31\uef32\uef33\uef34\uef35\uef36\uef37\uef38\uef39\uef3a\uef3b\uef3c\uef3d\uef3e\uef3f\uef40\uef41\uef42\uef43\uef44\uef45\uef46\uef47\uef48\uef49\uef4a\uef4b\uef4c\uef4d\uef4e\uef4f\uef50\uef51\uef52\uef53\uef54\uef55\uef56\uef57\uef58\uef59\uef5a\uef5b\uef5c\uef5d\uef5e\uef5f\uef60\uef61\uef62\uef63\uef64\uef65\uef66\uef67\uef68\uef69\uef6a\uef6b\uef6c\uef6d\uef6e\uef6f\uef70\uef71\uef72\uef73\uef74\uef75\uef76\uef77\uef78\uef79\uef7a\uef7b\uef7c\uef7d\uef7e\uef7f\uef80\uef81\uef82\uef83\uef84\uef85\uef86\uef87\uef88\uef89\uef8a\uef8b\uef8c\uef8d\uef8e\uef8f\uef90\uef91\uef92\uef93\uef94\uef95\uef96\uef97\uef98\uef99\uef9a\uef9b\uef9c\uef9d\uef9e\uef9f\uefa0\uefa1\uefa2\uefa3\uefa4\uefa5\uefa6\uefa7\uefa8\uefa9\uefaa\uefab\uefac\uefad\uefae\uefaf\uefb0\uefb1\uefb2\uefb3\uefb4\uefb5\uefb6\uefb7\uefb8\uefb9\uefba\uefbb\uefbc\uefbd\uefbe\uefbf\uefc0\uefc1\uefc2\uefc3\uefc4\uefc5\uefc6\uefc7\uefc8\uefc9\uefca\uefcb\uefcc\uefcd\uefce\uefcf\uefd0\uefd1\uefd2\uefd3\uefd4\uefd5\uefd6\uefd7\uefd8\uefd9\uefda\uefdb\uefdc\uefdd\uefde\uefdf\uefe0\uefe1\uefe2\uefe3\uefe4\uefe5\uefe6\uefe7\uefe8\uefe9\uefea\uefeb\uefec\uefed\uefee\uefef\ueff0\ueff1\ueff2\ueff3\ueff4\ueff5\ueff6\ueff7\ueff8\ueff9\ueffa\ueffb\ueffc\ueffd\ueffe\uefff\uf000\uf001\uf002\uf003\uf004\uf005\uf006\uf007\uf008\uf009\uf00a\uf00b\uf00c\uf00d\uf00e\uf00f\uf010\uf011\uf012\uf013\uf014\uf015\uf016\uf017\uf018\uf019\uf01a\uf01b\uf01c\uf01d\uf01e\uf01f\uf020\uf021\uf022\uf023\uf024\uf025\uf026\uf027\uf028\uf029\uf02a\uf02b\uf02c\uf02d\uf02e\uf02f\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf03a\uf03b\uf03c\uf03d\uf03e\uf03f\uf040\uf041\uf042\uf043\uf044\uf045\uf046\uf047\uf048\uf049\uf04a\uf04b\uf04c\uf04d\uf04e\uf04f\uf050\uf051\uf052\uf053\uf054\uf055\uf056\uf057\uf058\uf059\uf05a\uf05b\uf05c\uf05d\uf05e\uf05f\uf060\uf061\uf062\uf063\uf064\uf065\uf066\uf067\uf068\uf069\uf06a\uf06b\uf06c\uf06d\uf06e\uf06f\uf070\uf071\uf072\uf073\uf074\uf075\uf076\uf077\uf078\uf079\uf07a\uf07b\uf07c\uf07d\uf07e\uf07f\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf09e\uf09f\uf0a0\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a6\uf0a7\uf0a8\uf0a9\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0c7\uf0c8\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1\uf0d2\uf0d3\uf0d4\uf0d5\uf0d6\uf0d7\uf0d8\uf0d9\uf0da\uf0db\uf0dc\uf0dd\uf0de\uf0df\uf0e0\uf0e1\uf0e2\uf0e3\uf0e4\uf0e5\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0ec\uf0ed\uf0ee\uf0ef\uf0f0\uf0f1\uf0f2\uf0f3\uf0f4\uf0f5\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb\uf0fc\uf0fd\uf0fe\uf0ff\uf100\uf101\uf102\uf103\uf104\uf105\uf106\uf107\uf108\uf109\uf10a\uf10b\uf10c\uf10d\uf10e\uf10f\uf110\uf111\uf112\uf113\uf114\uf115\uf116\uf117\uf118\uf119\uf11a\uf11b\uf11c\uf11d\uf11e\uf11f\uf120\uf121\uf122\uf123\uf124\uf125\uf126\uf127\uf128\uf129\uf12a\uf12b\uf12c\uf12d\uf12e\uf12f\uf130\uf131\uf132\uf133\uf134\uf135\uf136\uf137\uf138\uf139\uf13a\uf13b\uf13c\uf13d\uf13e\uf13f\uf140\uf141\uf142\uf143\uf144\uf145\uf146\uf147\uf148\uf149\uf14a\uf14b\uf14c\uf14d\uf14e\uf14f\uf150\uf151\uf152\uf153\uf154\uf155\uf156\uf157\uf158\uf159\uf15a\uf15b\uf15c\uf15d\uf15e\uf15f\uf160\uf161\uf162\uf163\uf164\uf165\uf166\uf167\uf168\uf169\uf16a\uf16b\uf16c\uf16d\uf16e\uf16f\uf170\uf171\uf172\uf173\uf174\uf175\uf176\uf177\uf178\uf179\uf17a\uf17b\uf17c\uf17d\uf17e\uf17f\uf180\uf181\uf182\uf183\uf184\uf185\uf186\uf187\uf188\uf189\uf18a\uf18b\uf18c\uf18d\uf18e\uf18f\uf190\uf191\uf192\uf193\uf194\uf195\uf196\uf197\uf198\uf199\uf19a\uf19b\uf19c\uf19d\uf19e\uf19f\uf1a0\uf1a1\uf1a2\uf1a3\uf1a4\uf1a5\uf1a6\uf1a7\uf1a8\uf1a9\uf1aa\uf1ab\uf1ac\uf1ad\uf1ae\uf1af\uf1b0\uf1b1\uf1b2\uf1b3\uf1b4\uf1b5\uf1b6\uf1b7\uf1b8\uf1b9\uf1ba\uf1bb\uf1bc\uf1bd\uf1be\uf1bf\uf1c0\uf1c1\uf1c2\uf1c3\uf1c4\uf1c5\uf1c6\uf1c7\uf1c8\uf1c9\uf1ca\uf1cb\uf1cc\uf1cd\uf1ce\uf1cf\uf1d0\uf1d1\uf1d2\uf1d3\uf1d4\uf1d5\uf1d6\uf1d7\uf1d8\uf1d9\uf1da\uf1db\uf1dc\uf1dd\uf1de\uf1df\uf1e0\uf1e1\uf1e2\uf1e3\uf1e4\uf1e5\uf1e6\uf1e7\uf1e8\uf1e9\uf1ea\uf1eb\uf1ec\uf1ed\uf1ee\uf1ef\uf1f0\uf1f1\uf1f2\uf1f3\uf1f4\uf1f5\uf1f6\uf1f7\uf1f8\uf1f9\uf1fa\uf1fb\uf1fc\uf1fd\uf1fe\uf1ff\uf200\uf201\uf202\uf203\uf204\uf205\uf206\uf207\uf208\uf209\uf20a\uf20b\uf20c\uf20d\uf20e\uf20f\uf210\uf211\uf212\uf213\uf214\uf215\uf216\uf217\uf218\uf219\uf21a\uf21b\uf21c\uf21d\uf21e\uf21f\uf220\uf221\uf222\uf223\uf224\uf225\uf226\uf227\uf228\uf229\uf22a\uf22b\uf22c\uf22d\uf22e\uf22f\uf230\uf231\uf232\uf233\uf234\uf235\uf236\uf237\uf238\uf239\uf23a\uf23b\uf23c\uf23d\uf23e\uf23f\uf240\uf241\uf242\uf243\uf244\uf245\uf246\uf247\uf248\uf249\uf24a\uf24b\uf24c\uf24d\uf24e\uf24f\uf250\uf251\uf252\uf253\uf254\uf255\uf256\uf257\uf258\uf259\uf25a\uf25b\uf25c\uf25d\uf25e\uf25f\uf260\uf261\uf262\uf263\uf264\uf265\uf266\uf267\uf268\uf269\uf26a\uf26b\uf26c\uf26d\uf26e\uf26f\uf270\uf271\uf272\uf273\uf274\uf275\uf276\uf277\uf278\uf279\uf27a\uf27b\uf27c\uf27d\uf27e\uf27f\uf280\uf281\uf282\uf283\uf284\uf285\uf286\uf287\uf288\uf289\uf28a\uf28b\uf28c\uf28d\uf28e\uf28f\uf290\uf291\uf292\uf293\uf294\uf295\uf296\uf297\uf298\uf299\uf29a\uf29b\uf29c\uf29d\uf29e\uf29f\uf2a0\uf2a1\uf2a2\uf2a3\uf2a4\uf2a5\uf2a6\uf2a7\uf2a8\uf2a9\uf2aa\uf2ab\uf2ac\uf2ad\uf2ae\uf2af\uf2b0\uf2b1\uf2b2\uf2b3\uf2b4\uf2b5\uf2b6\uf2b7\uf2b8\uf2b9\uf2ba\uf2bb\uf2bc\uf2bd\uf2be\uf2bf\uf2c0\uf2c1\uf2c2\uf2c3\uf2c4\uf2c5\uf2c6\uf2c7\uf2c8\uf2c9\uf2ca\uf2cb\uf2cc\uf2cd\uf2ce\uf2cf\uf2d0\uf2d1\uf2d2\uf2d3\uf2d4\uf2d5\uf2d6\uf2d7\uf2d8\uf2d9\uf2da\uf2db\uf2dc\uf2dd\uf2de\uf2df\uf2e0\uf2e1\uf2e2\uf2e3\uf2e4\uf2e5\uf2e6\uf2e7\uf2e8\uf2e9\uf2ea\uf2eb\uf2ec\uf2ed\uf2ee\uf2ef\uf2f0\uf2f1\uf2f2\uf2f3\uf2f4\uf2f5\uf2f6\uf2f7\uf2f8\uf2f9\uf2fa\uf2fb\uf2fc\uf2fd\uf2fe\uf2ff\uf300\uf301\uf302\uf303\uf304\uf305\uf306\uf307\uf308\uf309\uf30a\uf30b\uf30c\uf30d\uf30e\uf30f\uf310\uf311\uf312\uf313\uf314\uf315\uf316\uf317\uf318\uf319\uf31a\uf31b\uf31c\uf31d\uf31e\uf31f\uf320\uf321\uf322\uf323\uf324\uf325\uf326\uf327\uf328\uf329\uf32a\uf32b\uf32c\uf32d\uf32e\uf32f\uf330\uf331\uf332\uf333\uf334\uf335\uf336\uf337\uf338\uf339\uf33a\uf33b\uf33c\uf33d\uf33e\uf33f\uf340\uf341\uf342\uf343\uf344\uf345\uf346\uf347\uf348\uf349\uf34a\uf34b\uf34c\uf34d\uf34e\uf34f\uf350\uf351\uf352\uf353\uf354\uf355\uf356\uf357\uf358\uf359\uf35a\uf35b\uf35c\uf35d\uf35e\uf35f\uf360\uf361\uf362\uf363\uf364\uf365\uf366\uf367\uf368\uf369\uf36a\uf36b\uf36c\uf36d\uf36e\uf36f\uf370\uf371\uf372\uf373\uf374\uf375\uf376\uf377\uf378\uf379\uf37a\uf37b\uf37c\uf37d\uf37e\uf37f\uf380\uf381\uf382\uf383\uf384\uf385\uf386\uf387\uf388\uf389\uf38a\uf38b\uf38c\uf38d\uf38e\uf38f\uf390\uf391\uf392\uf393\uf394\uf395\uf396\uf397\uf398\uf399\uf39a\uf39b\uf39c\uf39d\uf39e\uf39f\uf3a0\uf3a1\uf3a2\uf3a3\uf3a4\uf3a5\uf3a6\uf3a7\uf3a8\uf3a9\uf3aa\uf3ab\uf3ac\uf3ad\uf3ae\uf3af\uf3b0\uf3b1\uf3b2\uf3b3\uf3b4\uf3b5\uf3b6\uf3b7\uf3b8\uf3b9\uf3ba\uf3bb\uf3bc\uf3bd\uf3be\uf3bf\uf3c0\uf3c1\uf3c2\uf3c3\uf3c4\uf3c5\uf3c6\uf3c7\uf3c8\uf3c9\uf3ca\uf3cb\uf3cc\uf3cd\uf3ce\uf3cf\uf3d0\uf3d1\uf3d2\uf3d3\uf3d4\uf3d5\uf3d6\uf3d7\uf3d8\uf3d9\uf3da\uf3db\uf3dc\uf3dd\uf3de\uf3df\uf3e0\uf3e1\uf3e2\uf3e3\uf3e4\uf3e5\uf3e6\uf3e7\uf3e8\uf3e9\uf3ea\uf3eb\uf3ec\uf3ed\uf3ee\uf3ef\uf3f0\uf3f1\uf3f2\uf3f3\uf3f4\uf3f5\uf3f6\uf3f7\uf3f8\uf3f9\uf3fa\uf3fb\uf3fc\uf3fd\uf3fe\uf3ff\uf400\uf401\uf402\uf403\uf404\uf405\uf406\uf407\uf408\uf409\uf40a\uf40b\uf40c\uf40d\uf40e\uf40f\uf410\uf411\uf412\uf413\uf414\uf415\uf416\uf417\uf418\uf419\uf41a\uf41b\uf41c\uf41d\uf41e\uf41f\uf420\uf421\uf422\uf423\uf424\uf425\uf426\uf427\uf428\uf429\uf42a\uf42b\uf42c\uf42d\uf42e\uf42f\uf430\uf431\uf432\uf433\uf434\uf435\uf436\uf437\uf438\uf439\uf43a\uf43b\uf43c\uf43d\uf43e\uf43f\uf440\uf441\uf442\uf443\uf444\uf445\uf446\uf447\uf448\uf449\uf44a\uf44b\uf44c\uf44d\uf44e\uf44f\uf450\uf451\uf452\uf453\uf454\uf455\uf456\uf457\uf458\uf459\uf45a\uf45b\uf45c\uf45d\uf45e\uf45f\uf460\uf461\uf462\uf463\uf464\uf465\uf466\uf467\uf468\uf469\uf46a\uf46b\uf46c\uf46d\uf46e\uf46f\uf470\uf471\uf472\uf473\uf474\uf475\uf476\uf477\uf478\uf479\uf47a\uf47b\uf47c\uf47d\uf47e\uf47f\uf480\uf481\uf482\uf483\uf484\uf485\uf486\uf487\uf488\uf489\uf48a\uf48b\uf48c\uf48d\uf48e\uf48f\uf490\uf491\uf492\uf493\uf494\uf495\uf496\uf497\uf498\uf499\uf49a\uf49b\uf49c\uf49d\uf49e\uf49f\uf4a0\uf4a1\uf4a2\uf4a3\uf4a4\uf4a5\uf4a6\uf4a7\uf4a8\uf4a9\uf4aa\uf4ab\uf4ac\uf4ad\uf4ae\uf4af\uf4b0\uf4b1\uf4b2\uf4b3\uf4b4\uf4b5\uf4b6\uf4b7\uf4b8\uf4b9\uf4ba\uf4bb\uf4bc\uf4bd\uf4be\uf4bf\uf4c0\uf4c1\uf4c2\uf4c3\uf4c4\uf4c5\uf4c6\uf4c7\uf4c8\uf4c9\uf4ca\uf4cb\uf4cc\uf4cd\uf4ce\uf4cf\uf4d0\uf4d1\uf4d2\uf4d3\uf4d4\uf4d5\uf4d6\uf4d7\uf4d8\uf4d9\uf4da\uf4db\uf4dc\uf4dd\uf4de\uf4df\uf4e0\uf4e1\uf4e2\uf4e3\uf4e4\uf4e5\uf4e6\uf4e7\uf4e8\uf4e9\uf4ea\uf4eb\uf4ec\uf4ed\uf4ee\uf4ef\uf4f0\uf4f1\uf4f2\uf4f3\uf4f4\uf4f5\uf4f6\uf4f7\uf4f8\uf4f9\uf4fa\uf4fb\uf4fc\uf4fd\uf4fe\uf4ff\uf500\uf501\uf502\uf503\uf504\uf505\uf506\uf507\uf508\uf509\uf50a\uf50b\uf50c\uf50d\uf50e\uf50f\uf510\uf511\uf512\uf513\uf514\uf515\uf516\uf517\uf518\uf519\uf51a\uf51b\uf51c\uf51d\uf51e\uf51f\uf520\uf521\uf522\uf523\uf524\uf525\uf526\uf527\uf528\uf529\uf52a\uf52b\uf52c\uf52d\uf52e\uf52f\uf530\uf531\uf532\uf533\uf534\uf535\uf536\uf537\uf538\uf539\uf53a\uf53b\uf53c\uf53d\uf53e\uf53f\uf540\uf541\uf542\uf543\uf544\uf545\uf546\uf547\uf548\uf549\uf54a\uf54b\uf54c\uf54d\uf54e\uf54f\uf550\uf551\uf552\uf553\uf554\uf555\uf556\uf557\uf558\uf559\uf55a\uf55b\uf55c\uf55d\uf55e\uf55f\uf560\uf561\uf562\uf563\uf564\uf565\uf566\uf567\uf568\uf569\uf56a\uf56b\uf56c\uf56d\uf56e\uf56f\uf570\uf571\uf572\uf573\uf574\uf575\uf576\uf577\uf578\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf582\uf583\uf584\uf585\uf586\uf587\uf588\uf589\uf58a\uf58b\uf58c\uf58d\uf58e\uf58f\uf590\uf591\uf592\uf593\uf594\uf595\uf596\uf597\uf598\uf599\uf59a\uf59b\uf59c\uf59d\uf59e\uf59f\uf5a0\uf5a1\uf5a2\uf5a3\uf5a4\uf5a5\uf5a6\uf5a7\uf5a8\uf5a9\uf5aa\uf5ab\uf5ac\uf5ad\uf5ae\uf5af\uf5b0\uf5b1\uf5b2\uf5b3\uf5b4\uf5b5\uf5b6\uf5b7\uf5b8\uf5b9\uf5ba\uf5bb\uf5bc\uf5bd\uf5be\uf5bf\uf5c0\uf5c1\uf5c2\uf5c3\uf5c4\uf5c5\uf5c6\uf5c7\uf5c8\uf5c9\uf5ca\uf5cb\uf5cc\uf5cd\uf5ce\uf5cf\uf5d0\uf5d1\uf5d2\uf5d3\uf5d4\uf5d5\uf5d6\uf5d7\uf5d8\uf5d9\uf5da\uf5db\uf5dc\uf5dd\uf5de\uf5df\uf5e0\uf5e1\uf5e2\uf5e3\uf5e4\uf5e5\uf5e6\uf5e7\uf5e8\uf5e9\uf5ea\uf5eb\uf5ec\uf5ed\uf5ee\uf5ef\uf5f0\uf5f1\uf5f2\uf5f3\uf5f4\uf5f5\uf5f6\uf5f7\uf5f8\uf5f9\uf5fa\uf5fb\uf5fc\uf5fd\uf5fe\uf5ff\uf600\uf601\uf602\uf603\uf604\uf605\uf606\uf607\uf608\uf609\uf60a\uf60b\uf60c\uf60d\uf60e\uf60f\uf610\uf611\uf612\uf613\uf614\uf615\uf616\uf617\uf618\uf619\uf61a\uf61b\uf61c\uf61d\uf61e\uf61f\uf620\uf621\uf622\uf623\uf624\uf625\uf626\uf627\uf628\uf629\uf62a\uf62b\uf62c\uf62d\uf62e\uf62f\uf630\uf631\uf632\uf633\uf634\uf635\uf636\uf637\uf638\uf639\uf63a\uf63b\uf63c\uf63d\uf63e\uf63f\uf640\uf641\uf642\uf643\uf644\uf645\uf646\uf647\uf648\uf649\uf64a\uf64b\uf64c\uf64d\uf64e\uf64f\uf650\uf651\uf652\uf653\uf654\uf655\uf656\uf657\uf658\uf659\uf65a\uf65b\uf65c\uf65d\uf65e\uf65f\uf660\uf661\uf662\uf663\uf664\uf665\uf666\uf667\uf668\uf669\uf66a\uf66b\uf66c\uf66d\uf66e\uf66f\uf670\uf671\uf672\uf673\uf674\uf675\uf676\uf677\uf678\uf679\uf67a\uf67b\uf67c\uf67d\uf67e\uf67f\uf680\uf681\uf682\uf683\uf684\uf685\uf686\uf687\uf688\uf689\uf68a\uf68b\uf68c\uf68d\uf68e\uf68f\uf690\uf691\uf692\uf693\uf694\uf695\uf696\uf697\uf698\uf699\uf69a\uf69b\uf69c\uf69d\uf69e\uf69f\uf6a0\uf6a1\uf6a2\uf6a3\uf6a4\uf6a5\uf6a6\uf6a7\uf6a8\uf6a9\uf6aa\uf6ab\uf6ac\uf6ad\uf6ae\uf6af\uf6b0\uf6b1\uf6b2\uf6b3\uf6b4\uf6b5\uf6b6\uf6b7\uf6b8\uf6b9\uf6ba\uf6bb\uf6bc\uf6bd\uf6be\uf6bf\uf6c0\uf6c1\uf6c2\uf6c3\uf6c4\uf6c5\uf6c6\uf6c7\uf6c8\uf6c9\uf6ca\uf6cb\uf6cc\uf6cd\uf6ce\uf6cf\uf6d0\uf6d1\uf6d2\uf6d3\uf6d4\uf6d5\uf6d6\uf6d7\uf6d8\uf6d9\uf6da\uf6db\uf6dc\uf6dd\uf6de\uf6df\uf6e0\uf6e1\uf6e2\uf6e3\uf6e4\uf6e5\uf6e6\uf6e7\uf6e8\uf6e9\uf6ea\uf6eb\uf6ec\uf6ed\uf6ee\uf6ef\uf6f0\uf6f1\uf6f2\uf6f3\uf6f4\uf6f5\uf6f6\uf6f7\uf6f8\uf6f9\uf6fa\uf6fb\uf6fc\uf6fd\uf6fe\uf6ff\uf700\uf701\uf702\uf703\uf704\uf705\uf706\uf707\uf708\uf709\uf70a\uf70b\uf70c\uf70d\uf70e\uf70f\uf710\uf711\uf712\uf713\uf714\uf715\uf716\uf717\uf718\uf719\uf71a\uf71b\uf71c\uf71d\uf71e\uf71f\uf720\uf721\uf722\uf723\uf724\uf725\uf726\uf727\uf728\uf729\uf72a\uf72b\uf72c\uf72d\uf72e\uf72f\uf730\uf731\uf732\uf733\uf734\uf735\uf736\uf737\uf738\uf739\uf73a\uf73b\uf73c\uf73d\uf73e\uf73f\uf740\uf741\uf742\uf743\uf744\uf745\uf746\uf747\uf748\uf749\uf74a\uf74b\uf74c\uf74d\uf74e\uf74f\uf750\uf751\uf752\uf753\uf754\uf755\uf756\uf757\uf758\uf759\uf75a\uf75b\uf75c\uf75d\uf75e\uf75f\uf760\uf761\uf762\uf763\uf764\uf765\uf766\uf767\uf768\uf769\uf76a\uf76b\uf76c\uf76d\uf76e\uf76f\uf770\uf771\uf772\uf773\uf774\uf775\uf776\uf777\uf778\uf779\uf77a\uf77b\uf77c\uf77d\uf77e\uf77f\uf780\uf781\uf782\uf783\uf784\uf785\uf786\uf787\uf788\uf789\uf78a\uf78b\uf78c\uf78d\uf78e\uf78f\uf790\uf791\uf792\uf793\uf794\uf795\uf796\uf797\uf798\uf799\uf79a\uf79b\uf79c\uf79d\uf79e\uf79f\uf7a0\uf7a1\uf7a2\uf7a3\uf7a4\uf7a5\uf7a6\uf7a7\uf7a8\uf7a9\uf7aa\uf7ab\uf7ac\uf7ad\uf7ae\uf7af\uf7b0\uf7b1\uf7b2\uf7b3\uf7b4\uf7b5\uf7b6\uf7b7\uf7b8\uf7b9\uf7ba\uf7bb\uf7bc\uf7bd\uf7be\uf7bf\uf7c0\uf7c1\uf7c2\uf7c3\uf7c4\uf7c5\uf7c6\uf7c7\uf7c8\uf7c9\uf7ca\uf7cb\uf7cc\uf7cd\uf7ce\uf7cf\uf7d0\uf7d1\uf7d2\uf7d3\uf7d4\uf7d5\uf7d6\uf7d7\uf7d8\uf7d9\uf7da\uf7db\uf7dc\uf7dd\uf7de\uf7df\uf7e0\uf7e1\uf7e2\uf7e3\uf7e4\uf7e5\uf7e6\uf7e7\uf7e8\uf7e9\uf7ea\uf7eb\uf7ec\uf7ed\uf7ee\uf7ef\uf7f0\uf7f1\uf7f2\uf7f3\uf7f4\uf7f5\uf7f6\uf7f7\uf7f8\uf7f9\uf7fa\uf7fb\uf7fc\uf7fd\uf7fe\uf7ff\uf800\uf801\uf802\uf803\uf804\uf805\uf806\uf807\uf808\uf809\uf80a\uf80b\uf80c\uf80d\uf80e\uf80f\uf810\uf811\uf812\uf813\uf814\uf815\uf816\uf817\uf818\uf819\uf81a\uf81b\uf81c\uf81d\uf81e\uf81f\uf820\uf821\uf822\uf823\uf824\uf825\uf826\uf827\uf828\uf829\uf82a\uf82b\uf82c\uf82d\uf82e\uf82f\uf830\uf831\uf832\uf833\uf834\uf835\uf836\uf837\uf838\uf839\uf83a\uf83b\uf83c\uf83d\uf83e\uf83f\uf840\uf841\uf842\uf843\uf844\uf845\uf846\uf847\uf848\uf849\uf84a\uf84b\uf84c\uf84d\uf84e\uf84f\uf850\uf851\uf852\uf853\uf854\uf855\uf856\uf857\uf858\uf859\uf85a\uf85b\uf85c\uf85d\uf85e\uf85f\uf860\uf861\uf862\uf863\uf864\uf865\uf866\uf867\uf868\uf869\uf86a\uf86b\uf86c\uf86d\uf86e\uf86f\uf870\uf871\uf872\uf873\uf874\uf875\uf876\uf877\uf878\uf879\uf87a\uf87b\uf87c\uf87d\uf87e\uf87f\uf880\uf881\uf882\uf883\uf884\uf885\uf886\uf887\uf888\uf889\uf88a\uf88b\uf88c\uf88d\uf88e\uf88f\uf890\uf891\uf892\uf893\uf894\uf895\uf896\uf897\uf898\uf899\uf89a\uf89b\uf89c\uf89d\uf89e\uf89f\uf8a0\uf8a1\uf8a2\uf8a3\uf8a4\uf8a5\uf8a6\uf8a7\uf8a8\uf8a9\uf8aa\uf8ab\uf8ac\uf8ad\uf8ae\uf8af\uf8b0\uf8b1\uf8b2\uf8b3\uf8b4\uf8b5\uf8b6\uf8b7\uf8b8\uf8b9\uf8ba\uf8bb\uf8bc\uf8bd\uf8be\uf8bf\uf8c0\uf8c1\uf8c2\uf8c3\uf8c4\uf8c5\uf8c6\uf8c7\uf8c8\uf8c9\uf8ca\uf8cb\uf8cc\uf8cd\uf8ce\uf8cf\uf8d0\uf8d1\uf8d2\uf8d3\uf8d4\uf8d5\uf8d6\uf8d7\uf8d8\uf8d9\uf8da\uf8db\uf8dc\uf8dd\uf8de\uf8df\uf8e0\uf8e1\uf8e2\uf8e3\uf8e4\uf8e5\uf8e6\uf8e7\uf8e8\uf8e9\uf8ea\uf8eb\uf8ec\uf8ed\uf8ee\uf8ef\uf8f0\uf8f1\uf8f2\uf8f3\uf8f4\uf8f5\uf8f6\uf8f7\uf8f8\uf8f9\uf8fa\uf8fb\uf8fc\uf8fd\uf8fe\uf8ff'
+
+try:
+ Cs = eval(r"'\ud800\ud801\ud802\ud803\ud804\ud805\ud806\ud807\ud808\ud809\ud80a\ud80b\ud80c\ud80d\ud80e\ud80f\ud810\ud811\ud812\ud813\ud814\ud815\ud816\ud817\ud818\ud819\ud81a\ud81b\ud81c\ud81d\ud81e\ud81f\ud820\ud821\ud822\ud823\ud824\ud825\ud826\ud827\ud828\ud829\ud82a\ud82b\ud82c\ud82d\ud82e\ud82f\ud830\ud831\ud832\ud833\ud834\ud835\ud836\ud837\ud838\ud839\ud83a\ud83b\ud83c\ud83d\ud83e\ud83f\ud840\ud841\ud842\ud843\ud844\ud845\ud846\ud847\ud848\ud849\ud84a\ud84b\ud84c\ud84d\ud84e\ud84f\ud850\ud851\ud852\ud853\ud854\ud855\ud856\ud857\ud858\ud859\ud85a\ud85b\ud85c\ud85d\ud85e\ud85f\ud860\ud861\ud862\ud863\ud864\ud865\ud866\ud867\ud868\ud869\ud86a\ud86b\ud86c\ud86d\ud86e\ud86f\ud870\ud871\ud872\ud873\ud874\ud875\ud876\ud877\ud878\ud879\ud87a\ud87b\ud87c\ud87d\ud87e\ud87f\ud880\ud881\ud882\ud883\ud884\ud885\ud886\ud887\ud888\ud889\ud88a\ud88b\ud88c\ud88d\ud88e\ud88f\ud890\ud891\ud892\ud893\ud894\ud895\ud896\ud897\ud898\ud899\ud89a\ud89b\ud89c\ud89d\ud89e\ud89f\ud8a0\ud8a1\ud8a2\ud8a3\ud8a4\ud8a5\ud8a6\ud8a7\ud8a8\ud8a9\ud8aa\ud8ab\ud8ac\ud8ad\ud8ae\ud8af\ud8b0\ud8b1\ud8b2\ud8b3\ud8b4\ud8b5\ud8b6\ud8b7\ud8b8\ud8b9\ud8ba\ud8bb\ud8bc\ud8bd\ud8be\ud8bf\ud8c0\ud8c1\ud8c2\ud8c3\ud8c4\ud8c5\ud8c6\ud8c7\ud8c8\ud8c9\ud8ca\ud8cb\ud8cc\ud8cd\ud8ce\ud8cf\ud8d0\ud8d1\ud8d2\ud8d3\ud8d4\ud8d5\ud8d6\ud8d7\ud8d8\ud8d9\ud8da\ud8db\ud8dc\ud8dd\ud8de\ud8df\ud8e0\ud8e1\ud8e2\ud8e3\ud8e4\ud8e5\ud8e6\ud8e7\ud8e8\ud8e9\ud8ea\ud8eb\ud8ec\ud8ed\ud8ee\ud8ef\ud8f0\ud8f1\ud8f2\ud8f3\ud8f4\ud8f5\ud8f6\ud8f7\ud8f8\ud8f9\ud8fa\ud8fb\ud8fc\ud8fd\ud8fe\ud8ff\ud900\ud901\ud902\ud903\ud904\ud905\ud906\ud907\ud908\ud909\ud90a\ud90b\ud90c\ud90d\ud90e\ud90f\ud910\ud911\ud912\ud913\ud914\ud915\ud916\ud917\ud918\ud919\ud91a\ud91b\ud91c\ud91d\ud91e\ud91f\ud920\ud921\ud922\ud923\ud924\ud925\ud926\ud927\ud928\ud929\ud92a\ud92b\ud92c\ud92d\ud92e\ud92f\ud930\ud931\ud932\ud933\ud934\ud935\ud936\ud937\ud938\ud939\ud93a\ud93b\ud93c\ud93d\ud93e\ud93f\ud940\ud941\ud942\ud943\ud944\ud945\ud946\ud947\ud948\ud949\ud94a\ud94b\ud94c\ud94d\ud94e\ud94f\ud950\ud951\ud952\ud953\ud954\ud955\ud956\ud957\ud958\ud959\ud95a\ud95b\ud95c\ud95d\ud95e\ud95f\ud960\ud961\ud962\ud963\ud964\ud965\ud966\ud967\ud968\ud969\ud96a\ud96b\ud96c\ud96d\ud96e\ud96f\ud970\ud971\ud972\ud973\ud974\ud975\ud976\ud977\ud978\ud979\ud97a\ud97b\ud97c\ud97d\ud97e\ud97f\ud980\ud981\ud982\ud983\ud984\ud985\ud986\ud987\ud988\ud989\ud98a\ud98b\ud98c\ud98d\ud98e\ud98f\ud990\ud991\ud992\ud993\ud994\ud995\ud996\ud997\ud998\ud999\ud99a\ud99b\ud99c\ud99d\ud99e\ud99f\ud9a0\ud9a1\ud9a2\ud9a3\ud9a4\ud9a5\ud9a6\ud9a7\ud9a8\ud9a9\ud9aa\ud9ab\ud9ac\ud9ad\ud9ae\ud9af\ud9b0\ud9b1\ud9b2\ud9b3\ud9b4\ud9b5\ud9b6\ud9b7\ud9b8\ud9b9\ud9ba\ud9bb\ud9bc\ud9bd\ud9be\ud9bf\ud9c0\ud9c1\ud9c2\ud9c3\ud9c4\ud9c5\ud9c6\ud9c7\ud9c8\ud9c9\ud9ca\ud9cb\ud9cc\ud9cd\ud9ce\ud9cf\ud9d0\ud9d1\ud9d2\ud9d3\ud9d4\ud9d5\ud9d6\ud9d7\ud9d8\ud9d9\ud9da\ud9db\ud9dc\ud9dd\ud9de\ud9df\ud9e0\ud9e1\ud9e2\ud9e3\ud9e4\ud9e5\ud9e6\ud9e7\ud9e8\ud9e9\ud9ea\ud9eb\ud9ec\ud9ed\ud9ee\ud9ef\ud9f0\ud9f1\ud9f2\ud9f3\ud9f4\ud9f5\ud9f6\ud9f7\ud9f8\ud9f9\ud9fa\ud9fb\ud9fc\ud9fd\ud9fe\ud9ff\uda00\uda01\uda02\uda03\uda04\uda05\uda06\uda07\uda08\uda09\uda0a\uda0b\uda0c\uda0d\uda0e\uda0f\uda10\uda11\uda12\uda13\uda14\uda15\uda16\uda17\uda18\uda19\uda1a\uda1b\uda1c\uda1d\uda1e\uda1f\uda20\uda21\uda22\uda23\uda24\uda25\uda26\uda27\uda28\uda29\uda2a\uda2b\uda2c\uda2d\uda2e\uda2f\uda30\uda31\uda32\uda33\uda34\uda35\uda36\uda37\uda38\uda39\uda3a\uda3b\uda3c\uda3d\uda3e\uda3f\uda40\uda41\uda42\uda43\uda44\uda45\uda46\uda47\uda48\uda49\uda4a\uda4b\uda4c\uda4d\uda4e\uda4f\uda50\uda51\uda52\uda53\uda54\uda55\uda56\uda57\uda58\uda59\uda5a\uda5b\uda5c\uda5d\uda5e\uda5f\uda60\uda61\uda62\uda63\uda64\uda65\uda66\uda67\uda68\uda69\uda6a\uda6b\uda6c\uda6d\uda6e\uda6f\uda70\uda71\uda72\uda73\uda74\uda75\uda76\uda77\uda78\uda79\uda7a\uda7b\uda7c\uda7d\uda7e\uda7f\uda80\uda81\uda82\uda83\uda84\uda85\uda86\uda87\uda88\uda89\uda8a\uda8b\uda8c\uda8d\uda8e\uda8f\uda90\uda91\uda92\uda93\uda94\uda95\uda96\uda97\uda98\uda99\uda9a\uda9b\uda9c\uda9d\uda9e\uda9f\udaa0\udaa1\udaa2\udaa3\udaa4\udaa5\udaa6\udaa7\udaa8\udaa9\udaaa\udaab\udaac\udaad\udaae\udaaf\udab0\udab1\udab2\udab3\udab4\udab5\udab6\udab7\udab8\udab9\udaba\udabb\udabc\udabd\udabe\udabf\udac0\udac1\udac2\udac3\udac4\udac5\udac6\udac7\udac8\udac9\udaca\udacb\udacc\udacd\udace\udacf\udad0\udad1\udad2\udad3\udad4\udad5\udad6\udad7\udad8\udad9\udada\udadb\udadc\udadd\udade\udadf\udae0\udae1\udae2\udae3\udae4\udae5\udae6\udae7\udae8\udae9\udaea\udaeb\udaec\udaed\udaee\udaef\udaf0\udaf1\udaf2\udaf3\udaf4\udaf5\udaf6\udaf7\udaf8\udaf9\udafa\udafb\udafc\udafd\udafe\udaff\udb00\udb01\udb02\udb03\udb04\udb05\udb06\udb07\udb08\udb09\udb0a\udb0b\udb0c\udb0d\udb0e\udb0f\udb10\udb11\udb12\udb13\udb14\udb15\udb16\udb17\udb18\udb19\udb1a\udb1b\udb1c\udb1d\udb1e\udb1f\udb20\udb21\udb22\udb23\udb24\udb25\udb26\udb27\udb28\udb29\udb2a\udb2b\udb2c\udb2d\udb2e\udb2f\udb30\udb31\udb32\udb33\udb34\udb35\udb36\udb37\udb38\udb39\udb3a\udb3b\udb3c\udb3d\udb3e\udb3f\udb40\udb41\udb42\udb43\udb44\udb45\udb46\udb47\udb48\udb49\udb4a\udb4b\udb4c\udb4d\udb4e\udb4f\udb50\udb51\udb52\udb53\udb54\udb55\udb56\udb57\udb58\udb59\udb5a\udb5b\udb5c\udb5d\udb5e\udb5f\udb60\udb61\udb62\udb63\udb64\udb65\udb66\udb67\udb68\udb69\udb6a\udb6b\udb6c\udb6d\udb6e\udb6f\udb70\udb71\udb72\udb73\udb74\udb75\udb76\udb77\udb78\udb79\udb7a\udb7b\udb7c\udb7d\udb7e\udb7f\udb80\udb81\udb82\udb83\udb84\udb85\udb86\udb87\udb88\udb89\udb8a\udb8b\udb8c\udb8d\udb8e\udb8f\udb90\udb91\udb92\udb93\udb94\udb95\udb96\udb97\udb98\udb99\udb9a\udb9b\udb9c\udb9d\udb9e\udb9f\udba0\udba1\udba2\udba3\udba4\udba5\udba6\udba7\udba8\udba9\udbaa\udbab\udbac\udbad\udbae\udbaf\udbb0\udbb1\udbb2\udbb3\udbb4\udbb5\udbb6\udbb7\udbb8\udbb9\udbba\udbbb\udbbc\udbbd\udbbe\udbbf\udbc0\udbc1\udbc2\udbc3\udbc4\udbc5\udbc6\udbc7\udbc8\udbc9\udbca\udbcb\udbcc\udbcd\udbce\udbcf\udbd0\udbd1\udbd2\udbd3\udbd4\udbd5\udbd6\udbd7\udbd8\udbd9\udbda\udbdb\udbdc\udbdd\udbde\udbdf\udbe0\udbe1\udbe2\udbe3\udbe4\udbe5\udbe6\udbe7\udbe8\udbe9\udbea\udbeb\udbec\udbed\udbee\udbef\udbf0\udbf1\udbf2\udbf3\udbf4\udbf5\udbf6\udbf7\udbf8\udbf9\udbfa\udbfb\udbfc\udbfd\udbfe\U0010fc00\udc01\udc02\udc03\udc04\udc05\udc06\udc07\udc08\udc09\udc0a\udc0b\udc0c\udc0d\udc0e\udc0f\udc10\udc11\udc12\udc13\udc14\udc15\udc16\udc17\udc18\udc19\udc1a\udc1b\udc1c\udc1d\udc1e\udc1f\udc20\udc21\udc22\udc23\udc24\udc25\udc26\udc27\udc28\udc29\udc2a\udc2b\udc2c\udc2d\udc2e\udc2f\udc30\udc31\udc32\udc33\udc34\udc35\udc36\udc37\udc38\udc39\udc3a\udc3b\udc3c\udc3d\udc3e\udc3f\udc40\udc41\udc42\udc43\udc44\udc45\udc46\udc47\udc48\udc49\udc4a\udc4b\udc4c\udc4d\udc4e\udc4f\udc50\udc51\udc52\udc53\udc54\udc55\udc56\udc57\udc58\udc59\udc5a\udc5b\udc5c\udc5d\udc5e\udc5f\udc60\udc61\udc62\udc63\udc64\udc65\udc66\udc67\udc68\udc69\udc6a\udc6b\udc6c\udc6d\udc6e\udc6f\udc70\udc71\udc72\udc73\udc74\udc75\udc76\udc77\udc78\udc79\udc7a\udc7b\udc7c\udc7d\udc7e\udc7f\udc80\udc81\udc82\udc83\udc84\udc85\udc86\udc87\udc88\udc89\udc8a\udc8b\udc8c\udc8d\udc8e\udc8f\udc90\udc91\udc92\udc93\udc94\udc95\udc96\udc97\udc98\udc99\udc9a\udc9b\udc9c\udc9d\udc9e\udc9f\udca0\udca1\udca2\udca3\udca4\udca5\udca6\udca7\udca8\udca9\udcaa\udcab\udcac\udcad\udcae\udcaf\udcb0\udcb1\udcb2\udcb3\udcb4\udcb5\udcb6\udcb7\udcb8\udcb9\udcba\udcbb\udcbc\udcbd\udcbe\udcbf\udcc0\udcc1\udcc2\udcc3\udcc4\udcc5\udcc6\udcc7\udcc8\udcc9\udcca\udccb\udccc\udccd\udcce\udccf\udcd0\udcd1\udcd2\udcd3\udcd4\udcd5\udcd6\udcd7\udcd8\udcd9\udcda\udcdb\udcdc\udcdd\udcde\udcdf\udce0\udce1\udce2\udce3\udce4\udce5\udce6\udce7\udce8\udce9\udcea\udceb\udcec\udced\udcee\udcef\udcf0\udcf1\udcf2\udcf3\udcf4\udcf5\udcf6\udcf7\udcf8\udcf9\udcfa\udcfb\udcfc\udcfd\udcfe\udcff\udd00\udd01\udd02\udd03\udd04\udd05\udd06\udd07\udd08\udd09\udd0a\udd0b\udd0c\udd0d\udd0e\udd0f\udd10\udd11\udd12\udd13\udd14\udd15\udd16\udd17\udd18\udd19\udd1a\udd1b\udd1c\udd1d\udd1e\udd1f\udd20\udd21\udd22\udd23\udd24\udd25\udd26\udd27\udd28\udd29\udd2a\udd2b\udd2c\udd2d\udd2e\udd2f\udd30\udd31\udd32\udd33\udd34\udd35\udd36\udd37\udd38\udd39\udd3a\udd3b\udd3c\udd3d\udd3e\udd3f\udd40\udd41\udd42\udd43\udd44\udd45\udd46\udd47\udd48\udd49\udd4a\udd4b\udd4c\udd4d\udd4e\udd4f\udd50\udd51\udd52\udd53\udd54\udd55\udd56\udd57\udd58\udd59\udd5a\udd5b\udd5c\udd5d\udd5e\udd5f\udd60\udd61\udd62\udd63\udd64\udd65\udd66\udd67\udd68\udd69\udd6a\udd6b\udd6c\udd6d\udd6e\udd6f\udd70\udd71\udd72\udd73\udd74\udd75\udd76\udd77\udd78\udd79\udd7a\udd7b\udd7c\udd7d\udd7e\udd7f\udd80\udd81\udd82\udd83\udd84\udd85\udd86\udd87\udd88\udd89\udd8a\udd8b\udd8c\udd8d\udd8e\udd8f\udd90\udd91\udd92\udd93\udd94\udd95\udd96\udd97\udd98\udd99\udd9a\udd9b\udd9c\udd9d\udd9e\udd9f\udda0\udda1\udda2\udda3\udda4\udda5\udda6\udda7\udda8\udda9\uddaa\uddab\uddac\uddad\uddae\uddaf\uddb0\uddb1\uddb2\uddb3\uddb4\uddb5\uddb6\uddb7\uddb8\uddb9\uddba\uddbb\uddbc\uddbd\uddbe\uddbf\uddc0\uddc1\uddc2\uddc3\uddc4\uddc5\uddc6\uddc7\uddc8\uddc9\uddca\uddcb\uddcc\uddcd\uddce\uddcf\uddd0\uddd1\uddd2\uddd3\uddd4\uddd5\uddd6\uddd7\uddd8\uddd9\uddda\udddb\udddc\udddd\uddde\udddf\udde0\udde1\udde2\udde3\udde4\udde5\udde6\udde7\udde8\udde9\uddea\uddeb\uddec\udded\uddee\uddef\uddf0\uddf1\uddf2\uddf3\uddf4\uddf5\uddf6\uddf7\uddf8\uddf9\uddfa\uddfb\uddfc\uddfd\uddfe\uddff\ude00\ude01\ude02\ude03\ude04\ude05\ude06\ude07\ude08\ude09\ude0a\ude0b\ude0c\ude0d\ude0e\ude0f\ude10\ude11\ude12\ude13\ude14\ude15\ude16\ude17\ude18\ude19\ude1a\ude1b\ude1c\ude1d\ude1e\ude1f\ude20\ude21\ude22\ude23\ude24\ude25\ude26\ude27\ude28\ude29\ude2a\ude2b\ude2c\ude2d\ude2e\ude2f\ude30\ude31\ude32\ude33\ude34\ude35\ude36\ude37\ude38\ude39\ude3a\ude3b\ude3c\ude3d\ude3e\ude3f\ude40\ude41\ude42\ude43\ude44\ude45\ude46\ude47\ude48\ude49\ude4a\ude4b\ude4c\ude4d\ude4e\ude4f\ude50\ude51\ude52\ude53\ude54\ude55\ude56\ude57\ude58\ude59\ude5a\ude5b\ude5c\ude5d\ude5e\ude5f\ude60\ude61\ude62\ude63\ude64\ude65\ude66\ude67\ude68\ude69\ude6a\ude6b\ude6c\ude6d\ude6e\ude6f\ude70\ude71\ude72\ude73\ude74\ude75\ude76\ude77\ude78\ude79\ude7a\ude7b\ude7c\ude7d\ude7e\ude7f\ude80\ude81\ude82\ude83\ude84\ude85\ude86\ude87\ude88\ude89\ude8a\ude8b\ude8c\ude8d\ude8e\ude8f\ude90\ude91\ude92\ude93\ude94\ude95\ude96\ude97\ude98\ude99\ude9a\ude9b\ude9c\ude9d\ude9e\ude9f\udea0\udea1\udea2\udea3\udea4\udea5\udea6\udea7\udea8\udea9\udeaa\udeab\udeac\udead\udeae\udeaf\udeb0\udeb1\udeb2\udeb3\udeb4\udeb5\udeb6\udeb7\udeb8\udeb9\udeba\udebb\udebc\udebd\udebe\udebf\udec0\udec1\udec2\udec3\udec4\udec5\udec6\udec7\udec8\udec9\udeca\udecb\udecc\udecd\udece\udecf\uded0\uded1\uded2\uded3\uded4\uded5\uded6\uded7\uded8\uded9\udeda\udedb\udedc\udedd\udede\udedf\udee0\udee1\udee2\udee3\udee4\udee5\udee6\udee7\udee8\udee9\udeea\udeeb\udeec\udeed\udeee\udeef\udef0\udef1\udef2\udef3\udef4\udef5\udef6\udef7\udef8\udef9\udefa\udefb\udefc\udefd\udefe\udeff\udf00\udf01\udf02\udf03\udf04\udf05\udf06\udf07\udf08\udf09\udf0a\udf0b\udf0c\udf0d\udf0e\udf0f\udf10\udf11\udf12\udf13\udf14\udf15\udf16\udf17\udf18\udf19\udf1a\udf1b\udf1c\udf1d\udf1e\udf1f\udf20\udf21\udf22\udf23\udf24\udf25\udf26\udf27\udf28\udf29\udf2a\udf2b\udf2c\udf2d\udf2e\udf2f\udf30\udf31\udf32\udf33\udf34\udf35\udf36\udf37\udf38\udf39\udf3a\udf3b\udf3c\udf3d\udf3e\udf3f\udf40\udf41\udf42\udf43\udf44\udf45\udf46\udf47\udf48\udf49\udf4a\udf4b\udf4c\udf4d\udf4e\udf4f\udf50\udf51\udf52\udf53\udf54\udf55\udf56\udf57\udf58\udf59\udf5a\udf5b\udf5c\udf5d\udf5e\udf5f\udf60\udf61\udf62\udf63\udf64\udf65\udf66\udf67\udf68\udf69\udf6a\udf6b\udf6c\udf6d\udf6e\udf6f\udf70\udf71\udf72\udf73\udf74\udf75\udf76\udf77\udf78\udf79\udf7a\udf7b\udf7c\udf7d\udf7e\udf7f\udf80\udf81\udf82\udf83\udf84\udf85\udf86\udf87\udf88\udf89\udf8a\udf8b\udf8c\udf8d\udf8e\udf8f\udf90\udf91\udf92\udf93\udf94\udf95\udf96\udf97\udf98\udf99\udf9a\udf9b\udf9c\udf9d\udf9e\udf9f\udfa0\udfa1\udfa2\udfa3\udfa4\udfa5\udfa6\udfa7\udfa8\udfa9\udfaa\udfab\udfac\udfad\udfae\udfaf\udfb0\udfb1\udfb2\udfb3\udfb4\udfb5\udfb6\udfb7\udfb8\udfb9\udfba\udfbb\udfbc\udfbd\udfbe\udfbf\udfc0\udfc1\udfc2\udfc3\udfc4\udfc5\udfc6\udfc7\udfc8\udfc9\udfca\udfcb\udfcc\udfcd\udfce\udfcf\udfd0\udfd1\udfd2\udfd3\udfd4\udfd5\udfd6\udfd7\udfd8\udfd9\udfda\udfdb\udfdc\udfdd\udfde\udfdf\udfe0\udfe1\udfe2\udfe3\udfe4\udfe5\udfe6\udfe7\udfe8\udfe9\udfea\udfeb\udfec\udfed\udfee\udfef\udff0\udff1\udff2\udff3\udff4\udff5\udff6\udff7\udff8\udff9\udffa\udffb\udffc\udffd\udffe\udfff'")
+except UnicodeDecodeError:
+ Cs = '' # Jython can't handle isolated surrogates
+
+Ll = u'abcdefghijklmnopqrstuvwxyz\xaa\xb5\xba\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0221\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023c\u023f\u0240\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u02ae\u02af\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u03f8\u03fb\u03fc\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f7\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1d00\u1d01\u1d02\u1d03\u1d04\u1d05\u1d06\u1d07\u1d08\u1d09\u1d0a\u1d0b\u1d0c\u1d0d\u1d0e\u1d0f\u1d10\u1d11\u1d12\u1d13\u1d14\u1d15\u1d16\u1d17\u1d18\u1d19\u1d1a\u1d1b\u1d1c\u1d1d\u1d1e\u1d1f\u1d20\u1d21\u1d22\u1d23\u1d24\u1d25\u1d26\u1d27\u1d28\u1d29\u1d2a\u1d2b\u1d62\u1d63\u1d64\u1d65\u1d66\u1d67\u1d68\u1d69\u1d6a\u1d6b\u1d6c\u1d6d\u1d6e\u1d6f\u1d70\u1d71\u1d72\u1d73\u1d74\u1d75\u1d76\u1d77\u1d79\u1d7a\u1d7b\u1d7c\u1d7d\u1d7e\u1d7f\u1d80\u1d81\u1d82\u1d83\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a\u1d8b\u1d8c\u1d8d\u1d8e\u1d8f\u1d90\u1d91\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213c\u213d\u2146\u2147\u2148\u2149\u2c30\u2c31\u2c32\u2c33\u2c34\u2c35\u2c36\u2c37\u2c38\u2c39\u2c3a\u2c3b\u2c3c\u2c3d\u2c3e\u2c3f\u2c40\u2c41\u2c42\u2c43\u2c44\u2c45\u2c46\u2c47\u2c48\u2c49\u2c4a\u2c4b\u2c4c\u2c4d\u2c4e\u2c4f\u2c50\u2c51\u2c52\u2c53\u2c54\u2c55\u2c56\u2c57\u2c58\u2c59\u2c5a\u2c5b\u2c5c\u2c5d\u2c5e\u2c81\u2c83\u2c85\u2c87\u2c89\u2c8b\u2c8d\u2c8f\u2c91\u2c93\u2c95\u2c97\u2c99\u2c9b\u2c9d\u2c9f\u2ca1\u2ca3\u2ca5\u2ca7\u2ca9\u2cab\u2cad\u2caf\u2cb1\u2cb3\u2cb5\u2cb7\u2cb9\u2cbb\u2cbd\u2cbf\u2cc1\u2cc3\u2cc5\u2cc7\u2cc9\u2ccb\u2ccd\u2ccf\u2cd1\u2cd3\u2cd5\u2cd7\u2cd9\u2cdb\u2cdd\u2cdf\u2ce1\u2ce3\u2ce4\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d07\u2d08\u2d09\u2d0a\u2d0b\u2d0c\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d13\u2d14\u2d15\u2d16\u2d17\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d1f\u2d20\u2d21\u2d22\u2d23\u2d24\u2d25\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a'
+
+Lm = u'\u02b0\u02b1\u02b2\u02b3\u02b4\u02b5\u02b6\u02b7\u02b8\u02b9\u02ba\u02bb\u02bc\u02bd\u02be\u02bf\u02c0\u02c1\u02c6\u02c7\u02c8\u02c9\u02ca\u02cb\u02cc\u02cd\u02ce\u02cf\u02d0\u02d1\u02e0\u02e1\u02e2\u02e3\u02e4\u02ee\u037a\u0559\u0640\u06e5\u06e6\u0e46\u0ec6\u10fc\u17d7\u1843\u1d2c\u1d2d\u1d2e\u1d2f\u1d30\u1d31\u1d32\u1d33\u1d34\u1d35\u1d36\u1d37\u1d38\u1d39\u1d3a\u1d3b\u1d3c\u1d3d\u1d3e\u1d3f\u1d40\u1d41\u1d42\u1d43\u1d44\u1d45\u1d46\u1d47\u1d48\u1d49\u1d4a\u1d4b\u1d4c\u1d4d\u1d4e\u1d4f\u1d50\u1d51\u1d52\u1d53\u1d54\u1d55\u1d56\u1d57\u1d58\u1d59\u1d5a\u1d5b\u1d5c\u1d5d\u1d5e\u1d5f\u1d60\u1d61\u1d78\u1d9b\u1d9c\u1d9d\u1d9e\u1d9f\u1da0\u1da1\u1da2\u1da3\u1da4\u1da5\u1da6\u1da7\u1da8\u1da9\u1daa\u1dab\u1dac\u1dad\u1dae\u1daf\u1db0\u1db1\u1db2\u1db3\u1db4\u1db5\u1db6\u1db7\u1db8\u1db9\u1dba\u1dbb\u1dbc\u1dbd\u1dbe\u1dbf\u2090\u2091\u2092\u2093\u2094\u2d6f\u3005\u3031\u3032\u3033\u3034\u3035\u303b\u309d\u309e\u30fc\u30fd\u30fe\ua015\uff70\uff9e\uff9f'
+
+Lo = u'\u01bb\u01c0\u01c1\u01c2\u01c3\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\u05e4\u05e5\u05e6\u05e7\u05e8\u05e9\u05ea\u05f0\u05f1\u05f2\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u066e\u066f\u0671\u0672\u0673\u0674\u0675\u0676\u0677\u0678\u0679\u067a\u067b\u067c\u067d\u067e\u067f\u0680\u0681\u0682\u0683\u0684\u0685\u0686\u0687\u0688\u0689\u068a\u068b\u068c\u068d\u068e\u068f\u0690\u0691\u0692\u0693\u0694\u0695\u0696\u0697\u0698\u0699\u069a\u069b\u069c\u069d\u069e\u069f\u06a0\u06a1\u06a2\u06a3\u06a4\u06a5\u06a6\u06a7\u06a8\u06a9\u06aa\u06ab\u06ac\u06ad\u06ae\u06af\u06b0\u06b1\u06b2\u06b3\u06b4\u06b5\u06b6\u06b7\u06b8\u06b9\u06ba\u06bb\u06bc\u06bd\u06be\u06bf\u06c0\u06c1\u06c2\u06c3\u06c4\u06c5\u06c6\u06c7\u06c8\u06c9\u06ca\u06cb\u06cc\u06cd\u06ce\u06cf\u06d0\u06d1\u06d2\u06d3\u06d5\u06ee\u06ef\u06fa\u06fb\u06fc\u06ff\u0710\u0712\u0713\u0714\u0715\u0716\u0717\u0718\u0719\u071a\u071b\u071c\u071d\u071e\u071f\u0720\u0721\u0722\u0723\u0724\u0725\u0726\u0727\u0728\u0729\u072a\u072b\u072c\u072d\u072e\u072f\u074d\u074e\u074f\u0750\u0751\u0752\u0753\u0754\u0755\u0756\u0757\u0758\u0759\u075a\u075b\u075c\u075d\u075e\u075f\u0760\u0761\u0762\u0763\u0764\u0765\u0766\u0767\u0768\u0769\u076a\u076b\u076c\u076d\u0780\u0781\u0782\u0783\u0784\u0785\u0786\u0787\u0788\u0789\u078a\u078b\u078c\u078d\u078e\u078f\u0790\u0791\u0792\u0793\u0794\u0795\u0796\u0797\u0798\u0799\u079a\u079b\u079c\u079d\u079e\u079f\u07a0\u07a1\u07a2\u07a3\u07a4\u07a5\u07b1\u0904\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u090c\u090d\u090e\u090f\u0910\u0911\u0912\u0913\u0914\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u0929\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0931\u0932\u0933\u0934\u0935\u0936\u0937\u0938\u0939\u093d\u0950\u0958\u0959\u095a\u095b\u095c\u095d\u095e\u095f\u0960\u0961\u097d\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098c\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9\u09bd\u09ce\u09dc\u09dd\u09df\u09e0\u09e1\u09f0\u09f1\u0a05\u0a06\u0a07\u0a08\u0a09\u0a0a\u0a0f\u0a10\u0a13\u0a14\u0a15\u0a16\u0a17\u0a18\u0a19\u0a1a\u0a1b\u0a1c\u0a1d\u0a1e\u0a1f\u0a20\u0a21\u0a22\u0a23\u0a24\u0a25\u0a26\u0a27\u0a28\u0a2a\u0a2b\u0a2c\u0a2d\u0a2e\u0a2f\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59\u0a5a\u0a5b\u0a5c\u0a5e\u0a72\u0a73\u0a74\u0a85\u0a86\u0a87\u0a88\u0a89\u0a8a\u0a8b\u0a8c\u0a8d\u0a8f\u0a90\u0a91\u0a93\u0a94\u0a95\u0a96\u0a97\u0a98\u0a99\u0a9a\u0a9b\u0a9c\u0a9d\u0a9e\u0a9f\u0aa0\u0aa1\u0aa2\u0aa3\u0aa4\u0aa5\u0aa6\u0aa7\u0aa8\u0aaa\u0aab\u0aac\u0aad\u0aae\u0aaf\u0ab0\u0ab2\u0ab3\u0ab5\u0ab6\u0ab7\u0ab8\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05\u0b06\u0b07\u0b08\u0b09\u0b0a\u0b0b\u0b0c\u0b0f\u0b10\u0b13\u0b14\u0b15\u0b16\u0b17\u0b18\u0b19\u0b1a\u0b1b\u0b1c\u0b1d\u0b1e\u0b1f\u0b20\u0b21\u0b22\u0b23\u0b24\u0b25\u0b26\u0b27\u0b28\u0b2a\u0b2b\u0b2c\u0b2d\u0b2e\u0b2f\u0b30\u0b32\u0b33\u0b35\u0b36\u0b37\u0b38\u0b39\u0b3d\u0b5c\u0b5d\u0b5f\u0b60\u0b61\u0b71\u0b83\u0b85\u0b86\u0b87\u0b88\u0b89\u0b8a\u0b8e\u0b8f\u0b90\u0b92\u0b93\u0b94\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8\u0ba9\u0baa\u0bae\u0baf\u0bb0\u0bb1\u0bb2\u0bb3\u0bb4\u0bb5\u0bb6\u0bb7\u0bb8\u0bb9\u0c05\u0c06\u0c07\u0c08\u0c09\u0c0a\u0c0b\u0c0c\u0c0e\u0c0f\u0c10\u0c12\u0c13\u0c14\u0c15\u0c16\u0c17\u0c18\u0c19\u0c1a\u0c1b\u0c1c\u0c1d\u0c1e\u0c1f\u0c20\u0c21\u0c22\u0c23\u0c24\u0c25\u0c26\u0c27\u0c28\u0c2a\u0c2b\u0c2c\u0c2d\u0c2e\u0c2f\u0c30\u0c31\u0c32\u0c33\u0c35\u0c36\u0c37\u0c38\u0c39\u0c60\u0c61\u0c85\u0c86\u0c87\u0c88\u0c89\u0c8a\u0c8b\u0c8c\u0c8e\u0c8f\u0c90\u0c92\u0c93\u0c94\u0c95\u0c96\u0c97\u0c98\u0c99\u0c9a\u0c9b\u0c9c\u0c9d\u0c9e\u0c9f\u0ca0\u0ca1\u0ca2\u0ca3\u0ca4\u0ca5\u0ca6\u0ca7\u0ca8\u0caa\u0cab\u0cac\u0cad\u0cae\u0caf\u0cb0\u0cb1\u0cb2\u0cb3\u0cb5\u0cb6\u0cb7\u0cb8\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0d05\u0d06\u0d07\u0d08\u0d09\u0d0a\u0d0b\u0d0c\u0d0e\u0d0f\u0d10\u0d12\u0d13\u0d14\u0d15\u0d16\u0d17\u0d18\u0d19\u0d1a\u0d1b\u0d1c\u0d1d\u0d1e\u0d1f\u0d20\u0d21\u0d22\u0d23\u0d24\u0d25\u0d26\u0d27\u0d28\u0d2a\u0d2b\u0d2c\u0d2d\u0d2e\u0d2f\u0d30\u0d31\u0d32\u0d33\u0d34\u0d35\u0d36\u0d37\u0d38\u0d39\u0d60\u0d61\u0d85\u0d86\u0d87\u0d88\u0d89\u0d8a\u0d8b\u0d8c\u0d8d\u0d8e\u0d8f\u0d90\u0d91\u0d92\u0d93\u0d94\u0d95\u0d96\u0d9a\u0d9b\u0d9c\u0d9d\u0d9e\u0d9f\u0da0\u0da1\u0da2\u0da3\u0da4\u0da5\u0da6\u0da7\u0da8\u0da9\u0daa\u0dab\u0dac\u0dad\u0dae\u0daf\u0db0\u0db1\u0db3\u0db4\u0db5\u0db6\u0db7\u0db8\u0db9\u0dba\u0dbb\u0dbd\u0dc0\u0dc1\u0dc2\u0dc3\u0dc4\u0dc5\u0dc6\u0e01\u0e02\u0e03\u0e04\u0e05\u0e06\u0e07\u0e08\u0e09\u0e0a\u0e0b\u0e0c\u0e0d\u0e0e\u0e0f\u0e10\u0e11\u0e12\u0e13\u0e14\u0e15\u0e16\u0e17\u0e18\u0e19\u0e1a\u0e1b\u0e1c\u0e1d\u0e1e\u0e1f\u0e20\u0e21\u0e22\u0e23\u0e24\u0e25\u0e26\u0e27\u0e28\u0e29\u0e2a\u0e2b\u0e2c\u0e2d\u0e2e\u0e2f\u0e30\u0e32\u0e33\u0e40\u0e41\u0e42\u0e43\u0e44\u0e45\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94\u0e95\u0e96\u0e97\u0e99\u0e9a\u0e9b\u0e9c\u0e9d\u0e9e\u0e9f\u0ea1\u0ea2\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead\u0eae\u0eaf\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0\u0ec1\u0ec2\u0ec3\u0ec4\u0edc\u0edd\u0f00\u0f40\u0f41\u0f42\u0f43\u0f44\u0f45\u0f46\u0f47\u0f49\u0f4a\u0f4b\u0f4c\u0f4d\u0f4e\u0f4f\u0f50\u0f51\u0f52\u0f53\u0f54\u0f55\u0f56\u0f57\u0f58\u0f59\u0f5a\u0f5b\u0f5c\u0f5d\u0f5e\u0f5f\u0f60\u0f61\u0f62\u0f63\u0f64\u0f65\u0f66\u0f67\u0f68\u0f69\u0f6a\u0f88\u0f89\u0f8a\u0f8b\u1000\u1001\u1002\u1003\u1004\u1005\u1006\u1007\u1008\u1009\u100a\u100b\u100c\u100d\u100e\u100f\u1010\u1011\u1012\u1013\u1014\u1015\u1016\u1017\u1018\u1019\u101a\u101b\u101c\u101d\u101e\u101f\u1020\u1021\u1023\u1024\u1025\u1026\u1027\u1029\u102a\u1050\u1051\u1052\u1053\u1054\u1055\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6\u10f7\u10f8\u10f9\u10fa\u1100\u1101\u1102\u1103\u1104\u1105\u1106\u1107\u1108\u1109\u110a\u110b\u110c\u110d\u110e\u110f\u1110\u1111\u1112\u1113\u1114\u1115\u1116\u1117\u1118\u1119\u111a\u111b\u111c\u111d\u111e\u111f\u1120\u1121\u1122\u1123\u1124\u1125\u1126\u1127\u1128\u1129\u112a\u112b\u112c\u112d\u112e\u112f\u1130\u1131\u1132\u1133\u1134\u1135\u1136\u1137\u1138\u1139\u113a\u113b\u113c\u113d\u113e\u113f\u1140\u1141\u1142\u1143\u1144\u1145\u1146\u1147\u1148\u1149\u114a\u114b\u114c\u114d\u114e\u114f\u1150\u1151\u1152\u1153\u1154\u1155\u1156\u1157\u1158\u1159\u115f\u1160\u1161\u1162\u1163\u1164\u1165\u1166\u1167\u1168\u1169\u116a\u116b\u116c\u116d\u116e\u116f\u1170\u1171\u1172\u1173\u1174\u1175\u1176\u1177\u1178\u1179\u117a\u117b\u117c\u117d\u117e\u117f\u1180\u1181\u1182\u1183\u1184\u1185\u1186\u1187\u1188\u1189\u118a\u118b\u118c\u118d\u118e\u118f\u1190\u1191\u1192\u1193\u1194\u1195\u1196\u1197\u1198\u1199\u119a\u119b\u119c\u119d\u119e\u119f\u11a0\u11a1\u11a2\u11a8\u11a9\u11aa\u11ab\u11ac\u11ad\u11ae\u11af\u11b0\u11b1\u11b2\u11b3\u11b4\u11b5\u11b6\u11b7\u11b8\u11b9\u11ba\u11bb\u11bc\u11bd\u11be\u11bf\u11c0\u11c1\u11c2\u11c3\u11c4\u11c5\u11c6\u11c7\u11c8\u11c9\u11ca\u11cb\u11cc\u11cd\u11ce\u11cf\u11d0\u11d1\u11d2\u11d3\u11d4\u11d5\u11d6\u11d7\u11d8\u11d9\u11da\u11db\u11dc\u11dd\u11de\u11df\u11e0\u11e1\u11e2\u11e3\u11e4\u11e5\u11e6\u11e7\u11e8\u11e9\u11ea\u11eb\u11ec\u11ed\u11ee\u11ef\u11f0\u11f1\u11f2\u11f3\u11f4\u11f5\u11f6\u11f7\u11f8\u11f9\u1200\u1201\u1202\u1203\u1204\u1205\u1206\u1207\u1208\u1209\u120a\u120b\u120c\u120d\u120e\u120f\u1210\u1211\u1212\u1213\u1214\u1215\u1216\u1217\u1218\u1219\u121a\u121b\u121c\u121d\u121e\u121f\u1220\u1221\u1222\u1223\u1224\u1225\u1226\u1227\u1228\u1229\u122a\u122b\u122c\u122d\u122e\u122f\u1230\u1231\u1232\u1233\u1234\u1235\u1236\u1237\u1238\u1239\u123a\u123b\u123c\u123d\u123e\u123f\u1240\u1241\u1242\u1243\u1244\u1245\u1246\u1247\u1248\u124a\u124b\u124c\u124d\u1250\u1251\u1252\u1253\u1254\u1255\u1256\u1258\u125a\u125b\u125c\u125d\u1260\u1261\u1262\u1263\u1264\u1265\u1266\u1267\u1268\u1269\u126a\u126b\u126c\u126d\u126e\u126f\u1270\u1271\u1272\u1273\u1274\u1275\u1276\u1277\u1278\u1279\u127a\u127b\u127c\u127d\u127e\u127f\u1280\u1281\u1282\u1283\u1284\u1285\u1286\u1287\u1288\u128a\u128b\u128c\u128d\u1290\u1291\u1292\u1293\u1294\u1295\u1296\u1297\u1298\u1299\u129a\u129b\u129c\u129d\u129e\u129f\u12a0\u12a1\u12a2\u12a3\u12a4\u12a5\u12a6\u12a7\u12a8\u12a9\u12aa\u12ab\u12ac\u12ad\u12ae\u12af\u12b0\u12b2\u12b3\u12b4\u12b5\u12b8\u12b9\u12ba\u12bb\u12bc\u12bd\u12be\u12c0\u12c2\u12c3\u12c4\u12c5\u12c8\u12c9\u12ca\u12cb\u12cc\u12cd\u12ce\u12cf\u12d0\u12d1\u12d2\u12d3\u12d4\u12d5\u12d6\u12d8\u12d9\u12da\u12db\u12dc\u12dd\u12de\u12df\u12e0\u12e1\u12e2\u12e3\u12e4\u12e5\u12e6\u12e7\u12e8\u12e9\u12ea\u12eb\u12ec\u12ed\u12ee\u12ef\u12f0\u12f1\u12f2\u12f3\u12f4\u12f5\u12f6\u12f7\u12f8\u12f9\u12fa\u12fb\u12fc\u12fd\u12fe\u12ff\u1300\u1301\u1302\u1303\u1304\u1305\u1306\u1307\u1308\u1309\u130a\u130b\u130c\u130d\u130e\u130f\u1310\u1312\u1313\u1314\u1315\u1318\u1319\u131a\u131b\u131c\u131d\u131e\u131f\u1320\u1321\u1322\u1323\u1324\u1325\u1326\u1327\u1328\u1329\u132a\u132b\u132c\u132d\u132e\u132f\u1330\u1331\u1332\u1333\u1334\u1335\u1336\u1337\u1338\u1339\u133a\u133b\u133c\u133d\u133e\u133f\u1340\u1341\u1342\u1343\u1344\u1345\u1346\u1347\u1348\u1349\u134a\u134b\u134c\u134d\u134e\u134f\u1350\u1351\u1352\u1353\u1354\u1355\u1356\u1357\u1358\u1359\u135a\u1380\u1381\u1382\u1383\u1384\u1385\u1386\u1387\u1388\u1389\u138a\u138b\u138c\u138d\u138e\u138f\u13a0\u13a1\u13a2\u13a3\u13a4\u13a5\u13a6\u13a7\u13a8\u13a9\u13aa\u13ab\u13ac\u13ad\u13ae\u13af\u13b0\u13b1\u13b2\u13b3\u13b4\u13b5\u13b6\u13b7\u13b8\u13b9\u13ba\u13bb\u13bc\u13bd\u13be\u13bf\u13c0\u13c1\u13c2\u13c3\u13c4\u13c5\u13c6\u13c7\u13c8\u13c9\u13ca\u13cb\u13cc\u13cd\u13ce\u13cf\u13d0\u13d1\u13d2\u13d3\u13d4\u13d5\u13d6\u13d7\u13d8\u13d9\u13da\u13db\u13dc\u13dd\u13de\u13df\u13e0\u13e1\u13e2\u13e3\u13e4\u13e5\u13e6\u13e7\u13e8\u13e9\u13ea\u13eb\u13ec\u13ed\u13ee\u13ef\u13f0\u13f1\u13f2\u13f3\u13f4\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140a\u140b\u140c\u140d\u140e\u140f\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141a\u141b\u141c\u141d\u141e\u141f\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142a\u142b\u142c\u142d\u142e\u142f\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143a\u143b\u143c\u143d\u143e\u143f\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144a\u144b\u144c\u144d\u144e\u144f\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145a\u145b\u145c\u145d\u145e\u145f\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146a\u146b\u146c\u146d\u146e\u146f\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147a\u147b\u147c\u147d\u147e\u147f\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148a\u148b\u148c\u148d\u148e\u148f\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149a\u149b\u149c\u149d\u149e\u149f\u14a0\u14a1\u14a2\u14a3\u14a4\u14a5\u14a6\u14a7\u14a8\u14a9\u14aa\u14ab\u14ac\u14ad\u14ae\u14af\u14b0\u14b1\u14b2\u14b3\u14b4\u14b5\u14b6\u14b7\u14b8\u14b9\u14ba\u14bb\u14bc\u14bd\u14be\u14bf\u14c0\u14c1\u14c2\u14c3\u14c4\u14c5\u14c6\u14c7\u14c8\u14c9\u14ca\u14cb\u14cc\u14cd\u14ce\u14cf\u14d0\u14d1\u14d2\u14d3\u14d4\u14d5\u14d6\u14d7\u14d8\u14d9\u14da\u14db\u14dc\u14dd\u14de\u14df\u14e0\u14e1\u14e2\u14e3\u14e4\u14e5\u14e6\u14e7\u14e8\u14e9\u14ea\u14eb\u14ec\u14ed\u14ee\u14ef\u14f0\u14f1\u14f2\u14f3\u14f4\u14f5\u14f6\u14f7\u14f8\u14f9\u14fa\u14fb\u14fc\u14fd\u14fe\u14ff\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150a\u150b\u150c\u150d\u150e\u150f\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151a\u151b\u151c\u151d\u151e\u151f\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152a\u152b\u152c\u152d\u152e\u152f\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153a\u153b\u153c\u153d\u153e\u153f\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154a\u154b\u154c\u154d\u154e\u154f\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155a\u155b\u155c\u155d\u155e\u155f\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156a\u156b\u156c\u156d\u156e\u156f\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157a\u157b\u157c\u157d\u157e\u157f\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158a\u158b\u158c\u158d\u158e\u158f\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159a\u159b\u159c\u159d\u159e\u159f\u15a0\u15a1\u15a2\u15a3\u15a4\u15a5\u15a6\u15a7\u15a8\u15a9\u15aa\u15ab\u15ac\u15ad\u15ae\u15af\u15b0\u15b1\u15b2\u15b3\u15b4\u15b5\u15b6\u15b7\u15b8\u15b9\u15ba\u15bb\u15bc\u15bd\u15be\u15bf\u15c0\u15c1\u15c2\u15c3\u15c4\u15c5\u15c6\u15c7\u15c8\u15c9\u15ca\u15cb\u15cc\u15cd\u15ce\u15cf\u15d0\u15d1\u15d2\u15d3\u15d4\u15d5\u15d6\u15d7\u15d8\u15d9\u15da\u15db\u15dc\u15dd\u15de\u15df\u15e0\u15e1\u15e2\u15e3\u15e4\u15e5\u15e6\u15e7\u15e8\u15e9\u15ea\u15eb\u15ec\u15ed\u15ee\u15ef\u15f0\u15f1\u15f2\u15f3\u15f4\u15f5\u15f6\u15f7\u15f8\u15f9\u15fa\u15fb\u15fc\u15fd\u15fe\u15ff\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160a\u160b\u160c\u160d\u160e\u160f\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161a\u161b\u161c\u161d\u161e\u161f\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162a\u162b\u162c\u162d\u162e\u162f\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163a\u163b\u163c\u163d\u163e\u163f\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164a\u164b\u164c\u164d\u164e\u164f\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165a\u165b\u165c\u165d\u165e\u165f\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166a\u166b\u166c\u166f\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168a\u168b\u168c\u168d\u168e\u168f\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169a\u16a0\u16a1\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af\u16b0\u16b1\u16b2\u16b3\u16b4\u16b5\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170a\u170b\u170c\u170e\u170f\u1710\u1711\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172a\u172b\u172c\u172d\u172e\u172f\u1730\u1731\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174a\u174b\u174c\u174d\u174e\u174f\u1750\u1751\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176a\u176b\u176c\u176e\u176f\u1770\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178a\u178b\u178c\u178d\u178e\u178f\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179a\u179b\u179c\u179d\u179e\u179f\u17a0\u17a1\u17a2\u17a3\u17a4\u17a5\u17a6\u17a7\u17a8\u17a9\u17aa\u17ab\u17ac\u17ad\u17ae\u17af\u17b0\u17b1\u17b2\u17b3\u17dc\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182a\u182b\u182c\u182d\u182e\u182f\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183a\u183b\u183c\u183d\u183e\u183f\u1840\u1841\u1842\u1844\u1845\u1846\u1847\u1848\u1849\u184a\u184b\u184c\u184d\u184e\u184f\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185a\u185b\u185c\u185d\u185e\u185f\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186a\u186b\u186c\u186d\u186e\u186f\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188a\u188b\u188c\u188d\u188e\u188f\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189a\u189b\u189c\u189d\u189e\u189f\u18a0\u18a1\u18a2\u18a3\u18a4\u18a5\u18a6\u18a7\u18a8\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190a\u190b\u190c\u190d\u190e\u190f\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191a\u191b\u191c\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195a\u195b\u195c\u195d\u195e\u195f\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196a\u196b\u196c\u196d\u1970\u1971\u1972\u1973\u1974\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198a\u198b\u198c\u198d\u198e\u198f\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199a\u199b\u199c\u199d\u199e\u199f\u19a0\u19a1\u19a2\u19a3\u19a4\u19a5\u19a6\u19a7\u19a8\u19a9\u19c1\u19c2\u19c3\u19c4\u19c5\u19c6\u19c7\u1a00\u1a01\u1a02\u1a03\u1a04\u1a05\u1a06\u1a07\u1a08\u1a09\u1a0a\u1a0b\u1a0c\u1a0d\u1a0e\u1a0f\u1a10\u1a11\u1a12\u1a13\u1a14\u1a15\u1a16\u2135\u2136\u2137\u2138\u2d30\u2d31\u2d32\u2d33\u2d34\u2d35\u2d36\u2d37\u2d38\u2d39\u2d3a\u2d3b\u2d3c\u2d3d\u2d3e\u2d3f\u2d40\u2d41\u2d42\u2d43\u2d44\u2d45\u2d46\u2d47\u2d48\u2d49\u2d4a\u2d4b\u2d4c\u2d4d\u2d4e\u2d4f\u2d50\u2d51\u2d52\u2d53\u2d54\u2d55\u2d56\u2d57\u2d58\u2d59\u2d5a\u2d5b\u2d5c\u2d5d\u2d5e\u2d5f\u2d60\u2d61\u2d62\u2d63\u2d64\u2d65\u2d80\u2d81\u2d82\u2d83\u2d84\u2d85\u2d86\u2d87\u2d88\u2d89\u2d8a\u2d8b\u2d8c\u2d8d\u2d8e\u2d8f\u2d90\u2d91\u2d92\u2d93\u2d94\u2d95\u2d96\u2da0\u2da1\u2da2\u2da3\u2da4\u2da5\u2da6\u2da8\u2da9\u2daa\u2dab\u2dac\u2dad\u2dae\u2db0\u2db1\u2db2\u2db3\u2db4\u2db5\u2db6\u2db8\u2db9\u2dba\u2dbb\u2dbc\u2dbd\u2dbe\u2dc0\u2dc1\u2dc2\u2dc3\u2dc4\u2dc5\u2dc6\u2dc8\u2dc9\u2dca\u2dcb\u2dcc\u2dcd\u2dce\u2dd0\u2dd1\u2dd2\u2dd3\u2dd4\u2dd5\u2dd6\u2dd8\u2dd9\u2dda\u2ddb\u2ddc\u2ddd\u2dde\u3006\u303c\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304a\u304b\u304c\u304d\u304e\u304f\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305a\u305b\u305c\u305d\u305e\u305f\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306a\u306b\u306c\u306d\u306e\u306f\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307a\u307b\u307c\u307d\u307e\u307f\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308a\u308b\u308c\u308d\u308e\u308f\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u309f\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8\u30a9\u30aa\u30ab\u30ac\u30ad\u30ae\u30af\u30b0\u30b1\u30b2\u30b3\u30b4\u30b5\u30b6\u30b7\u30b8\u30b9\u30ba\u30bb\u30bc\u30bd\u30be\u30bf\u30c0\u30c1\u30c2\u30c3\u30c4\u30c5\u30c6\u30c7\u30c8\u30c9\u30ca\u30cb\u30cc\u30cd\u30ce\u30cf\u30d0\u30d1\u30d2\u30d3\u30d4\u30d5\u30d6\u30d7\u30d8\u30d9\u30da\u30db\u30dc\u30dd\u30de\u30df\u30e0\u30e1\u30e2\u30e3\u30e4\u30e5\u30e6\u30e7\u30e8\u30e9\u30ea\u30eb\u30ec\u30ed\u30ee\u30ef\u30f0\u30f1\u30f2\u30f3\u30f4\u30f5\u30f6\u30f7\u30f8\u30f9\u30fa\u30ff\u3105\u3106\u3107\u3108\u3109\u310a\u310b\u310c\u310d\u310e\u310f\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311a\u311b\u311c\u311d\u311e\u311f\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312a\u312b\u312c\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313a\u313b\u313c\u313d\u313e\u313f\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314a\u314b\u314c\u314d\u314e\u314f\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315a\u315b\u315c\u315d\u315e\u315f\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316a\u316b\u316c\u316d\u316e\u316f\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317a\u317b\u317c\u317d\u317e\u317f\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318a\u318b\u318c\u318d\u318e\u31a0\u31a1\u31a2\u31a3\u31a4\u31a5\u31a6\u31a7\u31a8\u31a9\u31aa\u31ab\u31ac\u31ad\u31ae\u31af\u31b0\u31b1\u31b2\u31b3\u31b4\u31b5\u31b6\u31b7\u31f0\u31f1\u31f2\u31f3\u31f4\u31f5\u31f6\u31f7\u31f8\u31f9\u31fa\u31fb\u31fc\u31fd\u31fe\u31ff\u3400\u3401\u3402\u3403\u3404\u3405\u3406\u3407\u3408\u3409\u340a\u340b\u340c\u340d\u340e\u340f\u3410\u3411\u3412\u3413\u3414\u3415\u3416\u3417\u3418\u3419\u341a\u341b\u341c\u341d\u341e\u341f\u3420\u3421\u3422\u3423\u3424\u3425\u3426\u3427\u3428\u3429\u342a\u342b\u342c\u342d\u342e\u342f\u3430\u3431\u3432\u3433\u3434\u3435\u3436\u3437\u3438\u3439\u343a\u343b\u343c\u343d\u343e\u343f\u3440\u3441\u3442\u3443\u3444\u3445\u3446\u3447\u3448\u3449\u344a\u344b\u344c\u344d\u344e\u344f\u3450\u3451\u3452\u3453\u3454\u3455\u3456\u3457\u3458\u3459\u345a\u345b\u345c\u345d\u345e\u345f\u3460\u3461\u3462\u3463\u3464\u3465\u3466\u3467\u3468\u3469\u346a\u346b\u346c\u346d\u346e\u346f\u3470\u3471\u3472\u3473\u3474\u3475\u3476\u3477\u3478\u3479\u347a\u347b\u347c\u347d\u347e\u347f\u3480\u3481\u3482\u3483\u3484\u3485\u3486\u3487\u3488\u3489\u348a\u348b\u348c\u348d\u348e\u348f\u3490\u3491\u3492\u3493\u3494\u3495\u3496\u3497\u3498\u3499\u349a\u349b\u349c\u349d\u349e\u349f\u34a0\u34a1\u34a2\u34a3\u34a4\u34a5\u34a6\u34a7\u34a8\u34a9\u34aa\u34ab\u34ac\u34ad\u34ae\u34af\u34b0\u34b1\u34b2\u34b3\u34b4\u34b5\u34b6\u34b7\u34b8\u34b9\u34ba\u34bb\u34bc\u34bd\u34be\u34bf\u34c0\u34c1\u34c2\u34c3\u34c4\u34c5\u34c6\u34c7\u34c8\u34c9\u34ca\u34cb\u34cc\u34cd\u34ce\u34cf\u34d0\u34d1\u34d2\u34d3\u34d4\u34d5\u34d6\u34d7\u34d8\u34d9\u34da\u34db\u34dc\u34dd\u34de\u34df\u34e0\u34e1\u34e2\u34e3\u34e4\u34e5\u34e6\u34e7\u34e8\u34e9\u34ea\u34eb\u34ec\u34ed\u34ee\u34ef\u34f0\u34f1\u34f2\u34f3\u34f4\u34f5\u34f6\u34f7\u34f8\u34f9\u34fa\u34fb\u34fc\u34fd\u34fe\u34ff\u3500\u3501\u3502\u3503\u3504\u3505\u3506\u3507\u3508\u3509\u350a\u350b\u350c\u350d\u350e\u350f\u3510\u3511\u3512\u3513\u3514\u3515\u3516\u3517\u3518\u3519\u351a\u351b\u351c\u351d\u351e\u351f\u3520\u3521\u3522\u3523\u3524\u3525\u3526\u3527\u3528\u3529\u352a\u352b\u352c\u352d\u352e\u352f\u3530\u3531\u3532\u3533\u3534\u3535\u3536\u3537\u3538\u3539\u353a\u353b\u353c\u353d\u353e\u353f\u3540\u3541\u3542\u3543\u3544\u3545\u3546\u3547\u3548\u3549\u354a\u354b\u354c\u354d\u354e\u354f\u3550\u3551\u3552\u3553\u3554\u3555\u3556\u3557\u3558\u3559\u355a\u355b\u355c\u355d\u355e\u355f\u3560\u3561\u3562\u3563\u3564\u3565\u3566\u3567\u3568\u3569\u356a\u356b\u356c\u356d\u356e\u356f\u3570\u3571\u3572\u3573\u3574\u3575\u3576\u3577\u3578\u3579\u357a\u357b\u357c\u357d\u357e\u357f\u3580\u3581\u3582\u3583\u3584\u3585\u3586\u3587\u3588\u3589\u358a\u358b\u358c\u358d\u358e\u358f\u3590\u3591\u3592\u3593\u3594\u3595\u3596\u3597\u3598\u3599\u359a\u359b\u359c\u359d\u359e\u359f\u35a0\u35a1\u35a2\u35a3\u35a4\u35a5\u35a6\u35a7\u35a8\u35a9\u35aa\u35ab\u35ac\u35ad\u35ae\u35af\u35b0\u35b1\u35b2\u35b3\u35b4\u35b5\u35b6\u35b7\u35b8\u35b9\u35ba\u35bb\u35bc\u35bd\u35be\u35bf\u35c0\u35c1\u35c2\u35c3\u35c4\u35c5\u35c6\u35c7\u35c8\u35c9\u35ca\u35cb\u35cc\u35cd\u35ce\u35cf\u35d0\u35d1\u35d2\u35d3\u35d4\u35d5\u35d6\u35d7\u35d8\u35d9\u35da\u35db\u35dc\u35dd\u35de\u35df\u35e0\u35e1\u35e2\u35e3\u35e4\u35e5\u35e6\u35e7\u35e8\u35e9\u35ea\u35eb\u35ec\u35ed\u35ee\u35ef\u35f0\u35f1\u35f2\u35f3\u35f4\u35f5\u35f6\u35f7\u35f8\u35f9\u35fa\u35fb\u35fc\u35fd\u35fe\u35ff\u3600\u3601\u3602\u3603\u3604\u3605\u3606\u3607\u3608\u3609\u360a\u360b\u360c\u360d\u360e\u360f\u3610\u3611\u3612\u3613\u3614\u3615\u3616\u3617\u3618\u3619\u361a\u361b\u361c\u361d\u361e\u361f\u3620\u3621\u3622\u3623\u3624\u3625\u3626\u3627\u3628\u3629\u362a\u362b\u362c\u362d\u362e\u362f\u3630\u3631\u3632\u3633\u3634\u3635\u3636\u3637\u3638\u3639\u363a\u363b\u363c\u363d\u363e\u363f\u3640\u3641\u3642\u3643\u3644\u3645\u3646\u3647\u3648\u3649\u364a\u364b\u364c\u364d\u364e\u364f\u3650\u3651\u3652\u3653\u3654\u3655\u3656\u3657\u3658\u3659\u365a\u365b\u365c\u365d\u365e\u365f\u3660\u3661\u3662\u3663\u3664\u3665\u3666\u3667\u3668\u3669\u366a\u366b\u366c\u366d\u366e\u366f\u3670\u3671\u3672\u3673\u3674\u3675\u3676\u3677\u3678\u3679\u367a\u367b\u367c\u367d\u367e\u367f\u3680\u3681\u3682\u3683\u3684\u3685\u3686\u3687\u3688\u3689\u368a\u368b\u368c\u368d\u368e\u368f\u3690\u3691\u3692\u3693\u3694\u3695\u3696\u3697\u3698\u3699\u369a\u369b\u369c\u369d\u369e\u369f\u36a0\u36a1\u36a2\u36a3\u36a4\u36a5\u36a6\u36a7\u36a8\u36a9\u36aa\u36ab\u36ac\u36ad\u36ae\u36af\u36b0\u36b1\u36b2\u36b3\u36b4\u36b5\u36b6\u36b7\u36b8\u36b9\u36ba\u36bb\u36bc\u36bd\u36be\u36bf\u36c0\u36c1\u36c2\u36c3\u36c4\u36c5\u36c6\u36c7\u36c8\u36c9\u36ca\u36cb\u36cc\u36cd\u36ce\u36cf\u36d0\u36d1\u36d2\u36d3\u36d4\u36d5\u36d6\u36d7\u36d8\u36d9\u36da\u36db\u36dc\u36dd\u36de\u36df\u36e0\u36e1\u36e2\u36e3\u36e4\u36e5\u36e6\u36e7\u36e8\u36e9\u36ea\u36eb\u36ec\u36ed\u36ee\u36ef\u36f0\u36f1\u36f2\u36f3\u36f4\u36f5\u36f6\u36f7\u36f8\u36f9\u36fa\u36fb\u36fc\u36fd\u36fe\u36ff\u3700\u3701\u3702\u3703\u3704\u3705\u3706\u3707\u3708\u3709\u370a\u370b\u370c\u370d\u370e\u370f\u3710\u3711\u3712\u3713\u3714\u3715\u3716\u3717\u3718\u3719\u371a\u371b\u371c\u371d\u371e\u371f\u3720\u3721\u3722\u3723\u3724\u3725\u3726\u3727\u3728\u3729\u372a\u372b\u372c\u372d\u372e\u372f\u3730\u3731\u3732\u3733\u3734\u3735\u3736\u3737\u3738\u3739\u373a\u373b\u373c\u373d\u373e\u373f\u3740\u3741\u3742\u3743\u3744\u3745\u3746\u3747\u3748\u3749\u374a\u374b\u374c\u374d\u374e\u374f\u3750\u3751\u3752\u3753\u3754\u3755\u3756\u3757\u3758\u3759\u375a\u375b\u375c\u375d\u375e\u375f\u3760\u3761\u3762\u3763\u3764\u3765\u3766\u3767\u3768\u3769\u376a\u376b\u376c\u376d\u376e\u376f\u3770\u3771\u3772\u3773\u3774\u3775\u3776\u3777\u3778\u3779\u377a\u377b\u377c\u377d\u377e\u377f\u3780\u3781\u3782\u3783\u3784\u3785\u3786\u3787\u3788\u3789\u378a\u378b\u378c\u378d\u378e\u378f\u3790\u3791\u3792\u3793\u3794\u3795\u3796\u3797\u3798\u3799\u379a\u379b\u379c\u379d\u379e\u379f\u37a0\u37a1\u37a2\u37a3\u37a4\u37a5\u37a6\u37a7\u37a8\u37a9\u37aa\u37ab\u37ac\u37ad\u37ae\u37af\u37b0\u37b1\u37b2\u37b3\u37b4\u37b5\u37b6\u37b7\u37b8\u37b9\u37ba\u37bb\u37bc\u37bd\u37be\u37bf\u37c0\u37c1\u37c2\u37c3\u37c4\u37c5\u37c6\u37c7\u37c8\u37c9\u37ca\u37cb\u37cc\u37cd\u37ce\u37cf\u37d0\u37d1\u37d2\u37d3\u37d4\u37d5\u37d6\u37d7\u37d8\u37d9\u37da\u37db\u37dc\u37dd\u37de\u37df\u37e0\u37e1\u37e2\u37e3\u37e4\u37e5\u37e6\u37e7\u37e8\u37e9\u37ea\u37eb\u37ec\u37ed\u37ee\u37ef\u37f0\u37f1\u37f2\u37f3\u37f4\u37f5\u37f6\u37f7\u37f8\u37f9\u37fa\u37fb\u37fc\u37fd\u37fe\u37ff\u3800\u3801\u3802\u3803\u3804\u3805\u3806\u3807\u3808\u3809\u380a\u380b\u380c\u380d\u380e\u380f\u3810\u3811\u3812\u3813\u3814\u3815\u3816\u3817\u3818\u3819\u381a\u381b\u381c\u381d\u381e\u381f\u3820\u3821\u3822\u3823\u3824\u3825\u3826\u3827\u3828\u3829\u382a\u382b\u382c\u382d\u382e\u382f\u3830\u3831\u3832\u3833\u3834\u3835\u3836\u3837\u3838\u3839\u383a\u383b\u383c\u383d\u383e\u383f\u3840\u3841\u3842\u3843\u3844\u3845\u3846\u3847\u3848\u3849\u384a\u384b\u384c\u384d\u384e\u384f\u3850\u3851\u3852\u3853\u3854\u3855\u3856\u3857\u3858\u3859\u385a\u385b\u385c\u385d\u385e\u385f\u3860\u3861\u3862\u3863\u3864\u3865\u3866\u3867\u3868\u3869\u386a\u386b\u386c\u386d\u386e\u386f\u3870\u3871\u3872\u3873\u3874\u3875\u3876\u3877\u3878\u3879\u387a\u387b\u387c\u387d\u387e\u387f\u3880\u3881\u3882\u3883\u3884\u3885\u3886\u3887\u3888\u3889\u388a\u388b\u388c\u388d\u388e\u388f\u3890\u3891\u3892\u3893\u3894\u3895\u3896\u3897\u3898\u3899\u389a\u389b\u389c\u389d\u389e\u389f\u38a0\u38a1\u38a2\u38a3\u38a4\u38a5\u38a6\u38a7\u38a8\u38a9\u38aa\u38ab\u38ac\u38ad\u38ae\u38af\u38b0\u38b1\u38b2\u38b3\u38b4\u38b5\u38b6\u38b7\u38b8\u38b9\u38ba\u38bb\u38bc\u38bd\u38be\u38bf\u38c0\u38c1\u38c2\u38c3\u38c4\u38c5\u38c6\u38c7\u38c8\u38c9\u38ca\u38cb\u38cc\u38cd\u38ce\u38cf\u38d0\u38d1\u38d2\u38d3\u38d4\u38d5\u38d6\u38d7\u38d8\u38d9\u38da\u38db\u38dc\u38dd\u38de\u38df\u38e0\u38e1\u38e2\u38e3\u38e4\u38e5\u38e6\u38e7\u38e8\u38e9\u38ea\u38eb\u38ec\u38ed\u38ee\u38ef\u38f0\u38f1\u38f2\u38f3\u38f4\u38f5\u38f6\u38f7\u38f8\u38f9\u38fa\u38fb\u38fc\u38fd\u38fe\u38ff\u3900\u3901\u3902\u3903\u3904\u3905\u3906\u3907\u3908\u3909\u390a\u390b\u390c\u390d\u390e\u390f\u3910\u3911\u3912\u3913\u3914\u3915\u3916\u3917\u3918\u3919\u391a\u391b\u391c\u391d\u391e\u391f\u3920\u3921\u3922\u3923\u3924\u3925\u3926\u3927\u3928\u3929\u392a\u392b\u392c\u392d\u392e\u392f\u3930\u3931\u3932\u3933\u3934\u3935\u3936\u3937\u3938\u3939\u393a\u393b\u393c\u393d\u393e\u393f\u3940\u3941\u3942\u3943\u3944\u3945\u3946\u3947\u3948\u3949\u394a\u394b\u394c\u394d\u394e\u394f\u3950\u3951\u3952\u3953\u3954\u3955\u3956\u3957\u3958\u3959\u395a\u395b\u395c\u395d\u395e\u395f\u3960\u3961\u3962\u3963\u3964\u3965\u3966\u3967\u3968\u3969\u396a\u396b\u396c\u396d\u396e\u396f\u3970\u3971\u3972\u3973\u3974\u3975\u3976\u3977\u3978\u3979\u397a\u397b\u397c\u397d\u397e\u397f\u3980\u3981\u3982\u3983\u3984\u3985\u3986\u3987\u3988\u3989\u398a\u398b\u398c\u398d\u398e\u398f\u3990\u3991\u3992\u3993\u3994\u3995\u3996\u3997\u3998\u3999\u399a\u399b\u399c\u399d\u399e\u399f\u39a0\u39a1\u39a2\u39a3\u39a4\u39a5\u39a6\u39a7\u39a8\u39a9\u39aa\u39ab\u39ac\u39ad\u39ae\u39af\u39b0\u39b1\u39b2\u39b3\u39b4\u39b5\u39b6\u39b7\u39b8\u39b9\u39ba\u39bb\u39bc\u39bd\u39be\u39bf\u39c0\u39c1\u39c2\u39c3\u39c4\u39c5\u39c6\u39c7\u39c8\u39c9\u39ca\u39cb\u39cc\u39cd\u39ce\u39cf\u39d0\u39d1\u39d2\u39d3\u39d4\u39d5\u39d6\u39d7\u39d8\u39d9\u39da\u39db\u39dc\u39dd\u39de\u39df\u39e0\u39e1\u39e2\u39e3\u39e4\u39e5\u39e6\u39e7\u39e8\u39e9\u39ea\u39eb\u39ec\u39ed\u39ee\u39ef\u39f0\u39f1\u39f2\u39f3\u39f4\u39f5\u39f6\u39f7\u39f8\u39f9\u39fa\u39fb\u39fc\u39fd\u39fe\u39ff\u3a00\u3a01\u3a02\u3a03\u3a04\u3a05\u3a06\u3a07\u3a08\u3a09\u3a0a\u3a0b\u3a0c\u3a0d\u3a0e\u3a0f\u3a10\u3a11\u3a12\u3a13\u3a14\u3a15\u3a16\u3a17\u3a18\u3a19\u3a1a\u3a1b\u3a1c\u3a1d\u3a1e\u3a1f\u3a20\u3a21\u3a22\u3a23\u3a24\u3a25\u3a26\u3a27\u3a28\u3a29\u3a2a\u3a2b\u3a2c\u3a2d\u3a2e\u3a2f\u3a30\u3a31\u3a32\u3a33\u3a34\u3a35\u3a36\u3a37\u3a38\u3a39\u3a3a\u3a3b\u3a3c\u3a3d\u3a3e\u3a3f\u3a40\u3a41\u3a42\u3a43\u3a44\u3a45\u3a46\u3a47\u3a48\u3a49\u3a4a\u3a4b\u3a4c\u3a4d\u3a4e\u3a4f\u3a50\u3a51\u3a52\u3a53\u3a54\u3a55\u3a56\u3a57\u3a58\u3a59\u3a5a\u3a5b\u3a5c\u3a5d\u3a5e\u3a5f\u3a60\u3a61\u3a62\u3a63\u3a64\u3a65\u3a66\u3a67\u3a68\u3a69\u3a6a\u3a6b\u3a6c\u3a6d\u3a6e\u3a6f\u3a70\u3a71\u3a72\u3a73\u3a74\u3a75\u3a76\u3a77\u3a78\u3a79\u3a7a\u3a7b\u3a7c\u3a7d\u3a7e\u3a7f\u3a80\u3a81\u3a82\u3a83\u3a84\u3a85\u3a86\u3a87\u3a88\u3a89\u3a8a\u3a8b\u3a8c\u3a8d\u3a8e\u3a8f\u3a90\u3a91\u3a92\u3a93\u3a94\u3a95\u3a96\u3a97\u3a98\u3a99\u3a9a\u3a9b\u3a9c\u3a9d\u3a9e\u3a9f\u3aa0\u3aa1\u3aa2\u3aa3\u3aa4\u3aa5\u3aa6\u3aa7\u3aa8\u3aa9\u3aaa\u3aab\u3aac\u3aad\u3aae\u3aaf\u3ab0\u3ab1\u3ab2\u3ab3\u3ab4\u3ab5\u3ab6\u3ab7\u3ab8\u3ab9\u3aba\u3abb\u3abc\u3abd\u3abe\u3abf\u3ac0\u3ac1\u3ac2\u3ac3\u3ac4\u3ac5\u3ac6\u3ac7\u3ac8\u3ac9\u3aca\u3acb\u3acc\u3acd\u3ace\u3acf\u3ad0\u3ad1\u3ad2\u3ad3\u3ad4\u3ad5\u3ad6\u3ad7\u3ad8\u3ad9\u3ada\u3adb\u3adc\u3add\u3ade\u3adf\u3ae0\u3ae1\u3ae2\u3ae3\u3ae4\u3ae5\u3ae6\u3ae7\u3ae8\u3ae9\u3aea\u3aeb\u3aec\u3aed\u3aee\u3aef\u3af0\u3af1\u3af2\u3af3\u3af4\u3af5\u3af6\u3af7\u3af8\u3af9\u3afa\u3afb\u3afc\u3afd\u3afe\u3aff\u3b00\u3b01\u3b02\u3b03\u3b04\u3b05\u3b06\u3b07\u3b08\u3b09\u3b0a\u3b0b\u3b0c\u3b0d\u3b0e\u3b0f\u3b10\u3b11\u3b12\u3b13\u3b14\u3b15\u3b16\u3b17\u3b18\u3b19\u3b1a\u3b1b\u3b1c\u3b1d\u3b1e\u3b1f\u3b20\u3b21\u3b22\u3b23\u3b24\u3b25\u3b26\u3b27\u3b28\u3b29\u3b2a\u3b2b\u3b2c\u3b2d\u3b2e\u3b2f\u3b30\u3b31\u3b32\u3b33\u3b34\u3b35\u3b36\u3b37\u3b38\u3b39\u3b3a\u3b3b\u3b3c\u3b3d\u3b3e\u3b3f\u3b40\u3b41\u3b42\u3b43\u3b44\u3b45\u3b46\u3b47\u3b48\u3b49\u3b4a\u3b4b\u3b4c\u3b4d\u3b4e\u3b4f\u3b50\u3b51\u3b52\u3b53\u3b54\u3b55\u3b56\u3b57\u3b58\u3b59\u3b5a\u3b5b\u3b5c\u3b5d\u3b5e\u3b5f\u3b60\u3b61\u3b62\u3b63\u3b64\u3b65\u3b66\u3b67\u3b68\u3b69\u3b6a\u3b6b\u3b6c\u3b6d\u3b6e\u3b6f\u3b70\u3b71\u3b72\u3b73\u3b74\u3b75\u3b76\u3b77\u3b78\u3b79\u3b7a\u3b7b\u3b7c\u3b7d\u3b7e\u3b7f\u3b80\u3b81\u3b82\u3b83\u3b84\u3b85\u3b86\u3b87\u3b88\u3b89\u3b8a\u3b8b\u3b8c\u3b8d\u3b8e\u3b8f\u3b90\u3b91\u3b92\u3b93\u3b94\u3b95\u3b96\u3b97\u3b98\u3b99\u3b9a\u3b9b\u3b9c\u3b9d\u3b9e\u3b9f\u3ba0\u3ba1\u3ba2\u3ba3\u3ba4\u3ba5\u3ba6\u3ba7\u3ba8\u3ba9\u3baa\u3bab\u3bac\u3bad\u3bae\u3baf\u3bb0\u3bb1\u3bb2\u3bb3\u3bb4\u3bb5\u3bb6\u3bb7\u3bb8\u3bb9\u3bba\u3bbb\u3bbc\u3bbd\u3bbe\u3bbf\u3bc0\u3bc1\u3bc2\u3bc3\u3bc4\u3bc5\u3bc6\u3bc7\u3bc8\u3bc9\u3bca\u3bcb\u3bcc\u3bcd\u3bce\u3bcf\u3bd0\u3bd1\u3bd2\u3bd3\u3bd4\u3bd5\u3bd6\u3bd7\u3bd8\u3bd9\u3bda\u3bdb\u3bdc\u3bdd\u3bde\u3bdf\u3be0\u3be1\u3be2\u3be3\u3be4\u3be5\u3be6\u3be7\u3be8\u3be9\u3bea\u3beb\u3bec\u3bed\u3bee\u3bef\u3bf0\u3bf1\u3bf2\u3bf3\u3bf4\u3bf5\u3bf6\u3bf7\u3bf8\u3bf9\u3bfa\u3bfb\u3bfc\u3bfd\u3bfe\u3bff\u3c00\u3c01\u3c02\u3c03\u3c04\u3c05\u3c06\u3c07\u3c08\u3c09\u3c0a\u3c0b\u3c0c\u3c0d\u3c0e\u3c0f\u3c10\u3c11\u3c12\u3c13\u3c14\u3c15\u3c16\u3c17\u3c18\u3c19\u3c1a\u3c1b\u3c1c\u3c1d\u3c1e\u3c1f\u3c20\u3c21\u3c22\u3c23\u3c24\u3c25\u3c26\u3c27\u3c28\u3c29\u3c2a\u3c2b\u3c2c\u3c2d\u3c2e\u3c2f\u3c30\u3c31\u3c32\u3c33\u3c34\u3c35\u3c36\u3c37\u3c38\u3c39\u3c3a\u3c3b\u3c3c\u3c3d\u3c3e\u3c3f\u3c40\u3c41\u3c42\u3c43\u3c44\u3c45\u3c46\u3c47\u3c48\u3c49\u3c4a\u3c4b\u3c4c\u3c4d\u3c4e\u3c4f\u3c50\u3c51\u3c52\u3c53\u3c54\u3c55\u3c56\u3c57\u3c58\u3c59\u3c5a\u3c5b\u3c5c\u3c5d\u3c5e\u3c5f\u3c60\u3c61\u3c62\u3c63\u3c64\u3c65\u3c66\u3c67\u3c68\u3c69\u3c6a\u3c6b\u3c6c\u3c6d\u3c6e\u3c6f\u3c70\u3c71\u3c72\u3c73\u3c74\u3c75\u3c76\u3c77\u3c78\u3c79\u3c7a\u3c7b\u3c7c\u3c7d\u3c7e\u3c7f\u3c80\u3c81\u3c82\u3c83\u3c84\u3c85\u3c86\u3c87\u3c88\u3c89\u3c8a\u3c8b\u3c8c\u3c8d\u3c8e\u3c8f\u3c90\u3c91\u3c92\u3c93\u3c94\u3c95\u3c96\u3c97\u3c98\u3c99\u3c9a\u3c9b\u3c9c\u3c9d\u3c9e\u3c9f\u3ca0\u3ca1\u3ca2\u3ca3\u3ca4\u3ca5\u3ca6\u3ca7\u3ca8\u3ca9\u3caa\u3cab\u3cac\u3cad\u3cae\u3caf\u3cb0\u3cb1\u3cb2\u3cb3\u3cb4\u3cb5\u3cb6\u3cb7\u3cb8\u3cb9\u3cba\u3cbb\u3cbc\u3cbd\u3cbe\u3cbf\u3cc0\u3cc1\u3cc2\u3cc3\u3cc4\u3cc5\u3cc6\u3cc7\u3cc8\u3cc9\u3cca\u3ccb\u3ccc\u3ccd\u3cce\u3ccf\u3cd0\u3cd1\u3cd2\u3cd3\u3cd4\u3cd5\u3cd6\u3cd7\u3cd8\u3cd9\u3cda\u3cdb\u3cdc\u3cdd\u3cde\u3cdf\u3ce0\u3ce1\u3ce2\u3ce3\u3ce4\u3ce5\u3ce6\u3ce7\u3ce8\u3ce9\u3cea\u3ceb\u3cec\u3ced\u3cee\u3cef\u3cf0\u3cf1\u3cf2\u3cf3\u3cf4\u3cf5\u3cf6\u3cf7\u3cf8\u3cf9\u3cfa\u3cfb\u3cfc\u3cfd\u3cfe\u3cff\u3d00\u3d01\u3d02\u3d03\u3d04\u3d05\u3d06\u3d07\u3d08\u3d09\u3d0a\u3d0b\u3d0c\u3d0d\u3d0e\u3d0f\u3d10\u3d11\u3d12\u3d13\u3d14\u3d15\u3d16\u3d17\u3d18\u3d19\u3d1a\u3d1b\u3d1c\u3d1d\u3d1e\u3d1f\u3d20\u3d21\u3d22\u3d23\u3d24\u3d25\u3d26\u3d27\u3d28\u3d29\u3d2a\u3d2b\u3d2c\u3d2d\u3d2e\u3d2f\u3d30\u3d31\u3d32\u3d33\u3d34\u3d35\u3d36\u3d37\u3d38\u3d39\u3d3a\u3d3b\u3d3c\u3d3d\u3d3e\u3d3f\u3d40\u3d41\u3d42\u3d43\u3d44\u3d45\u3d46\u3d47\u3d48\u3d49\u3d4a\u3d4b\u3d4c\u3d4d\u3d4e\u3d4f\u3d50\u3d51\u3d52\u3d53\u3d54\u3d55\u3d56\u3d57\u3d58\u3d59\u3d5a\u3d5b\u3d5c\u3d5d\u3d5e\u3d5f\u3d60\u3d61\u3d62\u3d63\u3d64\u3d65\u3d66\u3d67\u3d68\u3d69\u3d6a\u3d6b\u3d6c\u3d6d\u3d6e\u3d6f\u3d70\u3d71\u3d72\u3d73\u3d74\u3d75\u3d76\u3d77\u3d78\u3d79\u3d7a\u3d7b\u3d7c\u3d7d\u3d7e\u3d7f\u3d80\u3d81\u3d82\u3d83\u3d84\u3d85\u3d86\u3d87\u3d88\u3d89\u3d8a\u3d8b\u3d8c\u3d8d\u3d8e\u3d8f\u3d90\u3d91\u3d92\u3d93\u3d94\u3d95\u3d96\u3d97\u3d98\u3d99\u3d9a\u3d9b\u3d9c\u3d9d\u3d9e\u3d9f\u3da0\u3da1\u3da2\u3da3\u3da4\u3da5\u3da6\u3da7\u3da8\u3da9\u3daa\u3dab\u3dac\u3dad\u3dae\u3daf\u3db0\u3db1\u3db2\u3db3\u3db4\u3db5\u3db6\u3db7\u3db8\u3db9\u3dba\u3dbb\u3dbc\u3dbd\u3dbe\u3dbf\u3dc0\u3dc1\u3dc2\u3dc3\u3dc4\u3dc5\u3dc6\u3dc7\u3dc8\u3dc9\u3dca\u3dcb\u3dcc\u3dcd\u3dce\u3dcf\u3dd0\u3dd1\u3dd2\u3dd3\u3dd4\u3dd5\u3dd6\u3dd7\u3dd8\u3dd9\u3dda\u3ddb\u3ddc\u3ddd\u3dde\u3ddf\u3de0\u3de1\u3de2\u3de3\u3de4\u3de5\u3de6\u3de7\u3de8\u3de9\u3dea\u3deb\u3dec\u3ded\u3dee\u3def\u3df0\u3df1\u3df2\u3df3\u3df4\u3df5\u3df6\u3df7\u3df8\u3df9\u3dfa\u3dfb\u3dfc\u3dfd\u3dfe\u3dff\u3e00\u3e01\u3e02\u3e03\u3e04\u3e05\u3e06\u3e07\u3e08\u3e09\u3e0a\u3e0b\u3e0c\u3e0d\u3e0e\u3e0f\u3e10\u3e11\u3e12\u3e13\u3e14\u3e15\u3e16\u3e17\u3e18\u3e19\u3e1a\u3e1b\u3e1c\u3e1d\u3e1e\u3e1f\u3e20\u3e21\u3e22\u3e23\u3e24\u3e25\u3e26\u3e27\u3e28\u3e29\u3e2a\u3e2b\u3e2c\u3e2d\u3e2e\u3e2f\u3e30\u3e31\u3e32\u3e33\u3e34\u3e35\u3e36\u3e37\u3e38\u3e39\u3e3a\u3e3b\u3e3c\u3e3d\u3e3e\u3e3f\u3e40\u3e41\u3e42\u3e43\u3e44\u3e45\u3e46\u3e47\u3e48\u3e49\u3e4a\u3e4b\u3e4c\u3e4d\u3e4e\u3e4f\u3e50\u3e51\u3e52\u3e53\u3e54\u3e55\u3e56\u3e57\u3e58\u3e59\u3e5a\u3e5b\u3e5c\u3e5d\u3e5e\u3e5f\u3e60\u3e61\u3e62\u3e63\u3e64\u3e65\u3e66\u3e67\u3e68\u3e69\u3e6a\u3e6b\u3e6c\u3e6d\u3e6e\u3e6f\u3e70\u3e71\u3e72\u3e73\u3e74\u3e75\u3e76\u3e77\u3e78\u3e79\u3e7a\u3e7b\u3e7c\u3e7d\u3e7e\u3e7f\u3e80\u3e81\u3e82\u3e83\u3e84\u3e85\u3e86\u3e87\u3e88\u3e89\u3e8a\u3e8b\u3e8c\u3e8d\u3e8e\u3e8f\u3e90\u3e91\u3e92\u3e93\u3e94\u3e95\u3e96\u3e97\u3e98\u3e99\u3e9a\u3e9b\u3e9c\u3e9d\u3e9e\u3e9f\u3ea0\u3ea1\u3ea2\u3ea3\u3ea4\u3ea5\u3ea6\u3ea7\u3ea8\u3ea9\u3eaa\u3eab\u3eac\u3ead\u3eae\u3eaf\u3eb0\u3eb1\u3eb2\u3eb3\u3eb4\u3eb5\u3eb6\u3eb7\u3eb8\u3eb9\u3eba\u3ebb\u3ebc\u3ebd\u3ebe\u3ebf\u3ec0\u3ec1\u3ec2\u3ec3\u3ec4\u3ec5\u3ec6\u3ec7\u3ec8\u3ec9\u3eca\u3ecb\u3ecc\u3ecd\u3ece\u3ecf\u3ed0\u3ed1\u3ed2\u3ed3\u3ed4\u3ed5\u3ed6\u3ed7\u3ed8\u3ed9\u3eda\u3edb\u3edc\u3edd\u3ede\u3edf\u3ee0\u3ee1\u3ee2\u3ee3\u3ee4\u3ee5\u3ee6\u3ee7\u3ee8\u3ee9\u3eea\u3eeb\u3eec\u3eed\u3eee\u3eef\u3ef0\u3ef1\u3ef2\u3ef3\u3ef4\u3ef5\u3ef6\u3ef7\u3ef8\u3ef9\u3efa\u3efb\u3efc\u3efd\u3efe\u3eff\u3f00\u3f01\u3f02\u3f03\u3f04\u3f05\u3f06\u3f07\u3f08\u3f09\u3f0a\u3f0b\u3f0c\u3f0d\u3f0e\u3f0f\u3f10\u3f11\u3f12\u3f13\u3f14\u3f15\u3f16\u3f17\u3f18\u3f19\u3f1a\u3f1b\u3f1c\u3f1d\u3f1e\u3f1f\u3f20\u3f21\u3f22\u3f23\u3f24\u3f25\u3f26\u3f27\u3f28\u3f29\u3f2a\u3f2b\u3f2c\u3f2d\u3f2e\u3f2f\u3f30\u3f31\u3f32\u3f33\u3f34\u3f35\u3f36\u3f37\u3f38\u3f39\u3f3a\u3f3b\u3f3c\u3f3d\u3f3e\u3f3f\u3f40\u3f41\u3f42\u3f43\u3f44\u3f45\u3f46\u3f47\u3f48\u3f49\u3f4a\u3f4b\u3f4c\u3f4d\u3f4e\u3f4f\u3f50\u3f51\u3f52\u3f53\u3f54\u3f55\u3f56\u3f57\u3f58\u3f59\u3f5a\u3f5b\u3f5c\u3f5d\u3f5e\u3f5f\u3f60\u3f61\u3f62\u3f63\u3f64\u3f65\u3f66\u3f67\u3f68\u3f69\u3f6a\u3f6b\u3f6c\u3f6d\u3f6e\u3f6f\u3f70\u3f71\u3f72\u3f73\u3f74\u3f75\u3f76\u3f77\u3f78\u3f79\u3f7a\u3f7b\u3f7c\u3f7d\u3f7e\u3f7f\u3f80\u3f81\u3f82\u3f83\u3f84\u3f85\u3f86\u3f87\u3f88\u3f89\u3f8a\u3f8b\u3f8c\u3f8d\u3f8e\u3f8f\u3f90\u3f91\u3f92\u3f93\u3f94\u3f95\u3f96\u3f97\u3f98\u3f99\u3f9a\u3f9b\u3f9c\u3f9d\u3f9e\u3f9f\u3fa0\u3fa1\u3fa2\u3fa3\u3fa4\u3fa5\u3fa6\u3fa7\u3fa8\u3fa9\u3faa\u3fab\u3fac\u3fad\u3fae\u3faf\u3fb0\u3fb1\u3fb2\u3fb3\u3fb4\u3fb5\u3fb6\u3fb7\u3fb8\u3fb9\u3fba\u3fbb\u3fbc\u3fbd\u3fbe\u3fbf\u3fc0\u3fc1\u3fc2\u3fc3\u3fc4\u3fc5\u3fc6\u3fc7\u3fc8\u3fc9\u3fca\u3fcb\u3fcc\u3fcd\u3fce\u3fcf\u3fd0\u3fd1\u3fd2\u3fd3\u3fd4\u3fd5\u3fd6\u3fd7\u3fd8\u3fd9\u3fda\u3fdb\u3fdc\u3fdd\u3fde\u3fdf\u3fe0\u3fe1\u3fe2\u3fe3\u3fe4\u3fe5\u3fe6\u3fe7\u3fe8\u3fe9\u3fea\u3feb\u3fec\u3fed\u3fee\u3fef\u3ff0\u3ff1\u3ff2\u3ff3\u3ff4\u3ff5\u3ff6\u3ff7\u3ff8\u3ff9\u3ffa\u3ffb\u3ffc\u3ffd\u3ffe\u3fff\u4000\u4001\u4002\u4003\u4004\u4005\u4006\u4007\u4008\u4009\u400a\u400b\u400c\u400d\u400e\u400f\u4010\u4011\u4012\u4013\u4014\u4015\u4016\u4017\u4018\u4019\u401a\u401b\u401c\u401d\u401e\u401f\u4020\u4021\u4022\u4023\u4024\u4025\u4026\u4027\u4028\u4029\u402a\u402b\u402c\u402d\u402e\u402f\u4030\u4031\u4032\u4033\u4034\u4035\u4036\u4037\u4038\u4039\u403a\u403b\u403c\u403d\u403e\u403f\u4040\u4041\u4042\u4043\u4044\u4045\u4046\u4047\u4048\u4049\u404a\u404b\u404c\u404d\u404e\u404f\u4050\u4051\u4052\u4053\u4054\u4055\u4056\u4057\u4058\u4059\u405a\u405b\u405c\u405d\u405e\u405f\u4060\u4061\u4062\u4063\u4064\u4065\u4066\u4067\u4068\u4069\u406a\u406b\u406c\u406d\u406e\u406f\u4070\u4071\u4072\u4073\u4074\u4075\u4076\u4077\u4078\u4079\u407a\u407b\u407c\u407d\u407e\u407f\u4080\u4081\u4082\u4083\u4084\u4085\u4086\u4087\u4088\u4089\u408a\u408b\u408c\u408d\u408e\u408f\u4090\u4091\u4092\u4093\u4094\u4095\u4096\u4097\u4098\u4099\u409a\u409b\u409c\u409d\u409e\u409f\u40a0\u40a1\u40a2\u40a3\u40a4\u40a5\u40a6\u40a7\u40a8\u40a9\u40aa\u40ab\u40ac\u40ad\u40ae\u40af\u40b0\u40b1\u40b2\u40b3\u40b4\u40b5\u40b6\u40b7\u40b8\u40b9\u40ba\u40bb\u40bc\u40bd\u40be\u40bf\u40c0\u40c1\u40c2\u40c3\u40c4\u40c5\u40c6\u40c7\u40c8\u40c9\u40ca\u40cb\u40cc\u40cd\u40ce\u40cf\u40d0\u40d1\u40d2\u40d3\u40d4\u40d5\u40d6\u40d7\u40d8\u40d9\u40da\u40db\u40dc\u40dd\u40de\u40df\u40e0\u40e1\u40e2\u40e3\u40e4\u40e5\u40e6\u40e7\u40e8\u40e9\u40ea\u40eb\u40ec\u40ed\u40ee\u40ef\u40f0\u40f1\u40f2\u40f3\u40f4\u40f5\u40f6\u40f7\u40f8\u40f9\u40fa\u40fb\u40fc\u40fd\u40fe\u40ff\u4100\u4101\u4102\u4103\u4104\u4105\u4106\u4107\u4108\u4109\u410a\u410b\u410c\u410d\u410e\u410f\u4110\u4111\u4112\u4113\u4114\u4115\u4116\u4117\u4118\u4119\u411a\u411b\u411c\u411d\u411e\u411f\u4120\u4121\u4122\u4123\u4124\u4125\u4126\u4127\u4128\u4129\u412a\u412b\u412c\u412d\u412e\u412f\u4130\u4131\u4132\u4133\u4134\u4135\u4136\u4137\u4138\u4139\u413a\u413b\u413c\u413d\u413e\u413f\u4140\u4141\u4142\u4143\u4144\u4145\u4146\u4147\u4148\u4149\u414a\u414b\u414c\u414d\u414e\u414f\u4150\u4151\u4152\u4153\u4154\u4155\u4156\u4157\u4158\u4159\u415a\u415b\u415c\u415d\u415e\u415f\u4160\u4161\u4162\u4163\u4164\u4165\u4166\u4167\u4168\u4169\u416a\u416b\u416c\u416d\u416e\u416f\u4170\u4171\u4172\u4173\u4174\u4175\u4176\u4177\u4178\u4179\u417a\u417b\u417c\u417d\u417e\u417f\u4180\u4181\u4182\u4183\u4184\u4185\u4186\u4187\u4188\u4189\u418a\u418b\u418c\u418d\u418e\u418f\u4190\u4191\u4192\u4193\u4194\u4195\u4196\u4197\u4198\u4199\u419a\u419b\u419c\u419d\u419e\u419f\u41a0\u41a1\u41a2\u41a3\u41a4\u41a5\u41a6\u41a7\u41a8\u41a9\u41aa\u41ab\u41ac\u41ad\u41ae\u41af\u41b0\u41b1\u41b2\u41b3\u41b4\u41b5\u41b6\u41b7\u41b8\u41b9\u41ba\u41bb\u41bc\u41bd\u41be\u41bf\u41c0\u41c1\u41c2\u41c3\u41c4\u41c5\u41c6\u41c7\u41c8\u41c9\u41ca\u41cb\u41cc\u41cd\u41ce\u41cf\u41d0\u41d1\u41d2\u41d3\u41d4\u41d5\u41d6\u41d7\u41d8\u41d9\u41da\u41db\u41dc\u41dd\u41de\u41df\u41e0\u41e1\u41e2\u41e3\u41e4\u41e5\u41e6\u41e7\u41e8\u41e9\u41ea\u41eb\u41ec\u41ed\u41ee\u41ef\u41f0\u41f1\u41f2\u41f3\u41f4\u41f5\u41f6\u41f7\u41f8\u41f9\u41fa\u41fb\u41fc\u41fd\u41fe\u41ff\u4200\u4201\u4202\u4203\u4204\u4205\u4206\u4207\u4208\u4209\u420a\u420b\u420c\u420d\u420e\u420f\u4210\u4211\u4212\u4213\u4214\u4215\u4216\u4217\u4218\u4219\u421a\u421b\u421c\u421d\u421e\u421f\u4220\u4221\u4222\u4223\u4224\u4225\u4226\u4227\u4228\u4229\u422a\u422b\u422c\u422d\u422e\u422f\u4230\u4231\u4232\u4233\u4234\u4235\u4236\u4237\u4238\u4239\u423a\u423b\u423c\u423d\u423e\u423f\u4240\u4241\u4242\u4243\u4244\u4245\u4246\u4247\u4248\u4249\u424a\u424b\u424c\u424d\u424e\u424f\u4250\u4251\u4252\u4253\u4254\u4255\u4256\u4257\u4258\u4259\u425a\u425b\u425c\u425d\u425e\u425f\u4260\u4261\u4262\u4263\u4264\u4265\u4266\u4267\u4268\u4269\u426a\u426b\u426c\u426d\u426e\u426f\u4270\u4271\u4272\u4273\u4274\u4275\u4276\u4277\u4278\u4279\u427a\u427b\u427c\u427d\u427e\u427f\u4280\u4281\u4282\u4283\u4284\u4285\u4286\u4287\u4288\u4289\u428a\u428b\u428c\u428d\u428e\u428f\u4290\u4291\u4292\u4293\u4294\u4295\u4296\u4297\u4298\u4299\u429a\u429b\u429c\u429d\u429e\u429f\u42a0\u42a1\u42a2\u42a3\u42a4\u42a5\u42a6\u42a7\u42a8\u42a9\u42aa\u42ab\u42ac\u42ad\u42ae\u42af\u42b0\u42b1\u42b2\u42b3\u42b4\u42b5\u42b6\u42b7\u42b8\u42b9\u42ba\u42bb\u42bc\u42bd\u42be\u42bf\u42c0\u42c1\u42c2\u42c3\u42c4\u42c5\u42c6\u42c7\u42c8\u42c9\u42ca\u42cb\u42cc\u42cd\u42ce\u42cf\u42d0\u42d1\u42d2\u42d3\u42d4\u42d5\u42d6\u42d7\u42d8\u42d9\u42da\u42db\u42dc\u42dd\u42de\u42df\u42e0\u42e1\u42e2\u42e3\u42e4\u42e5\u42e6\u42e7\u42e8\u42e9\u42ea\u42eb\u42ec\u42ed\u42ee\u42ef\u42f0\u42f1\u42f2\u42f3\u42f4\u42f5\u42f6\u42f7\u42f8\u42f9\u42fa\u42fb\u42fc\u42fd\u42fe\u42ff\u4300\u4301\u4302\u4303\u4304\u4305\u4306\u4307\u4308\u4309\u430a\u430b\u430c\u430d\u430e\u430f\u4310\u4311\u4312\u4313\u4314\u4315\u4316\u4317\u4318\u4319\u431a\u431b\u431c\u431d\u431e\u431f\u4320\u4321\u4322\u4323\u4324\u4325\u4326\u4327\u4328\u4329\u432a\u432b\u432c\u432d\u432e\u432f\u4330\u4331\u4332\u4333\u4334\u4335\u4336\u4337\u4338\u4339\u433a\u433b\u433c\u433d\u433e\u433f\u4340\u4341\u4342\u4343\u4344\u4345\u4346\u4347\u4348\u4349\u434a\u434b\u434c\u434d\u434e\u434f\u4350\u4351\u4352\u4353\u4354\u4355\u4356\u4357\u4358\u4359\u435a\u435b\u435c\u435d\u435e\u435f\u4360\u4361\u4362\u4363\u4364\u4365\u4366\u4367\u4368\u4369\u436a\u436b\u436c\u436d\u436e\u436f\u4370\u4371\u4372\u4373\u4374\u4375\u4376\u4377\u4378\u4379\u437a\u437b\u437c\u437d\u437e\u437f\u4380\u4381\u4382\u4383\u4384\u4385\u4386\u4387\u4388\u4389\u438a\u438b\u438c\u438d\u438e\u438f\u4390\u4391\u4392\u4393\u4394\u4395\u4396\u4397\u4398\u4399\u439a\u439b\u439c\u439d\u439e\u439f\u43a0\u43a1\u43a2\u43a3\u43a4\u43a5\u43a6\u43a7\u43a8\u43a9\u43aa\u43ab\u43ac\u43ad\u43ae\u43af\u43b0\u43b1\u43b2\u43b3\u43b4\u43b5\u43b6\u43b7\u43b8\u43b9\u43ba\u43bb\u43bc\u43bd\u43be\u43bf\u43c0\u43c1\u43c2\u43c3\u43c4\u43c5\u43c6\u43c7\u43c8\u43c9\u43ca\u43cb\u43cc\u43cd\u43ce\u43cf\u43d0\u43d1\u43d2\u43d3\u43d4\u43d5\u43d6\u43d7\u43d8\u43d9\u43da\u43db\u43dc\u43dd\u43de\u43df\u43e0\u43e1\u43e2\u43e3\u43e4\u43e5\u43e6\u43e7\u43e8\u43e9\u43ea\u43eb\u43ec\u43ed\u43ee\u43ef\u43f0\u43f1\u43f2\u43f3\u43f4\u43f5\u43f6\u43f7\u43f8\u43f9\u43fa\u43fb\u43fc\u43fd\u43fe\u43ff\u4400\u4401\u4402\u4403\u4404\u4405\u4406\u4407\u4408\u4409\u440a\u440b\u440c\u440d\u440e\u440f\u4410\u4411\u4412\u4413\u4414\u4415\u4416\u4417\u4418\u4419\u441a\u441b\u441c\u441d\u441e\u441f\u4420\u4421\u4422\u4423\u4424\u4425\u4426\u4427\u4428\u4429\u442a\u442b\u442c\u442d\u442e\u442f\u4430\u4431\u4432\u4433\u4434\u4435\u4436\u4437\u4438\u4439\u443a\u443b\u443c\u443d\u443e\u443f\u4440\u4441\u4442\u4443\u4444\u4445\u4446\u4447\u4448\u4449\u444a\u444b\u444c\u444d\u444e\u444f\u4450\u4451\u4452\u4453\u4454\u4455\u4456\u4457\u4458\u4459\u445a\u445b\u445c\u445d\u445e\u445f\u4460\u4461\u4462\u4463\u4464\u4465\u4466\u4467\u4468\u4469\u446a\u446b\u446c\u446d\u446e\u446f\u4470\u4471\u4472\u4473\u4474\u4475\u4476\u4477\u4478\u4479\u447a\u447b\u447c\u447d\u447e\u447f\u4480\u4481\u4482\u4483\u4484\u4485\u4486\u4487\u4488\u4489\u448a\u448b\u448c\u448d\u448e\u448f\u4490\u4491\u4492\u4493\u4494\u4495\u4496\u4497\u4498\u4499\u449a\u449b\u449c\u449d\u449e\u449f\u44a0\u44a1\u44a2\u44a3\u44a4\u44a5\u44a6\u44a7\u44a8\u44a9\u44aa\u44ab\u44ac\u44ad\u44ae\u44af\u44b0\u44b1\u44b2\u44b3\u44b4\u44b5\u44b6\u44b7\u44b8\u44b9\u44ba\u44bb\u44bc\u44bd\u44be\u44bf\u44c0\u44c1\u44c2\u44c3\u44c4\u44c5\u44c6\u44c7\u44c8\u44c9\u44ca\u44cb\u44cc\u44cd\u44ce\u44cf\u44d0\u44d1\u44d2\u44d3\u44d4\u44d5\u44d6\u44d7\u44d8\u44d9\u44da\u44db\u44dc\u44dd\u44de\u44df\u44e0\u44e1\u44e2\u44e3\u44e4\u44e5\u44e6\u44e7\u44e8\u44e9\u44ea\u44eb\u44ec\u44ed\u44ee\u44ef\u44f0\u44f1\u44f2\u44f3\u44f4\u44f5\u44f6\u44f7\u44f8\u44f9\u44fa\u44fb\u44fc\u44fd\u44fe\u44ff\u4500\u4501\u4502\u4503\u4504\u4505\u4506\u4507\u4508\u4509\u450a\u450b\u450c\u450d\u450e\u450f\u4510\u4511\u4512\u4513\u4514\u4515\u4516\u4517\u4518\u4519\u451a\u451b\u451c\u451d\u451e\u451f\u4520\u4521\u4522\u4523\u4524\u4525\u4526\u4527\u4528\u4529\u452a\u452b\u452c\u452d\u452e\u452f\u4530\u4531\u4532\u4533\u4534\u4535\u4536\u4537\u4538\u4539\u453a\u453b\u453c\u453d\u453e\u453f\u4540\u4541\u4542\u4543\u4544\u4545\u4546\u4547\u4548\u4549\u454a\u454b\u454c\u454d\u454e\u454f\u4550\u4551\u4552\u4553\u4554\u4555\u4556\u4557\u4558\u4559\u455a\u455b\u455c\u455d\u455e\u455f\u4560\u4561\u4562\u4563\u4564\u4565\u4566\u4567\u4568\u4569\u456a\u456b\u456c\u456d\u456e\u456f\u4570\u4571\u4572\u4573\u4574\u4575\u4576\u4577\u4578\u4579\u457a\u457b\u457c\u457d\u457e\u457f\u4580\u4581\u4582\u4583\u4584\u4585\u4586\u4587\u4588\u4589\u458a\u458b\u458c\u458d\u458e\u458f\u4590\u4591\u4592\u4593\u4594\u4595\u4596\u4597\u4598\u4599\u459a\u459b\u459c\u459d\u459e\u459f\u45a0\u45a1\u45a2\u45a3\u45a4\u45a5\u45a6\u45a7\u45a8\u45a9\u45aa\u45ab\u45ac\u45ad\u45ae\u45af\u45b0\u45b1\u45b2\u45b3\u45b4\u45b5\u45b6\u45b7\u45b8\u45b9\u45ba\u45bb\u45bc\u45bd\u45be\u45bf\u45c0\u45c1\u45c2\u45c3\u45c4\u45c5\u45c6\u45c7\u45c8\u45c9\u45ca\u45cb\u45cc\u45cd\u45ce\u45cf\u45d0\u45d1\u45d2\u45d3\u45d4\u45d5\u45d6\u45d7\u45d8\u45d9\u45da\u45db\u45dc\u45dd\u45de\u45df\u45e0\u45e1\u45e2\u45e3\u45e4\u45e5\u45e6\u45e7\u45e8\u45e9\u45ea\u45eb\u45ec\u45ed\u45ee\u45ef\u45f0\u45f1\u45f2\u45f3\u45f4\u45f5\u45f6\u45f7\u45f8\u45f9\u45fa\u45fb\u45fc\u45fd\u45fe\u45ff\u4600\u4601\u4602\u4603\u4604\u4605\u4606\u4607\u4608\u4609\u460a\u460b\u460c\u460d\u460e\u460f\u4610\u4611\u4612\u4613\u4614\u4615\u4616\u4617\u4618\u4619\u461a\u461b\u461c\u461d\u461e\u461f\u4620\u4621\u4622\u4623\u4624\u4625\u4626\u4627\u4628\u4629\u462a\u462b\u462c\u462d\u462e\u462f\u4630\u4631\u4632\u4633\u4634\u4635\u4636\u4637\u4638\u4639\u463a\u463b\u463c\u463d\u463e\u463f\u4640\u4641\u4642\u4643\u4644\u4645\u4646\u4647\u4648\u4649\u464a\u464b\u464c\u464d\u464e\u464f\u4650\u4651\u4652\u4653\u4654\u4655\u4656\u4657\u4658\u4659\u465a\u465b\u465c\u465d\u465e\u465f\u4660\u4661\u4662\u4663\u4664\u4665\u4666\u4667\u4668\u4669\u466a\u466b\u466c\u466d\u466e\u466f\u4670\u4671\u4672\u4673\u4674\u4675\u4676\u4677\u4678\u4679\u467a\u467b\u467c\u467d\u467e\u467f\u4680\u4681\u4682\u4683\u4684\u4685\u4686\u4687\u4688\u4689\u468a\u468b\u468c\u468d\u468e\u468f\u4690\u4691\u4692\u4693\u4694\u4695\u4696\u4697\u4698\u4699\u469a\u469b\u469c\u469d\u469e\u469f\u46a0\u46a1\u46a2\u46a3\u46a4\u46a5\u46a6\u46a7\u46a8\u46a9\u46aa\u46ab\u46ac\u46ad\u46ae\u46af\u46b0\u46b1\u46b2\u46b3\u46b4\u46b5\u46b6\u46b7\u46b8\u46b9\u46ba\u46bb\u46bc\u46bd\u46be\u46bf\u46c0\u46c1\u46c2\u46c3\u46c4\u46c5\u46c6\u46c7\u46c8\u46c9\u46ca\u46cb\u46cc\u46cd\u46ce\u46cf\u46d0\u46d1\u46d2\u46d3\u46d4\u46d5\u46d6\u46d7\u46d8\u46d9\u46da\u46db\u46dc\u46dd\u46de\u46df\u46e0\u46e1\u46e2\u46e3\u46e4\u46e5\u46e6\u46e7\u46e8\u46e9\u46ea\u46eb\u46ec\u46ed\u46ee\u46ef\u46f0\u46f1\u46f2\u46f3\u46f4\u46f5\u46f6\u46f7\u46f8\u46f9\u46fa\u46fb\u46fc\u46fd\u46fe\u46ff\u4700\u4701\u4702\u4703\u4704\u4705\u4706\u4707\u4708\u4709\u470a\u470b\u470c\u470d\u470e\u470f\u4710\u4711\u4712\u4713\u4714\u4715\u4716\u4717\u4718\u4719\u471a\u471b\u471c\u471d\u471e\u471f\u4720\u4721\u4722\u4723\u4724\u4725\u4726\u4727\u4728\u4729\u472a\u472b\u472c\u472d\u472e\u472f\u4730\u4731\u4732\u4733\u4734\u4735\u4736\u4737\u4738\u4739\u473a\u473b\u473c\u473d\u473e\u473f\u4740\u4741\u4742\u4743\u4744\u4745\u4746\u4747\u4748\u4749\u474a\u474b\u474c\u474d\u474e\u474f\u4750\u4751\u4752\u4753\u4754\u4755\u4756\u4757\u4758\u4759\u475a\u475b\u475c\u475d\u475e\u475f\u4760\u4761\u4762\u4763\u4764\u4765\u4766\u4767\u4768\u4769\u476a\u476b\u476c\u476d\u476e\u476f\u4770\u4771\u4772\u4773\u4774\u4775\u4776\u4777\u4778\u4779\u477a\u477b\u477c\u477d\u477e\u477f\u4780\u4781\u4782\u4783\u4784\u4785\u4786\u4787\u4788\u4789\u478a\u478b\u478c\u478d\u478e\u478f\u4790\u4791\u4792\u4793\u4794\u4795\u4796\u4797\u4798\u4799\u479a\u479b\u479c\u479d\u479e\u479f\u47a0\u47a1\u47a2\u47a3\u47a4\u47a5\u47a6\u47a7\u47a8\u47a9\u47aa\u47ab\u47ac\u47ad\u47ae\u47af\u47b0\u47b1\u47b2\u47b3\u47b4\u47b5\u47b6\u47b7\u47b8\u47b9\u47ba\u47bb\u47bc\u47bd\u47be\u47bf\u47c0\u47c1\u47c2\u47c3\u47c4\u47c5\u47c6\u47c7\u47c8\u47c9\u47ca\u47cb\u47cc\u47cd\u47ce\u47cf\u47d0\u47d1\u47d2\u47d3\u47d4\u47d5\u47d6\u47d7\u47d8\u47d9\u47da\u47db\u47dc\u47dd\u47de\u47df\u47e0\u47e1\u47e2\u47e3\u47e4\u47e5\u47e6\u47e7\u47e8\u47e9\u47ea\u47eb\u47ec\u47ed\u47ee\u47ef\u47f0\u47f1\u47f2\u47f3\u47f4\u47f5\u47f6\u47f7\u47f8\u47f9\u47fa\u47fb\u47fc\u47fd\u47fe\u47ff\u4800\u4801\u4802\u4803\u4804\u4805\u4806\u4807\u4808\u4809\u480a\u480b\u480c\u480d\u480e\u480f\u4810\u4811\u4812\u4813\u4814\u4815\u4816\u4817\u4818\u4819\u481a\u481b\u481c\u481d\u481e\u481f\u4820\u4821\u4822\u4823\u4824\u4825\u4826\u4827\u4828\u4829\u482a\u482b\u482c\u482d\u482e\u482f\u4830\u4831\u4832\u4833\u4834\u4835\u4836\u4837\u4838\u4839\u483a\u483b\u483c\u483d\u483e\u483f\u4840\u4841\u4842\u4843\u4844\u4845\u4846\u4847\u4848\u4849\u484a\u484b\u484c\u484d\u484e\u484f\u4850\u4851\u4852\u4853\u4854\u4855\u4856\u4857\u4858\u4859\u485a\u485b\u485c\u485d\u485e\u485f\u4860\u4861\u4862\u4863\u4864\u4865\u4866\u4867\u4868\u4869\u486a\u486b\u486c\u486d\u486e\u486f\u4870\u4871\u4872\u4873\u4874\u4875\u4876\u4877\u4878\u4879\u487a\u487b\u487c\u487d\u487e\u487f\u4880\u4881\u4882\u4883\u4884\u4885\u4886\u4887\u4888\u4889\u488a\u488b\u488c\u488d\u488e\u488f\u4890\u4891\u4892\u4893\u4894\u4895\u4896\u4897\u4898\u4899\u489a\u489b\u489c\u489d\u489e\u489f\u48a0\u48a1\u48a2\u48a3\u48a4\u48a5\u48a6\u48a7\u48a8\u48a9\u48aa\u48ab\u48ac\u48ad\u48ae\u48af\u48b0\u48b1\u48b2\u48b3\u48b4\u48b5\u48b6\u48b7\u48b8\u48b9\u48ba\u48bb\u48bc\u48bd\u48be\u48bf\u48c0\u48c1\u48c2\u48c3\u48c4\u48c5\u48c6\u48c7\u48c8\u48c9\u48ca\u48cb\u48cc\u48cd\u48ce\u48cf\u48d0\u48d1\u48d2\u48d3\u48d4\u48d5\u48d6\u48d7\u48d8\u48d9\u48da\u48db\u48dc\u48dd\u48de\u48df\u48e0\u48e1\u48e2\u48e3\u48e4\u48e5\u48e6\u48e7\u48e8\u48e9\u48ea\u48eb\u48ec\u48ed\u48ee\u48ef\u48f0\u48f1\u48f2\u48f3\u48f4\u48f5\u48f6\u48f7\u48f8\u48f9\u48fa\u48fb\u48fc\u48fd\u48fe\u48ff\u4900\u4901\u4902\u4903\u4904\u4905\u4906\u4907\u4908\u4909\u490a\u490b\u490c\u490d\u490e\u490f\u4910\u4911\u4912\u4913\u4914\u4915\u4916\u4917\u4918\u4919\u491a\u491b\u491c\u491d\u491e\u491f\u4920\u4921\u4922\u4923\u4924\u4925\u4926\u4927\u4928\u4929\u492a\u492b\u492c\u492d\u492e\u492f\u4930\u4931\u4932\u4933\u4934\u4935\u4936\u4937\u4938\u4939\u493a\u493b\u493c\u493d\u493e\u493f\u4940\u4941\u4942\u4943\u4944\u4945\u4946\u4947\u4948\u4949\u494a\u494b\u494c\u494d\u494e\u494f\u4950\u4951\u4952\u4953\u4954\u4955\u4956\u4957\u4958\u4959\u495a\u495b\u495c\u495d\u495e\u495f\u4960\u4961\u4962\u4963\u4964\u4965\u4966\u4967\u4968\u4969\u496a\u496b\u496c\u496d\u496e\u496f\u4970\u4971\u4972\u4973\u4974\u4975\u4976\u4977\u4978\u4979\u497a\u497b\u497c\u497d\u497e\u497f\u4980\u4981\u4982\u4983\u4984\u4985\u4986\u4987\u4988\u4989\u498a\u498b\u498c\u498d\u498e\u498f\u4990\u4991\u4992\u4993\u4994\u4995\u4996\u4997\u4998\u4999\u499a\u499b\u499c\u499d\u499e\u499f\u49a0\u49a1\u49a2\u49a3\u49a4\u49a5\u49a6\u49a7\u49a8\u49a9\u49aa\u49ab\u49ac\u49ad\u49ae\u49af\u49b0\u49b1\u49b2\u49b3\u49b4\u49b5\u49b6\u49b7\u49b8\u49b9\u49ba\u49bb\u49bc\u49bd\u49be\u49bf\u49c0\u49c1\u49c2\u49c3\u49c4\u49c5\u49c6\u49c7\u49c8\u49c9\u49ca\u49cb\u49cc\u49cd\u49ce\u49cf\u49d0\u49d1\u49d2\u49d3\u49d4\u49d5\u49d6\u49d7\u49d8\u49d9\u49da\u49db\u49dc\u49dd\u49de\u49df\u49e0\u49e1\u49e2\u49e3\u49e4\u49e5\u49e6\u49e7\u49e8\u49e9\u49ea\u49eb\u49ec\u49ed\u49ee\u49ef\u49f0\u49f1\u49f2\u49f3\u49f4\u49f5\u49f6\u49f7\u49f8\u49f9\u49fa\u49fb\u49fc\u49fd\u49fe\u49ff\u4a00\u4a01\u4a02\u4a03\u4a04\u4a05\u4a06\u4a07\u4a08\u4a09\u4a0a\u4a0b\u4a0c\u4a0d\u4a0e\u4a0f\u4a10\u4a11\u4a12\u4a13\u4a14\u4a15\u4a16\u4a17\u4a18\u4a19\u4a1a\u4a1b\u4a1c\u4a1d\u4a1e\u4a1f\u4a20\u4a21\u4a22\u4a23\u4a24\u4a25\u4a26\u4a27\u4a28\u4a29\u4a2a\u4a2b\u4a2c\u4a2d\u4a2e\u4a2f\u4a30\u4a31\u4a32\u4a33\u4a34\u4a35\u4a36\u4a37\u4a38\u4a39\u4a3a\u4a3b\u4a3c\u4a3d\u4a3e\u4a3f\u4a40\u4a41\u4a42\u4a43\u4a44\u4a45\u4a46\u4a47\u4a48\u4a49\u4a4a\u4a4b\u4a4c\u4a4d\u4a4e\u4a4f\u4a50\u4a51\u4a52\u4a53\u4a54\u4a55\u4a56\u4a57\u4a58\u4a59\u4a5a\u4a5b\u4a5c\u4a5d\u4a5e\u4a5f\u4a60\u4a61\u4a62\u4a63\u4a64\u4a65\u4a66\u4a67\u4a68\u4a69\u4a6a\u4a6b\u4a6c\u4a6d\u4a6e\u4a6f\u4a70\u4a71\u4a72\u4a73\u4a74\u4a75\u4a76\u4a77\u4a78\u4a79\u4a7a\u4a7b\u4a7c\u4a7d\u4a7e\u4a7f\u4a80\u4a81\u4a82\u4a83\u4a84\u4a85\u4a86\u4a87\u4a88\u4a89\u4a8a\u4a8b\u4a8c\u4a8d\u4a8e\u4a8f\u4a90\u4a91\u4a92\u4a93\u4a94\u4a95\u4a96\u4a97\u4a98\u4a99\u4a9a\u4a9b\u4a9c\u4a9d\u4a9e\u4a9f\u4aa0\u4aa1\u4aa2\u4aa3\u4aa4\u4aa5\u4aa6\u4aa7\u4aa8\u4aa9\u4aaa\u4aab\u4aac\u4aad\u4aae\u4aaf\u4ab0\u4ab1\u4ab2\u4ab3\u4ab4\u4ab5\u4ab6\u4ab7\u4ab8\u4ab9\u4aba\u4abb\u4abc\u4abd\u4abe\u4abf\u4ac0\u4ac1\u4ac2\u4ac3\u4ac4\u4ac5\u4ac6\u4ac7\u4ac8\u4ac9\u4aca\u4acb\u4acc\u4acd\u4ace\u4acf\u4ad0\u4ad1\u4ad2\u4ad3\u4ad4\u4ad5\u4ad6\u4ad7\u4ad8\u4ad9\u4ada\u4adb\u4adc\u4add\u4ade\u4adf\u4ae0\u4ae1\u4ae2\u4ae3\u4ae4\u4ae5\u4ae6\u4ae7\u4ae8\u4ae9\u4aea\u4aeb\u4aec\u4aed\u4aee\u4aef\u4af0\u4af1\u4af2\u4af3\u4af4\u4af5\u4af6\u4af7\u4af8\u4af9\u4afa\u4afb\u4afc\u4afd\u4afe\u4aff\u4b00\u4b01\u4b02\u4b03\u4b04\u4b05\u4b06\u4b07\u4b08\u4b09\u4b0a\u4b0b\u4b0c\u4b0d\u4b0e\u4b0f\u4b10\u4b11\u4b12\u4b13\u4b14\u4b15\u4b16\u4b17\u4b18\u4b19\u4b1a\u4b1b\u4b1c\u4b1d\u4b1e\u4b1f\u4b20\u4b21\u4b22\u4b23\u4b24\u4b25\u4b26\u4b27\u4b28\u4b29\u4b2a\u4b2b\u4b2c\u4b2d\u4b2e\u4b2f\u4b30\u4b31\u4b32\u4b33\u4b34\u4b35\u4b36\u4b37\u4b38\u4b39\u4b3a\u4b3b\u4b3c\u4b3d\u4b3e\u4b3f\u4b40\u4b41\u4b42\u4b43\u4b44\u4b45\u4b46\u4b47\u4b48\u4b49\u4b4a\u4b4b\u4b4c\u4b4d\u4b4e\u4b4f\u4b50\u4b51\u4b52\u4b53\u4b54\u4b55\u4b56\u4b57\u4b58\u4b59\u4b5a\u4b5b\u4b5c\u4b5d\u4b5e\u4b5f\u4b60\u4b61\u4b62\u4b63\u4b64\u4b65\u4b66\u4b67\u4b68\u4b69\u4b6a\u4b6b\u4b6c\u4b6d\u4b6e\u4b6f\u4b70\u4b71\u4b72\u4b73\u4b74\u4b75\u4b76\u4b77\u4b78\u4b79\u4b7a\u4b7b\u4b7c\u4b7d\u4b7e\u4b7f\u4b80\u4b81\u4b82\u4b83\u4b84\u4b85\u4b86\u4b87\u4b88\u4b89\u4b8a\u4b8b\u4b8c\u4b8d\u4b8e\u4b8f\u4b90\u4b91\u4b92\u4b93\u4b94\u4b95\u4b96\u4b97\u4b98\u4b99\u4b9a\u4b9b\u4b9c\u4b9d\u4b9e\u4b9f\u4ba0\u4ba1\u4ba2\u4ba3\u4ba4\u4ba5\u4ba6\u4ba7\u4ba8\u4ba9\u4baa\u4bab\u4bac\u4bad\u4bae\u4baf\u4bb0\u4bb1\u4bb2\u4bb3\u4bb4\u4bb5\u4bb6\u4bb7\u4bb8\u4bb9\u4bba\u4bbb\u4bbc\u4bbd\u4bbe\u4bbf\u4bc0\u4bc1\u4bc2\u4bc3\u4bc4\u4bc5\u4bc6\u4bc7\u4bc8\u4bc9\u4bca\u4bcb\u4bcc\u4bcd\u4bce\u4bcf\u4bd0\u4bd1\u4bd2\u4bd3\u4bd4\u4bd5\u4bd6\u4bd7\u4bd8\u4bd9\u4bda\u4bdb\u4bdc\u4bdd\u4bde\u4bdf\u4be0\u4be1\u4be2\u4be3\u4be4\u4be5\u4be6\u4be7\u4be8\u4be9\u4bea\u4beb\u4bec\u4bed\u4bee\u4bef\u4bf0\u4bf1\u4bf2\u4bf3\u4bf4\u4bf5\u4bf6\u4bf7\u4bf8\u4bf9\u4bfa\u4bfb\u4bfc\u4bfd\u4bfe\u4bff\u4c00\u4c01\u4c02\u4c03\u4c04\u4c05\u4c06\u4c07\u4c08\u4c09\u4c0a\u4c0b\u4c0c\u4c0d\u4c0e\u4c0f\u4c10\u4c11\u4c12\u4c13\u4c14\u4c15\u4c16\u4c17\u4c18\u4c19\u4c1a\u4c1b\u4c1c\u4c1d\u4c1e\u4c1f\u4c20\u4c21\u4c22\u4c23\u4c24\u4c25\u4c26\u4c27\u4c28\u4c29\u4c2a\u4c2b\u4c2c\u4c2d\u4c2e\u4c2f\u4c30\u4c31\u4c32\u4c33\u4c34\u4c35\u4c36\u4c37\u4c38\u4c39\u4c3a\u4c3b\u4c3c\u4c3d\u4c3e\u4c3f\u4c40\u4c41\u4c42\u4c43\u4c44\u4c45\u4c46\u4c47\u4c48\u4c49\u4c4a\u4c4b\u4c4c\u4c4d\u4c4e\u4c4f\u4c50\u4c51\u4c52\u4c53\u4c54\u4c55\u4c56\u4c57\u4c58\u4c59\u4c5a\u4c5b\u4c5c\u4c5d\u4c5e\u4c5f\u4c60\u4c61\u4c62\u4c63\u4c64\u4c65\u4c66\u4c67\u4c68\u4c69\u4c6a\u4c6b\u4c6c\u4c6d\u4c6e\u4c6f\u4c70\u4c71\u4c72\u4c73\u4c74\u4c75\u4c76\u4c77\u4c78\u4c79\u4c7a\u4c7b\u4c7c\u4c7d\u4c7e\u4c7f\u4c80\u4c81\u4c82\u4c83\u4c84\u4c85\u4c86\u4c87\u4c88\u4c89\u4c8a\u4c8b\u4c8c\u4c8d\u4c8e\u4c8f\u4c90\u4c91\u4c92\u4c93\u4c94\u4c95\u4c96\u4c97\u4c98\u4c99\u4c9a\u4c9b\u4c9c\u4c9d\u4c9e\u4c9f\u4ca0\u4ca1\u4ca2\u4ca3\u4ca4\u4ca5\u4ca6\u4ca7\u4ca8\u4ca9\u4caa\u4cab\u4cac\u4cad\u4cae\u4caf\u4cb0\u4cb1\u4cb2\u4cb3\u4cb4\u4cb5\u4cb6\u4cb7\u4cb8\u4cb9\u4cba\u4cbb\u4cbc\u4cbd\u4cbe\u4cbf\u4cc0\u4cc1\u4cc2\u4cc3\u4cc4\u4cc5\u4cc6\u4cc7\u4cc8\u4cc9\u4cca\u4ccb\u4ccc\u4ccd\u4cce\u4ccf\u4cd0\u4cd1\u4cd2\u4cd3\u4cd4\u4cd5\u4cd6\u4cd7\u4cd8\u4cd9\u4cda\u4cdb\u4cdc\u4cdd\u4cde\u4cdf\u4ce0\u4ce1\u4ce2\u4ce3\u4ce4\u4ce5\u4ce6\u4ce7\u4ce8\u4ce9\u4cea\u4ceb\u4cec\u4ced\u4cee\u4cef\u4cf0\u4cf1\u4cf2\u4cf3\u4cf4\u4cf5\u4cf6\u4cf7\u4cf8\u4cf9\u4cfa\u4cfb\u4cfc\u4cfd\u4cfe\u4cff\u4d00\u4d01\u4d02\u4d03\u4d04\u4d05\u4d06\u4d07\u4d08\u4d09\u4d0a\u4d0b\u4d0c\u4d0d\u4d0e\u4d0f\u4d10\u4d11\u4d12\u4d13\u4d14\u4d15\u4d16\u4d17\u4d18\u4d19\u4d1a\u4d1b\u4d1c\u4d1d\u4d1e\u4d1f\u4d20\u4d21\u4d22\u4d23\u4d24\u4d25\u4d26\u4d27\u4d28\u4d29\u4d2a\u4d2b\u4d2c\u4d2d\u4d2e\u4d2f\u4d30\u4d31\u4d32\u4d33\u4d34\u4d35\u4d36\u4d37\u4d38\u4d39\u4d3a\u4d3b\u4d3c\u4d3d\u4d3e\u4d3f\u4d40\u4d41\u4d42\u4d43\u4d44\u4d45\u4d46\u4d47\u4d48\u4d49\u4d4a\u4d4b\u4d4c\u4d4d\u4d4e\u4d4f\u4d50\u4d51\u4d52\u4d53\u4d54\u4d55\u4d56\u4d57\u4d58\u4d59\u4d5a\u4d5b\u4d5c\u4d5d\u4d5e\u4d5f\u4d60\u4d61\u4d62\u4d63\u4d64\u4d65\u4d66\u4d67\u4d68\u4d69\u4d6a\u4d6b\u4d6c\u4d6d\u4d6e\u4d6f\u4d70\u4d71\u4d72\u4d73\u4d74\u4d75\u4d76\u4d77\u4d78\u4d79\u4d7a\u4d7b\u4d7c\u4d7d\u4d7e\u4d7f\u4d80\u4d81\u4d82\u4d83\u4d84\u4d85\u4d86\u4d87\u4d88\u4d89\u4d8a\u4d8b\u4d8c\u4d8d\u4d8e\u4d8f\u4d90\u4d91\u4d92\u4d93\u4d94\u4d95\u4d96\u4d97\u4d98\u4d99\u4d9a\u4d9b\u4d9c\u4d9d\u4d9e\u4d9f\u4da0\u4da1\u4da2\u4da3\u4da4\u4da5\u4da6\u4da7\u4da8\u4da9\u4daa\u4dab\u4dac\u4dad\u4dae\u4daf\u4db0\u4db1\u4db2\u4db3\u4db4\u4db5\u4e00\u4e01\u4e02\u4e03\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d\u4e0e\u4e0f\u4e10\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e18\u4e19\u4e1a\u4e1b\u4e1c\u4e1d\u4e1e\u4e1f\u4e20\u4e21\u4e22\u4e23\u4e24\u4e25\u4e26\u4e27\u4e28\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\u4e30\u4e31\u4e32\u4e33\u4e34\u4e35\u4e36\u4e37\u4e38\u4e39\u4e3a\u4e3b\u4e3c\u4e3d\u4e3e\u4e3f\u4e40\u4e41\u4e42\u4e43\u4e44\u4e45\u4e46\u4e47\u4e48\u4e49\u4e4a\u4e4b\u4e4c\u4e4d\u4e4e\u4e4f\u4e50\u4e51\u4e52\u4e53\u4e54\u4e55\u4e56\u4e57\u4e58\u4e59\u4e5a\u4e5b\u4e5c\u4e5d\u4e5e\u4e5f\u4e60\u4e61\u4e62\u4e63\u4e64\u4e65\u4e66\u4e67\u4e68\u4e69\u4e6a\u4e6b\u4e6c\u4e6d\u4e6e\u4e6f\u4e70\u4e71\u4e72\u4e73\u4e74\u4e75\u4e76\u4e77\u4e78\u4e79\u4e7a\u4e7b\u4e7c\u4e7d\u4e7e\u4e7f\u4e80\u4e81\u4e82\u4e83\u4e84\u4e85\u4e86\u4e87\u4e88\u4e89\u4e8a\u4e8b\u4e8c\u4e8d\u4e8e\u4e8f\u4e90\u4e91\u4e92\u4e93\u4e94\u4e95\u4e96\u4e97\u4e98\u4e99\u4e9a\u4e9b\u4e9c\u4e9d\u4e9e\u4e9f\u4ea0\u4ea1\u4ea2\u4ea3\u4ea4\u4ea5\u4ea6\u4ea7\u4ea8\u4ea9\u4eaa\u4eab\u4eac\u4ead\u4eae\u4eaf\u4eb0\u4eb1\u4eb2\u4eb3\u4eb4\u4eb5\u4eb6\u4eb7\u4eb8\u4eb9\u4eba\u4ebb\u4ebc\u4ebd\u4ebe\u4ebf\u4ec0\u4ec1\u4ec2\u4ec3\u4ec4\u4ec5\u4ec6\u4ec7\u4ec8\u4ec9\u4eca\u4ecb\u4ecc\u4ecd\u4ece\u4ecf\u4ed0\u4ed1\u4ed2\u4ed3\u4ed4\u4ed5\u4ed6\u4ed7\u4ed8\u4ed9\u4eda\u4edb\u4edc\u4edd\u4ede\u4edf\u4ee0\u4ee1\u4ee2\u4ee3\u4ee4\u4ee5\u4ee6\u4ee7\u4ee8\u4ee9\u4eea\u4eeb\u4eec\u4eed\u4eee\u4eef\u4ef0\u4ef1\u4ef2\u4ef3\u4ef4\u4ef5\u4ef6\u4ef7\u4ef8\u4ef9\u4efa\u4efb\u4efc\u4efd\u4efe\u4eff\u4f00\u4f01\u4f02\u4f03\u4f04\u4f05\u4f06\u4f07\u4f08\u4f09\u4f0a\u4f0b\u4f0c\u4f0d\u4f0e\u4f0f\u4f10\u4f11\u4f12\u4f13\u4f14\u4f15\u4f16\u4f17\u4f18\u4f19\u4f1a\u4f1b\u4f1c\u4f1d\u4f1e\u4f1f\u4f20\u4f21\u4f22\u4f23\u4f24\u4f25\u4f26\u4f27\u4f28\u4f29\u4f2a\u4f2b\u4f2c\u4f2d\u4f2e\u4f2f\u4f30\u4f31\u4f32\u4f33\u4f34\u4f35\u4f36\u4f37\u4f38\u4f39\u4f3a\u4f3b\u4f3c\u4f3d\u4f3e\u4f3f\u4f40\u4f41\u4f42\u4f43\u4f44\u4f45\u4f46\u4f47\u4f48\u4f49\u4f4a\u4f4b\u4f4c\u4f4d\u4f4e\u4f4f\u4f50\u4f51\u4f52\u4f53\u4f54\u4f55\u4f56\u4f57\u4f58\u4f59\u4f5a\u4f5b\u4f5c\u4f5d\u4f5e\u4f5f\u4f60\u4f61\u4f62\u4f63\u4f64\u4f65\u4f66\u4f67\u4f68\u4f69\u4f6a\u4f6b\u4f6c\u4f6d\u4f6e\u4f6f\u4f70\u4f71\u4f72\u4f73\u4f74\u4f75\u4f76\u4f77\u4f78\u4f79\u4f7a\u4f7b\u4f7c\u4f7d\u4f7e\u4f7f\u4f80\u4f81\u4f82\u4f83\u4f84\u4f85\u4f86\u4f87\u4f88\u4f89\u4f8a\u4f8b\u4f8c\u4f8d\u4f8e\u4f8f\u4f90\u4f91\u4f92\u4f93\u4f94\u4f95\u4f96\u4f97\u4f98\u4f99\u4f9a\u4f9b\u4f9c\u4f9d\u4f9e\u4f9f\u4fa0\u4fa1\u4fa2\u4fa3\u4fa4\u4fa5\u4fa6\u4fa7\u4fa8\u4fa9\u4faa\u4fab\u4fac\u4fad\u4fae\u4faf\u4fb0\u4fb1\u4fb2\u4fb3\u4fb4\u4fb5\u4fb6\u4fb7\u4fb8\u4fb9\u4fba\u4fbb\u4fbc\u4fbd\u4fbe\u4fbf\u4fc0\u4fc1\u4fc2\u4fc3\u4fc4\u4fc5\u4fc6\u4fc7\u4fc8\u4fc9\u4fca\u4fcb\u4fcc\u4fcd\u4fce\u4fcf\u4fd0\u4fd1\u4fd2\u4fd3\u4fd4\u4fd5\u4fd6\u4fd7\u4fd8\u4fd9\u4fda\u4fdb\u4fdc\u4fdd\u4fde\u4fdf\u4fe0\u4fe1\u4fe2\u4fe3\u4fe4\u4fe5\u4fe6\u4fe7\u4fe8\u4fe9\u4fea\u4feb\u4fec\u4fed\u4fee\u4fef\u4ff0\u4ff1\u4ff2\u4ff3\u4ff4\u4ff5\u4ff6\u4ff7\u4ff8\u4ff9\u4ffa\u4ffb\u4ffc\u4ffd\u4ffe\u4fff\u5000\u5001\u5002\u5003\u5004\u5005\u5006\u5007\u5008\u5009\u500a\u500b\u500c\u500d\u500e\u500f\u5010\u5011\u5012\u5013\u5014\u5015\u5016\u5017\u5018\u5019\u501a\u501b\u501c\u501d\u501e\u501f\u5020\u5021\u5022\u5023\u5024\u5025\u5026\u5027\u5028\u5029\u502a\u502b\u502c\u502d\u502e\u502f\u5030\u5031\u5032\u5033\u5034\u5035\u5036\u5037\u5038\u5039\u503a\u503b\u503c\u503d\u503e\u503f\u5040\u5041\u5042\u5043\u5044\u5045\u5046\u5047\u5048\u5049\u504a\u504b\u504c\u504d\u504e\u504f\u5050\u5051\u5052\u5053\u5054\u5055\u5056\u5057\u5058\u5059\u505a\u505b\u505c\u505d\u505e\u505f\u5060\u5061\u5062\u5063\u5064\u5065\u5066\u5067\u5068\u5069\u506a\u506b\u506c\u506d\u506e\u506f\u5070\u5071\u5072\u5073\u5074\u5075\u5076\u5077\u5078\u5079\u507a\u507b\u507c\u507d\u507e\u507f\u5080\u5081\u5082\u5083\u5084\u5085\u5086\u5087\u5088\u5089\u508a\u508b\u508c\u508d\u508e\u508f\u5090\u5091\u5092\u5093\u5094\u5095\u5096\u5097\u5098\u5099\u509a\u509b\u509c\u509d\u509e\u509f\u50a0\u50a1\u50a2\u50a3\u50a4\u50a5\u50a6\u50a7\u50a8\u50a9\u50aa\u50ab\u50ac\u50ad\u50ae\u50af\u50b0\u50b1\u50b2\u50b3\u50b4\u50b5\u50b6\u50b7\u50b8\u50b9\u50ba\u50bb\u50bc\u50bd\u50be\u50bf\u50c0\u50c1\u50c2\u50c3\u50c4\u50c5\u50c6\u50c7\u50c8\u50c9\u50ca\u50cb\u50cc\u50cd\u50ce\u50cf\u50d0\u50d1\u50d2\u50d3\u50d4\u50d5\u50d6\u50d7\u50d8\u50d9\u50da\u50db\u50dc\u50dd\u50de\u50df\u50e0\u50e1\u50e2\u50e3\u50e4\u50e5\u50e6\u50e7\u50e8\u50e9\u50ea\u50eb\u50ec\u50ed\u50ee\u50ef\u50f0\u50f1\u50f2\u50f3\u50f4\u50f5\u50f6\u50f7\u50f8\u50f9\u50fa\u50fb\u50fc\u50fd\u50fe\u50ff\u5100\u5101\u5102\u5103\u5104\u5105\u5106\u5107\u5108\u5109\u510a\u510b\u510c\u510d\u510e\u510f\u5110\u5111\u5112\u5113\u5114\u5115\u5116\u5117\u5118\u5119\u511a\u511b\u511c\u511d\u511e\u511f\u5120\u5121\u5122\u5123\u5124\u5125\u5126\u5127\u5128\u5129\u512a\u512b\u512c\u512d\u512e\u512f\u5130\u5131\u5132\u5133\u5134\u5135\u5136\u5137\u5138\u5139\u513a\u513b\u513c\u513d\u513e\u513f\u5140\u5141\u5142\u5143\u5144\u5145\u5146\u5147\u5148\u5149\u514a\u514b\u514c\u514d\u514e\u514f\u5150\u5151\u5152\u5153\u5154\u5155\u5156\u5157\u5158\u5159\u515a\u515b\u515c\u515d\u515e\u515f\u5160\u5161\u5162\u5163\u5164\u5165\u5166\u5167\u5168\u5169\u516a\u516b\u516c\u516d\u516e\u516f\u5170\u5171\u5172\u5173\u5174\u5175\u5176\u5177\u5178\u5179\u517a\u517b\u517c\u517d\u517e\u517f\u5180\u5181\u5182\u5183\u5184\u5185\u5186\u5187\u5188\u5189\u518a\u518b\u518c\u518d\u518e\u518f\u5190\u5191\u5192\u5193\u5194\u5195\u5196\u5197\u5198\u5199\u519a\u519b\u519c\u519d\u519e\u519f\u51a0\u51a1\u51a2\u51a3\u51a4\u51a5\u51a6\u51a7\u51a8\u51a9\u51aa\u51ab\u51ac\u51ad\u51ae\u51af\u51b0\u51b1\u51b2\u51b3\u51b4\u51b5\u51b6\u51b7\u51b8\u51b9\u51ba\u51bb\u51bc\u51bd\u51be\u51bf\u51c0\u51c1\u51c2\u51c3\u51c4\u51c5\u51c6\u51c7\u51c8\u51c9\u51ca\u51cb\u51cc\u51cd\u51ce\u51cf\u51d0\u51d1\u51d2\u51d3\u51d4\u51d5\u51d6\u51d7\u51d8\u51d9\u51da\u51db\u51dc\u51dd\u51de\u51df\u51e0\u51e1\u51e2\u51e3\u51e4\u51e5\u51e6\u51e7\u51e8\u51e9\u51ea\u51eb\u51ec\u51ed\u51ee\u51ef\u51f0\u51f1\u51f2\u51f3\u51f4\u51f5\u51f6\u51f7\u51f8\u51f9\u51fa\u51fb\u51fc\u51fd\u51fe\u51ff\u5200\u5201\u5202\u5203\u5204\u5205\u5206\u5207\u5208\u5209\u520a\u520b\u520c\u520d\u520e\u520f\u5210\u5211\u5212\u5213\u5214\u5215\u5216\u5217\u5218\u5219\u521a\u521b\u521c\u521d\u521e\u521f\u5220\u5221\u5222\u5223\u5224\u5225\u5226\u5227\u5228\u5229\u522a\u522b\u522c\u522d\u522e\u522f\u5230\u5231\u5232\u5233\u5234\u5235\u5236\u5237\u5238\u5239\u523a\u523b\u523c\u523d\u523e\u523f\u5240\u5241\u5242\u5243\u5244\u5245\u5246\u5247\u5248\u5249\u524a\u524b\u524c\u524d\u524e\u524f\u5250\u5251\u5252\u5253\u5254\u5255\u5256\u5257\u5258\u5259\u525a\u525b\u525c\u525d\u525e\u525f\u5260\u5261\u5262\u5263\u5264\u5265\u5266\u5267\u5268\u5269\u526a\u526b\u526c\u526d\u526e\u526f\u5270\u5271\u5272\u5273\u5274\u5275\u5276\u5277\u5278\u5279\u527a\u527b\u527c\u527d\u527e\u527f\u5280\u5281\u5282\u5283\u5284\u5285\u5286\u5287\u5288\u5289\u528a\u528b\u528c\u528d\u528e\u528f\u5290\u5291\u5292\u5293\u5294\u5295\u5296\u5297\u5298\u5299\u529a\u529b\u529c\u529d\u529e\u529f\u52a0\u52a1\u52a2\u52a3\u52a4\u52a5\u52a6\u52a7\u52a8\u52a9\u52aa\u52ab\u52ac\u52ad\u52ae\u52af\u52b0\u52b1\u52b2\u52b3\u52b4\u52b5\u52b6\u52b7\u52b8\u52b9\u52ba\u52bb\u52bc\u52bd\u52be\u52bf\u52c0\u52c1\u52c2\u52c3\u52c4\u52c5\u52c6\u52c7\u52c8\u52c9\u52ca\u52cb\u52cc\u52cd\u52ce\u52cf\u52d0\u52d1\u52d2\u52d3\u52d4\u52d5\u52d6\u52d7\u52d8\u52d9\u52da\u52db\u52dc\u52dd\u52de\u52df\u52e0\u52e1\u52e2\u52e3\u52e4\u52e5\u52e6\u52e7\u52e8\u52e9\u52ea\u52eb\u52ec\u52ed\u52ee\u52ef\u52f0\u52f1\u52f2\u52f3\u52f4\u52f5\u52f6\u52f7\u52f8\u52f9\u52fa\u52fb\u52fc\u52fd\u52fe\u52ff\u5300\u5301\u5302\u5303\u5304\u5305\u5306\u5307\u5308\u5309\u530a\u530b\u530c\u530d\u530e\u530f\u5310\u5311\u5312\u5313\u5314\u5315\u5316\u5317\u5318\u5319\u531a\u531b\u531c\u531d\u531e\u531f\u5320\u5321\u5322\u5323\u5324\u5325\u5326\u5327\u5328\u5329\u532a\u532b\u532c\u532d\u532e\u532f\u5330\u5331\u5332\u5333\u5334\u5335\u5336\u5337\u5338\u5339\u533a\u533b\u533c\u533d\u533e\u533f\u5340\u5341\u5342\u5343\u5344\u5345\u5346\u5347\u5348\u5349\u534a\u534b\u534c\u534d\u534e\u534f\u5350\u5351\u5352\u5353\u5354\u5355\u5356\u5357\u5358\u5359\u535a\u535b\u535c\u535d\u535e\u535f\u5360\u5361\u5362\u5363\u5364\u5365\u5366\u5367\u5368\u5369\u536a\u536b\u536c\u536d\u536e\u536f\u5370\u5371\u5372\u5373\u5374\u5375\u5376\u5377\u5378\u5379\u537a\u537b\u537c\u537d\u537e\u537f\u5380\u5381\u5382\u5383\u5384\u5385\u5386\u5387\u5388\u5389\u538a\u538b\u538c\u538d\u538e\u538f\u5390\u5391\u5392\u5393\u5394\u5395\u5396\u5397\u5398\u5399\u539a\u539b\u539c\u539d\u539e\u539f\u53a0\u53a1\u53a2\u53a3\u53a4\u53a5\u53a6\u53a7\u53a8\u53a9\u53aa\u53ab\u53ac\u53ad\u53ae\u53af\u53b0\u53b1\u53b2\u53b3\u53b4\u53b5\u53b6\u53b7\u53b8\u53b9\u53ba\u53bb\u53bc\u53bd\u53be\u53bf\u53c0\u53c1\u53c2\u53c3\u53c4\u53c5\u53c6\u53c7\u53c8\u53c9\u53ca\u53cb\u53cc\u53cd\u53ce\u53cf\u53d0\u53d1\u53d2\u53d3\u53d4\u53d5\u53d6\u53d7\u53d8\u53d9\u53da\u53db\u53dc\u53dd\u53de\u53df\u53e0\u53e1\u53e2\u53e3\u53e4\u53e5\u53e6\u53e7\u53e8\u53e9\u53ea\u53eb\u53ec\u53ed\u53ee\u53ef\u53f0\u53f1\u53f2\u53f3\u53f4\u53f5\u53f6\u53f7\u53f8\u53f9\u53fa\u53fb\u53fc\u53fd\u53fe\u53ff\u5400\u5401\u5402\u5403\u5404\u5405\u5406\u5407\u5408\u5409\u540a\u540b\u540c\u540d\u540e\u540f\u5410\u5411\u5412\u5413\u5414\u5415\u5416\u5417\u5418\u5419\u541a\u541b\u541c\u541d\u541e\u541f\u5420\u5421\u5422\u5423\u5424\u5425\u5426\u5427\u5428\u5429\u542a\u542b\u542c\u542d\u542e\u542f\u5430\u5431\u5432\u5433\u5434\u5435\u5436\u5437\u5438\u5439\u543a\u543b\u543c\u543d\u543e\u543f\u5440\u5441\u5442\u5443\u5444\u5445\u5446\u5447\u5448\u5449\u544a\u544b\u544c\u544d\u544e\u544f\u5450\u5451\u5452\u5453\u5454\u5455\u5456\u5457\u5458\u5459\u545a\u545b\u545c\u545d\u545e\u545f\u5460\u5461\u5462\u5463\u5464\u5465\u5466\u5467\u5468\u5469\u546a\u546b\u546c\u546d\u546e\u546f\u5470\u5471\u5472\u5473\u5474\u5475\u5476\u5477\u5478\u5479\u547a\u547b\u547c\u547d\u547e\u547f\u5480\u5481\u5482\u5483\u5484\u5485\u5486\u5487\u5488\u5489\u548a\u548b\u548c\u548d\u548e\u548f\u5490\u5491\u5492\u5493\u5494\u5495\u5496\u5497\u5498\u5499\u549a\u549b\u549c\u549d\u549e\u549f\u54a0\u54a1\u54a2\u54a3\u54a4\u54a5\u54a6\u54a7\u54a8\u54a9\u54aa\u54ab\u54ac\u54ad\u54ae\u54af\u54b0\u54b1\u54b2\u54b3\u54b4\u54b5\u54b6\u54b7\u54b8\u54b9\u54ba\u54bb\u54bc\u54bd\u54be\u54bf\u54c0\u54c1\u54c2\u54c3\u54c4\u54c5\u54c6\u54c7\u54c8\u54c9\u54ca\u54cb\u54cc\u54cd\u54ce\u54cf\u54d0\u54d1\u54d2\u54d3\u54d4\u54d5\u54d6\u54d7\u54d8\u54d9\u54da\u54db\u54dc\u54dd\u54de\u54df\u54e0\u54e1\u54e2\u54e3\u54e4\u54e5\u54e6\u54e7\u54e8\u54e9\u54ea\u54eb\u54ec\u54ed\u54ee\u54ef\u54f0\u54f1\u54f2\u54f3\u54f4\u54f5\u54f6\u54f7\u54f8\u54f9\u54fa\u54fb\u54fc\u54fd\u54fe\u54ff\u5500\u5501\u5502\u5503\u5504\u5505\u5506\u5507\u5508\u5509\u550a\u550b\u550c\u550d\u550e\u550f\u5510\u5511\u5512\u5513\u5514\u5515\u5516\u5517\u5518\u5519\u551a\u551b\u551c\u551d\u551e\u551f\u5520\u5521\u5522\u5523\u5524\u5525\u5526\u5527\u5528\u5529\u552a\u552b\u552c\u552d\u552e\u552f\u5530\u5531\u5532\u5533\u5534\u5535\u5536\u5537\u5538\u5539\u553a\u553b\u553c\u553d\u553e\u553f\u5540\u5541\u5542\u5543\u5544\u5545\u5546\u5547\u5548\u5549\u554a\u554b\u554c\u554d\u554e\u554f\u5550\u5551\u5552\u5553\u5554\u5555\u5556\u5557\u5558\u5559\u555a\u555b\u555c\u555d\u555e\u555f\u5560\u5561\u5562\u5563\u5564\u5565\u5566\u5567\u5568\u5569\u556a\u556b\u556c\u556d\u556e\u556f\u5570\u5571\u5572\u5573\u5574\u5575\u5576\u5577\u5578\u5579\u557a\u557b\u557c\u557d\u557e\u557f\u5580\u5581\u5582\u5583\u5584\u5585\u5586\u5587\u5588\u5589\u558a\u558b\u558c\u558d\u558e\u558f\u5590\u5591\u5592\u5593\u5594\u5595\u5596\u5597\u5598\u5599\u559a\u559b\u559c\u559d\u559e\u559f\u55a0\u55a1\u55a2\u55a3\u55a4\u55a5\u55a6\u55a7\u55a8\u55a9\u55aa\u55ab\u55ac\u55ad\u55ae\u55af\u55b0\u55b1\u55b2\u55b3\u55b4\u55b5\u55b6\u55b7\u55b8\u55b9\u55ba\u55bb\u55bc\u55bd\u55be\u55bf\u55c0\u55c1\u55c2\u55c3\u55c4\u55c5\u55c6\u55c7\u55c8\u55c9\u55ca\u55cb\u55cc\u55cd\u55ce\u55cf\u55d0\u55d1\u55d2\u55d3\u55d4\u55d5\u55d6\u55d7\u55d8\u55d9\u55da\u55db\u55dc\u55dd\u55de\u55df\u55e0\u55e1\u55e2\u55e3\u55e4\u55e5\u55e6\u55e7\u55e8\u55e9\u55ea\u55eb\u55ec\u55ed\u55ee\u55ef\u55f0\u55f1\u55f2\u55f3\u55f4\u55f5\u55f6\u55f7\u55f8\u55f9\u55fa\u55fb\u55fc\u55fd\u55fe\u55ff\u5600\u5601\u5602\u5603\u5604\u5605\u5606\u5607\u5608\u5609\u560a\u560b\u560c\u560d\u560e\u560f\u5610\u5611\u5612\u5613\u5614\u5615\u5616\u5617\u5618\u5619\u561a\u561b\u561c\u561d\u561e\u561f\u5620\u5621\u5622\u5623\u5624\u5625\u5626\u5627\u5628\u5629\u562a\u562b\u562c\u562d\u562e\u562f\u5630\u5631\u5632\u5633\u5634\u5635\u5636\u5637\u5638\u5639\u563a\u563b\u563c\u563d\u563e\u563f\u5640\u5641\u5642\u5643\u5644\u5645\u5646\u5647\u5648\u5649\u564a\u564b\u564c\u564d\u564e\u564f\u5650\u5651\u5652\u5653\u5654\u5655\u5656\u5657\u5658\u5659\u565a\u565b\u565c\u565d\u565e\u565f\u5660\u5661\u5662\u5663\u5664\u5665\u5666\u5667\u5668\u5669\u566a\u566b\u566c\u566d\u566e\u566f\u5670\u5671\u5672\u5673\u5674\u5675\u5676\u5677\u5678\u5679\u567a\u567b\u567c\u567d\u567e\u567f\u5680\u5681\u5682\u5683\u5684\u5685\u5686\u5687\u5688\u5689\u568a\u568b\u568c\u568d\u568e\u568f\u5690\u5691\u5692\u5693\u5694\u5695\u5696\u5697\u5698\u5699\u569a\u569b\u569c\u569d\u569e\u569f\u56a0\u56a1\u56a2\u56a3\u56a4\u56a5\u56a6\u56a7\u56a8\u56a9\u56aa\u56ab\u56ac\u56ad\u56ae\u56af\u56b0\u56b1\u56b2\u56b3\u56b4\u56b5\u56b6\u56b7\u56b8\u56b9\u56ba\u56bb\u56bc\u56bd\u56be\u56bf\u56c0\u56c1\u56c2\u56c3\u56c4\u56c5\u56c6\u56c7\u56c8\u56c9\u56ca\u56cb\u56cc\u56cd\u56ce\u56cf\u56d0\u56d1\u56d2\u56d3\u56d4\u56d5\u56d6\u56d7\u56d8\u56d9\u56da\u56db\u56dc\u56dd\u56de\u56df\u56e0\u56e1\u56e2\u56e3\u56e4\u56e5\u56e6\u56e7\u56e8\u56e9\u56ea\u56eb\u56ec\u56ed\u56ee\u56ef\u56f0\u56f1\u56f2\u56f3\u56f4\u56f5\u56f6\u56f7\u56f8\u56f9\u56fa\u56fb\u56fc\u56fd\u56fe\u56ff\u5700\u5701\u5702\u5703\u5704\u5705\u5706\u5707\u5708\u5709\u570a\u570b\u570c\u570d\u570e\u570f\u5710\u5711\u5712\u5713\u5714\u5715\u5716\u5717\u5718\u5719\u571a\u571b\u571c\u571d\u571e\u571f\u5720\u5721\u5722\u5723\u5724\u5725\u5726\u5727\u5728\u5729\u572a\u572b\u572c\u572d\u572e\u572f\u5730\u5731\u5732\u5733\u5734\u5735\u5736\u5737\u5738\u5739\u573a\u573b\u573c\u573d\u573e\u573f\u5740\u5741\u5742\u5743\u5744\u5745\u5746\u5747\u5748\u5749\u574a\u574b\u574c\u574d\u574e\u574f\u5750\u5751\u5752\u5753\u5754\u5755\u5756\u5757\u5758\u5759\u575a\u575b\u575c\u575d\u575e\u575f\u5760\u5761\u5762\u5763\u5764\u5765\u5766\u5767\u5768\u5769\u576a\u576b\u576c\u576d\u576e\u576f\u5770\u5771\u5772\u5773\u5774\u5775\u5776\u5777\u5778\u5779\u577a\u577b\u577c\u577d\u577e\u577f\u5780\u5781\u5782\u5783\u5784\u5785\u5786\u5787\u5788\u5789\u578a\u578b\u578c\u578d\u578e\u578f\u5790\u5791\u5792\u5793\u5794\u5795\u5796\u5797\u5798\u5799\u579a\u579b\u579c\u579d\u579e\u579f\u57a0\u57a1\u57a2\u57a3\u57a4\u57a5\u57a6\u57a7\u57a8\u57a9\u57aa\u57ab\u57ac\u57ad\u57ae\u57af\u57b0\u57b1\u57b2\u57b3\u57b4\u57b5\u57b6\u57b7\u57b8\u57b9\u57ba\u57bb\u57bc\u57bd\u57be\u57bf\u57c0\u57c1\u57c2\u57c3\u57c4\u57c5\u57c6\u57c7\u57c8\u57c9\u57ca\u57cb\u57cc\u57cd\u57ce\u57cf\u57d0\u57d1\u57d2\u57d3\u57d4\u57d5\u57d6\u57d7\u57d8\u57d9\u57da\u57db\u57dc\u57dd\u57de\u57df\u57e0\u57e1\u57e2\u57e3\u57e4\u57e5\u57e6\u57e7\u57e8\u57e9\u57ea\u57eb\u57ec\u57ed\u57ee\u57ef\u57f0\u57f1\u57f2\u57f3\u57f4\u57f5\u57f6\u57f7\u57f8\u57f9\u57fa\u57fb\u57fc\u57fd\u57fe\u57ff\u5800\u5801\u5802\u5803\u5804\u5805\u5806\u5807\u5808\u5809\u580a\u580b\u580c\u580d\u580e\u580f\u5810\u5811\u5812\u5813\u5814\u5815\u5816\u5817\u5818\u5819\u581a\u581b\u581c\u581d\u581e\u581f\u5820\u5821\u5822\u5823\u5824\u5825\u5826\u5827\u5828\u5829\u582a\u582b\u582c\u582d\u582e\u582f\u5830\u5831\u5832\u5833\u5834\u5835\u5836\u5837\u5838\u5839\u583a\u583b\u583c\u583d\u583e\u583f\u5840\u5841\u5842\u5843\u5844\u5845\u5846\u5847\u5848\u5849\u584a\u584b\u584c\u584d\u584e\u584f\u5850\u5851\u5852\u5853\u5854\u5855\u5856\u5857\u5858\u5859\u585a\u585b\u585c\u585d\u585e\u585f\u5860\u5861\u5862\u5863\u5864\u5865\u5866\u5867\u5868\u5869\u586a\u586b\u586c\u586d\u586e\u586f\u5870\u5871\u5872\u5873\u5874\u5875\u5876\u5877\u5878\u5879\u587a\u587b\u587c\u587d\u587e\u587f\u5880\u5881\u5882\u5883\u5884\u5885\u5886\u5887\u5888\u5889\u588a\u588b\u588c\u588d\u588e\u588f\u5890\u5891\u5892\u5893\u5894\u5895\u5896\u5897\u5898\u5899\u589a\u589b\u589c\u589d\u589e\u589f\u58a0\u58a1\u58a2\u58a3\u58a4\u58a5\u58a6\u58a7\u58a8\u58a9\u58aa\u58ab\u58ac\u58ad\u58ae\u58af\u58b0\u58b1\u58b2\u58b3\u58b4\u58b5\u58b6\u58b7\u58b8\u58b9\u58ba\u58bb\u58bc\u58bd\u58be\u58bf\u58c0\u58c1\u58c2\u58c3\u58c4\u58c5\u58c6\u58c7\u58c8\u58c9\u58ca\u58cb\u58cc\u58cd\u58ce\u58cf\u58d0\u58d1\u58d2\u58d3\u58d4\u58d5\u58d6\u58d7\u58d8\u58d9\u58da\u58db\u58dc\u58dd\u58de\u58df\u58e0\u58e1\u58e2\u58e3\u58e4\u58e5\u58e6\u58e7\u58e8\u58e9\u58ea\u58eb\u58ec\u58ed\u58ee\u58ef\u58f0\u58f1\u58f2\u58f3\u58f4\u58f5\u58f6\u58f7\u58f8\u58f9\u58fa\u58fb\u58fc\u58fd\u58fe\u58ff\u5900\u5901\u5902\u5903\u5904\u5905\u5906\u5907\u5908\u5909\u590a\u590b\u590c\u590d\u590e\u590f\u5910\u5911\u5912\u5913\u5914\u5915\u5916\u5917\u5918\u5919\u591a\u591b\u591c\u591d\u591e\u591f\u5920\u5921\u5922\u5923\u5924\u5925\u5926\u5927\u5928\u5929\u592a\u592b\u592c\u592d\u592e\u592f\u5930\u5931\u5932\u5933\u5934\u5935\u5936\u5937\u5938\u5939\u593a\u593b\u593c\u593d\u593e\u593f\u5940\u5941\u5942\u5943\u5944\u5945\u5946\u5947\u5948\u5949\u594a\u594b\u594c\u594d\u594e\u594f\u5950\u5951\u5952\u5953\u5954\u5955\u5956\u5957\u5958\u5959\u595a\u595b\u595c\u595d\u595e\u595f\u5960\u5961\u5962\u5963\u5964\u5965\u5966\u5967\u5968\u5969\u596a\u596b\u596c\u596d\u596e\u596f\u5970\u5971\u5972\u5973\u5974\u5975\u5976\u5977\u5978\u5979\u597a\u597b\u597c\u597d\u597e\u597f\u5980\u5981\u5982\u5983\u5984\u5985\u5986\u5987\u5988\u5989\u598a\u598b\u598c\u598d\u598e\u598f\u5990\u5991\u5992\u5993\u5994\u5995\u5996\u5997\u5998\u5999\u599a\u599b\u599c\u599d\u599e\u599f\u59a0\u59a1\u59a2\u59a3\u59a4\u59a5\u59a6\u59a7\u59a8\u59a9\u59aa\u59ab\u59ac\u59ad\u59ae\u59af\u59b0\u59b1\u59b2\u59b3\u59b4\u59b5\u59b6\u59b7\u59b8\u59b9\u59ba\u59bb\u59bc\u59bd\u59be\u59bf\u59c0\u59c1\u59c2\u59c3\u59c4\u59c5\u59c6\u59c7\u59c8\u59c9\u59ca\u59cb\u59cc\u59cd\u59ce\u59cf\u59d0\u59d1\u59d2\u59d3\u59d4\u59d5\u59d6\u59d7\u59d8\u59d9\u59da\u59db\u59dc\u59dd\u59de\u59df\u59e0\u59e1\u59e2\u59e3\u59e4\u59e5\u59e6\u59e7\u59e8\u59e9\u59ea\u59eb\u59ec\u59ed\u59ee\u59ef\u59f0\u59f1\u59f2\u59f3\u59f4\u59f5\u59f6\u59f7\u59f8\u59f9\u59fa\u59fb\u59fc\u59fd\u59fe\u59ff\u5a00\u5a01\u5a02\u5a03\u5a04\u5a05\u5a06\u5a07\u5a08\u5a09\u5a0a\u5a0b\u5a0c\u5a0d\u5a0e\u5a0f\u5a10\u5a11\u5a12\u5a13\u5a14\u5a15\u5a16\u5a17\u5a18\u5a19\u5a1a\u5a1b\u5a1c\u5a1d\u5a1e\u5a1f\u5a20\u5a21\u5a22\u5a23\u5a24\u5a25\u5a26\u5a27\u5a28\u5a29\u5a2a\u5a2b\u5a2c\u5a2d\u5a2e\u5a2f\u5a30\u5a31\u5a32\u5a33\u5a34\u5a35\u5a36\u5a37\u5a38\u5a39\u5a3a\u5a3b\u5a3c\u5a3d\u5a3e\u5a3f\u5a40\u5a41\u5a42\u5a43\u5a44\u5a45\u5a46\u5a47\u5a48\u5a49\u5a4a\u5a4b\u5a4c\u5a4d\u5a4e\u5a4f\u5a50\u5a51\u5a52\u5a53\u5a54\u5a55\u5a56\u5a57\u5a58\u5a59\u5a5a\u5a5b\u5a5c\u5a5d\u5a5e\u5a5f\u5a60\u5a61\u5a62\u5a63\u5a64\u5a65\u5a66\u5a67\u5a68\u5a69\u5a6a\u5a6b\u5a6c\u5a6d\u5a6e\u5a6f\u5a70\u5a71\u5a72\u5a73\u5a74\u5a75\u5a76\u5a77\u5a78\u5a79\u5a7a\u5a7b\u5a7c\u5a7d\u5a7e\u5a7f\u5a80\u5a81\u5a82\u5a83\u5a84\u5a85\u5a86\u5a87\u5a88\u5a89\u5a8a\u5a8b\u5a8c\u5a8d\u5a8e\u5a8f\u5a90\u5a91\u5a92\u5a93\u5a94\u5a95\u5a96\u5a97\u5a98\u5a99\u5a9a\u5a9b\u5a9c\u5a9d\u5a9e\u5a9f\u5aa0\u5aa1\u5aa2\u5aa3\u5aa4\u5aa5\u5aa6\u5aa7\u5aa8\u5aa9\u5aaa\u5aab\u5aac\u5aad\u5aae\u5aaf\u5ab0\u5ab1\u5ab2\u5ab3\u5ab4\u5ab5\u5ab6\u5ab7\u5ab8\u5ab9\u5aba\u5abb\u5abc\u5abd\u5abe\u5abf\u5ac0\u5ac1\u5ac2\u5ac3\u5ac4\u5ac5\u5ac6\u5ac7\u5ac8\u5ac9\u5aca\u5acb\u5acc\u5acd\u5ace\u5acf\u5ad0\u5ad1\u5ad2\u5ad3\u5ad4\u5ad5\u5ad6\u5ad7\u5ad8\u5ad9\u5ada\u5adb\u5adc\u5add\u5ade\u5adf\u5ae0\u5ae1\u5ae2\u5ae3\u5ae4\u5ae5\u5ae6\u5ae7\u5ae8\u5ae9\u5aea\u5aeb\u5aec\u5aed\u5aee\u5aef\u5af0\u5af1\u5af2\u5af3\u5af4\u5af5\u5af6\u5af7\u5af8\u5af9\u5afa\u5afb\u5afc\u5afd\u5afe\u5aff\u5b00\u5b01\u5b02\u5b03\u5b04\u5b05\u5b06\u5b07\u5b08\u5b09\u5b0a\u5b0b\u5b0c\u5b0d\u5b0e\u5b0f\u5b10\u5b11\u5b12\u5b13\u5b14\u5b15\u5b16\u5b17\u5b18\u5b19\u5b1a\u5b1b\u5b1c\u5b1d\u5b1e\u5b1f\u5b20\u5b21\u5b22\u5b23\u5b24\u5b25\u5b26\u5b27\u5b28\u5b29\u5b2a\u5b2b\u5b2c\u5b2d\u5b2e\u5b2f\u5b30\u5b31\u5b32\u5b33\u5b34\u5b35\u5b36\u5b37\u5b38\u5b39\u5b3a\u5b3b\u5b3c\u5b3d\u5b3e\u5b3f\u5b40\u5b41\u5b42\u5b43\u5b44\u5b45\u5b46\u5b47\u5b48\u5b49\u5b4a\u5b4b\u5b4c\u5b4d\u5b4e\u5b4f\u5b50\u5b51\u5b52\u5b53\u5b54\u5b55\u5b56\u5b57\u5b58\u5b59\u5b5a\u5b5b\u5b5c\u5b5d\u5b5e\u5b5f\u5b60\u5b61\u5b62\u5b63\u5b64\u5b65\u5b66\u5b67\u5b68\u5b69\u5b6a\u5b6b\u5b6c\u5b6d\u5b6e\u5b6f\u5b70\u5b71\u5b72\u5b73\u5b74\u5b75\u5b76\u5b77\u5b78\u5b79\u5b7a\u5b7b\u5b7c\u5b7d\u5b7e\u5b7f\u5b80\u5b81\u5b82\u5b83\u5b84\u5b85\u5b86\u5b87\u5b88\u5b89\u5b8a\u5b8b\u5b8c\u5b8d\u5b8e\u5b8f\u5b90\u5b91\u5b92\u5b93\u5b94\u5b95\u5b96\u5b97\u5b98\u5b99\u5b9a\u5b9b\u5b9c\u5b9d\u5b9e\u5b9f\u5ba0\u5ba1\u5ba2\u5ba3\u5ba4\u5ba5\u5ba6\u5ba7\u5ba8\u5ba9\u5baa\u5bab\u5bac\u5bad\u5bae\u5baf\u5bb0\u5bb1\u5bb2\u5bb3\u5bb4\u5bb5\u5bb6\u5bb7\u5bb8\u5bb9\u5bba\u5bbb\u5bbc\u5bbd\u5bbe\u5bbf\u5bc0\u5bc1\u5bc2\u5bc3\u5bc4\u5bc5\u5bc6\u5bc7\u5bc8\u5bc9\u5bca\u5bcb\u5bcc\u5bcd\u5bce\u5bcf\u5bd0\u5bd1\u5bd2\u5bd3\u5bd4\u5bd5\u5bd6\u5bd7\u5bd8\u5bd9\u5bda\u5bdb\u5bdc\u5bdd\u5bde\u5bdf\u5be0\u5be1\u5be2\u5be3\u5be4\u5be5\u5be6\u5be7\u5be8\u5be9\u5bea\u5beb\u5bec\u5bed\u5bee\u5bef\u5bf0\u5bf1\u5bf2\u5bf3\u5bf4\u5bf5\u5bf6\u5bf7\u5bf8\u5bf9\u5bfa\u5bfb\u5bfc\u5bfd\u5bfe\u5bff\u5c00\u5c01\u5c02\u5c03\u5c04\u5c05\u5c06\u5c07\u5c08\u5c09\u5c0a\u5c0b\u5c0c\u5c0d\u5c0e\u5c0f\u5c10\u5c11\u5c12\u5c13\u5c14\u5c15\u5c16\u5c17\u5c18\u5c19\u5c1a\u5c1b\u5c1c\u5c1d\u5c1e\u5c1f\u5c20\u5c21\u5c22\u5c23\u5c24\u5c25\u5c26\u5c27\u5c28\u5c29\u5c2a\u5c2b\u5c2c\u5c2d\u5c2e\u5c2f\u5c30\u5c31\u5c32\u5c33\u5c34\u5c35\u5c36\u5c37\u5c38\u5c39\u5c3a\u5c3b\u5c3c\u5c3d\u5c3e\u5c3f\u5c40\u5c41\u5c42\u5c43\u5c44\u5c45\u5c46\u5c47\u5c48\u5c49\u5c4a\u5c4b\u5c4c\u5c4d\u5c4e\u5c4f\u5c50\u5c51\u5c52\u5c53\u5c54\u5c55\u5c56\u5c57\u5c58\u5c59\u5c5a\u5c5b\u5c5c\u5c5d\u5c5e\u5c5f\u5c60\u5c61\u5c62\u5c63\u5c64\u5c65\u5c66\u5c67\u5c68\u5c69\u5c6a\u5c6b\u5c6c\u5c6d\u5c6e\u5c6f\u5c70\u5c71\u5c72\u5c73\u5c74\u5c75\u5c76\u5c77\u5c78\u5c79\u5c7a\u5c7b\u5c7c\u5c7d\u5c7e\u5c7f\u5c80\u5c81\u5c82\u5c83\u5c84\u5c85\u5c86\u5c87\u5c88\u5c89\u5c8a\u5c8b\u5c8c\u5c8d\u5c8e\u5c8f\u5c90\u5c91\u5c92\u5c93\u5c94\u5c95\u5c96\u5c97\u5c98\u5c99\u5c9a\u5c9b\u5c9c\u5c9d\u5c9e\u5c9f\u5ca0\u5ca1\u5ca2\u5ca3\u5ca4\u5ca5\u5ca6\u5ca7\u5ca8\u5ca9\u5caa\u5cab\u5cac\u5cad\u5cae\u5caf\u5cb0\u5cb1\u5cb2\u5cb3\u5cb4\u5cb5\u5cb6\u5cb7\u5cb8\u5cb9\u5cba\u5cbb\u5cbc\u5cbd\u5cbe\u5cbf\u5cc0\u5cc1\u5cc2\u5cc3\u5cc4\u5cc5\u5cc6\u5cc7\u5cc8\u5cc9\u5cca\u5ccb\u5ccc\u5ccd\u5cce\u5ccf\u5cd0\u5cd1\u5cd2\u5cd3\u5cd4\u5cd5\u5cd6\u5cd7\u5cd8\u5cd9\u5cda\u5cdb\u5cdc\u5cdd\u5cde\u5cdf\u5ce0\u5ce1\u5ce2\u5ce3\u5ce4\u5ce5\u5ce6\u5ce7\u5ce8\u5ce9\u5cea\u5ceb\u5cec\u5ced\u5cee\u5cef\u5cf0\u5cf1\u5cf2\u5cf3\u5cf4\u5cf5\u5cf6\u5cf7\u5cf8\u5cf9\u5cfa\u5cfb\u5cfc\u5cfd\u5cfe\u5cff\u5d00\u5d01\u5d02\u5d03\u5d04\u5d05\u5d06\u5d07\u5d08\u5d09\u5d0a\u5d0b\u5d0c\u5d0d\u5d0e\u5d0f\u5d10\u5d11\u5d12\u5d13\u5d14\u5d15\u5d16\u5d17\u5d18\u5d19\u5d1a\u5d1b\u5d1c\u5d1d\u5d1e\u5d1f\u5d20\u5d21\u5d22\u5d23\u5d24\u5d25\u5d26\u5d27\u5d28\u5d29\u5d2a\u5d2b\u5d2c\u5d2d\u5d2e\u5d2f\u5d30\u5d31\u5d32\u5d33\u5d34\u5d35\u5d36\u5d37\u5d38\u5d39\u5d3a\u5d3b\u5d3c\u5d3d\u5d3e\u5d3f\u5d40\u5d41\u5d42\u5d43\u5d44\u5d45\u5d46\u5d47\u5d48\u5d49\u5d4a\u5d4b\u5d4c\u5d4d\u5d4e\u5d4f\u5d50\u5d51\u5d52\u5d53\u5d54\u5d55\u5d56\u5d57\u5d58\u5d59\u5d5a\u5d5b\u5d5c\u5d5d\u5d5e\u5d5f\u5d60\u5d61\u5d62\u5d63\u5d64\u5d65\u5d66\u5d67\u5d68\u5d69\u5d6a\u5d6b\u5d6c\u5d6d\u5d6e\u5d6f\u5d70\u5d71\u5d72\u5d73\u5d74\u5d75\u5d76\u5d77\u5d78\u5d79\u5d7a\u5d7b\u5d7c\u5d7d\u5d7e\u5d7f\u5d80\u5d81\u5d82\u5d83\u5d84\u5d85\u5d86\u5d87\u5d88\u5d89\u5d8a\u5d8b\u5d8c\u5d8d\u5d8e\u5d8f\u5d90\u5d91\u5d92\u5d93\u5d94\u5d95\u5d96\u5d97\u5d98\u5d99\u5d9a\u5d9b\u5d9c\u5d9d\u5d9e\u5d9f\u5da0\u5da1\u5da2\u5da3\u5da4\u5da5\u5da6\u5da7\u5da8\u5da9\u5daa\u5dab\u5dac\u5dad\u5dae\u5daf\u5db0\u5db1\u5db2\u5db3\u5db4\u5db5\u5db6\u5db7\u5db8\u5db9\u5dba\u5dbb\u5dbc\u5dbd\u5dbe\u5dbf\u5dc0\u5dc1\u5dc2\u5dc3\u5dc4\u5dc5\u5dc6\u5dc7\u5dc8\u5dc9\u5dca\u5dcb\u5dcc\u5dcd\u5dce\u5dcf\u5dd0\u5dd1\u5dd2\u5dd3\u5dd4\u5dd5\u5dd6\u5dd7\u5dd8\u5dd9\u5dda\u5ddb\u5ddc\u5ddd\u5dde\u5ddf\u5de0\u5de1\u5de2\u5de3\u5de4\u5de5\u5de6\u5de7\u5de8\u5de9\u5dea\u5deb\u5dec\u5ded\u5dee\u5def\u5df0\u5df1\u5df2\u5df3\u5df4\u5df5\u5df6\u5df7\u5df8\u5df9\u5dfa\u5dfb\u5dfc\u5dfd\u5dfe\u5dff\u5e00\u5e01\u5e02\u5e03\u5e04\u5e05\u5e06\u5e07\u5e08\u5e09\u5e0a\u5e0b\u5e0c\u5e0d\u5e0e\u5e0f\u5e10\u5e11\u5e12\u5e13\u5e14\u5e15\u5e16\u5e17\u5e18\u5e19\u5e1a\u5e1b\u5e1c\u5e1d\u5e1e\u5e1f\u5e20\u5e21\u5e22\u5e23\u5e24\u5e25\u5e26\u5e27\u5e28\u5e29\u5e2a\u5e2b\u5e2c\u5e2d\u5e2e\u5e2f\u5e30\u5e31\u5e32\u5e33\u5e34\u5e35\u5e36\u5e37\u5e38\u5e39\u5e3a\u5e3b\u5e3c\u5e3d\u5e3e\u5e3f\u5e40\u5e41\u5e42\u5e43\u5e44\u5e45\u5e46\u5e47\u5e48\u5e49\u5e4a\u5e4b\u5e4c\u5e4d\u5e4e\u5e4f\u5e50\u5e51\u5e52\u5e53\u5e54\u5e55\u5e56\u5e57\u5e58\u5e59\u5e5a\u5e5b\u5e5c\u5e5d\u5e5e\u5e5f\u5e60\u5e61\u5e62\u5e63\u5e64\u5e65\u5e66\u5e67\u5e68\u5e69\u5e6a\u5e6b\u5e6c\u5e6d\u5e6e\u5e6f\u5e70\u5e71\u5e72\u5e73\u5e74\u5e75\u5e76\u5e77\u5e78\u5e79\u5e7a\u5e7b\u5e7c\u5e7d\u5e7e\u5e7f\u5e80\u5e81\u5e82\u5e83\u5e84\u5e85\u5e86\u5e87\u5e88\u5e89\u5e8a\u5e8b\u5e8c\u5e8d\u5e8e\u5e8f\u5e90\u5e91\u5e92\u5e93\u5e94\u5e95\u5e96\u5e97\u5e98\u5e99\u5e9a\u5e9b\u5e9c\u5e9d\u5e9e\u5e9f\u5ea0\u5ea1\u5ea2\u5ea3\u5ea4\u5ea5\u5ea6\u5ea7\u5ea8\u5ea9\u5eaa\u5eab\u5eac\u5ead\u5eae\u5eaf\u5eb0\u5eb1\u5eb2\u5eb3\u5eb4\u5eb5\u5eb6\u5eb7\u5eb8\u5eb9\u5eba\u5ebb\u5ebc\u5ebd\u5ebe\u5ebf\u5ec0\u5ec1\u5ec2\u5ec3\u5ec4\u5ec5\u5ec6\u5ec7\u5ec8\u5ec9\u5eca\u5ecb\u5ecc\u5ecd\u5ece\u5ecf\u5ed0\u5ed1\u5ed2\u5ed3\u5ed4\u5ed5\u5ed6\u5ed7\u5ed8\u5ed9\u5eda\u5edb\u5edc\u5edd\u5ede\u5edf\u5ee0\u5ee1\u5ee2\u5ee3\u5ee4\u5ee5\u5ee6\u5ee7\u5ee8\u5ee9\u5eea\u5eeb\u5eec\u5eed\u5eee\u5eef\u5ef0\u5ef1\u5ef2\u5ef3\u5ef4\u5ef5\u5ef6\u5ef7\u5ef8\u5ef9\u5efa\u5efb\u5efc\u5efd\u5efe\u5eff\u5f00\u5f01\u5f02\u5f03\u5f04\u5f05\u5f06\u5f07\u5f08\u5f09\u5f0a\u5f0b\u5f0c\u5f0d\u5f0e\u5f0f\u5f10\u5f11\u5f12\u5f13\u5f14\u5f15\u5f16\u5f17\u5f18\u5f19\u5f1a\u5f1b\u5f1c\u5f1d\u5f1e\u5f1f\u5f20\u5f21\u5f22\u5f23\u5f24\u5f25\u5f26\u5f27\u5f28\u5f29\u5f2a\u5f2b\u5f2c\u5f2d\u5f2e\u5f2f\u5f30\u5f31\u5f32\u5f33\u5f34\u5f35\u5f36\u5f37\u5f38\u5f39\u5f3a\u5f3b\u5f3c\u5f3d\u5f3e\u5f3f\u5f40\u5f41\u5f42\u5f43\u5f44\u5f45\u5f46\u5f47\u5f48\u5f49\u5f4a\u5f4b\u5f4c\u5f4d\u5f4e\u5f4f\u5f50\u5f51\u5f52\u5f53\u5f54\u5f55\u5f56\u5f57\u5f58\u5f59\u5f5a\u5f5b\u5f5c\u5f5d\u5f5e\u5f5f\u5f60\u5f61\u5f62\u5f63\u5f64\u5f65\u5f66\u5f67\u5f68\u5f69\u5f6a\u5f6b\u5f6c\u5f6d\u5f6e\u5f6f\u5f70\u5f71\u5f72\u5f73\u5f74\u5f75\u5f76\u5f77\u5f78\u5f79\u5f7a\u5f7b\u5f7c\u5f7d\u5f7e\u5f7f\u5f80\u5f81\u5f82\u5f83\u5f84\u5f85\u5f86\u5f87\u5f88\u5f89\u5f8a\u5f8b\u5f8c\u5f8d\u5f8e\u5f8f\u5f90\u5f91\u5f92\u5f93\u5f94\u5f95\u5f96\u5f97\u5f98\u5f99\u5f9a\u5f9b\u5f9c\u5f9d\u5f9e\u5f9f\u5fa0\u5fa1\u5fa2\u5fa3\u5fa4\u5fa5\u5fa6\u5fa7\u5fa8\u5fa9\u5faa\u5fab\u5fac\u5fad\u5fae\u5faf\u5fb0\u5fb1\u5fb2\u5fb3\u5fb4\u5fb5\u5fb6\u5fb7\u5fb8\u5fb9\u5fba\u5fbb\u5fbc\u5fbd\u5fbe\u5fbf\u5fc0\u5fc1\u5fc2\u5fc3\u5fc4\u5fc5\u5fc6\u5fc7\u5fc8\u5fc9\u5fca\u5fcb\u5fcc\u5fcd\u5fce\u5fcf\u5fd0\u5fd1\u5fd2\u5fd3\u5fd4\u5fd5\u5fd6\u5fd7\u5fd8\u5fd9\u5fda\u5fdb\u5fdc\u5fdd\u5fde\u5fdf\u5fe0\u5fe1\u5fe2\u5fe3\u5fe4\u5fe5\u5fe6\u5fe7\u5fe8\u5fe9\u5fea\u5feb\u5fec\u5fed\u5fee\u5fef\u5ff0\u5ff1\u5ff2\u5ff3\u5ff4\u5ff5\u5ff6\u5ff7\u5ff8\u5ff9\u5ffa\u5ffb\u5ffc\u5ffd\u5ffe\u5fff\u6000\u6001\u6002\u6003\u6004\u6005\u6006\u6007\u6008\u6009\u600a\u600b\u600c\u600d\u600e\u600f\u6010\u6011\u6012\u6013\u6014\u6015\u6016\u6017\u6018\u6019\u601a\u601b\u601c\u601d\u601e\u601f\u6020\u6021\u6022\u6023\u6024\u6025\u6026\u6027\u6028\u6029\u602a\u602b\u602c\u602d\u602e\u602f\u6030\u6031\u6032\u6033\u6034\u6035\u6036\u6037\u6038\u6039\u603a\u603b\u603c\u603d\u603e\u603f\u6040\u6041\u6042\u6043\u6044\u6045\u6046\u6047\u6048\u6049\u604a\u604b\u604c\u604d\u604e\u604f\u6050\u6051\u6052\u6053\u6054\u6055\u6056\u6057\u6058\u6059\u605a\u605b\u605c\u605d\u605e\u605f\u6060\u6061\u6062\u6063\u6064\u6065\u6066\u6067\u6068\u6069\u606a\u606b\u606c\u606d\u606e\u606f\u6070\u6071\u6072\u6073\u6074\u6075\u6076\u6077\u6078\u6079\u607a\u607b\u607c\u607d\u607e\u607f\u6080\u6081\u6082\u6083\u6084\u6085\u6086\u6087\u6088\u6089\u608a\u608b\u608c\u608d\u608e\u608f\u6090\u6091\u6092\u6093\u6094\u6095\u6096\u6097\u6098\u6099\u609a\u609b\u609c\u609d\u609e\u609f\u60a0\u60a1\u60a2\u60a3\u60a4\u60a5\u60a6\u60a7\u60a8\u60a9\u60aa\u60ab\u60ac\u60ad\u60ae\u60af\u60b0\u60b1\u60b2\u60b3\u60b4\u60b5\u60b6\u60b7\u60b8\u60b9\u60ba\u60bb\u60bc\u60bd\u60be\u60bf\u60c0\u60c1\u60c2\u60c3\u60c4\u60c5\u60c6\u60c7\u60c8\u60c9\u60ca\u60cb\u60cc\u60cd\u60ce\u60cf\u60d0\u60d1\u60d2\u60d3\u60d4\u60d5\u60d6\u60d7\u60d8\u60d9\u60da\u60db\u60dc\u60dd\u60de\u60df\u60e0\u60e1\u60e2\u60e3\u60e4\u60e5\u60e6\u60e7\u60e8\u60e9\u60ea\u60eb\u60ec\u60ed\u60ee\u60ef\u60f0\u60f1\u60f2\u60f3\u60f4\u60f5\u60f6\u60f7\u60f8\u60f9\u60fa\u60fb\u60fc\u60fd\u60fe\u60ff\u6100\u6101\u6102\u6103\u6104\u6105\u6106\u6107\u6108\u6109\u610a\u610b\u610c\u610d\u610e\u610f\u6110\u6111\u6112\u6113\u6114\u6115\u6116\u6117\u6118\u6119\u611a\u611b\u611c\u611d\u611e\u611f\u6120\u6121\u6122\u6123\u6124\u6125\u6126\u6127\u6128\u6129\u612a\u612b\u612c\u612d\u612e\u612f\u6130\u6131\u6132\u6133\u6134\u6135\u6136\u6137\u6138\u6139\u613a\u613b\u613c\u613d\u613e\u613f\u6140\u6141\u6142\u6143\u6144\u6145\u6146\u6147\u6148\u6149\u614a\u614b\u614c\u614d\u614e\u614f\u6150\u6151\u6152\u6153\u6154\u6155\u6156\u6157\u6158\u6159\u615a\u615b\u615c\u615d\u615e\u615f\u6160\u6161\u6162\u6163\u6164\u6165\u6166\u6167\u6168\u6169\u616a\u616b\u616c\u616d\u616e\u616f\u6170\u6171\u6172\u6173\u6174\u6175\u6176\u6177\u6178\u6179\u617a\u617b\u617c\u617d\u617e\u617f\u6180\u6181\u6182\u6183\u6184\u6185\u6186\u6187\u6188\u6189\u618a\u618b\u618c\u618d\u618e\u618f\u6190\u6191\u6192\u6193\u6194\u6195\u6196\u6197\u6198\u6199\u619a\u619b\u619c\u619d\u619e\u619f\u61a0\u61a1\u61a2\u61a3\u61a4\u61a5\u61a6\u61a7\u61a8\u61a9\u61aa\u61ab\u61ac\u61ad\u61ae\u61af\u61b0\u61b1\u61b2\u61b3\u61b4\u61b5\u61b6\u61b7\u61b8\u61b9\u61ba\u61bb\u61bc\u61bd\u61be\u61bf\u61c0\u61c1\u61c2\u61c3\u61c4\u61c5\u61c6\u61c7\u61c8\u61c9\u61ca\u61cb\u61cc\u61cd\u61ce\u61cf\u61d0\u61d1\u61d2\u61d3\u61d4\u61d5\u61d6\u61d7\u61d8\u61d9\u61da\u61db\u61dc\u61dd\u61de\u61df\u61e0\u61e1\u61e2\u61e3\u61e4\u61e5\u61e6\u61e7\u61e8\u61e9\u61ea\u61eb\u61ec\u61ed\u61ee\u61ef\u61f0\u61f1\u61f2\u61f3\u61f4\u61f5\u61f6\u61f7\u61f8\u61f9\u61fa\u61fb\u61fc\u61fd\u61fe\u61ff\u6200\u6201\u6202\u6203\u6204\u6205\u6206\u6207\u6208\u6209\u620a\u620b\u620c\u620d\u620e\u620f\u6210\u6211\u6212\u6213\u6214\u6215\u6216\u6217\u6218\u6219\u621a\u621b\u621c\u621d\u621e\u621f\u6220\u6221\u6222\u6223\u6224\u6225\u6226\u6227\u6228\u6229\u622a\u622b\u622c\u622d\u622e\u622f\u6230\u6231\u6232\u6233\u6234\u6235\u6236\u6237\u6238\u6239\u623a\u623b\u623c\u623d\u623e\u623f\u6240\u6241\u6242\u6243\u6244\u6245\u6246\u6247\u6248\u6249\u624a\u624b\u624c\u624d\u624e\u624f\u6250\u6251\u6252\u6253\u6254\u6255\u6256\u6257\u6258\u6259\u625a\u625b\u625c\u625d\u625e\u625f\u6260\u6261\u6262\u6263\u6264\u6265\u6266\u6267\u6268\u6269\u626a\u626b\u626c\u626d\u626e\u626f\u6270\u6271\u6272\u6273\u6274\u6275\u6276\u6277\u6278\u6279\u627a\u627b\u627c\u627d\u627e\u627f\u6280\u6281\u6282\u6283\u6284\u6285\u6286\u6287\u6288\u6289\u628a\u628b\u628c\u628d\u628e\u628f\u6290\u6291\u6292\u6293\u6294\u6295\u6296\u6297\u6298\u6299\u629a\u629b\u629c\u629d\u629e\u629f\u62a0\u62a1\u62a2\u62a3\u62a4\u62a5\u62a6\u62a7\u62a8\u62a9\u62aa\u62ab\u62ac\u62ad\u62ae\u62af\u62b0\u62b1\u62b2\u62b3\u62b4\u62b5\u62b6\u62b7\u62b8\u62b9\u62ba\u62bb\u62bc\u62bd\u62be\u62bf\u62c0\u62c1\u62c2\u62c3\u62c4\u62c5\u62c6\u62c7\u62c8\u62c9\u62ca\u62cb\u62cc\u62cd\u62ce\u62cf\u62d0\u62d1\u62d2\u62d3\u62d4\u62d5\u62d6\u62d7\u62d8\u62d9\u62da\u62db\u62dc\u62dd\u62de\u62df\u62e0\u62e1\u62e2\u62e3\u62e4\u62e5\u62e6\u62e7\u62e8\u62e9\u62ea\u62eb\u62ec\u62ed\u62ee\u62ef\u62f0\u62f1\u62f2\u62f3\u62f4\u62f5\u62f6\u62f7\u62f8\u62f9\u62fa\u62fb\u62fc\u62fd\u62fe\u62ff\u6300\u6301\u6302\u6303\u6304\u6305\u6306\u6307\u6308\u6309\u630a\u630b\u630c\u630d\u630e\u630f\u6310\u6311\u6312\u6313\u6314\u6315\u6316\u6317\u6318\u6319\u631a\u631b\u631c\u631d\u631e\u631f\u6320\u6321\u6322\u6323\u6324\u6325\u6326\u6327\u6328\u6329\u632a\u632b\u632c\u632d\u632e\u632f\u6330\u6331\u6332\u6333\u6334\u6335\u6336\u6337\u6338\u6339\u633a\u633b\u633c\u633d\u633e\u633f\u6340\u6341\u6342\u6343\u6344\u6345\u6346\u6347\u6348\u6349\u634a\u634b\u634c\u634d\u634e\u634f\u6350\u6351\u6352\u6353\u6354\u6355\u6356\u6357\u6358\u6359\u635a\u635b\u635c\u635d\u635e\u635f\u6360\u6361\u6362\u6363\u6364\u6365\u6366\u6367\u6368\u6369\u636a\u636b\u636c\u636d\u636e\u636f\u6370\u6371\u6372\u6373\u6374\u6375\u6376\u6377\u6378\u6379\u637a\u637b\u637c\u637d\u637e\u637f\u6380\u6381\u6382\u6383\u6384\u6385\u6386\u6387\u6388\u6389\u638a\u638b\u638c\u638d\u638e\u638f\u6390\u6391\u6392\u6393\u6394\u6395\u6396\u6397\u6398\u6399\u639a\u639b\u639c\u639d\u639e\u639f\u63a0\u63a1\u63a2\u63a3\u63a4\u63a5\u63a6\u63a7\u63a8\u63a9\u63aa\u63ab\u63ac\u63ad\u63ae\u63af\u63b0\u63b1\u63b2\u63b3\u63b4\u63b5\u63b6\u63b7\u63b8\u63b9\u63ba\u63bb\u63bc\u63bd\u63be\u63bf\u63c0\u63c1\u63c2\u63c3\u63c4\u63c5\u63c6\u63c7\u63c8\u63c9\u63ca\u63cb\u63cc\u63cd\u63ce\u63cf\u63d0\u63d1\u63d2\u63d3\u63d4\u63d5\u63d6\u63d7\u63d8\u63d9\u63da\u63db\u63dc\u63dd\u63de\u63df\u63e0\u63e1\u63e2\u63e3\u63e4\u63e5\u63e6\u63e7\u63e8\u63e9\u63ea\u63eb\u63ec\u63ed\u63ee\u63ef\u63f0\u63f1\u63f2\u63f3\u63f4\u63f5\u63f6\u63f7\u63f8\u63f9\u63fa\u63fb\u63fc\u63fd\u63fe\u63ff\u6400\u6401\u6402\u6403\u6404\u6405\u6406\u6407\u6408\u6409\u640a\u640b\u640c\u640d\u640e\u640f\u6410\u6411\u6412\u6413\u6414\u6415\u6416\u6417\u6418\u6419\u641a\u641b\u641c\u641d\u641e\u641f\u6420\u6421\u6422\u6423\u6424\u6425\u6426\u6427\u6428\u6429\u642a\u642b\u642c\u642d\u642e\u642f\u6430\u6431\u6432\u6433\u6434\u6435\u6436\u6437\u6438\u6439\u643a\u643b\u643c\u643d\u643e\u643f\u6440\u6441\u6442\u6443\u6444\u6445\u6446\u6447\u6448\u6449\u644a\u644b\u644c\u644d\u644e\u644f\u6450\u6451\u6452\u6453\u6454\u6455\u6456\u6457\u6458\u6459\u645a\u645b\u645c\u645d\u645e\u645f\u6460\u6461\u6462\u6463\u6464\u6465\u6466\u6467\u6468\u6469\u646a\u646b\u646c\u646d\u646e\u646f\u6470\u6471\u6472\u6473\u6474\u6475\u6476\u6477\u6478\u6479\u647a\u647b\u647c\u647d\u647e\u647f\u6480\u6481\u6482\u6483\u6484\u6485\u6486\u6487\u6488\u6489\u648a\u648b\u648c\u648d\u648e\u648f\u6490\u6491\u6492\u6493\u6494\u6495\u6496\u6497\u6498\u6499\u649a\u649b\u649c\u649d\u649e\u649f\u64a0\u64a1\u64a2\u64a3\u64a4\u64a5\u64a6\u64a7\u64a8\u64a9\u64aa\u64ab\u64ac\u64ad\u64ae\u64af\u64b0\u64b1\u64b2\u64b3\u64b4\u64b5\u64b6\u64b7\u64b8\u64b9\u64ba\u64bb\u64bc\u64bd\u64be\u64bf\u64c0\u64c1\u64c2\u64c3\u64c4\u64c5\u64c6\u64c7\u64c8\u64c9\u64ca\u64cb\u64cc\u64cd\u64ce\u64cf\u64d0\u64d1\u64d2\u64d3\u64d4\u64d5\u64d6\u64d7\u64d8\u64d9\u64da\u64db\u64dc\u64dd\u64de\u64df\u64e0\u64e1\u64e2\u64e3\u64e4\u64e5\u64e6\u64e7\u64e8\u64e9\u64ea\u64eb\u64ec\u64ed\u64ee\u64ef\u64f0\u64f1\u64f2\u64f3\u64f4\u64f5\u64f6\u64f7\u64f8\u64f9\u64fa\u64fb\u64fc\u64fd\u64fe\u64ff\u6500\u6501\u6502\u6503\u6504\u6505\u6506\u6507\u6508\u6509\u650a\u650b\u650c\u650d\u650e\u650f\u6510\u6511\u6512\u6513\u6514\u6515\u6516\u6517\u6518\u6519\u651a\u651b\u651c\u651d\u651e\u651f\u6520\u6521\u6522\u6523\u6524\u6525\u6526\u6527\u6528\u6529\u652a\u652b\u652c\u652d\u652e\u652f\u6530\u6531\u6532\u6533\u6534\u6535\u6536\u6537\u6538\u6539\u653a\u653b\u653c\u653d\u653e\u653f\u6540\u6541\u6542\u6543\u6544\u6545\u6546\u6547\u6548\u6549\u654a\u654b\u654c\u654d\u654e\u654f\u6550\u6551\u6552\u6553\u6554\u6555\u6556\u6557\u6558\u6559\u655a\u655b\u655c\u655d\u655e\u655f\u6560\u6561\u6562\u6563\u6564\u6565\u6566\u6567\u6568\u6569\u656a\u656b\u656c\u656d\u656e\u656f\u6570\u6571\u6572\u6573\u6574\u6575\u6576\u6577\u6578\u6579\u657a\u657b\u657c\u657d\u657e\u657f\u6580\u6581\u6582\u6583\u6584\u6585\u6586\u6587\u6588\u6589\u658a\u658b\u658c\u658d\u658e\u658f\u6590\u6591\u6592\u6593\u6594\u6595\u6596\u6597\u6598\u6599\u659a\u659b\u659c\u659d\u659e\u659f\u65a0\u65a1\u65a2\u65a3\u65a4\u65a5\u65a6\u65a7\u65a8\u65a9\u65aa\u65ab\u65ac\u65ad\u65ae\u65af\u65b0\u65b1\u65b2\u65b3\u65b4\u65b5\u65b6\u65b7\u65b8\u65b9\u65ba\u65bb\u65bc\u65bd\u65be\u65bf\u65c0\u65c1\u65c2\u65c3\u65c4\u65c5\u65c6\u65c7\u65c8\u65c9\u65ca\u65cb\u65cc\u65cd\u65ce\u65cf\u65d0\u65d1\u65d2\u65d3\u65d4\u65d5\u65d6\u65d7\u65d8\u65d9\u65da\u65db\u65dc\u65dd\u65de\u65df\u65e0\u65e1\u65e2\u65e3\u65e4\u65e5\u65e6\u65e7\u65e8\u65e9\u65ea\u65eb\u65ec\u65ed\u65ee\u65ef\u65f0\u65f1\u65f2\u65f3\u65f4\u65f5\u65f6\u65f7\u65f8\u65f9\u65fa\u65fb\u65fc\u65fd\u65fe\u65ff\u6600\u6601\u6602\u6603\u6604\u6605\u6606\u6607\u6608\u6609\u660a\u660b\u660c\u660d\u660e\u660f\u6610\u6611\u6612\u6613\u6614\u6615\u6616\u6617\u6618\u6619\u661a\u661b\u661c\u661d\u661e\u661f\u6620\u6621\u6622\u6623\u6624\u6625\u6626\u6627\u6628\u6629\u662a\u662b\u662c\u662d\u662e\u662f\u6630\u6631\u6632\u6633\u6634\u6635\u6636\u6637\u6638\u6639\u663a\u663b\u663c\u663d\u663e\u663f\u6640\u6641\u6642\u6643\u6644\u6645\u6646\u6647\u6648\u6649\u664a\u664b\u664c\u664d\u664e\u664f\u6650\u6651\u6652\u6653\u6654\u6655\u6656\u6657\u6658\u6659\u665a\u665b\u665c\u665d\u665e\u665f\u6660\u6661\u6662\u6663\u6664\u6665\u6666\u6667\u6668\u6669\u666a\u666b\u666c\u666d\u666e\u666f\u6670\u6671\u6672\u6673\u6674\u6675\u6676\u6677\u6678\u6679\u667a\u667b\u667c\u667d\u667e\u667f\u6680\u6681\u6682\u6683\u6684\u6685\u6686\u6687\u6688\u6689\u668a\u668b\u668c\u668d\u668e\u668f\u6690\u6691\u6692\u6693\u6694\u6695\u6696\u6697\u6698\u6699\u669a\u669b\u669c\u669d\u669e\u669f\u66a0\u66a1\u66a2\u66a3\u66a4\u66a5\u66a6\u66a7\u66a8\u66a9\u66aa\u66ab\u66ac\u66ad\u66ae\u66af\u66b0\u66b1\u66b2\u66b3\u66b4\u66b5\u66b6\u66b7\u66b8\u66b9\u66ba\u66bb\u66bc\u66bd\u66be\u66bf\u66c0\u66c1\u66c2\u66c3\u66c4\u66c5\u66c6\u66c7\u66c8\u66c9\u66ca\u66cb\u66cc\u66cd\u66ce\u66cf\u66d0\u66d1\u66d2\u66d3\u66d4\u66d5\u66d6\u66d7\u66d8\u66d9\u66da\u66db\u66dc\u66dd\u66de\u66df\u66e0\u66e1\u66e2\u66e3\u66e4\u66e5\u66e6\u66e7\u66e8\u66e9\u66ea\u66eb\u66ec\u66ed\u66ee\u66ef\u66f0\u66f1\u66f2\u66f3\u66f4\u66f5\u66f6\u66f7\u66f8\u66f9\u66fa\u66fb\u66fc\u66fd\u66fe\u66ff\u6700\u6701\u6702\u6703\u6704\u6705\u6706\u6707\u6708\u6709\u670a\u670b\u670c\u670d\u670e\u670f\u6710\u6711\u6712\u6713\u6714\u6715\u6716\u6717\u6718\u6719\u671a\u671b\u671c\u671d\u671e\u671f\u6720\u6721\u6722\u6723\u6724\u6725\u6726\u6727\u6728\u6729\u672a\u672b\u672c\u672d\u672e\u672f\u6730\u6731\u6732\u6733\u6734\u6735\u6736\u6737\u6738\u6739\u673a\u673b\u673c\u673d\u673e\u673f\u6740\u6741\u6742\u6743\u6744\u6745\u6746\u6747\u6748\u6749\u674a\u674b\u674c\u674d\u674e\u674f\u6750\u6751\u6752\u6753\u6754\u6755\u6756\u6757\u6758\u6759\u675a\u675b\u675c\u675d\u675e\u675f\u6760\u6761\u6762\u6763\u6764\u6765\u6766\u6767\u6768\u6769\u676a\u676b\u676c\u676d\u676e\u676f\u6770\u6771\u6772\u6773\u6774\u6775\u6776\u6777\u6778\u6779\u677a\u677b\u677c\u677d\u677e\u677f\u6780\u6781\u6782\u6783\u6784\u6785\u6786\u6787\u6788\u6789\u678a\u678b\u678c\u678d\u678e\u678f\u6790\u6791\u6792\u6793\u6794\u6795\u6796\u6797\u6798\u6799\u679a\u679b\u679c\u679d\u679e\u679f\u67a0\u67a1\u67a2\u67a3\u67a4\u67a5\u67a6\u67a7\u67a8\u67a9\u67aa\u67ab\u67ac\u67ad\u67ae\u67af\u67b0\u67b1\u67b2\u67b3\u67b4\u67b5\u67b6\u67b7\u67b8\u67b9\u67ba\u67bb\u67bc\u67bd\u67be\u67bf\u67c0\u67c1\u67c2\u67c3\u67c4\u67c5\u67c6\u67c7\u67c8\u67c9\u67ca\u67cb\u67cc\u67cd\u67ce\u67cf\u67d0\u67d1\u67d2\u67d3\u67d4\u67d5\u67d6\u67d7\u67d8\u67d9\u67da\u67db\u67dc\u67dd\u67de\u67df\u67e0\u67e1\u67e2\u67e3\u67e4\u67e5\u67e6\u67e7\u67e8\u67e9\u67ea\u67eb\u67ec\u67ed\u67ee\u67ef\u67f0\u67f1\u67f2\u67f3\u67f4\u67f5\u67f6\u67f7\u67f8\u67f9\u67fa\u67fb\u67fc\u67fd\u67fe\u67ff\u6800\u6801\u6802\u6803\u6804\u6805\u6806\u6807\u6808\u6809\u680a\u680b\u680c\u680d\u680e\u680f\u6810\u6811\u6812\u6813\u6814\u6815\u6816\u6817\u6818\u6819\u681a\u681b\u681c\u681d\u681e\u681f\u6820\u6821\u6822\u6823\u6824\u6825\u6826\u6827\u6828\u6829\u682a\u682b\u682c\u682d\u682e\u682f\u6830\u6831\u6832\u6833\u6834\u6835\u6836\u6837\u6838\u6839\u683a\u683b\u683c\u683d\u683e\u683f\u6840\u6841\u6842\u6843\u6844\u6845\u6846\u6847\u6848\u6849\u684a\u684b\u684c\u684d\u684e\u684f\u6850\u6851\u6852\u6853\u6854\u6855\u6856\u6857\u6858\u6859\u685a\u685b\u685c\u685d\u685e\u685f\u6860\u6861\u6862\u6863\u6864\u6865\u6866\u6867\u6868\u6869\u686a\u686b\u686c\u686d\u686e\u686f\u6870\u6871\u6872\u6873\u6874\u6875\u6876\u6877\u6878\u6879\u687a\u687b\u687c\u687d\u687e\u687f\u6880\u6881\u6882\u6883\u6884\u6885\u6886\u6887\u6888\u6889\u688a\u688b\u688c\u688d\u688e\u688f\u6890\u6891\u6892\u6893\u6894\u6895\u6896\u6897\u6898\u6899\u689a\u689b\u689c\u689d\u689e\u689f\u68a0\u68a1\u68a2\u68a3\u68a4\u68a5\u68a6\u68a7\u68a8\u68a9\u68aa\u68ab\u68ac\u68ad\u68ae\u68af\u68b0\u68b1\u68b2\u68b3\u68b4\u68b5\u68b6\u68b7\u68b8\u68b9\u68ba\u68bb\u68bc\u68bd\u68be\u68bf\u68c0\u68c1\u68c2\u68c3\u68c4\u68c5\u68c6\u68c7\u68c8\u68c9\u68ca\u68cb\u68cc\u68cd\u68ce\u68cf\u68d0\u68d1\u68d2\u68d3\u68d4\u68d5\u68d6\u68d7\u68d8\u68d9\u68da\u68db\u68dc\u68dd\u68de\u68df\u68e0\u68e1\u68e2\u68e3\u68e4\u68e5\u68e6\u68e7\u68e8\u68e9\u68ea\u68eb\u68ec\u68ed\u68ee\u68ef\u68f0\u68f1\u68f2\u68f3\u68f4\u68f5\u68f6\u68f7\u68f8\u68f9\u68fa\u68fb\u68fc\u68fd\u68fe\u68ff\u6900\u6901\u6902\u6903\u6904\u6905\u6906\u6907\u6908\u6909\u690a\u690b\u690c\u690d\u690e\u690f\u6910\u6911\u6912\u6913\u6914\u6915\u6916\u6917\u6918\u6919\u691a\u691b\u691c\u691d\u691e\u691f\u6920\u6921\u6922\u6923\u6924\u6925\u6926\u6927\u6928\u6929\u692a\u692b\u692c\u692d\u692e\u692f\u6930\u6931\u6932\u6933\u6934\u6935\u6936\u6937\u6938\u6939\u693a\u693b\u693c\u693d\u693e\u693f\u6940\u6941\u6942\u6943\u6944\u6945\u6946\u6947\u6948\u6949\u694a\u694b\u694c\u694d\u694e\u694f\u6950\u6951\u6952\u6953\u6954\u6955\u6956\u6957\u6958\u6959\u695a\u695b\u695c\u695d\u695e\u695f\u6960\u6961\u6962\u6963\u6964\u6965\u6966\u6967\u6968\u6969\u696a\u696b\u696c\u696d\u696e\u696f\u6970\u6971\u6972\u6973\u6974\u6975\u6976\u6977\u6978\u6979\u697a\u697b\u697c\u697d\u697e\u697f\u6980\u6981\u6982\u6983\u6984\u6985\u6986\u6987\u6988\u6989\u698a\u698b\u698c\u698d\u698e\u698f\u6990\u6991\u6992\u6993\u6994\u6995\u6996\u6997\u6998\u6999\u699a\u699b\u699c\u699d\u699e\u699f\u69a0\u69a1\u69a2\u69a3\u69a4\u69a5\u69a6\u69a7\u69a8\u69a9\u69aa\u69ab\u69ac\u69ad\u69ae\u69af\u69b0\u69b1\u69b2\u69b3\u69b4\u69b5\u69b6\u69b7\u69b8\u69b9\u69ba\u69bb\u69bc\u69bd\u69be\u69bf\u69c0\u69c1\u69c2\u69c3\u69c4\u69c5\u69c6\u69c7\u69c8\u69c9\u69ca\u69cb\u69cc\u69cd\u69ce\u69cf\u69d0\u69d1\u69d2\u69d3\u69d4\u69d5\u69d6\u69d7\u69d8\u69d9\u69da\u69db\u69dc\u69dd\u69de\u69df\u69e0\u69e1\u69e2\u69e3\u69e4\u69e5\u69e6\u69e7\u69e8\u69e9\u69ea\u69eb\u69ec\u69ed\u69ee\u69ef\u69f0\u69f1\u69f2\u69f3\u69f4\u69f5\u69f6\u69f7\u69f8\u69f9\u69fa\u69fb\u69fc\u69fd\u69fe\u69ff\u6a00\u6a01\u6a02\u6a03\u6a04\u6a05\u6a06\u6a07\u6a08\u6a09\u6a0a\u6a0b\u6a0c\u6a0d\u6a0e\u6a0f\u6a10\u6a11\u6a12\u6a13\u6a14\u6a15\u6a16\u6a17\u6a18\u6a19\u6a1a\u6a1b\u6a1c\u6a1d\u6a1e\u6a1f\u6a20\u6a21\u6a22\u6a23\u6a24\u6a25\u6a26\u6a27\u6a28\u6a29\u6a2a\u6a2b\u6a2c\u6a2d\u6a2e\u6a2f\u6a30\u6a31\u6a32\u6a33\u6a34\u6a35\u6a36\u6a37\u6a38\u6a39\u6a3a\u6a3b\u6a3c\u6a3d\u6a3e\u6a3f\u6a40\u6a41\u6a42\u6a43\u6a44\u6a45\u6a46\u6a47\u6a48\u6a49\u6a4a\u6a4b\u6a4c\u6a4d\u6a4e\u6a4f\u6a50\u6a51\u6a52\u6a53\u6a54\u6a55\u6a56\u6a57\u6a58\u6a59\u6a5a\u6a5b\u6a5c\u6a5d\u6a5e\u6a5f\u6a60\u6a61\u6a62\u6a63\u6a64\u6a65\u6a66\u6a67\u6a68\u6a69\u6a6a\u6a6b\u6a6c\u6a6d\u6a6e\u6a6f\u6a70\u6a71\u6a72\u6a73\u6a74\u6a75\u6a76\u6a77\u6a78\u6a79\u6a7a\u6a7b\u6a7c\u6a7d\u6a7e\u6a7f\u6a80\u6a81\u6a82\u6a83\u6a84\u6a85\u6a86\u6a87\u6a88\u6a89\u6a8a\u6a8b\u6a8c\u6a8d\u6a8e\u6a8f\u6a90\u6a91\u6a92\u6a93\u6a94\u6a95\u6a96\u6a97\u6a98\u6a99\u6a9a\u6a9b\u6a9c\u6a9d\u6a9e\u6a9f\u6aa0\u6aa1\u6aa2\u6aa3\u6aa4\u6aa5\u6aa6\u6aa7\u6aa8\u6aa9\u6aaa\u6aab\u6aac\u6aad\u6aae\u6aaf\u6ab0\u6ab1\u6ab2\u6ab3\u6ab4\u6ab5\u6ab6\u6ab7\u6ab8\u6ab9\u6aba\u6abb\u6abc\u6abd\u6abe\u6abf\u6ac0\u6ac1\u6ac2\u6ac3\u6ac4\u6ac5\u6ac6\u6ac7\u6ac8\u6ac9\u6aca\u6acb\u6acc\u6acd\u6ace\u6acf\u6ad0\u6ad1\u6ad2\u6ad3\u6ad4\u6ad5\u6ad6\u6ad7\u6ad8\u6ad9\u6ada\u6adb\u6adc\u6add\u6ade\u6adf\u6ae0\u6ae1\u6ae2\u6ae3\u6ae4\u6ae5\u6ae6\u6ae7\u6ae8\u6ae9\u6aea\u6aeb\u6aec\u6aed\u6aee\u6aef\u6af0\u6af1\u6af2\u6af3\u6af4\u6af5\u6af6\u6af7\u6af8\u6af9\u6afa\u6afb\u6afc\u6afd\u6afe\u6aff\u6b00\u6b01\u6b02\u6b03\u6b04\u6b05\u6b06\u6b07\u6b08\u6b09\u6b0a\u6b0b\u6b0c\u6b0d\u6b0e\u6b0f\u6b10\u6b11\u6b12\u6b13\u6b14\u6b15\u6b16\u6b17\u6b18\u6b19\u6b1a\u6b1b\u6b1c\u6b1d\u6b1e\u6b1f\u6b20\u6b21\u6b22\u6b23\u6b24\u6b25\u6b26\u6b27\u6b28\u6b29\u6b2a\u6b2b\u6b2c\u6b2d\u6b2e\u6b2f\u6b30\u6b31\u6b32\u6b33\u6b34\u6b35\u6b36\u6b37\u6b38\u6b39\u6b3a\u6b3b\u6b3c\u6b3d\u6b3e\u6b3f\u6b40\u6b41\u6b42\u6b43\u6b44\u6b45\u6b46\u6b47\u6b48\u6b49\u6b4a\u6b4b\u6b4c\u6b4d\u6b4e\u6b4f\u6b50\u6b51\u6b52\u6b53\u6b54\u6b55\u6b56\u6b57\u6b58\u6b59\u6b5a\u6b5b\u6b5c\u6b5d\u6b5e\u6b5f\u6b60\u6b61\u6b62\u6b63\u6b64\u6b65\u6b66\u6b67\u6b68\u6b69\u6b6a\u6b6b\u6b6c\u6b6d\u6b6e\u6b6f\u6b70\u6b71\u6b72\u6b73\u6b74\u6b75\u6b76\u6b77\u6b78\u6b79\u6b7a\u6b7b\u6b7c\u6b7d\u6b7e\u6b7f\u6b80\u6b81\u6b82\u6b83\u6b84\u6b85\u6b86\u6b87\u6b88\u6b89\u6b8a\u6b8b\u6b8c\u6b8d\u6b8e\u6b8f\u6b90\u6b91\u6b92\u6b93\u6b94\u6b95\u6b96\u6b97\u6b98\u6b99\u6b9a\u6b9b\u6b9c\u6b9d\u6b9e\u6b9f\u6ba0\u6ba1\u6ba2\u6ba3\u6ba4\u6ba5\u6ba6\u6ba7\u6ba8\u6ba9\u6baa\u6bab\u6bac\u6bad\u6bae\u6baf\u6bb0\u6bb1\u6bb2\u6bb3\u6bb4\u6bb5\u6bb6\u6bb7\u6bb8\u6bb9\u6bba\u6bbb\u6bbc\u6bbd\u6bbe\u6bbf\u6bc0\u6bc1\u6bc2\u6bc3\u6bc4\u6bc5\u6bc6\u6bc7\u6bc8\u6bc9\u6bca\u6bcb\u6bcc\u6bcd\u6bce\u6bcf\u6bd0\u6bd1\u6bd2\u6bd3\u6bd4\u6bd5\u6bd6\u6bd7\u6bd8\u6bd9\u6bda\u6bdb\u6bdc\u6bdd\u6bde\u6bdf\u6be0\u6be1\u6be2\u6be3\u6be4\u6be5\u6be6\u6be7\u6be8\u6be9\u6bea\u6beb\u6bec\u6bed\u6bee\u6bef\u6bf0\u6bf1\u6bf2\u6bf3\u6bf4\u6bf5\u6bf6\u6bf7\u6bf8\u6bf9\u6bfa\u6bfb\u6bfc\u6bfd\u6bfe\u6bff\u6c00\u6c01\u6c02\u6c03\u6c04\u6c05\u6c06\u6c07\u6c08\u6c09\u6c0a\u6c0b\u6c0c\u6c0d\u6c0e\u6c0f\u6c10\u6c11\u6c12\u6c13\u6c14\u6c15\u6c16\u6c17\u6c18\u6c19\u6c1a\u6c1b\u6c1c\u6c1d\u6c1e\u6c1f\u6c20\u6c21\u6c22\u6c23\u6c24\u6c25\u6c26\u6c27\u6c28\u6c29\u6c2a\u6c2b\u6c2c\u6c2d\u6c2e\u6c2f\u6c30\u6c31\u6c32\u6c33\u6c34\u6c35\u6c36\u6c37\u6c38\u6c39\u6c3a\u6c3b\u6c3c\u6c3d\u6c3e\u6c3f\u6c40\u6c41\u6c42\u6c43\u6c44\u6c45\u6c46\u6c47\u6c48\u6c49\u6c4a\u6c4b\u6c4c\u6c4d\u6c4e\u6c4f\u6c50\u6c51\u6c52\u6c53\u6c54\u6c55\u6c56\u6c57\u6c58\u6c59\u6c5a\u6c5b\u6c5c\u6c5d\u6c5e\u6c5f\u6c60\u6c61\u6c62\u6c63\u6c64\u6c65\u6c66\u6c67\u6c68\u6c69\u6c6a\u6c6b\u6c6c\u6c6d\u6c6e\u6c6f\u6c70\u6c71\u6c72\u6c73\u6c74\u6c75\u6c76\u6c77\u6c78\u6c79\u6c7a\u6c7b\u6c7c\u6c7d\u6c7e\u6c7f\u6c80\u6c81\u6c82\u6c83\u6c84\u6c85\u6c86\u6c87\u6c88\u6c89\u6c8a\u6c8b\u6c8c\u6c8d\u6c8e\u6c8f\u6c90\u6c91\u6c92\u6c93\u6c94\u6c95\u6c96\u6c97\u6c98\u6c99\u6c9a\u6c9b\u6c9c\u6c9d\u6c9e\u6c9f\u6ca0\u6ca1\u6ca2\u6ca3\u6ca4\u6ca5\u6ca6\u6ca7\u6ca8\u6ca9\u6caa\u6cab\u6cac\u6cad\u6cae\u6caf\u6cb0\u6cb1\u6cb2\u6cb3\u6cb4\u6cb5\u6cb6\u6cb7\u6cb8\u6cb9\u6cba\u6cbb\u6cbc\u6cbd\u6cbe\u6cbf\u6cc0\u6cc1\u6cc2\u6cc3\u6cc4\u6cc5\u6cc6\u6cc7\u6cc8\u6cc9\u6cca\u6ccb\u6ccc\u6ccd\u6cce\u6ccf\u6cd0\u6cd1\u6cd2\u6cd3\u6cd4\u6cd5\u6cd6\u6cd7\u6cd8\u6cd9\u6cda\u6cdb\u6cdc\u6cdd\u6cde\u6cdf\u6ce0\u6ce1\u6ce2\u6ce3\u6ce4\u6ce5\u6ce6\u6ce7\u6ce8\u6ce9\u6cea\u6ceb\u6cec\u6ced\u6cee\u6cef\u6cf0\u6cf1\u6cf2\u6cf3\u6cf4\u6cf5\u6cf6\u6cf7\u6cf8\u6cf9\u6cfa\u6cfb\u6cfc\u6cfd\u6cfe\u6cff\u6d00\u6d01\u6d02\u6d03\u6d04\u6d05\u6d06\u6d07\u6d08\u6d09\u6d0a\u6d0b\u6d0c\u6d0d\u6d0e\u6d0f\u6d10\u6d11\u6d12\u6d13\u6d14\u6d15\u6d16\u6d17\u6d18\u6d19\u6d1a\u6d1b\u6d1c\u6d1d\u6d1e\u6d1f\u6d20\u6d21\u6d22\u6d23\u6d24\u6d25\u6d26\u6d27\u6d28\u6d29\u6d2a\u6d2b\u6d2c\u6d2d\u6d2e\u6d2f\u6d30\u6d31\u6d32\u6d33\u6d34\u6d35\u6d36\u6d37\u6d38\u6d39\u6d3a\u6d3b\u6d3c\u6d3d\u6d3e\u6d3f\u6d40\u6d41\u6d42\u6d43\u6d44\u6d45\u6d46\u6d47\u6d48\u6d49\u6d4a\u6d4b\u6d4c\u6d4d\u6d4e\u6d4f\u6d50\u6d51\u6d52\u6d53\u6d54\u6d55\u6d56\u6d57\u6d58\u6d59\u6d5a\u6d5b\u6d5c\u6d5d\u6d5e\u6d5f\u6d60\u6d61\u6d62\u6d63\u6d64\u6d65\u6d66\u6d67\u6d68\u6d69\u6d6a\u6d6b\u6d6c\u6d6d\u6d6e\u6d6f\u6d70\u6d71\u6d72\u6d73\u6d74\u6d75\u6d76\u6d77\u6d78\u6d79\u6d7a\u6d7b\u6d7c\u6d7d\u6d7e\u6d7f\u6d80\u6d81\u6d82\u6d83\u6d84\u6d85\u6d86\u6d87\u6d88\u6d89\u6d8a\u6d8b\u6d8c\u6d8d\u6d8e\u6d8f\u6d90\u6d91\u6d92\u6d93\u6d94\u6d95\u6d96\u6d97\u6d98\u6d99\u6d9a\u6d9b\u6d9c\u6d9d\u6d9e\u6d9f\u6da0\u6da1\u6da2\u6da3\u6da4\u6da5\u6da6\u6da7\u6da8\u6da9\u6daa\u6dab\u6dac\u6dad\u6dae\u6daf\u6db0\u6db1\u6db2\u6db3\u6db4\u6db5\u6db6\u6db7\u6db8\u6db9\u6dba\u6dbb\u6dbc\u6dbd\u6dbe\u6dbf\u6dc0\u6dc1\u6dc2\u6dc3\u6dc4\u6dc5\u6dc6\u6dc7\u6dc8\u6dc9\u6dca\u6dcb\u6dcc\u6dcd\u6dce\u6dcf\u6dd0\u6dd1\u6dd2\u6dd3\u6dd4\u6dd5\u6dd6\u6dd7\u6dd8\u6dd9\u6dda\u6ddb\u6ddc\u6ddd\u6dde\u6ddf\u6de0\u6de1\u6de2\u6de3\u6de4\u6de5\u6de6\u6de7\u6de8\u6de9\u6dea\u6deb\u6dec\u6ded\u6dee\u6def\u6df0\u6df1\u6df2\u6df3\u6df4\u6df5\u6df6\u6df7\u6df8\u6df9\u6dfa\u6dfb\u6dfc\u6dfd\u6dfe\u6dff\u6e00\u6e01\u6e02\u6e03\u6e04\u6e05\u6e06\u6e07\u6e08\u6e09\u6e0a\u6e0b\u6e0c\u6e0d\u6e0e\u6e0f\u6e10\u6e11\u6e12\u6e13\u6e14\u6e15\u6e16\u6e17\u6e18\u6e19\u6e1a\u6e1b\u6e1c\u6e1d\u6e1e\u6e1f\u6e20\u6e21\u6e22\u6e23\u6e24\u6e25\u6e26\u6e27\u6e28\u6e29\u6e2a\u6e2b\u6e2c\u6e2d\u6e2e\u6e2f\u6e30\u6e31\u6e32\u6e33\u6e34\u6e35\u6e36\u6e37\u6e38\u6e39\u6e3a\u6e3b\u6e3c\u6e3d\u6e3e\u6e3f\u6e40\u6e41\u6e42\u6e43\u6e44\u6e45\u6e46\u6e47\u6e48\u6e49\u6e4a\u6e4b\u6e4c\u6e4d\u6e4e\u6e4f\u6e50\u6e51\u6e52\u6e53\u6e54\u6e55\u6e56\u6e57\u6e58\u6e59\u6e5a\u6e5b\u6e5c\u6e5d\u6e5e\u6e5f\u6e60\u6e61\u6e62\u6e63\u6e64\u6e65\u6e66\u6e67\u6e68\u6e69\u6e6a\u6e6b\u6e6c\u6e6d\u6e6e\u6e6f\u6e70\u6e71\u6e72\u6e73\u6e74\u6e75\u6e76\u6e77\u6e78\u6e79\u6e7a\u6e7b\u6e7c\u6e7d\u6e7e\u6e7f\u6e80\u6e81\u6e82\u6e83\u6e84\u6e85\u6e86\u6e87\u6e88\u6e89\u6e8a\u6e8b\u6e8c\u6e8d\u6e8e\u6e8f\u6e90\u6e91\u6e92\u6e93\u6e94\u6e95\u6e96\u6e97\u6e98\u6e99\u6e9a\u6e9b\u6e9c\u6e9d\u6e9e\u6e9f\u6ea0\u6ea1\u6ea2\u6ea3\u6ea4\u6ea5\u6ea6\u6ea7\u6ea8\u6ea9\u6eaa\u6eab\u6eac\u6ead\u6eae\u6eaf\u6eb0\u6eb1\u6eb2\u6eb3\u6eb4\u6eb5\u6eb6\u6eb7\u6eb8\u6eb9\u6eba\u6ebb\u6ebc\u6ebd\u6ebe\u6ebf\u6ec0\u6ec1\u6ec2\u6ec3\u6ec4\u6ec5\u6ec6\u6ec7\u6ec8\u6ec9\u6eca\u6ecb\u6ecc\u6ecd\u6ece\u6ecf\u6ed0\u6ed1\u6ed2\u6ed3\u6ed4\u6ed5\u6ed6\u6ed7\u6ed8\u6ed9\u6eda\u6edb\u6edc\u6edd\u6ede\u6edf\u6ee0\u6ee1\u6ee2\u6ee3\u6ee4\u6ee5\u6ee6\u6ee7\u6ee8\u6ee9\u6eea\u6eeb\u6eec\u6eed\u6eee\u6eef\u6ef0\u6ef1\u6ef2\u6ef3\u6ef4\u6ef5\u6ef6\u6ef7\u6ef8\u6ef9\u6efa\u6efb\u6efc\u6efd\u6efe\u6eff\u6f00\u6f01\u6f02\u6f03\u6f04\u6f05\u6f06\u6f07\u6f08\u6f09\u6f0a\u6f0b\u6f0c\u6f0d\u6f0e\u6f0f\u6f10\u6f11\u6f12\u6f13\u6f14\u6f15\u6f16\u6f17\u6f18\u6f19\u6f1a\u6f1b\u6f1c\u6f1d\u6f1e\u6f1f\u6f20\u6f21\u6f22\u6f23\u6f24\u6f25\u6f26\u6f27\u6f28\u6f29\u6f2a\u6f2b\u6f2c\u6f2d\u6f2e\u6f2f\u6f30\u6f31\u6f32\u6f33\u6f34\u6f35\u6f36\u6f37\u6f38\u6f39\u6f3a\u6f3b\u6f3c\u6f3d\u6f3e\u6f3f\u6f40\u6f41\u6f42\u6f43\u6f44\u6f45\u6f46\u6f47\u6f48\u6f49\u6f4a\u6f4b\u6f4c\u6f4d\u6f4e\u6f4f\u6f50\u6f51\u6f52\u6f53\u6f54\u6f55\u6f56\u6f57\u6f58\u6f59\u6f5a\u6f5b\u6f5c\u6f5d\u6f5e\u6f5f\u6f60\u6f61\u6f62\u6f63\u6f64\u6f65\u6f66\u6f67\u6f68\u6f69\u6f6a\u6f6b\u6f6c\u6f6d\u6f6e\u6f6f\u6f70\u6f71\u6f72\u6f73\u6f74\u6f75\u6f76\u6f77\u6f78\u6f79\u6f7a\u6f7b\u6f7c\u6f7d\u6f7e\u6f7f\u6f80\u6f81\u6f82\u6f83\u6f84\u6f85\u6f86\u6f87\u6f88\u6f89\u6f8a\u6f8b\u6f8c\u6f8d\u6f8e\u6f8f\u6f90\u6f91\u6f92\u6f93\u6f94\u6f95\u6f96\u6f97\u6f98\u6f99\u6f9a\u6f9b\u6f9c\u6f9d\u6f9e\u6f9f\u6fa0\u6fa1\u6fa2\u6fa3\u6fa4\u6fa5\u6fa6\u6fa7\u6fa8\u6fa9\u6faa\u6fab\u6fac\u6fad\u6fae\u6faf\u6fb0\u6fb1\u6fb2\u6fb3\u6fb4\u6fb5\u6fb6\u6fb7\u6fb8\u6fb9\u6fba\u6fbb\u6fbc\u6fbd\u6fbe\u6fbf\u6fc0\u6fc1\u6fc2\u6fc3\u6fc4\u6fc5\u6fc6\u6fc7\u6fc8\u6fc9\u6fca\u6fcb\u6fcc\u6fcd\u6fce\u6fcf\u6fd0\u6fd1\u6fd2\u6fd3\u6fd4\u6fd5\u6fd6\u6fd7\u6fd8\u6fd9\u6fda\u6fdb\u6fdc\u6fdd\u6fde\u6fdf\u6fe0\u6fe1\u6fe2\u6fe3\u6fe4\u6fe5\u6fe6\u6fe7\u6fe8\u6fe9\u6fea\u6feb\u6fec\u6fed\u6fee\u6fef\u6ff0\u6ff1\u6ff2\u6ff3\u6ff4\u6ff5\u6ff6\u6ff7\u6ff8\u6ff9\u6ffa\u6ffb\u6ffc\u6ffd\u6ffe\u6fff\u7000\u7001\u7002\u7003\u7004\u7005\u7006\u7007\u7008\u7009\u700a\u700b\u700c\u700d\u700e\u700f\u7010\u7011\u7012\u7013\u7014\u7015\u7016\u7017\u7018\u7019\u701a\u701b\u701c\u701d\u701e\u701f\u7020\u7021\u7022\u7023\u7024\u7025\u7026\u7027\u7028\u7029\u702a\u702b\u702c\u702d\u702e\u702f\u7030\u7031\u7032\u7033\u7034\u7035\u7036\u7037\u7038\u7039\u703a\u703b\u703c\u703d\u703e\u703f\u7040\u7041\u7042\u7043\u7044\u7045\u7046\u7047\u7048\u7049\u704a\u704b\u704c\u704d\u704e\u704f\u7050\u7051\u7052\u7053\u7054\u7055\u7056\u7057\u7058\u7059\u705a\u705b\u705c\u705d\u705e\u705f\u7060\u7061\u7062\u7063\u7064\u7065\u7066\u7067\u7068\u7069\u706a\u706b\u706c\u706d\u706e\u706f\u7070\u7071\u7072\u7073\u7074\u7075\u7076\u7077\u7078\u7079\u707a\u707b\u707c\u707d\u707e\u707f\u7080\u7081\u7082\u7083\u7084\u7085\u7086\u7087\u7088\u7089\u708a\u708b\u708c\u708d\u708e\u708f\u7090\u7091\u7092\u7093\u7094\u7095\u7096\u7097\u7098\u7099\u709a\u709b\u709c\u709d\u709e\u709f\u70a0\u70a1\u70a2\u70a3\u70a4\u70a5\u70a6\u70a7\u70a8\u70a9\u70aa\u70ab\u70ac\u70ad\u70ae\u70af\u70b0\u70b1\u70b2\u70b3\u70b4\u70b5\u70b6\u70b7\u70b8\u70b9\u70ba\u70bb\u70bc\u70bd\u70be\u70bf\u70c0\u70c1\u70c2\u70c3\u70c4\u70c5\u70c6\u70c7\u70c8\u70c9\u70ca\u70cb\u70cc\u70cd\u70ce\u70cf\u70d0\u70d1\u70d2\u70d3\u70d4\u70d5\u70d6\u70d7\u70d8\u70d9\u70da\u70db\u70dc\u70dd\u70de\u70df\u70e0\u70e1\u70e2\u70e3\u70e4\u70e5\u70e6\u70e7\u70e8\u70e9\u70ea\u70eb\u70ec\u70ed\u70ee\u70ef\u70f0\u70f1\u70f2\u70f3\u70f4\u70f5\u70f6\u70f7\u70f8\u70f9\u70fa\u70fb\u70fc\u70fd\u70fe\u70ff\u7100\u7101\u7102\u7103\u7104\u7105\u7106\u7107\u7108\u7109\u710a\u710b\u710c\u710d\u710e\u710f\u7110\u7111\u7112\u7113\u7114\u7115\u7116\u7117\u7118\u7119\u711a\u711b\u711c\u711d\u711e\u711f\u7120\u7121\u7122\u7123\u7124\u7125\u7126\u7127\u7128\u7129\u712a\u712b\u712c\u712d\u712e\u712f\u7130\u7131\u7132\u7133\u7134\u7135\u7136\u7137\u7138\u7139\u713a\u713b\u713c\u713d\u713e\u713f\u7140\u7141\u7142\u7143\u7144\u7145\u7146\u7147\u7148\u7149\u714a\u714b\u714c\u714d\u714e\u714f\u7150\u7151\u7152\u7153\u7154\u7155\u7156\u7157\u7158\u7159\u715a\u715b\u715c\u715d\u715e\u715f\u7160\u7161\u7162\u7163\u7164\u7165\u7166\u7167\u7168\u7169\u716a\u716b\u716c\u716d\u716e\u716f\u7170\u7171\u7172\u7173\u7174\u7175\u7176\u7177\u7178\u7179\u717a\u717b\u717c\u717d\u717e\u717f\u7180\u7181\u7182\u7183\u7184\u7185\u7186\u7187\u7188\u7189\u718a\u718b\u718c\u718d\u718e\u718f\u7190\u7191\u7192\u7193\u7194\u7195\u7196\u7197\u7198\u7199\u719a\u719b\u719c\u719d\u719e\u719f\u71a0\u71a1\u71a2\u71a3\u71a4\u71a5\u71a6\u71a7\u71a8\u71a9\u71aa\u71ab\u71ac\u71ad\u71ae\u71af\u71b0\u71b1\u71b2\u71b3\u71b4\u71b5\u71b6\u71b7\u71b8\u71b9\u71ba\u71bb\u71bc\u71bd\u71be\u71bf\u71c0\u71c1\u71c2\u71c3\u71c4\u71c5\u71c6\u71c7\u71c8\u71c9\u71ca\u71cb\u71cc\u71cd\u71ce\u71cf\u71d0\u71d1\u71d2\u71d3\u71d4\u71d5\u71d6\u71d7\u71d8\u71d9\u71da\u71db\u71dc\u71dd\u71de\u71df\u71e0\u71e1\u71e2\u71e3\u71e4\u71e5\u71e6\u71e7\u71e8\u71e9\u71ea\u71eb\u71ec\u71ed\u71ee\u71ef\u71f0\u71f1\u71f2\u71f3\u71f4\u71f5\u71f6\u71f7\u71f8\u71f9\u71fa\u71fb\u71fc\u71fd\u71fe\u71ff\u7200\u7201\u7202\u7203\u7204\u7205\u7206\u7207\u7208\u7209\u720a\u720b\u720c\u720d\u720e\u720f\u7210\u7211\u7212\u7213\u7214\u7215\u7216\u7217\u7218\u7219\u721a\u721b\u721c\u721d\u721e\u721f\u7220\u7221\u7222\u7223\u7224\u7225\u7226\u7227\u7228\u7229\u722a\u722b\u722c\u722d\u722e\u722f\u7230\u7231\u7232\u7233\u7234\u7235\u7236\u7237\u7238\u7239\u723a\u723b\u723c\u723d\u723e\u723f\u7240\u7241\u7242\u7243\u7244\u7245\u7246\u7247\u7248\u7249\u724a\u724b\u724c\u724d\u724e\u724f\u7250\u7251\u7252\u7253\u7254\u7255\u7256\u7257\u7258\u7259\u725a\u725b\u725c\u725d\u725e\u725f\u7260\u7261\u7262\u7263\u7264\u7265\u7266\u7267\u7268\u7269\u726a\u726b\u726c\u726d\u726e\u726f\u7270\u7271\u7272\u7273\u7274\u7275\u7276\u7277\u7278\u7279\u727a\u727b\u727c\u727d\u727e\u727f\u7280\u7281\u7282\u7283\u7284\u7285\u7286\u7287\u7288\u7289\u728a\u728b\u728c\u728d\u728e\u728f\u7290\u7291\u7292\u7293\u7294\u7295\u7296\u7297\u7298\u7299\u729a\u729b\u729c\u729d\u729e\u729f\u72a0\u72a1\u72a2\u72a3\u72a4\u72a5\u72a6\u72a7\u72a8\u72a9\u72aa\u72ab\u72ac\u72ad\u72ae\u72af\u72b0\u72b1\u72b2\u72b3\u72b4\u72b5\u72b6\u72b7\u72b8\u72b9\u72ba\u72bb\u72bc\u72bd\u72be\u72bf\u72c0\u72c1\u72c2\u72c3\u72c4\u72c5\u72c6\u72c7\u72c8\u72c9\u72ca\u72cb\u72cc\u72cd\u72ce\u72cf\u72d0\u72d1\u72d2\u72d3\u72d4\u72d5\u72d6\u72d7\u72d8\u72d9\u72da\u72db\u72dc\u72dd\u72de\u72df\u72e0\u72e1\u72e2\u72e3\u72e4\u72e5\u72e6\u72e7\u72e8\u72e9\u72ea\u72eb\u72ec\u72ed\u72ee\u72ef\u72f0\u72f1\u72f2\u72f3\u72f4\u72f5\u72f6\u72f7\u72f8\u72f9\u72fa\u72fb\u72fc\u72fd\u72fe\u72ff\u7300\u7301\u7302\u7303\u7304\u7305\u7306\u7307\u7308\u7309\u730a\u730b\u730c\u730d\u730e\u730f\u7310\u7311\u7312\u7313\u7314\u7315\u7316\u7317\u7318\u7319\u731a\u731b\u731c\u731d\u731e\u731f\u7320\u7321\u7322\u7323\u7324\u7325\u7326\u7327\u7328\u7329\u732a\u732b\u732c\u732d\u732e\u732f\u7330\u7331\u7332\u7333\u7334\u7335\u7336\u7337\u7338\u7339\u733a\u733b\u733c\u733d\u733e\u733f\u7340\u7341\u7342\u7343\u7344\u7345\u7346\u7347\u7348\u7349\u734a\u734b\u734c\u734d\u734e\u734f\u7350\u7351\u7352\u7353\u7354\u7355\u7356\u7357\u7358\u7359\u735a\u735b\u735c\u735d\u735e\u735f\u7360\u7361\u7362\u7363\u7364\u7365\u7366\u7367\u7368\u7369\u736a\u736b\u736c\u736d\u736e\u736f\u7370\u7371\u7372\u7373\u7374\u7375\u7376\u7377\u7378\u7379\u737a\u737b\u737c\u737d\u737e\u737f\u7380\u7381\u7382\u7383\u7384\u7385\u7386\u7387\u7388\u7389\u738a\u738b\u738c\u738d\u738e\u738f\u7390\u7391\u7392\u7393\u7394\u7395\u7396\u7397\u7398\u7399\u739a\u739b\u739c\u739d\u739e\u739f\u73a0\u73a1\u73a2\u73a3\u73a4\u73a5\u73a6\u73a7\u73a8\u73a9\u73aa\u73ab\u73ac\u73ad\u73ae\u73af\u73b0\u73b1\u73b2\u73b3\u73b4\u73b5\u73b6\u73b7\u73b8\u73b9\u73ba\u73bb\u73bc\u73bd\u73be\u73bf\u73c0\u73c1\u73c2\u73c3\u73c4\u73c5\u73c6\u73c7\u73c8\u73c9\u73ca\u73cb\u73cc\u73cd\u73ce\u73cf\u73d0\u73d1\u73d2\u73d3\u73d4\u73d5\u73d6\u73d7\u73d8\u73d9\u73da\u73db\u73dc\u73dd\u73de\u73df\u73e0\u73e1\u73e2\u73e3\u73e4\u73e5\u73e6\u73e7\u73e8\u73e9\u73ea\u73eb\u73ec\u73ed\u73ee\u73ef\u73f0\u73f1\u73f2\u73f3\u73f4\u73f5\u73f6\u73f7\u73f8\u73f9\u73fa\u73fb\u73fc\u73fd\u73fe\u73ff\u7400\u7401\u7402\u7403\u7404\u7405\u7406\u7407\u7408\u7409\u740a\u740b\u740c\u740d\u740e\u740f\u7410\u7411\u7412\u7413\u7414\u7415\u7416\u7417\u7418\u7419\u741a\u741b\u741c\u741d\u741e\u741f\u7420\u7421\u7422\u7423\u7424\u7425\u7426\u7427\u7428\u7429\u742a\u742b\u742c\u742d\u742e\u742f\u7430\u7431\u7432\u7433\u7434\u7435\u7436\u7437\u7438\u7439\u743a\u743b\u743c\u743d\u743e\u743f\u7440\u7441\u7442\u7443\u7444\u7445\u7446\u7447\u7448\u7449\u744a\u744b\u744c\u744d\u744e\u744f\u7450\u7451\u7452\u7453\u7454\u7455\u7456\u7457\u7458\u7459\u745a\u745b\u745c\u745d\u745e\u745f\u7460\u7461\u7462\u7463\u7464\u7465\u7466\u7467\u7468\u7469\u746a\u746b\u746c\u746d\u746e\u746f\u7470\u7471\u7472\u7473\u7474\u7475\u7476\u7477\u7478\u7479\u747a\u747b\u747c\u747d\u747e\u747f\u7480\u7481\u7482\u7483\u7484\u7485\u7486\u7487\u7488\u7489\u748a\u748b\u748c\u748d\u748e\u748f\u7490\u7491\u7492\u7493\u7494\u7495\u7496\u7497\u7498\u7499\u749a\u749b\u749c\u749d\u749e\u749f\u74a0\u74a1\u74a2\u74a3\u74a4\u74a5\u74a6\u74a7\u74a8\u74a9\u74aa\u74ab\u74ac\u74ad\u74ae\u74af\u74b0\u74b1\u74b2\u74b3\u74b4\u74b5\u74b6\u74b7\u74b8\u74b9\u74ba\u74bb\u74bc\u74bd\u74be\u74bf\u74c0\u74c1\u74c2\u74c3\u74c4\u74c5\u74c6\u74c7\u74c8\u74c9\u74ca\u74cb\u74cc\u74cd\u74ce\u74cf\u74d0\u74d1\u74d2\u74d3\u74d4\u74d5\u74d6\u74d7\u74d8\u74d9\u74da\u74db\u74dc\u74dd\u74de\u74df\u74e0\u74e1\u74e2\u74e3\u74e4\u74e5\u74e6\u74e7\u74e8\u74e9\u74ea\u74eb\u74ec\u74ed\u74ee\u74ef\u74f0\u74f1\u74f2\u74f3\u74f4\u74f5\u74f6\u74f7\u74f8\u74f9\u74fa\u74fb\u74fc\u74fd\u74fe\u74ff\u7500\u7501\u7502\u7503\u7504\u7505\u7506\u7507\u7508\u7509\u750a\u750b\u750c\u750d\u750e\u750f\u7510\u7511\u7512\u7513\u7514\u7515\u7516\u7517\u7518\u7519\u751a\u751b\u751c\u751d\u751e\u751f\u7520\u7521\u7522\u7523\u7524\u7525\u7526\u7527\u7528\u7529\u752a\u752b\u752c\u752d\u752e\u752f\u7530\u7531\u7532\u7533\u7534\u7535\u7536\u7537\u7538\u7539\u753a\u753b\u753c\u753d\u753e\u753f\u7540\u7541\u7542\u7543\u7544\u7545\u7546\u7547\u7548\u7549\u754a\u754b\u754c\u754d\u754e\u754f\u7550\u7551\u7552\u7553\u7554\u7555\u7556\u7557\u7558\u7559\u755a\u755b\u755c\u755d\u755e\u755f\u7560\u7561\u7562\u7563\u7564\u7565\u7566\u7567\u7568\u7569\u756a\u756b\u756c\u756d\u756e\u756f\u7570\u7571\u7572\u7573\u7574\u7575\u7576\u7577\u7578\u7579\u757a\u757b\u757c\u757d\u757e\u757f\u7580\u7581\u7582\u7583\u7584\u7585\u7586\u7587\u7588\u7589\u758a\u758b\u758c\u758d\u758e\u758f\u7590\u7591\u7592\u7593\u7594\u7595\u7596\u7597\u7598\u7599\u759a\u759b\u759c\u759d\u759e\u759f\u75a0\u75a1\u75a2\u75a3\u75a4\u75a5\u75a6\u75a7\u75a8\u75a9\u75aa\u75ab\u75ac\u75ad\u75ae\u75af\u75b0\u75b1\u75b2\u75b3\u75b4\u75b5\u75b6\u75b7\u75b8\u75b9\u75ba\u75bb\u75bc\u75bd\u75be\u75bf\u75c0\u75c1\u75c2\u75c3\u75c4\u75c5\u75c6\u75c7\u75c8\u75c9\u75ca\u75cb\u75cc\u75cd\u75ce\u75cf\u75d0\u75d1\u75d2\u75d3\u75d4\u75d5\u75d6\u75d7\u75d8\u75d9\u75da\u75db\u75dc\u75dd\u75de\u75df\u75e0\u75e1\u75e2\u75e3\u75e4\u75e5\u75e6\u75e7\u75e8\u75e9\u75ea\u75eb\u75ec\u75ed\u75ee\u75ef\u75f0\u75f1\u75f2\u75f3\u75f4\u75f5\u75f6\u75f7\u75f8\u75f9\u75fa\u75fb\u75fc\u75fd\u75fe\u75ff\u7600\u7601\u7602\u7603\u7604\u7605\u7606\u7607\u7608\u7609\u760a\u760b\u760c\u760d\u760e\u760f\u7610\u7611\u7612\u7613\u7614\u7615\u7616\u7617\u7618\u7619\u761a\u761b\u761c\u761d\u761e\u761f\u7620\u7621\u7622\u7623\u7624\u7625\u7626\u7627\u7628\u7629\u762a\u762b\u762c\u762d\u762e\u762f\u7630\u7631\u7632\u7633\u7634\u7635\u7636\u7637\u7638\u7639\u763a\u763b\u763c\u763d\u763e\u763f\u7640\u7641\u7642\u7643\u7644\u7645\u7646\u7647\u7648\u7649\u764a\u764b\u764c\u764d\u764e\u764f\u7650\u7651\u7652\u7653\u7654\u7655\u7656\u7657\u7658\u7659\u765a\u765b\u765c\u765d\u765e\u765f\u7660\u7661\u7662\u7663\u7664\u7665\u7666\u7667\u7668\u7669\u766a\u766b\u766c\u766d\u766e\u766f\u7670\u7671\u7672\u7673\u7674\u7675\u7676\u7677\u7678\u7679\u767a\u767b\u767c\u767d\u767e\u767f\u7680\u7681\u7682\u7683\u7684\u7685\u7686\u7687\u7688\u7689\u768a\u768b\u768c\u768d\u768e\u768f\u7690\u7691\u7692\u7693\u7694\u7695\u7696\u7697\u7698\u7699\u769a\u769b\u769c\u769d\u769e\u769f\u76a0\u76a1\u76a2\u76a3\u76a4\u76a5\u76a6\u76a7\u76a8\u76a9\u76aa\u76ab\u76ac\u76ad\u76ae\u76af\u76b0\u76b1\u76b2\u76b3\u76b4\u76b5\u76b6\u76b7\u76b8\u76b9\u76ba\u76bb\u76bc\u76bd\u76be\u76bf\u76c0\u76c1\u76c2\u76c3\u76c4\u76c5\u76c6\u76c7\u76c8\u76c9\u76ca\u76cb\u76cc\u76cd\u76ce\u76cf\u76d0\u76d1\u76d2\u76d3\u76d4\u76d5\u76d6\u76d7\u76d8\u76d9\u76da\u76db\u76dc\u76dd\u76de\u76df\u76e0\u76e1\u76e2\u76e3\u76e4\u76e5\u76e6\u76e7\u76e8\u76e9\u76ea\u76eb\u76ec\u76ed\u76ee\u76ef\u76f0\u76f1\u76f2\u76f3\u76f4\u76f5\u76f6\u76f7\u76f8\u76f9\u76fa\u76fb\u76fc\u76fd\u76fe\u76ff\u7700\u7701\u7702\u7703\u7704\u7705\u7706\u7707\u7708\u7709\u770a\u770b\u770c\u770d\u770e\u770f\u7710\u7711\u7712\u7713\u7714\u7715\u7716\u7717\u7718\u7719\u771a\u771b\u771c\u771d\u771e\u771f\u7720\u7721\u7722\u7723\u7724\u7725\u7726\u7727\u7728\u7729\u772a\u772b\u772c\u772d\u772e\u772f\u7730\u7731\u7732\u7733\u7734\u7735\u7736\u7737\u7738\u7739\u773a\u773b\u773c\u773d\u773e\u773f\u7740\u7741\u7742\u7743\u7744\u7745\u7746\u7747\u7748\u7749\u774a\u774b\u774c\u774d\u774e\u774f\u7750\u7751\u7752\u7753\u7754\u7755\u7756\u7757\u7758\u7759\u775a\u775b\u775c\u775d\u775e\u775f\u7760\u7761\u7762\u7763\u7764\u7765\u7766\u7767\u7768\u7769\u776a\u776b\u776c\u776d\u776e\u776f\u7770\u7771\u7772\u7773\u7774\u7775\u7776\u7777\u7778\u7779\u777a\u777b\u777c\u777d\u777e\u777f\u7780\u7781\u7782\u7783\u7784\u7785\u7786\u7787\u7788\u7789\u778a\u778b\u778c\u778d\u778e\u778f\u7790\u7791\u7792\u7793\u7794\u7795\u7796\u7797\u7798\u7799\u779a\u779b\u779c\u779d\u779e\u779f\u77a0\u77a1\u77a2\u77a3\u77a4\u77a5\u77a6\u77a7\u77a8\u77a9\u77aa\u77ab\u77ac\u77ad\u77ae\u77af\u77b0\u77b1\u77b2\u77b3\u77b4\u77b5\u77b6\u77b7\u77b8\u77b9\u77ba\u77bb\u77bc\u77bd\u77be\u77bf\u77c0\u77c1\u77c2\u77c3\u77c4\u77c5\u77c6\u77c7\u77c8\u77c9\u77ca\u77cb\u77cc\u77cd\u77ce\u77cf\u77d0\u77d1\u77d2\u77d3\u77d4\u77d5\u77d6\u77d7\u77d8\u77d9\u77da\u77db\u77dc\u77dd\u77de\u77df\u77e0\u77e1\u77e2\u77e3\u77e4\u77e5\u77e6\u77e7\u77e8\u77e9\u77ea\u77eb\u77ec\u77ed\u77ee\u77ef\u77f0\u77f1\u77f2\u77f3\u77f4\u77f5\u77f6\u77f7\u77f8\u77f9\u77fa\u77fb\u77fc\u77fd\u77fe\u77ff\u7800\u7801\u7802\u7803\u7804\u7805\u7806\u7807\u7808\u7809\u780a\u780b\u780c\u780d\u780e\u780f\u7810\u7811\u7812\u7813\u7814\u7815\u7816\u7817\u7818\u7819\u781a\u781b\u781c\u781d\u781e\u781f\u7820\u7821\u7822\u7823\u7824\u7825\u7826\u7827\u7828\u7829\u782a\u782b\u782c\u782d\u782e\u782f\u7830\u7831\u7832\u7833\u7834\u7835\u7836\u7837\u7838\u7839\u783a\u783b\u783c\u783d\u783e\u783f\u7840\u7841\u7842\u7843\u7844\u7845\u7846\u7847\u7848\u7849\u784a\u784b\u784c\u784d\u784e\u784f\u7850\u7851\u7852\u7853\u7854\u7855\u7856\u7857\u7858\u7859\u785a\u785b\u785c\u785d\u785e\u785f\u7860\u7861\u7862\u7863\u7864\u7865\u7866\u7867\u7868\u7869\u786a\u786b\u786c\u786d\u786e\u786f\u7870\u7871\u7872\u7873\u7874\u7875\u7876\u7877\u7878\u7879\u787a\u787b\u787c\u787d\u787e\u787f\u7880\u7881\u7882\u7883\u7884\u7885\u7886\u7887\u7888\u7889\u788a\u788b\u788c\u788d\u788e\u788f\u7890\u7891\u7892\u7893\u7894\u7895\u7896\u7897\u7898\u7899\u789a\u789b\u789c\u789d\u789e\u789f\u78a0\u78a1\u78a2\u78a3\u78a4\u78a5\u78a6\u78a7\u78a8\u78a9\u78aa\u78ab\u78ac\u78ad\u78ae\u78af\u78b0\u78b1\u78b2\u78b3\u78b4\u78b5\u78b6\u78b7\u78b8\u78b9\u78ba\u78bb\u78bc\u78bd\u78be\u78bf\u78c0\u78c1\u78c2\u78c3\u78c4\u78c5\u78c6\u78c7\u78c8\u78c9\u78ca\u78cb\u78cc\u78cd\u78ce\u78cf\u78d0\u78d1\u78d2\u78d3\u78d4\u78d5\u78d6\u78d7\u78d8\u78d9\u78da\u78db\u78dc\u78dd\u78de\u78df\u78e0\u78e1\u78e2\u78e3\u78e4\u78e5\u78e6\u78e7\u78e8\u78e9\u78ea\u78eb\u78ec\u78ed\u78ee\u78ef\u78f0\u78f1\u78f2\u78f3\u78f4\u78f5\u78f6\u78f7\u78f8\u78f9\u78fa\u78fb\u78fc\u78fd\u78fe\u78ff\u7900\u7901\u7902\u7903\u7904\u7905\u7906\u7907\u7908\u7909\u790a\u790b\u790c\u790d\u790e\u790f\u7910\u7911\u7912\u7913\u7914\u7915\u7916\u7917\u7918\u7919\u791a\u791b\u791c\u791d\u791e\u791f\u7920\u7921\u7922\u7923\u7924\u7925\u7926\u7927\u7928\u7929\u792a\u792b\u792c\u792d\u792e\u792f\u7930\u7931\u7932\u7933\u7934\u7935\u7936\u7937\u7938\u7939\u793a\u793b\u793c\u793d\u793e\u793f\u7940\u7941\u7942\u7943\u7944\u7945\u7946\u7947\u7948\u7949\u794a\u794b\u794c\u794d\u794e\u794f\u7950\u7951\u7952\u7953\u7954\u7955\u7956\u7957\u7958\u7959\u795a\u795b\u795c\u795d\u795e\u795f\u7960\u7961\u7962\u7963\u7964\u7965\u7966\u7967\u7968\u7969\u796a\u796b\u796c\u796d\u796e\u796f\u7970\u7971\u7972\u7973\u7974\u7975\u7976\u7977\u7978\u7979\u797a\u797b\u797c\u797d\u797e\u797f\u7980\u7981\u7982\u7983\u7984\u7985\u7986\u7987\u7988\u7989\u798a\u798b\u798c\u798d\u798e\u798f\u7990\u7991\u7992\u7993\u7994\u7995\u7996\u7997\u7998\u7999\u799a\u799b\u799c\u799d\u799e\u799f\u79a0\u79a1\u79a2\u79a3\u79a4\u79a5\u79a6\u79a7\u79a8\u79a9\u79aa\u79ab\u79ac\u79ad\u79ae\u79af\u79b0\u79b1\u79b2\u79b3\u79b4\u79b5\u79b6\u79b7\u79b8\u79b9\u79ba\u79bb\u79bc\u79bd\u79be\u79bf\u79c0\u79c1\u79c2\u79c3\u79c4\u79c5\u79c6\u79c7\u79c8\u79c9\u79ca\u79cb\u79cc\u79cd\u79ce\u79cf\u79d0\u79d1\u79d2\u79d3\u79d4\u79d5\u79d6\u79d7\u79d8\u79d9\u79da\u79db\u79dc\u79dd\u79de\u79df\u79e0\u79e1\u79e2\u79e3\u79e4\u79e5\u79e6\u79e7\u79e8\u79e9\u79ea\u79eb\u79ec\u79ed\u79ee\u79ef\u79f0\u79f1\u79f2\u79f3\u79f4\u79f5\u79f6\u79f7\u79f8\u79f9\u79fa\u79fb\u79fc\u79fd\u79fe\u79ff\u7a00\u7a01\u7a02\u7a03\u7a04\u7a05\u7a06\u7a07\u7a08\u7a09\u7a0a\u7a0b\u7a0c\u7a0d\u7a0e\u7a0f\u7a10\u7a11\u7a12\u7a13\u7a14\u7a15\u7a16\u7a17\u7a18\u7a19\u7a1a\u7a1b\u7a1c\u7a1d\u7a1e\u7a1f\u7a20\u7a21\u7a22\u7a23\u7a24\u7a25\u7a26\u7a27\u7a28\u7a29\u7a2a\u7a2b\u7a2c\u7a2d\u7a2e\u7a2f\u7a30\u7a31\u7a32\u7a33\u7a34\u7a35\u7a36\u7a37\u7a38\u7a39\u7a3a\u7a3b\u7a3c\u7a3d\u7a3e\u7a3f\u7a40\u7a41\u7a42\u7a43\u7a44\u7a45\u7a46\u7a47\u7a48\u7a49\u7a4a\u7a4b\u7a4c\u7a4d\u7a4e\u7a4f\u7a50\u7a51\u7a52\u7a53\u7a54\u7a55\u7a56\u7a57\u7a58\u7a59\u7a5a\u7a5b\u7a5c\u7a5d\u7a5e\u7a5f\u7a60\u7a61\u7a62\u7a63\u7a64\u7a65\u7a66\u7a67\u7a68\u7a69\u7a6a\u7a6b\u7a6c\u7a6d\u7a6e\u7a6f\u7a70\u7a71\u7a72\u7a73\u7a74\u7a75\u7a76\u7a77\u7a78\u7a79\u7a7a\u7a7b\u7a7c\u7a7d\u7a7e\u7a7f\u7a80\u7a81\u7a82\u7a83\u7a84\u7a85\u7a86\u7a87\u7a88\u7a89\u7a8a\u7a8b\u7a8c\u7a8d\u7a8e\u7a8f\u7a90\u7a91\u7a92\u7a93\u7a94\u7a95\u7a96\u7a97\u7a98\u7a99\u7a9a\u7a9b\u7a9c\u7a9d\u7a9e\u7a9f\u7aa0\u7aa1\u7aa2\u7aa3\u7aa4\u7aa5\u7aa6\u7aa7\u7aa8\u7aa9\u7aaa\u7aab\u7aac\u7aad\u7aae\u7aaf\u7ab0\u7ab1\u7ab2\u7ab3\u7ab4\u7ab5\u7ab6\u7ab7\u7ab8\u7ab9\u7aba\u7abb\u7abc\u7abd\u7abe\u7abf\u7ac0\u7ac1\u7ac2\u7ac3\u7ac4\u7ac5\u7ac6\u7ac7\u7ac8\u7ac9\u7aca\u7acb\u7acc\u7acd\u7ace\u7acf\u7ad0\u7ad1\u7ad2\u7ad3\u7ad4\u7ad5\u7ad6\u7ad7\u7ad8\u7ad9\u7ada\u7adb\u7adc\u7add\u7ade\u7adf\u7ae0\u7ae1\u7ae2\u7ae3\u7ae4\u7ae5\u7ae6\u7ae7\u7ae8\u7ae9\u7aea\u7aeb\u7aec\u7aed\u7aee\u7aef\u7af0\u7af1\u7af2\u7af3\u7af4\u7af5\u7af6\u7af7\u7af8\u7af9\u7afa\u7afb\u7afc\u7afd\u7afe\u7aff\u7b00\u7b01\u7b02\u7b03\u7b04\u7b05\u7b06\u7b07\u7b08\u7b09\u7b0a\u7b0b\u7b0c\u7b0d\u7b0e\u7b0f\u7b10\u7b11\u7b12\u7b13\u7b14\u7b15\u7b16\u7b17\u7b18\u7b19\u7b1a\u7b1b\u7b1c\u7b1d\u7b1e\u7b1f\u7b20\u7b21\u7b22\u7b23\u7b24\u7b25\u7b26\u7b27\u7b28\u7b29\u7b2a\u7b2b\u7b2c\u7b2d\u7b2e\u7b2f\u7b30\u7b31\u7b32\u7b33\u7b34\u7b35\u7b36\u7b37\u7b38\u7b39\u7b3a\u7b3b\u7b3c\u7b3d\u7b3e\u7b3f\u7b40\u7b41\u7b42\u7b43\u7b44\u7b45\u7b46\u7b47\u7b48\u7b49\u7b4a\u7b4b\u7b4c\u7b4d\u7b4e\u7b4f\u7b50\u7b51\u7b52\u7b53\u7b54\u7b55\u7b56\u7b57\u7b58\u7b59\u7b5a\u7b5b\u7b5c\u7b5d\u7b5e\u7b5f\u7b60\u7b61\u7b62\u7b63\u7b64\u7b65\u7b66\u7b67\u7b68\u7b69\u7b6a\u7b6b\u7b6c\u7b6d\u7b6e\u7b6f\u7b70\u7b71\u7b72\u7b73\u7b74\u7b75\u7b76\u7b77\u7b78\u7b79\u7b7a\u7b7b\u7b7c\u7b7d\u7b7e\u7b7f\u7b80\u7b81\u7b82\u7b83\u7b84\u7b85\u7b86\u7b87\u7b88\u7b89\u7b8a\u7b8b\u7b8c\u7b8d\u7b8e\u7b8f\u7b90\u7b91\u7b92\u7b93\u7b94\u7b95\u7b96\u7b97\u7b98\u7b99\u7b9a\u7b9b\u7b9c\u7b9d\u7b9e\u7b9f\u7ba0\u7ba1\u7ba2\u7ba3\u7ba4\u7ba5\u7ba6\u7ba7\u7ba8\u7ba9\u7baa\u7bab\u7bac\u7bad\u7bae\u7baf\u7bb0\u7bb1\u7bb2\u7bb3\u7bb4\u7bb5\u7bb6\u7bb7\u7bb8\u7bb9\u7bba\u7bbb\u7bbc\u7bbd\u7bbe\u7bbf\u7bc0\u7bc1\u7bc2\u7bc3\u7bc4\u7bc5\u7bc6\u7bc7\u7bc8\u7bc9\u7bca\u7bcb\u7bcc\u7bcd\u7bce\u7bcf\u7bd0\u7bd1\u7bd2\u7bd3\u7bd4\u7bd5\u7bd6\u7bd7\u7bd8\u7bd9\u7bda\u7bdb\u7bdc\u7bdd\u7bde\u7bdf\u7be0\u7be1\u7be2\u7be3\u7be4\u7be5\u7be6\u7be7\u7be8\u7be9\u7bea\u7beb\u7bec\u7bed\u7bee\u7bef\u7bf0\u7bf1\u7bf2\u7bf3\u7bf4\u7bf5\u7bf6\u7bf7\u7bf8\u7bf9\u7bfa\u7bfb\u7bfc\u7bfd\u7bfe\u7bff\u7c00\u7c01\u7c02\u7c03\u7c04\u7c05\u7c06\u7c07\u7c08\u7c09\u7c0a\u7c0b\u7c0c\u7c0d\u7c0e\u7c0f\u7c10\u7c11\u7c12\u7c13\u7c14\u7c15\u7c16\u7c17\u7c18\u7c19\u7c1a\u7c1b\u7c1c\u7c1d\u7c1e\u7c1f\u7c20\u7c21\u7c22\u7c23\u7c24\u7c25\u7c26\u7c27\u7c28\u7c29\u7c2a\u7c2b\u7c2c\u7c2d\u7c2e\u7c2f\u7c30\u7c31\u7c32\u7c33\u7c34\u7c35\u7c36\u7c37\u7c38\u7c39\u7c3a\u7c3b\u7c3c\u7c3d\u7c3e\u7c3f\u7c40\u7c41\u7c42\u7c43\u7c44\u7c45\u7c46\u7c47\u7c48\u7c49\u7c4a\u7c4b\u7c4c\u7c4d\u7c4e\u7c4f\u7c50\u7c51\u7c52\u7c53\u7c54\u7c55\u7c56\u7c57\u7c58\u7c59\u7c5a\u7c5b\u7c5c\u7c5d\u7c5e\u7c5f\u7c60\u7c61\u7c62\u7c63\u7c64\u7c65\u7c66\u7c67\u7c68\u7c69\u7c6a\u7c6b\u7c6c\u7c6d\u7c6e\u7c6f\u7c70\u7c71\u7c72\u7c73\u7c74\u7c75\u7c76\u7c77\u7c78\u7c79\u7c7a\u7c7b\u7c7c\u7c7d\u7c7e\u7c7f\u7c80\u7c81\u7c82\u7c83\u7c84\u7c85\u7c86\u7c87\u7c88\u7c89\u7c8a\u7c8b\u7c8c\u7c8d\u7c8e\u7c8f\u7c90\u7c91\u7c92\u7c93\u7c94\u7c95\u7c96\u7c97\u7c98\u7c99\u7c9a\u7c9b\u7c9c\u7c9d\u7c9e\u7c9f\u7ca0\u7ca1\u7ca2\u7ca3\u7ca4\u7ca5\u7ca6\u7ca7\u7ca8\u7ca9\u7caa\u7cab\u7cac\u7cad\u7cae\u7caf\u7cb0\u7cb1\u7cb2\u7cb3\u7cb4\u7cb5\u7cb6\u7cb7\u7cb8\u7cb9\u7cba\u7cbb\u7cbc\u7cbd\u7cbe\u7cbf\u7cc0\u7cc1\u7cc2\u7cc3\u7cc4\u7cc5\u7cc6\u7cc7\u7cc8\u7cc9\u7cca\u7ccb\u7ccc\u7ccd\u7cce\u7ccf\u7cd0\u7cd1\u7cd2\u7cd3\u7cd4\u7cd5\u7cd6\u7cd7\u7cd8\u7cd9\u7cda\u7cdb\u7cdc\u7cdd\u7cde\u7cdf\u7ce0\u7ce1\u7ce2\u7ce3\u7ce4\u7ce5\u7ce6\u7ce7\u7ce8\u7ce9\u7cea\u7ceb\u7cec\u7ced\u7cee\u7cef\u7cf0\u7cf1\u7cf2\u7cf3\u7cf4\u7cf5\u7cf6\u7cf7\u7cf8\u7cf9\u7cfa\u7cfb\u7cfc\u7cfd\u7cfe\u7cff\u7d00\u7d01\u7d02\u7d03\u7d04\u7d05\u7d06\u7d07\u7d08\u7d09\u7d0a\u7d0b\u7d0c\u7d0d\u7d0e\u7d0f\u7d10\u7d11\u7d12\u7d13\u7d14\u7d15\u7d16\u7d17\u7d18\u7d19\u7d1a\u7d1b\u7d1c\u7d1d\u7d1e\u7d1f\u7d20\u7d21\u7d22\u7d23\u7d24\u7d25\u7d26\u7d27\u7d28\u7d29\u7d2a\u7d2b\u7d2c\u7d2d\u7d2e\u7d2f\u7d30\u7d31\u7d32\u7d33\u7d34\u7d35\u7d36\u7d37\u7d38\u7d39\u7d3a\u7d3b\u7d3c\u7d3d\u7d3e\u7d3f\u7d40\u7d41\u7d42\u7d43\u7d44\u7d45\u7d46\u7d47\u7d48\u7d49\u7d4a\u7d4b\u7d4c\u7d4d\u7d4e\u7d4f\u7d50\u7d51\u7d52\u7d53\u7d54\u7d55\u7d56\u7d57\u7d58\u7d59\u7d5a\u7d5b\u7d5c\u7d5d\u7d5e\u7d5f\u7d60\u7d61\u7d62\u7d63\u7d64\u7d65\u7d66\u7d67\u7d68\u7d69\u7d6a\u7d6b\u7d6c\u7d6d\u7d6e\u7d6f\u7d70\u7d71\u7d72\u7d73\u7d74\u7d75\u7d76\u7d77\u7d78\u7d79\u7d7a\u7d7b\u7d7c\u7d7d\u7d7e\u7d7f\u7d80\u7d81\u7d82\u7d83\u7d84\u7d85\u7d86\u7d87\u7d88\u7d89\u7d8a\u7d8b\u7d8c\u7d8d\u7d8e\u7d8f\u7d90\u7d91\u7d92\u7d93\u7d94\u7d95\u7d96\u7d97\u7d98\u7d99\u7d9a\u7d9b\u7d9c\u7d9d\u7d9e\u7d9f\u7da0\u7da1\u7da2\u7da3\u7da4\u7da5\u7da6\u7da7\u7da8\u7da9\u7daa\u7dab\u7dac\u7dad\u7dae\u7daf\u7db0\u7db1\u7db2\u7db3\u7db4\u7db5\u7db6\u7db7\u7db8\u7db9\u7dba\u7dbb\u7dbc\u7dbd\u7dbe\u7dbf\u7dc0\u7dc1\u7dc2\u7dc3\u7dc4\u7dc5\u7dc6\u7dc7\u7dc8\u7dc9\u7dca\u7dcb\u7dcc\u7dcd\u7dce\u7dcf\u7dd0\u7dd1\u7dd2\u7dd3\u7dd4\u7dd5\u7dd6\u7dd7\u7dd8\u7dd9\u7dda\u7ddb\u7ddc\u7ddd\u7dde\u7ddf\u7de0\u7de1\u7de2\u7de3\u7de4\u7de5\u7de6\u7de7\u7de8\u7de9\u7dea\u7deb\u7dec\u7ded\u7dee\u7def\u7df0\u7df1\u7df2\u7df3\u7df4\u7df5\u7df6\u7df7\u7df8\u7df9\u7dfa\u7dfb\u7dfc\u7dfd\u7dfe\u7dff\u7e00\u7e01\u7e02\u7e03\u7e04\u7e05\u7e06\u7e07\u7e08\u7e09\u7e0a\u7e0b\u7e0c\u7e0d\u7e0e\u7e0f\u7e10\u7e11\u7e12\u7e13\u7e14\u7e15\u7e16\u7e17\u7e18\u7e19\u7e1a\u7e1b\u7e1c\u7e1d\u7e1e\u7e1f\u7e20\u7e21\u7e22\u7e23\u7e24\u7e25\u7e26\u7e27\u7e28\u7e29\u7e2a\u7e2b\u7e2c\u7e2d\u7e2e\u7e2f\u7e30\u7e31\u7e32\u7e33\u7e34\u7e35\u7e36\u7e37\u7e38\u7e39\u7e3a\u7e3b\u7e3c\u7e3d\u7e3e\u7e3f\u7e40\u7e41\u7e42\u7e43\u7e44\u7e45\u7e46\u7e47\u7e48\u7e49\u7e4a\u7e4b\u7e4c\u7e4d\u7e4e\u7e4f\u7e50\u7e51\u7e52\u7e53\u7e54\u7e55\u7e56\u7e57\u7e58\u7e59\u7e5a\u7e5b\u7e5c\u7e5d\u7e5e\u7e5f\u7e60\u7e61\u7e62\u7e63\u7e64\u7e65\u7e66\u7e67\u7e68\u7e69\u7e6a\u7e6b\u7e6c\u7e6d\u7e6e\u7e6f\u7e70\u7e71\u7e72\u7e73\u7e74\u7e75\u7e76\u7e77\u7e78\u7e79\u7e7a\u7e7b\u7e7c\u7e7d\u7e7e\u7e7f\u7e80\u7e81\u7e82\u7e83\u7e84\u7e85\u7e86\u7e87\u7e88\u7e89\u7e8a\u7e8b\u7e8c\u7e8d\u7e8e\u7e8f\u7e90\u7e91\u7e92\u7e93\u7e94\u7e95\u7e96\u7e97\u7e98\u7e99\u7e9a\u7e9b\u7e9c\u7e9d\u7e9e\u7e9f\u7ea0\u7ea1\u7ea2\u7ea3\u7ea4\u7ea5\u7ea6\u7ea7\u7ea8\u7ea9\u7eaa\u7eab\u7eac\u7ead\u7eae\u7eaf\u7eb0\u7eb1\u7eb2\u7eb3\u7eb4\u7eb5\u7eb6\u7eb7\u7eb8\u7eb9\u7eba\u7ebb\u7ebc\u7ebd\u7ebe\u7ebf\u7ec0\u7ec1\u7ec2\u7ec3\u7ec4\u7ec5\u7ec6\u7ec7\u7ec8\u7ec9\u7eca\u7ecb\u7ecc\u7ecd\u7ece\u7ecf\u7ed0\u7ed1\u7ed2\u7ed3\u7ed4\u7ed5\u7ed6\u7ed7\u7ed8\u7ed9\u7eda\u7edb\u7edc\u7edd\u7ede\u7edf\u7ee0\u7ee1\u7ee2\u7ee3\u7ee4\u7ee5\u7ee6\u7ee7\u7ee8\u7ee9\u7eea\u7eeb\u7eec\u7eed\u7eee\u7eef\u7ef0\u7ef1\u7ef2\u7ef3\u7ef4\u7ef5\u7ef6\u7ef7\u7ef8\u7ef9\u7efa\u7efb\u7efc\u7efd\u7efe\u7eff\u7f00\u7f01\u7f02\u7f03\u7f04\u7f05\u7f06\u7f07\u7f08\u7f09\u7f0a\u7f0b\u7f0c\u7f0d\u7f0e\u7f0f\u7f10\u7f11\u7f12\u7f13\u7f14\u7f15\u7f16\u7f17\u7f18\u7f19\u7f1a\u7f1b\u7f1c\u7f1d\u7f1e\u7f1f\u7f20\u7f21\u7f22\u7f23\u7f24\u7f25\u7f26\u7f27\u7f28\u7f29\u7f2a\u7f2b\u7f2c\u7f2d\u7f2e\u7f2f\u7f30\u7f31\u7f32\u7f33\u7f34\u7f35\u7f36\u7f37\u7f38\u7f39\u7f3a\u7f3b\u7f3c\u7f3d\u7f3e\u7f3f\u7f40\u7f41\u7f42\u7f43\u7f44\u7f45\u7f46\u7f47\u7f48\u7f49\u7f4a\u7f4b\u7f4c\u7f4d\u7f4e\u7f4f\u7f50\u7f51\u7f52\u7f53\u7f54\u7f55\u7f56\u7f57\u7f58\u7f59\u7f5a\u7f5b\u7f5c\u7f5d\u7f5e\u7f5f\u7f60\u7f61\u7f62\u7f63\u7f64\u7f65\u7f66\u7f67\u7f68\u7f69\u7f6a\u7f6b\u7f6c\u7f6d\u7f6e\u7f6f\u7f70\u7f71\u7f72\u7f73\u7f74\u7f75\u7f76\u7f77\u7f78\u7f79\u7f7a\u7f7b\u7f7c\u7f7d\u7f7e\u7f7f\u7f80\u7f81\u7f82\u7f83\u7f84\u7f85\u7f86\u7f87\u7f88\u7f89\u7f8a\u7f8b\u7f8c\u7f8d\u7f8e\u7f8f\u7f90\u7f91\u7f92\u7f93\u7f94\u7f95\u7f96\u7f97\u7f98\u7f99\u7f9a\u7f9b\u7f9c\u7f9d\u7f9e\u7f9f\u7fa0\u7fa1\u7fa2\u7fa3\u7fa4\u7fa5\u7fa6\u7fa7\u7fa8\u7fa9\u7faa\u7fab\u7fac\u7fad\u7fae\u7faf\u7fb0\u7fb1\u7fb2\u7fb3\u7fb4\u7fb5\u7fb6\u7fb7\u7fb8\u7fb9\u7fba\u7fbb\u7fbc\u7fbd\u7fbe\u7fbf\u7fc0\u7fc1\u7fc2\u7fc3\u7fc4\u7fc5\u7fc6\u7fc7\u7fc8\u7fc9\u7fca\u7fcb\u7fcc\u7fcd\u7fce\u7fcf\u7fd0\u7fd1\u7fd2\u7fd3\u7fd4\u7fd5\u7fd6\u7fd7\u7fd8\u7fd9\u7fda\u7fdb\u7fdc\u7fdd\u7fde\u7fdf\u7fe0\u7fe1\u7fe2\u7fe3\u7fe4\u7fe5\u7fe6\u7fe7\u7fe8\u7fe9\u7fea\u7feb\u7fec\u7fed\u7fee\u7fef\u7ff0\u7ff1\u7ff2\u7ff3\u7ff4\u7ff5\u7ff6\u7ff7\u7ff8\u7ff9\u7ffa\u7ffb\u7ffc\u7ffd\u7ffe\u7fff\u8000\u8001\u8002\u8003\u8004\u8005\u8006\u8007\u8008\u8009\u800a\u800b\u800c\u800d\u800e\u800f\u8010\u8011\u8012\u8013\u8014\u8015\u8016\u8017\u8018\u8019\u801a\u801b\u801c\u801d\u801e\u801f\u8020\u8021\u8022\u8023\u8024\u8025\u8026\u8027\u8028\u8029\u802a\u802b\u802c\u802d\u802e\u802f\u8030\u8031\u8032\u8033\u8034\u8035\u8036\u8037\u8038\u8039\u803a\u803b\u803c\u803d\u803e\u803f\u8040\u8041\u8042\u8043\u8044\u8045\u8046\u8047\u8048\u8049\u804a\u804b\u804c\u804d\u804e\u804f\u8050\u8051\u8052\u8053\u8054\u8055\u8056\u8057\u8058\u8059\u805a\u805b\u805c\u805d\u805e\u805f\u8060\u8061\u8062\u8063\u8064\u8065\u8066\u8067\u8068\u8069\u806a\u806b\u806c\u806d\u806e\u806f\u8070\u8071\u8072\u8073\u8074\u8075\u8076\u8077\u8078\u8079\u807a\u807b\u807c\u807d\u807e\u807f\u8080\u8081\u8082\u8083\u8084\u8085\u8086\u8087\u8088\u8089\u808a\u808b\u808c\u808d\u808e\u808f\u8090\u8091\u8092\u8093\u8094\u8095\u8096\u8097\u8098\u8099\u809a\u809b\u809c\u809d\u809e\u809f\u80a0\u80a1\u80a2\u80a3\u80a4\u80a5\u80a6\u80a7\u80a8\u80a9\u80aa\u80ab\u80ac\u80ad\u80ae\u80af\u80b0\u80b1\u80b2\u80b3\u80b4\u80b5\u80b6\u80b7\u80b8\u80b9\u80ba\u80bb\u80bc\u80bd\u80be\u80bf\u80c0\u80c1\u80c2\u80c3\u80c4\u80c5\u80c6\u80c7\u80c8\u80c9\u80ca\u80cb\u80cc\u80cd\u80ce\u80cf\u80d0\u80d1\u80d2\u80d3\u80d4\u80d5\u80d6\u80d7\u80d8\u80d9\u80da\u80db\u80dc\u80dd\u80de\u80df\u80e0\u80e1\u80e2\u80e3\u80e4\u80e5\u80e6\u80e7\u80e8\u80e9\u80ea\u80eb\u80ec\u80ed\u80ee\u80ef\u80f0\u80f1\u80f2\u80f3\u80f4\u80f5\u80f6\u80f7\u80f8\u80f9\u80fa\u80fb\u80fc\u80fd\u80fe\u80ff\u8100\u8101\u8102\u8103\u8104\u8105\u8106\u8107\u8108\u8109\u810a\u810b\u810c\u810d\u810e\u810f\u8110\u8111\u8112\u8113\u8114\u8115\u8116\u8117\u8118\u8119\u811a\u811b\u811c\u811d\u811e\u811f\u8120\u8121\u8122\u8123\u8124\u8125\u8126\u8127\u8128\u8129\u812a\u812b\u812c\u812d\u812e\u812f\u8130\u8131\u8132\u8133\u8134\u8135\u8136\u8137\u8138\u8139\u813a\u813b\u813c\u813d\u813e\u813f\u8140\u8141\u8142\u8143\u8144\u8145\u8146\u8147\u8148\u8149\u814a\u814b\u814c\u814d\u814e\u814f\u8150\u8151\u8152\u8153\u8154\u8155\u8156\u8157\u8158\u8159\u815a\u815b\u815c\u815d\u815e\u815f\u8160\u8161\u8162\u8163\u8164\u8165\u8166\u8167\u8168\u8169\u816a\u816b\u816c\u816d\u816e\u816f\u8170\u8171\u8172\u8173\u8174\u8175\u8176\u8177\u8178\u8179\u817a\u817b\u817c\u817d\u817e\u817f\u8180\u8181\u8182\u8183\u8184\u8185\u8186\u8187\u8188\u8189\u818a\u818b\u818c\u818d\u818e\u818f\u8190\u8191\u8192\u8193\u8194\u8195\u8196\u8197\u8198\u8199\u819a\u819b\u819c\u819d\u819e\u819f\u81a0\u81a1\u81a2\u81a3\u81a4\u81a5\u81a6\u81a7\u81a8\u81a9\u81aa\u81ab\u81ac\u81ad\u81ae\u81af\u81b0\u81b1\u81b2\u81b3\u81b4\u81b5\u81b6\u81b7\u81b8\u81b9\u81ba\u81bb\u81bc\u81bd\u81be\u81bf\u81c0\u81c1\u81c2\u81c3\u81c4\u81c5\u81c6\u81c7\u81c8\u81c9\u81ca\u81cb\u81cc\u81cd\u81ce\u81cf\u81d0\u81d1\u81d2\u81d3\u81d4\u81d5\u81d6\u81d7\u81d8\u81d9\u81da\u81db\u81dc\u81dd\u81de\u81df\u81e0\u81e1\u81e2\u81e3\u81e4\u81e5\u81e6\u81e7\u81e8\u81e9\u81ea\u81eb\u81ec\u81ed\u81ee\u81ef\u81f0\u81f1\u81f2\u81f3\u81f4\u81f5\u81f6\u81f7\u81f8\u81f9\u81fa\u81fb\u81fc\u81fd\u81fe\u81ff\u8200\u8201\u8202\u8203\u8204\u8205\u8206\u8207\u8208\u8209\u820a\u820b\u820c\u820d\u820e\u820f\u8210\u8211\u8212\u8213\u8214\u8215\u8216\u8217\u8218\u8219\u821a\u821b\u821c\u821d\u821e\u821f\u8220\u8221\u8222\u8223\u8224\u8225\u8226\u8227\u8228\u8229\u822a\u822b\u822c\u822d\u822e\u822f\u8230\u8231\u8232\u8233\u8234\u8235\u8236\u8237\u8238\u8239\u823a\u823b\u823c\u823d\u823e\u823f\u8240\u8241\u8242\u8243\u8244\u8245\u8246\u8247\u8248\u8249\u824a\u824b\u824c\u824d\u824e\u824f\u8250\u8251\u8252\u8253\u8254\u8255\u8256\u8257\u8258\u8259\u825a\u825b\u825c\u825d\u825e\u825f\u8260\u8261\u8262\u8263\u8264\u8265\u8266\u8267\u8268\u8269\u826a\u826b\u826c\u826d\u826e\u826f\u8270\u8271\u8272\u8273\u8274\u8275\u8276\u8277\u8278\u8279\u827a\u827b\u827c\u827d\u827e\u827f\u8280\u8281\u8282\u8283\u8284\u8285\u8286\u8287\u8288\u8289\u828a\u828b\u828c\u828d\u828e\u828f\u8290\u8291\u8292\u8293\u8294\u8295\u8296\u8297\u8298\u8299\u829a\u829b\u829c\u829d\u829e\u829f\u82a0\u82a1\u82a2\u82a3\u82a4\u82a5\u82a6\u82a7\u82a8\u82a9\u82aa\u82ab\u82ac\u82ad\u82ae\u82af\u82b0\u82b1\u82b2\u82b3\u82b4\u82b5\u82b6\u82b7\u82b8\u82b9\u82ba\u82bb\u82bc\u82bd\u82be\u82bf\u82c0\u82c1\u82c2\u82c3\u82c4\u82c5\u82c6\u82c7\u82c8\u82c9\u82ca\u82cb\u82cc\u82cd\u82ce\u82cf\u82d0\u82d1\u82d2\u82d3\u82d4\u82d5\u82d6\u82d7\u82d8\u82d9\u82da\u82db\u82dc\u82dd\u82de\u82df\u82e0\u82e1\u82e2\u82e3\u82e4\u82e5\u82e6\u82e7\u82e8\u82e9\u82ea\u82eb\u82ec\u82ed\u82ee\u82ef\u82f0\u82f1\u82f2\u82f3\u82f4\u82f5\u82f6\u82f7\u82f8\u82f9\u82fa\u82fb\u82fc\u82fd\u82fe\u82ff\u8300\u8301\u8302\u8303\u8304\u8305\u8306\u8307\u8308\u8309\u830a\u830b\u830c\u830d\u830e\u830f\u8310\u8311\u8312\u8313\u8314\u8315\u8316\u8317\u8318\u8319\u831a\u831b\u831c\u831d\u831e\u831f\u8320\u8321\u8322\u8323\u8324\u8325\u8326\u8327\u8328\u8329\u832a\u832b\u832c\u832d\u832e\u832f\u8330\u8331\u8332\u8333\u8334\u8335\u8336\u8337\u8338\u8339\u833a\u833b\u833c\u833d\u833e\u833f\u8340\u8341\u8342\u8343\u8344\u8345\u8346\u8347\u8348\u8349\u834a\u834b\u834c\u834d\u834e\u834f\u8350\u8351\u8352\u8353\u8354\u8355\u8356\u8357\u8358\u8359\u835a\u835b\u835c\u835d\u835e\u835f\u8360\u8361\u8362\u8363\u8364\u8365\u8366\u8367\u8368\u8369\u836a\u836b\u836c\u836d\u836e\u836f\u8370\u8371\u8372\u8373\u8374\u8375\u8376\u8377\u8378\u8379\u837a\u837b\u837c\u837d\u837e\u837f\u8380\u8381\u8382\u8383\u8384\u8385\u8386\u8387\u8388\u8389\u838a\u838b\u838c\u838d\u838e\u838f\u8390\u8391\u8392\u8393\u8394\u8395\u8396\u8397\u8398\u8399\u839a\u839b\u839c\u839d\u839e\u839f\u83a0\u83a1\u83a2\u83a3\u83a4\u83a5\u83a6\u83a7\u83a8\u83a9\u83aa\u83ab\u83ac\u83ad\u83ae\u83af\u83b0\u83b1\u83b2\u83b3\u83b4\u83b5\u83b6\u83b7\u83b8\u83b9\u83ba\u83bb\u83bc\u83bd\u83be\u83bf\u83c0\u83c1\u83c2\u83c3\u83c4\u83c5\u83c6\u83c7\u83c8\u83c9\u83ca\u83cb\u83cc\u83cd\u83ce\u83cf\u83d0\u83d1\u83d2\u83d3\u83d4\u83d5\u83d6\u83d7\u83d8\u83d9\u83da\u83db\u83dc\u83dd\u83de\u83df\u83e0\u83e1\u83e2\u83e3\u83e4\u83e5\u83e6\u83e7\u83e8\u83e9\u83ea\u83eb\u83ec\u83ed\u83ee\u83ef\u83f0\u83f1\u83f2\u83f3\u83f4\u83f5\u83f6\u83f7\u83f8\u83f9\u83fa\u83fb\u83fc\u83fd\u83fe\u83ff\u8400\u8401\u8402\u8403\u8404\u8405\u8406\u8407\u8408\u8409\u840a\u840b\u840c\u840d\u840e\u840f\u8410\u8411\u8412\u8413\u8414\u8415\u8416\u8417\u8418\u8419\u841a\u841b\u841c\u841d\u841e\u841f\u8420\u8421\u8422\u8423\u8424\u8425\u8426\u8427\u8428\u8429\u842a\u842b\u842c\u842d\u842e\u842f\u8430\u8431\u8432\u8433\u8434\u8435\u8436\u8437\u8438\u8439\u843a\u843b\u843c\u843d\u843e\u843f\u8440\u8441\u8442\u8443\u8444\u8445\u8446\u8447\u8448\u8449\u844a\u844b\u844c\u844d\u844e\u844f\u8450\u8451\u8452\u8453\u8454\u8455\u8456\u8457\u8458\u8459\u845a\u845b\u845c\u845d\u845e\u845f\u8460\u8461\u8462\u8463\u8464\u8465\u8466\u8467\u8468\u8469\u846a\u846b\u846c\u846d\u846e\u846f\u8470\u8471\u8472\u8473\u8474\u8475\u8476\u8477\u8478\u8479\u847a\u847b\u847c\u847d\u847e\u847f\u8480\u8481\u8482\u8483\u8484\u8485\u8486\u8487\u8488\u8489\u848a\u848b\u848c\u848d\u848e\u848f\u8490\u8491\u8492\u8493\u8494\u8495\u8496\u8497\u8498\u8499\u849a\u849b\u849c\u849d\u849e\u849f\u84a0\u84a1\u84a2\u84a3\u84a4\u84a5\u84a6\u84a7\u84a8\u84a9\u84aa\u84ab\u84ac\u84ad\u84ae\u84af\u84b0\u84b1\u84b2\u84b3\u84b4\u84b5\u84b6\u84b7\u84b8\u84b9\u84ba\u84bb\u84bc\u84bd\u84be\u84bf\u84c0\u84c1\u84c2\u84c3\u84c4\u84c5\u84c6\u84c7\u84c8\u84c9\u84ca\u84cb\u84cc\u84cd\u84ce\u84cf\u84d0\u84d1\u84d2\u84d3\u84d4\u84d5\u84d6\u84d7\u84d8\u84d9\u84da\u84db\u84dc\u84dd\u84de\u84df\u84e0\u84e1\u84e2\u84e3\u84e4\u84e5\u84e6\u84e7\u84e8\u84e9\u84ea\u84eb\u84ec\u84ed\u84ee\u84ef\u84f0\u84f1\u84f2\u84f3\u84f4\u84f5\u84f6\u84f7\u84f8\u84f9\u84fa\u84fb\u84fc\u84fd\u84fe\u84ff\u8500\u8501\u8502\u8503\u8504\u8505\u8506\u8507\u8508\u8509\u850a\u850b\u850c\u850d\u850e\u850f\u8510\u8511\u8512\u8513\u8514\u8515\u8516\u8517\u8518\u8519\u851a\u851b\u851c\u851d\u851e\u851f\u8520\u8521\u8522\u8523\u8524\u8525\u8526\u8527\u8528\u8529\u852a\u852b\u852c\u852d\u852e\u852f\u8530\u8531\u8532\u8533\u8534\u8535\u8536\u8537\u8538\u8539\u853a\u853b\u853c\u853d\u853e\u853f\u8540\u8541\u8542\u8543\u8544\u8545\u8546\u8547\u8548\u8549\u854a\u854b\u854c\u854d\u854e\u854f\u8550\u8551\u8552\u8553\u8554\u8555\u8556\u8557\u8558\u8559\u855a\u855b\u855c\u855d\u855e\u855f\u8560\u8561\u8562\u8563\u8564\u8565\u8566\u8567\u8568\u8569\u856a\u856b\u856c\u856d\u856e\u856f\u8570\u8571\u8572\u8573\u8574\u8575\u8576\u8577\u8578\u8579\u857a\u857b\u857c\u857d\u857e\u857f\u8580\u8581\u8582\u8583\u8584\u8585\u8586\u8587\u8588\u8589\u858a\u858b\u858c\u858d\u858e\u858f\u8590\u8591\u8592\u8593\u8594\u8595\u8596\u8597\u8598\u8599\u859a\u859b\u859c\u859d\u859e\u859f\u85a0\u85a1\u85a2\u85a3\u85a4\u85a5\u85a6\u85a7\u85a8\u85a9\u85aa\u85ab\u85ac\u85ad\u85ae\u85af\u85b0\u85b1\u85b2\u85b3\u85b4\u85b5\u85b6\u85b7\u85b8\u85b9\u85ba\u85bb\u85bc\u85bd\u85be\u85bf\u85c0\u85c1\u85c2\u85c3\u85c4\u85c5\u85c6\u85c7\u85c8\u85c9\u85ca\u85cb\u85cc\u85cd\u85ce\u85cf\u85d0\u85d1\u85d2\u85d3\u85d4\u85d5\u85d6\u85d7\u85d8\u85d9\u85da\u85db\u85dc\u85dd\u85de\u85df\u85e0\u85e1\u85e2\u85e3\u85e4\u85e5\u85e6\u85e7\u85e8\u85e9\u85ea\u85eb\u85ec\u85ed\u85ee\u85ef\u85f0\u85f1\u85f2\u85f3\u85f4\u85f5\u85f6\u85f7\u85f8\u85f9\u85fa\u85fb\u85fc\u85fd\u85fe\u85ff\u8600\u8601\u8602\u8603\u8604\u8605\u8606\u8607\u8608\u8609\u860a\u860b\u860c\u860d\u860e\u860f\u8610\u8611\u8612\u8613\u8614\u8615\u8616\u8617\u8618\u8619\u861a\u861b\u861c\u861d\u861e\u861f\u8620\u8621\u8622\u8623\u8624\u8625\u8626\u8627\u8628\u8629\u862a\u862b\u862c\u862d\u862e\u862f\u8630\u8631\u8632\u8633\u8634\u8635\u8636\u8637\u8638\u8639\u863a\u863b\u863c\u863d\u863e\u863f\u8640\u8641\u8642\u8643\u8644\u8645\u8646\u8647\u8648\u8649\u864a\u864b\u864c\u864d\u864e\u864f\u8650\u8651\u8652\u8653\u8654\u8655\u8656\u8657\u8658\u8659\u865a\u865b\u865c\u865d\u865e\u865f\u8660\u8661\u8662\u8663\u8664\u8665\u8666\u8667\u8668\u8669\u866a\u866b\u866c\u866d\u866e\u866f\u8670\u8671\u8672\u8673\u8674\u8675\u8676\u8677\u8678\u8679\u867a\u867b\u867c\u867d\u867e\u867f\u8680\u8681\u8682\u8683\u8684\u8685\u8686\u8687\u8688\u8689\u868a\u868b\u868c\u868d\u868e\u868f\u8690\u8691\u8692\u8693\u8694\u8695\u8696\u8697\u8698\u8699\u869a\u869b\u869c\u869d\u869e\u869f\u86a0\u86a1\u86a2\u86a3\u86a4\u86a5\u86a6\u86a7\u86a8\u86a9\u86aa\u86ab\u86ac\u86ad\u86ae\u86af\u86b0\u86b1\u86b2\u86b3\u86b4\u86b5\u86b6\u86b7\u86b8\u86b9\u86ba\u86bb\u86bc\u86bd\u86be\u86bf\u86c0\u86c1\u86c2\u86c3\u86c4\u86c5\u86c6\u86c7\u86c8\u86c9\u86ca\u86cb\u86cc\u86cd\u86ce\u86cf\u86d0\u86d1\u86d2\u86d3\u86d4\u86d5\u86d6\u86d7\u86d8\u86d9\u86da\u86db\u86dc\u86dd\u86de\u86df\u86e0\u86e1\u86e2\u86e3\u86e4\u86e5\u86e6\u86e7\u86e8\u86e9\u86ea\u86eb\u86ec\u86ed\u86ee\u86ef\u86f0\u86f1\u86f2\u86f3\u86f4\u86f5\u86f6\u86f7\u86f8\u86f9\u86fa\u86fb\u86fc\u86fd\u86fe\u86ff\u8700\u8701\u8702\u8703\u8704\u8705\u8706\u8707\u8708\u8709\u870a\u870b\u870c\u870d\u870e\u870f\u8710\u8711\u8712\u8713\u8714\u8715\u8716\u8717\u8718\u8719\u871a\u871b\u871c\u871d\u871e\u871f\u8720\u8721\u8722\u8723\u8724\u8725\u8726\u8727\u8728\u8729\u872a\u872b\u872c\u872d\u872e\u872f\u8730\u8731\u8732\u8733\u8734\u8735\u8736\u8737\u8738\u8739\u873a\u873b\u873c\u873d\u873e\u873f\u8740\u8741\u8742\u8743\u8744\u8745\u8746\u8747\u8748\u8749\u874a\u874b\u874c\u874d\u874e\u874f\u8750\u8751\u8752\u8753\u8754\u8755\u8756\u8757\u8758\u8759\u875a\u875b\u875c\u875d\u875e\u875f\u8760\u8761\u8762\u8763\u8764\u8765\u8766\u8767\u8768\u8769\u876a\u876b\u876c\u876d\u876e\u876f\u8770\u8771\u8772\u8773\u8774\u8775\u8776\u8777\u8778\u8779\u877a\u877b\u877c\u877d\u877e\u877f\u8780\u8781\u8782\u8783\u8784\u8785\u8786\u8787\u8788\u8789\u878a\u878b\u878c\u878d\u878e\u878f\u8790\u8791\u8792\u8793\u8794\u8795\u8796\u8797\u8798\u8799\u879a\u879b\u879c\u879d\u879e\u879f\u87a0\u87a1\u87a2\u87a3\u87a4\u87a5\u87a6\u87a7\u87a8\u87a9\u87aa\u87ab\u87ac\u87ad\u87ae\u87af\u87b0\u87b1\u87b2\u87b3\u87b4\u87b5\u87b6\u87b7\u87b8\u87b9\u87ba\u87bb\u87bc\u87bd\u87be\u87bf\u87c0\u87c1\u87c2\u87c3\u87c4\u87c5\u87c6\u87c7\u87c8\u87c9\u87ca\u87cb\u87cc\u87cd\u87ce\u87cf\u87d0\u87d1\u87d2\u87d3\u87d4\u87d5\u87d6\u87d7\u87d8\u87d9\u87da\u87db\u87dc\u87dd\u87de\u87df\u87e0\u87e1\u87e2\u87e3\u87e4\u87e5\u87e6\u87e7\u87e8\u87e9\u87ea\u87eb\u87ec\u87ed\u87ee\u87ef\u87f0\u87f1\u87f2\u87f3\u87f4\u87f5\u87f6\u87f7\u87f8\u87f9\u87fa\u87fb\u87fc\u87fd\u87fe\u87ff\u8800\u8801\u8802\u8803\u8804\u8805\u8806\u8807\u8808\u8809\u880a\u880b\u880c\u880d\u880e\u880f\u8810\u8811\u8812\u8813\u8814\u8815\u8816\u8817\u8818\u8819\u881a\u881b\u881c\u881d\u881e\u881f\u8820\u8821\u8822\u8823\u8824\u8825\u8826\u8827\u8828\u8829\u882a\u882b\u882c\u882d\u882e\u882f\u8830\u8831\u8832\u8833\u8834\u8835\u8836\u8837\u8838\u8839\u883a\u883b\u883c\u883d\u883e\u883f\u8840\u8841\u8842\u8843\u8844\u8845\u8846\u8847\u8848\u8849\u884a\u884b\u884c\u884d\u884e\u884f\u8850\u8851\u8852\u8853\u8854\u8855\u8856\u8857\u8858\u8859\u885a\u885b\u885c\u885d\u885e\u885f\u8860\u8861\u8862\u8863\u8864\u8865\u8866\u8867\u8868\u8869\u886a\u886b\u886c\u886d\u886e\u886f\u8870\u8871\u8872\u8873\u8874\u8875\u8876\u8877\u8878\u8879\u887a\u887b\u887c\u887d\u887e\u887f\u8880\u8881\u8882\u8883\u8884\u8885\u8886\u8887\u8888\u8889\u888a\u888b\u888c\u888d\u888e\u888f\u8890\u8891\u8892\u8893\u8894\u8895\u8896\u8897\u8898\u8899\u889a\u889b\u889c\u889d\u889e\u889f\u88a0\u88a1\u88a2\u88a3\u88a4\u88a5\u88a6\u88a7\u88a8\u88a9\u88aa\u88ab\u88ac\u88ad\u88ae\u88af\u88b0\u88b1\u88b2\u88b3\u88b4\u88b5\u88b6\u88b7\u88b8\u88b9\u88ba\u88bb\u88bc\u88bd\u88be\u88bf\u88c0\u88c1\u88c2\u88c3\u88c4\u88c5\u88c6\u88c7\u88c8\u88c9\u88ca\u88cb\u88cc\u88cd\u88ce\u88cf\u88d0\u88d1\u88d2\u88d3\u88d4\u88d5\u88d6\u88d7\u88d8\u88d9\u88da\u88db\u88dc\u88dd\u88de\u88df\u88e0\u88e1\u88e2\u88e3\u88e4\u88e5\u88e6\u88e7\u88e8\u88e9\u88ea\u88eb\u88ec\u88ed\u88ee\u88ef\u88f0\u88f1\u88f2\u88f3\u88f4\u88f5\u88f6\u88f7\u88f8\u88f9\u88fa\u88fb\u88fc\u88fd\u88fe\u88ff\u8900\u8901\u8902\u8903\u8904\u8905\u8906\u8907\u8908\u8909\u890a\u890b\u890c\u890d\u890e\u890f\u8910\u8911\u8912\u8913\u8914\u8915\u8916\u8917\u8918\u8919\u891a\u891b\u891c\u891d\u891e\u891f\u8920\u8921\u8922\u8923\u8924\u8925\u8926\u8927\u8928\u8929\u892a\u892b\u892c\u892d\u892e\u892f\u8930\u8931\u8932\u8933\u8934\u8935\u8936\u8937\u8938\u8939\u893a\u893b\u893c\u893d\u893e\u893f\u8940\u8941\u8942\u8943\u8944\u8945\u8946\u8947\u8948\u8949\u894a\u894b\u894c\u894d\u894e\u894f\u8950\u8951\u8952\u8953\u8954\u8955\u8956\u8957\u8958\u8959\u895a\u895b\u895c\u895d\u895e\u895f\u8960\u8961\u8962\u8963\u8964\u8965\u8966\u8967\u8968\u8969\u896a\u896b\u896c\u896d\u896e\u896f\u8970\u8971\u8972\u8973\u8974\u8975\u8976\u8977\u8978\u8979\u897a\u897b\u897c\u897d\u897e\u897f\u8980\u8981\u8982\u8983\u8984\u8985\u8986\u8987\u8988\u8989\u898a\u898b\u898c\u898d\u898e\u898f\u8990\u8991\u8992\u8993\u8994\u8995\u8996\u8997\u8998\u8999\u899a\u899b\u899c\u899d\u899e\u899f\u89a0\u89a1\u89a2\u89a3\u89a4\u89a5\u89a6\u89a7\u89a8\u89a9\u89aa\u89ab\u89ac\u89ad\u89ae\u89af\u89b0\u89b1\u89b2\u89b3\u89b4\u89b5\u89b6\u89b7\u89b8\u89b9\u89ba\u89bb\u89bc\u89bd\u89be\u89bf\u89c0\u89c1\u89c2\u89c3\u89c4\u89c5\u89c6\u89c7\u89c8\u89c9\u89ca\u89cb\u89cc\u89cd\u89ce\u89cf\u89d0\u89d1\u89d2\u89d3\u89d4\u89d5\u89d6\u89d7\u89d8\u89d9\u89da\u89db\u89dc\u89dd\u89de\u89df\u89e0\u89e1\u89e2\u89e3\u89e4\u89e5\u89e6\u89e7\u89e8\u89e9\u89ea\u89eb\u89ec\u89ed\u89ee\u89ef\u89f0\u89f1\u89f2\u89f3\u89f4\u89f5\u89f6\u89f7\u89f8\u89f9\u89fa\u89fb\u89fc\u89fd\u89fe\u89ff\u8a00\u8a01\u8a02\u8a03\u8a04\u8a05\u8a06\u8a07\u8a08\u8a09\u8a0a\u8a0b\u8a0c\u8a0d\u8a0e\u8a0f\u8a10\u8a11\u8a12\u8a13\u8a14\u8a15\u8a16\u8a17\u8a18\u8a19\u8a1a\u8a1b\u8a1c\u8a1d\u8a1e\u8a1f\u8a20\u8a21\u8a22\u8a23\u8a24\u8a25\u8a26\u8a27\u8a28\u8a29\u8a2a\u8a2b\u8a2c\u8a2d\u8a2e\u8a2f\u8a30\u8a31\u8a32\u8a33\u8a34\u8a35\u8a36\u8a37\u8a38\u8a39\u8a3a\u8a3b\u8a3c\u8a3d\u8a3e\u8a3f\u8a40\u8a41\u8a42\u8a43\u8a44\u8a45\u8a46\u8a47\u8a48\u8a49\u8a4a\u8a4b\u8a4c\u8a4d\u8a4e\u8a4f\u8a50\u8a51\u8a52\u8a53\u8a54\u8a55\u8a56\u8a57\u8a58\u8a59\u8a5a\u8a5b\u8a5c\u8a5d\u8a5e\u8a5f\u8a60\u8a61\u8a62\u8a63\u8a64\u8a65\u8a66\u8a67\u8a68\u8a69\u8a6a\u8a6b\u8a6c\u8a6d\u8a6e\u8a6f\u8a70\u8a71\u8a72\u8a73\u8a74\u8a75\u8a76\u8a77\u8a78\u8a79\u8a7a\u8a7b\u8a7c\u8a7d\u8a7e\u8a7f\u8a80\u8a81\u8a82\u8a83\u8a84\u8a85\u8a86\u8a87\u8a88\u8a89\u8a8a\u8a8b\u8a8c\u8a8d\u8a8e\u8a8f\u8a90\u8a91\u8a92\u8a93\u8a94\u8a95\u8a96\u8a97\u8a98\u8a99\u8a9a\u8a9b\u8a9c\u8a9d\u8a9e\u8a9f\u8aa0\u8aa1\u8aa2\u8aa3\u8aa4\u8aa5\u8aa6\u8aa7\u8aa8\u8aa9\u8aaa\u8aab\u8aac\u8aad\u8aae\u8aaf\u8ab0\u8ab1\u8ab2\u8ab3\u8ab4\u8ab5\u8ab6\u8ab7\u8ab8\u8ab9\u8aba\u8abb\u8abc\u8abd\u8abe\u8abf\u8ac0\u8ac1\u8ac2\u8ac3\u8ac4\u8ac5\u8ac6\u8ac7\u8ac8\u8ac9\u8aca\u8acb\u8acc\u8acd\u8ace\u8acf\u8ad0\u8ad1\u8ad2\u8ad3\u8ad4\u8ad5\u8ad6\u8ad7\u8ad8\u8ad9\u8ada\u8adb\u8adc\u8add\u8ade\u8adf\u8ae0\u8ae1\u8ae2\u8ae3\u8ae4\u8ae5\u8ae6\u8ae7\u8ae8\u8ae9\u8aea\u8aeb\u8aec\u8aed\u8aee\u8aef\u8af0\u8af1\u8af2\u8af3\u8af4\u8af5\u8af6\u8af7\u8af8\u8af9\u8afa\u8afb\u8afc\u8afd\u8afe\u8aff\u8b00\u8b01\u8b02\u8b03\u8b04\u8b05\u8b06\u8b07\u8b08\u8b09\u8b0a\u8b0b\u8b0c\u8b0d\u8b0e\u8b0f\u8b10\u8b11\u8b12\u8b13\u8b14\u8b15\u8b16\u8b17\u8b18\u8b19\u8b1a\u8b1b\u8b1c\u8b1d\u8b1e\u8b1f\u8b20\u8b21\u8b22\u8b23\u8b24\u8b25\u8b26\u8b27\u8b28\u8b29\u8b2a\u8b2b\u8b2c\u8b2d\u8b2e\u8b2f\u8b30\u8b31\u8b32\u8b33\u8b34\u8b35\u8b36\u8b37\u8b38\u8b39\u8b3a\u8b3b\u8b3c\u8b3d\u8b3e\u8b3f\u8b40\u8b41\u8b42\u8b43\u8b44\u8b45\u8b46\u8b47\u8b48\u8b49\u8b4a\u8b4b\u8b4c\u8b4d\u8b4e\u8b4f\u8b50\u8b51\u8b52\u8b53\u8b54\u8b55\u8b56\u8b57\u8b58\u8b59\u8b5a\u8b5b\u8b5c\u8b5d\u8b5e\u8b5f\u8b60\u8b61\u8b62\u8b63\u8b64\u8b65\u8b66\u8b67\u8b68\u8b69\u8b6a\u8b6b\u8b6c\u8b6d\u8b6e\u8b6f\u8b70\u8b71\u8b72\u8b73\u8b74\u8b75\u8b76\u8b77\u8b78\u8b79\u8b7a\u8b7b\u8b7c\u8b7d\u8b7e\u8b7f\u8b80\u8b81\u8b82\u8b83\u8b84\u8b85\u8b86\u8b87\u8b88\u8b89\u8b8a\u8b8b\u8b8c\u8b8d\u8b8e\u8b8f\u8b90\u8b91\u8b92\u8b93\u8b94\u8b95\u8b96\u8b97\u8b98\u8b99\u8b9a\u8b9b\u8b9c\u8b9d\u8b9e\u8b9f\u8ba0\u8ba1\u8ba2\u8ba3\u8ba4\u8ba5\u8ba6\u8ba7\u8ba8\u8ba9\u8baa\u8bab\u8bac\u8bad\u8bae\u8baf\u8bb0\u8bb1\u8bb2\u8bb3\u8bb4\u8bb5\u8bb6\u8bb7\u8bb8\u8bb9\u8bba\u8bbb\u8bbc\u8bbd\u8bbe\u8bbf\u8bc0\u8bc1\u8bc2\u8bc3\u8bc4\u8bc5\u8bc6\u8bc7\u8bc8\u8bc9\u8bca\u8bcb\u8bcc\u8bcd\u8bce\u8bcf\u8bd0\u8bd1\u8bd2\u8bd3\u8bd4\u8bd5\u8bd6\u8bd7\u8bd8\u8bd9\u8bda\u8bdb\u8bdc\u8bdd\u8bde\u8bdf\u8be0\u8be1\u8be2\u8be3\u8be4\u8be5\u8be6\u8be7\u8be8\u8be9\u8bea\u8beb\u8bec\u8bed\u8bee\u8bef\u8bf0\u8bf1\u8bf2\u8bf3\u8bf4\u8bf5\u8bf6\u8bf7\u8bf8\u8bf9\u8bfa\u8bfb\u8bfc\u8bfd\u8bfe\u8bff\u8c00\u8c01\u8c02\u8c03\u8c04\u8c05\u8c06\u8c07\u8c08\u8c09\u8c0a\u8c0b\u8c0c\u8c0d\u8c0e\u8c0f\u8c10\u8c11\u8c12\u8c13\u8c14\u8c15\u8c16\u8c17\u8c18\u8c19\u8c1a\u8c1b\u8c1c\u8c1d\u8c1e\u8c1f\u8c20\u8c21\u8c22\u8c23\u8c24\u8c25\u8c26\u8c27\u8c28\u8c29\u8c2a\u8c2b\u8c2c\u8c2d\u8c2e\u8c2f\u8c30\u8c31\u8c32\u8c33\u8c34\u8c35\u8c36\u8c37\u8c38\u8c39\u8c3a\u8c3b\u8c3c\u8c3d\u8c3e\u8c3f\u8c40\u8c41\u8c42\u8c43\u8c44\u8c45\u8c46\u8c47\u8c48\u8c49\u8c4a\u8c4b\u8c4c\u8c4d\u8c4e\u8c4f\u8c50\u8c51\u8c52\u8c53\u8c54\u8c55\u8c56\u8c57\u8c58\u8c59\u8c5a\u8c5b\u8c5c\u8c5d\u8c5e\u8c5f\u8c60\u8c61\u8c62\u8c63\u8c64\u8c65\u8c66\u8c67\u8c68\u8c69\u8c6a\u8c6b\u8c6c\u8c6d\u8c6e\u8c6f\u8c70\u8c71\u8c72\u8c73\u8c74\u8c75\u8c76\u8c77\u8c78\u8c79\u8c7a\u8c7b\u8c7c\u8c7d\u8c7e\u8c7f\u8c80\u8c81\u8c82\u8c83\u8c84\u8c85\u8c86\u8c87\u8c88\u8c89\u8c8a\u8c8b\u8c8c\u8c8d\u8c8e\u8c8f\u8c90\u8c91\u8c92\u8c93\u8c94\u8c95\u8c96\u8c97\u8c98\u8c99\u8c9a\u8c9b\u8c9c\u8c9d\u8c9e\u8c9f\u8ca0\u8ca1\u8ca2\u8ca3\u8ca4\u8ca5\u8ca6\u8ca7\u8ca8\u8ca9\u8caa\u8cab\u8cac\u8cad\u8cae\u8caf\u8cb0\u8cb1\u8cb2\u8cb3\u8cb4\u8cb5\u8cb6\u8cb7\u8cb8\u8cb9\u8cba\u8cbb\u8cbc\u8cbd\u8cbe\u8cbf\u8cc0\u8cc1\u8cc2\u8cc3\u8cc4\u8cc5\u8cc6\u8cc7\u8cc8\u8cc9\u8cca\u8ccb\u8ccc\u8ccd\u8cce\u8ccf\u8cd0\u8cd1\u8cd2\u8cd3\u8cd4\u8cd5\u8cd6\u8cd7\u8cd8\u8cd9\u8cda\u8cdb\u8cdc\u8cdd\u8cde\u8cdf\u8ce0\u8ce1\u8ce2\u8ce3\u8ce4\u8ce5\u8ce6\u8ce7\u8ce8\u8ce9\u8cea\u8ceb\u8cec\u8ced\u8cee\u8cef\u8cf0\u8cf1\u8cf2\u8cf3\u8cf4\u8cf5\u8cf6\u8cf7\u8cf8\u8cf9\u8cfa\u8cfb\u8cfc\u8cfd\u8cfe\u8cff\u8d00\u8d01\u8d02\u8d03\u8d04\u8d05\u8d06\u8d07\u8d08\u8d09\u8d0a\u8d0b\u8d0c\u8d0d\u8d0e\u8d0f\u8d10\u8d11\u8d12\u8d13\u8d14\u8d15\u8d16\u8d17\u8d18\u8d19\u8d1a\u8d1b\u8d1c\u8d1d\u8d1e\u8d1f\u8d20\u8d21\u8d22\u8d23\u8d24\u8d25\u8d26\u8d27\u8d28\u8d29\u8d2a\u8d2b\u8d2c\u8d2d\u8d2e\u8d2f\u8d30\u8d31\u8d32\u8d33\u8d34\u8d35\u8d36\u8d37\u8d38\u8d39\u8d3a\u8d3b\u8d3c\u8d3d\u8d3e\u8d3f\u8d40\u8d41\u8d42\u8d43\u8d44\u8d45\u8d46\u8d47\u8d48\u8d49\u8d4a\u8d4b\u8d4c\u8d4d\u8d4e\u8d4f\u8d50\u8d51\u8d52\u8d53\u8d54\u8d55\u8d56\u8d57\u8d58\u8d59\u8d5a\u8d5b\u8d5c\u8d5d\u8d5e\u8d5f\u8d60\u8d61\u8d62\u8d63\u8d64\u8d65\u8d66\u8d67\u8d68\u8d69\u8d6a\u8d6b\u8d6c\u8d6d\u8d6e\u8d6f\u8d70\u8d71\u8d72\u8d73\u8d74\u8d75\u8d76\u8d77\u8d78\u8d79\u8d7a\u8d7b\u8d7c\u8d7d\u8d7e\u8d7f\u8d80\u8d81\u8d82\u8d83\u8d84\u8d85\u8d86\u8d87\u8d88\u8d89\u8d8a\u8d8b\u8d8c\u8d8d\u8d8e\u8d8f\u8d90\u8d91\u8d92\u8d93\u8d94\u8d95\u8d96\u8d97\u8d98\u8d99\u8d9a\u8d9b\u8d9c\u8d9d\u8d9e\u8d9f\u8da0\u8da1\u8da2\u8da3\u8da4\u8da5\u8da6\u8da7\u8da8\u8da9\u8daa\u8dab\u8dac\u8dad\u8dae\u8daf\u8db0\u8db1\u8db2\u8db3\u8db4\u8db5\u8db6\u8db7\u8db8\u8db9\u8dba\u8dbb\u8dbc\u8dbd\u8dbe\u8dbf\u8dc0\u8dc1\u8dc2\u8dc3\u8dc4\u8dc5\u8dc6\u8dc7\u8dc8\u8dc9\u8dca\u8dcb\u8dcc\u8dcd\u8dce\u8dcf\u8dd0\u8dd1\u8dd2\u8dd3\u8dd4\u8dd5\u8dd6\u8dd7\u8dd8\u8dd9\u8dda\u8ddb\u8ddc\u8ddd\u8dde\u8ddf\u8de0\u8de1\u8de2\u8de3\u8de4\u8de5\u8de6\u8de7\u8de8\u8de9\u8dea\u8deb\u8dec\u8ded\u8dee\u8def\u8df0\u8df1\u8df2\u8df3\u8df4\u8df5\u8df6\u8df7\u8df8\u8df9\u8dfa\u8dfb\u8dfc\u8dfd\u8dfe\u8dff\u8e00\u8e01\u8e02\u8e03\u8e04\u8e05\u8e06\u8e07\u8e08\u8e09\u8e0a\u8e0b\u8e0c\u8e0d\u8e0e\u8e0f\u8e10\u8e11\u8e12\u8e13\u8e14\u8e15\u8e16\u8e17\u8e18\u8e19\u8e1a\u8e1b\u8e1c\u8e1d\u8e1e\u8e1f\u8e20\u8e21\u8e22\u8e23\u8e24\u8e25\u8e26\u8e27\u8e28\u8e29\u8e2a\u8e2b\u8e2c\u8e2d\u8e2e\u8e2f\u8e30\u8e31\u8e32\u8e33\u8e34\u8e35\u8e36\u8e37\u8e38\u8e39\u8e3a\u8e3b\u8e3c\u8e3d\u8e3e\u8e3f\u8e40\u8e41\u8e42\u8e43\u8e44\u8e45\u8e46\u8e47\u8e48\u8e49\u8e4a\u8e4b\u8e4c\u8e4d\u8e4e\u8e4f\u8e50\u8e51\u8e52\u8e53\u8e54\u8e55\u8e56\u8e57\u8e58\u8e59\u8e5a\u8e5b\u8e5c\u8e5d\u8e5e\u8e5f\u8e60\u8e61\u8e62\u8e63\u8e64\u8e65\u8e66\u8e67\u8e68\u8e69\u8e6a\u8e6b\u8e6c\u8e6d\u8e6e\u8e6f\u8e70\u8e71\u8e72\u8e73\u8e74\u8e75\u8e76\u8e77\u8e78\u8e79\u8e7a\u8e7b\u8e7c\u8e7d\u8e7e\u8e7f\u8e80\u8e81\u8e82\u8e83\u8e84\u8e85\u8e86\u8e87\u8e88\u8e89\u8e8a\u8e8b\u8e8c\u8e8d\u8e8e\u8e8f\u8e90\u8e91\u8e92\u8e93\u8e94\u8e95\u8e96\u8e97\u8e98\u8e99\u8e9a\u8e9b\u8e9c\u8e9d\u8e9e\u8e9f\u8ea0\u8ea1\u8ea2\u8ea3\u8ea4\u8ea5\u8ea6\u8ea7\u8ea8\u8ea9\u8eaa\u8eab\u8eac\u8ead\u8eae\u8eaf\u8eb0\u8eb1\u8eb2\u8eb3\u8eb4\u8eb5\u8eb6\u8eb7\u8eb8\u8eb9\u8eba\u8ebb\u8ebc\u8ebd\u8ebe\u8ebf\u8ec0\u8ec1\u8ec2\u8ec3\u8ec4\u8ec5\u8ec6\u8ec7\u8ec8\u8ec9\u8eca\u8ecb\u8ecc\u8ecd\u8ece\u8ecf\u8ed0\u8ed1\u8ed2\u8ed3\u8ed4\u8ed5\u8ed6\u8ed7\u8ed8\u8ed9\u8eda\u8edb\u8edc\u8edd\u8ede\u8edf\u8ee0\u8ee1\u8ee2\u8ee3\u8ee4\u8ee5\u8ee6\u8ee7\u8ee8\u8ee9\u8eea\u8eeb\u8eec\u8eed\u8eee\u8eef\u8ef0\u8ef1\u8ef2\u8ef3\u8ef4\u8ef5\u8ef6\u8ef7\u8ef8\u8ef9\u8efa\u8efb\u8efc\u8efd\u8efe\u8eff\u8f00\u8f01\u8f02\u8f03\u8f04\u8f05\u8f06\u8f07\u8f08\u8f09\u8f0a\u8f0b\u8f0c\u8f0d\u8f0e\u8f0f\u8f10\u8f11\u8f12\u8f13\u8f14\u8f15\u8f16\u8f17\u8f18\u8f19\u8f1a\u8f1b\u8f1c\u8f1d\u8f1e\u8f1f\u8f20\u8f21\u8f22\u8f23\u8f24\u8f25\u8f26\u8f27\u8f28\u8f29\u8f2a\u8f2b\u8f2c\u8f2d\u8f2e\u8f2f\u8f30\u8f31\u8f32\u8f33\u8f34\u8f35\u8f36\u8f37\u8f38\u8f39\u8f3a\u8f3b\u8f3c\u8f3d\u8f3e\u8f3f\u8f40\u8f41\u8f42\u8f43\u8f44\u8f45\u8f46\u8f47\u8f48\u8f49\u8f4a\u8f4b\u8f4c\u8f4d\u8f4e\u8f4f\u8f50\u8f51\u8f52\u8f53\u8f54\u8f55\u8f56\u8f57\u8f58\u8f59\u8f5a\u8f5b\u8f5c\u8f5d\u8f5e\u8f5f\u8f60\u8f61\u8f62\u8f63\u8f64\u8f65\u8f66\u8f67\u8f68\u8f69\u8f6a\u8f6b\u8f6c\u8f6d\u8f6e\u8f6f\u8f70\u8f71\u8f72\u8f73\u8f74\u8f75\u8f76\u8f77\u8f78\u8f79\u8f7a\u8f7b\u8f7c\u8f7d\u8f7e\u8f7f\u8f80\u8f81\u8f82\u8f83\u8f84\u8f85\u8f86\u8f87\u8f88\u8f89\u8f8a\u8f8b\u8f8c\u8f8d\u8f8e\u8f8f\u8f90\u8f91\u8f92\u8f93\u8f94\u8f95\u8f96\u8f97\u8f98\u8f99\u8f9a\u8f9b\u8f9c\u8f9d\u8f9e\u8f9f\u8fa0\u8fa1\u8fa2\u8fa3\u8fa4\u8fa5\u8fa6\u8fa7\u8fa8\u8fa9\u8faa\u8fab\u8fac\u8fad\u8fae\u8faf\u8fb0\u8fb1\u8fb2\u8fb3\u8fb4\u8fb5\u8fb6\u8fb7\u8fb8\u8fb9\u8fba\u8fbb\u8fbc\u8fbd\u8fbe\u8fbf\u8fc0\u8fc1\u8fc2\u8fc3\u8fc4\u8fc5\u8fc6\u8fc7\u8fc8\u8fc9\u8fca\u8fcb\u8fcc\u8fcd\u8fce\u8fcf\u8fd0\u8fd1\u8fd2\u8fd3\u8fd4\u8fd5\u8fd6\u8fd7\u8fd8\u8fd9\u8fda\u8fdb\u8fdc\u8fdd\u8fde\u8fdf\u8fe0\u8fe1\u8fe2\u8fe3\u8fe4\u8fe5\u8fe6\u8fe7\u8fe8\u8fe9\u8fea\u8feb\u8fec\u8fed\u8fee\u8fef\u8ff0\u8ff1\u8ff2\u8ff3\u8ff4\u8ff5\u8ff6\u8ff7\u8ff8\u8ff9\u8ffa\u8ffb\u8ffc\u8ffd\u8ffe\u8fff\u9000\u9001\u9002\u9003\u9004\u9005\u9006\u9007\u9008\u9009\u900a\u900b\u900c\u900d\u900e\u900f\u9010\u9011\u9012\u9013\u9014\u9015\u9016\u9017\u9018\u9019\u901a\u901b\u901c\u901d\u901e\u901f\u9020\u9021\u9022\u9023\u9024\u9025\u9026\u9027\u9028\u9029\u902a\u902b\u902c\u902d\u902e\u902f\u9030\u9031\u9032\u9033\u9034\u9035\u9036\u9037\u9038\u9039\u903a\u903b\u903c\u903d\u903e\u903f\u9040\u9041\u9042\u9043\u9044\u9045\u9046\u9047\u9048\u9049\u904a\u904b\u904c\u904d\u904e\u904f\u9050\u9051\u9052\u9053\u9054\u9055\u9056\u9057\u9058\u9059\u905a\u905b\u905c\u905d\u905e\u905f\u9060\u9061\u9062\u9063\u9064\u9065\u9066\u9067\u9068\u9069\u906a\u906b\u906c\u906d\u906e\u906f\u9070\u9071\u9072\u9073\u9074\u9075\u9076\u9077\u9078\u9079\u907a\u907b\u907c\u907d\u907e\u907f\u9080\u9081\u9082\u9083\u9084\u9085\u9086\u9087\u9088\u9089\u908a\u908b\u908c\u908d\u908e\u908f\u9090\u9091\u9092\u9093\u9094\u9095\u9096\u9097\u9098\u9099\u909a\u909b\u909c\u909d\u909e\u909f\u90a0\u90a1\u90a2\u90a3\u90a4\u90a5\u90a6\u90a7\u90a8\u90a9\u90aa\u90ab\u90ac\u90ad\u90ae\u90af\u90b0\u90b1\u90b2\u90b3\u90b4\u90b5\u90b6\u90b7\u90b8\u90b9\u90ba\u90bb\u90bc\u90bd\u90be\u90bf\u90c0\u90c1\u90c2\u90c3\u90c4\u90c5\u90c6\u90c7\u90c8\u90c9\u90ca\u90cb\u90cc\u90cd\u90ce\u90cf\u90d0\u90d1\u90d2\u90d3\u90d4\u90d5\u90d6\u90d7\u90d8\u90d9\u90da\u90db\u90dc\u90dd\u90de\u90df\u90e0\u90e1\u90e2\u90e3\u90e4\u90e5\u90e6\u90e7\u90e8\u90e9\u90ea\u90eb\u90ec\u90ed\u90ee\u90ef\u90f0\u90f1\u90f2\u90f3\u90f4\u90f5\u90f6\u90f7\u90f8\u90f9\u90fa\u90fb\u90fc\u90fd\u90fe\u90ff\u9100\u9101\u9102\u9103\u9104\u9105\u9106\u9107\u9108\u9109\u910a\u910b\u910c\u910d\u910e\u910f\u9110\u9111\u9112\u9113\u9114\u9115\u9116\u9117\u9118\u9119\u911a\u911b\u911c\u911d\u911e\u911f\u9120\u9121\u9122\u9123\u9124\u9125\u9126\u9127\u9128\u9129\u912a\u912b\u912c\u912d\u912e\u912f\u9130\u9131\u9132\u9133\u9134\u9135\u9136\u9137\u9138\u9139\u913a\u913b\u913c\u913d\u913e\u913f\u9140\u9141\u9142\u9143\u9144\u9145\u9146\u9147\u9148\u9149\u914a\u914b\u914c\u914d\u914e\u914f\u9150\u9151\u9152\u9153\u9154\u9155\u9156\u9157\u9158\u9159\u915a\u915b\u915c\u915d\u915e\u915f\u9160\u9161\u9162\u9163\u9164\u9165\u9166\u9167\u9168\u9169\u916a\u916b\u916c\u916d\u916e\u916f\u9170\u9171\u9172\u9173\u9174\u9175\u9176\u9177\u9178\u9179\u917a\u917b\u917c\u917d\u917e\u917f\u9180\u9181\u9182\u9183\u9184\u9185\u9186\u9187\u9188\u9189\u918a\u918b\u918c\u918d\u918e\u918f\u9190\u9191\u9192\u9193\u9194\u9195\u9196\u9197\u9198\u9199\u919a\u919b\u919c\u919d\u919e\u919f\u91a0\u91a1\u91a2\u91a3\u91a4\u91a5\u91a6\u91a7\u91a8\u91a9\u91aa\u91ab\u91ac\u91ad\u91ae\u91af\u91b0\u91b1\u91b2\u91b3\u91b4\u91b5\u91b6\u91b7\u91b8\u91b9\u91ba\u91bb\u91bc\u91bd\u91be\u91bf\u91c0\u91c1\u91c2\u91c3\u91c4\u91c5\u91c6\u91c7\u91c8\u91c9\u91ca\u91cb\u91cc\u91cd\u91ce\u91cf\u91d0\u91d1\u91d2\u91d3\u91d4\u91d5\u91d6\u91d7\u91d8\u91d9\u91da\u91db\u91dc\u91dd\u91de\u91df\u91e0\u91e1\u91e2\u91e3\u91e4\u91e5\u91e6\u91e7\u91e8\u91e9\u91ea\u91eb\u91ec\u91ed\u91ee\u91ef\u91f0\u91f1\u91f2\u91f3\u91f4\u91f5\u91f6\u91f7\u91f8\u91f9\u91fa\u91fb\u91fc\u91fd\u91fe\u91ff\u9200\u9201\u9202\u9203\u9204\u9205\u9206\u9207\u9208\u9209\u920a\u920b\u920c\u920d\u920e\u920f\u9210\u9211\u9212\u9213\u9214\u9215\u9216\u9217\u9218\u9219\u921a\u921b\u921c\u921d\u921e\u921f\u9220\u9221\u9222\u9223\u9224\u9225\u9226\u9227\u9228\u9229\u922a\u922b\u922c\u922d\u922e\u922f\u9230\u9231\u9232\u9233\u9234\u9235\u9236\u9237\u9238\u9239\u923a\u923b\u923c\u923d\u923e\u923f\u9240\u9241\u9242\u9243\u9244\u9245\u9246\u9247\u9248\u9249\u924a\u924b\u924c\u924d\u924e\u924f\u9250\u9251\u9252\u9253\u9254\u9255\u9256\u9257\u9258\u9259\u925a\u925b\u925c\u925d\u925e\u925f\u9260\u9261\u9262\u9263\u9264\u9265\u9266\u9267\u9268\u9269\u926a\u926b\u926c\u926d\u926e\u926f\u9270\u9271\u9272\u9273\u9274\u9275\u9276\u9277\u9278\u9279\u927a\u927b\u927c\u927d\u927e\u927f\u9280\u9281\u9282\u9283\u9284\u9285\u9286\u9287\u9288\u9289\u928a\u928b\u928c\u928d\u928e\u928f\u9290\u9291\u9292\u9293\u9294\u9295\u9296\u9297\u9298\u9299\u929a\u929b\u929c\u929d\u929e\u929f\u92a0\u92a1\u92a2\u92a3\u92a4\u92a5\u92a6\u92a7\u92a8\u92a9\u92aa\u92ab\u92ac\u92ad\u92ae\u92af\u92b0\u92b1\u92b2\u92b3\u92b4\u92b5\u92b6\u92b7\u92b8\u92b9\u92ba\u92bb\u92bc\u92bd\u92be\u92bf\u92c0\u92c1\u92c2\u92c3\u92c4\u92c5\u92c6\u92c7\u92c8\u92c9\u92ca\u92cb\u92cc\u92cd\u92ce\u92cf\u92d0\u92d1\u92d2\u92d3\u92d4\u92d5\u92d6\u92d7\u92d8\u92d9\u92da\u92db\u92dc\u92dd\u92de\u92df\u92e0\u92e1\u92e2\u92e3\u92e4\u92e5\u92e6\u92e7\u92e8\u92e9\u92ea\u92eb\u92ec\u92ed\u92ee\u92ef\u92f0\u92f1\u92f2\u92f3\u92f4\u92f5\u92f6\u92f7\u92f8\u92f9\u92fa\u92fb\u92fc\u92fd\u92fe\u92ff\u9300\u9301\u9302\u9303\u9304\u9305\u9306\u9307\u9308\u9309\u930a\u930b\u930c\u930d\u930e\u930f\u9310\u9311\u9312\u9313\u9314\u9315\u9316\u9317\u9318\u9319\u931a\u931b\u931c\u931d\u931e\u931f\u9320\u9321\u9322\u9323\u9324\u9325\u9326\u9327\u9328\u9329\u932a\u932b\u932c\u932d\u932e\u932f\u9330\u9331\u9332\u9333\u9334\u9335\u9336\u9337\u9338\u9339\u933a\u933b\u933c\u933d\u933e\u933f\u9340\u9341\u9342\u9343\u9344\u9345\u9346\u9347\u9348\u9349\u934a\u934b\u934c\u934d\u934e\u934f\u9350\u9351\u9352\u9353\u9354\u9355\u9356\u9357\u9358\u9359\u935a\u935b\u935c\u935d\u935e\u935f\u9360\u9361\u9362\u9363\u9364\u9365\u9366\u9367\u9368\u9369\u936a\u936b\u936c\u936d\u936e\u936f\u9370\u9371\u9372\u9373\u9374\u9375\u9376\u9377\u9378\u9379\u937a\u937b\u937c\u937d\u937e\u937f\u9380\u9381\u9382\u9383\u9384\u9385\u9386\u9387\u9388\u9389\u938a\u938b\u938c\u938d\u938e\u938f\u9390\u9391\u9392\u9393\u9394\u9395\u9396\u9397\u9398\u9399\u939a\u939b\u939c\u939d\u939e\u939f\u93a0\u93a1\u93a2\u93a3\u93a4\u93a5\u93a6\u93a7\u93a8\u93a9\u93aa\u93ab\u93ac\u93ad\u93ae\u93af\u93b0\u93b1\u93b2\u93b3\u93b4\u93b5\u93b6\u93b7\u93b8\u93b9\u93ba\u93bb\u93bc\u93bd\u93be\u93bf\u93c0\u93c1\u93c2\u93c3\u93c4\u93c5\u93c6\u93c7\u93c8\u93c9\u93ca\u93cb\u93cc\u93cd\u93ce\u93cf\u93d0\u93d1\u93d2\u93d3\u93d4\u93d5\u93d6\u93d7\u93d8\u93d9\u93da\u93db\u93dc\u93dd\u93de\u93df\u93e0\u93e1\u93e2\u93e3\u93e4\u93e5\u93e6\u93e7\u93e8\u93e9\u93ea\u93eb\u93ec\u93ed\u93ee\u93ef\u93f0\u93f1\u93f2\u93f3\u93f4\u93f5\u93f6\u93f7\u93f8\u93f9\u93fa\u93fb\u93fc\u93fd\u93fe\u93ff\u9400\u9401\u9402\u9403\u9404\u9405\u9406\u9407\u9408\u9409\u940a\u940b\u940c\u940d\u940e\u940f\u9410\u9411\u9412\u9413\u9414\u9415\u9416\u9417\u9418\u9419\u941a\u941b\u941c\u941d\u941e\u941f\u9420\u9421\u9422\u9423\u9424\u9425\u9426\u9427\u9428\u9429\u942a\u942b\u942c\u942d\u942e\u942f\u9430\u9431\u9432\u9433\u9434\u9435\u9436\u9437\u9438\u9439\u943a\u943b\u943c\u943d\u943e\u943f\u9440\u9441\u9442\u9443\u9444\u9445\u9446\u9447\u9448\u9449\u944a\u944b\u944c\u944d\u944e\u944f\u9450\u9451\u9452\u9453\u9454\u9455\u9456\u9457\u9458\u9459\u945a\u945b\u945c\u945d\u945e\u945f\u9460\u9461\u9462\u9463\u9464\u9465\u9466\u9467\u9468\u9469\u946a\u946b\u946c\u946d\u946e\u946f\u9470\u9471\u9472\u9473\u9474\u9475\u9476\u9477\u9478\u9479\u947a\u947b\u947c\u947d\u947e\u947f\u9480\u9481\u9482\u9483\u9484\u9485\u9486\u9487\u9488\u9489\u948a\u948b\u948c\u948d\u948e\u948f\u9490\u9491\u9492\u9493\u9494\u9495\u9496\u9497\u9498\u9499\u949a\u949b\u949c\u949d\u949e\u949f\u94a0\u94a1\u94a2\u94a3\u94a4\u94a5\u94a6\u94a7\u94a8\u94a9\u94aa\u94ab\u94ac\u94ad\u94ae\u94af\u94b0\u94b1\u94b2\u94b3\u94b4\u94b5\u94b6\u94b7\u94b8\u94b9\u94ba\u94bb\u94bc\u94bd\u94be\u94bf\u94c0\u94c1\u94c2\u94c3\u94c4\u94c5\u94c6\u94c7\u94c8\u94c9\u94ca\u94cb\u94cc\u94cd\u94ce\u94cf\u94d0\u94d1\u94d2\u94d3\u94d4\u94d5\u94d6\u94d7\u94d8\u94d9\u94da\u94db\u94dc\u94dd\u94de\u94df\u94e0\u94e1\u94e2\u94e3\u94e4\u94e5\u94e6\u94e7\u94e8\u94e9\u94ea\u94eb\u94ec\u94ed\u94ee\u94ef\u94f0\u94f1\u94f2\u94f3\u94f4\u94f5\u94f6\u94f7\u94f8\u94f9\u94fa\u94fb\u94fc\u94fd\u94fe\u94ff\u9500\u9501\u9502\u9503\u9504\u9505\u9506\u9507\u9508\u9509\u950a\u950b\u950c\u950d\u950e\u950f\u9510\u9511\u9512\u9513\u9514\u9515\u9516\u9517\u9518\u9519\u951a\u951b\u951c\u951d\u951e\u951f\u9520\u9521\u9522\u9523\u9524\u9525\u9526\u9527\u9528\u9529\u952a\u952b\u952c\u952d\u952e\u952f\u9530\u9531\u9532\u9533\u9534\u9535\u9536\u9537\u9538\u9539\u953a\u953b\u953c\u953d\u953e\u953f\u9540\u9541\u9542\u9543\u9544\u9545\u9546\u9547\u9548\u9549\u954a\u954b\u954c\u954d\u954e\u954f\u9550\u9551\u9552\u9553\u9554\u9555\u9556\u9557\u9558\u9559\u955a\u955b\u955c\u955d\u955e\u955f\u9560\u9561\u9562\u9563\u9564\u9565\u9566\u9567\u9568\u9569\u956a\u956b\u956c\u956d\u956e\u956f\u9570\u9571\u9572\u9573\u9574\u9575\u9576\u9577\u9578\u9579\u957a\u957b\u957c\u957d\u957e\u957f\u9580\u9581\u9582\u9583\u9584\u9585\u9586\u9587\u9588\u9589\u958a\u958b\u958c\u958d\u958e\u958f\u9590\u9591\u9592\u9593\u9594\u9595\u9596\u9597\u9598\u9599\u959a\u959b\u959c\u959d\u959e\u959f\u95a0\u95a1\u95a2\u95a3\u95a4\u95a5\u95a6\u95a7\u95a8\u95a9\u95aa\u95ab\u95ac\u95ad\u95ae\u95af\u95b0\u95b1\u95b2\u95b3\u95b4\u95b5\u95b6\u95b7\u95b8\u95b9\u95ba\u95bb\u95bc\u95bd\u95be\u95bf\u95c0\u95c1\u95c2\u95c3\u95c4\u95c5\u95c6\u95c7\u95c8\u95c9\u95ca\u95cb\u95cc\u95cd\u95ce\u95cf\u95d0\u95d1\u95d2\u95d3\u95d4\u95d5\u95d6\u95d7\u95d8\u95d9\u95da\u95db\u95dc\u95dd\u95de\u95df\u95e0\u95e1\u95e2\u95e3\u95e4\u95e5\u95e6\u95e7\u95e8\u95e9\u95ea\u95eb\u95ec\u95ed\u95ee\u95ef\u95f0\u95f1\u95f2\u95f3\u95f4\u95f5\u95f6\u95f7\u95f8\u95f9\u95fa\u95fb\u95fc\u95fd\u95fe\u95ff\u9600\u9601\u9602\u9603\u9604\u9605\u9606\u9607\u9608\u9609\u960a\u960b\u960c\u960d\u960e\u960f\u9610\u9611\u9612\u9613\u9614\u9615\u9616\u9617\u9618\u9619\u961a\u961b\u961c\u961d\u961e\u961f\u9620\u9621\u9622\u9623\u9624\u9625\u9626\u9627\u9628\u9629\u962a\u962b\u962c\u962d\u962e\u962f\u9630\u9631\u9632\u9633\u9634\u9635\u9636\u9637\u9638\u9639\u963a\u963b\u963c\u963d\u963e\u963f\u9640\u9641\u9642\u9643\u9644\u9645\u9646\u9647\u9648\u9649\u964a\u964b\u964c\u964d\u964e\u964f\u9650\u9651\u9652\u9653\u9654\u9655\u9656\u9657\u9658\u9659\u965a\u965b\u965c\u965d\u965e\u965f\u9660\u9661\u9662\u9663\u9664\u9665\u9666\u9667\u9668\u9669\u966a\u966b\u966c\u966d\u966e\u966f\u9670\u9671\u9672\u9673\u9674\u9675\u9676\u9677\u9678\u9679\u967a\u967b\u967c\u967d\u967e\u967f\u9680\u9681\u9682\u9683\u9684\u9685\u9686\u9687\u9688\u9689\u968a\u968b\u968c\u968d\u968e\u968f\u9690\u9691\u9692\u9693\u9694\u9695\u9696\u9697\u9698\u9699\u969a\u969b\u969c\u969d\u969e\u969f\u96a0\u96a1\u96a2\u96a3\u96a4\u96a5\u96a6\u96a7\u96a8\u96a9\u96aa\u96ab\u96ac\u96ad\u96ae\u96af\u96b0\u96b1\u96b2\u96b3\u96b4\u96b5\u96b6\u96b7\u96b8\u96b9\u96ba\u96bb\u96bc\u96bd\u96be\u96bf\u96c0\u96c1\u96c2\u96c3\u96c4\u96c5\u96c6\u96c7\u96c8\u96c9\u96ca\u96cb\u96cc\u96cd\u96ce\u96cf\u96d0\u96d1\u96d2\u96d3\u96d4\u96d5\u96d6\u96d7\u96d8\u96d9\u96da\u96db\u96dc\u96dd\u96de\u96df\u96e0\u96e1\u96e2\u96e3\u96e4\u96e5\u96e6\u96e7\u96e8\u96e9\u96ea\u96eb\u96ec\u96ed\u96ee\u96ef\u96f0\u96f1\u96f2\u96f3\u96f4\u96f5\u96f6\u96f7\u96f8\u96f9\u96fa\u96fb\u96fc\u96fd\u96fe\u96ff\u9700\u9701\u9702\u9703\u9704\u9705\u9706\u9707\u9708\u9709\u970a\u970b\u970c\u970d\u970e\u970f\u9710\u9711\u9712\u9713\u9714\u9715\u9716\u9717\u9718\u9719\u971a\u971b\u971c\u971d\u971e\u971f\u9720\u9721\u9722\u9723\u9724\u9725\u9726\u9727\u9728\u9729\u972a\u972b\u972c\u972d\u972e\u972f\u9730\u9731\u9732\u9733\u9734\u9735\u9736\u9737\u9738\u9739\u973a\u973b\u973c\u973d\u973e\u973f\u9740\u9741\u9742\u9743\u9744\u9745\u9746\u9747\u9748\u9749\u974a\u974b\u974c\u974d\u974e\u974f\u9750\u9751\u9752\u9753\u9754\u9755\u9756\u9757\u9758\u9759\u975a\u975b\u975c\u975d\u975e\u975f\u9760\u9761\u9762\u9763\u9764\u9765\u9766\u9767\u9768\u9769\u976a\u976b\u976c\u976d\u976e\u976f\u9770\u9771\u9772\u9773\u9774\u9775\u9776\u9777\u9778\u9779\u977a\u977b\u977c\u977d\u977e\u977f\u9780\u9781\u9782\u9783\u9784\u9785\u9786\u9787\u9788\u9789\u978a\u978b\u978c\u978d\u978e\u978f\u9790\u9791\u9792\u9793\u9794\u9795\u9796\u9797\u9798\u9799\u979a\u979b\u979c\u979d\u979e\u979f\u97a0\u97a1\u97a2\u97a3\u97a4\u97a5\u97a6\u97a7\u97a8\u97a9\u97aa\u97ab\u97ac\u97ad\u97ae\u97af\u97b0\u97b1\u97b2\u97b3\u97b4\u97b5\u97b6\u97b7\u97b8\u97b9\u97ba\u97bb\u97bc\u97bd\u97be\u97bf\u97c0\u97c1\u97c2\u97c3\u97c4\u97c5\u97c6\u97c7\u97c8\u97c9\u97ca\u97cb\u97cc\u97cd\u97ce\u97cf\u97d0\u97d1\u97d2\u97d3\u97d4\u97d5\u97d6\u97d7\u97d8\u97d9\u97da\u97db\u97dc\u97dd\u97de\u97df\u97e0\u97e1\u97e2\u97e3\u97e4\u97e5\u97e6\u97e7\u97e8\u97e9\u97ea\u97eb\u97ec\u97ed\u97ee\u97ef\u97f0\u97f1\u97f2\u97f3\u97f4\u97f5\u97f6\u97f7\u97f8\u97f9\u97fa\u97fb\u97fc\u97fd\u97fe\u97ff\u9800\u9801\u9802\u9803\u9804\u9805\u9806\u9807\u9808\u9809\u980a\u980b\u980c\u980d\u980e\u980f\u9810\u9811\u9812\u9813\u9814\u9815\u9816\u9817\u9818\u9819\u981a\u981b\u981c\u981d\u981e\u981f\u9820\u9821\u9822\u9823\u9824\u9825\u9826\u9827\u9828\u9829\u982a\u982b\u982c\u982d\u982e\u982f\u9830\u9831\u9832\u9833\u9834\u9835\u9836\u9837\u9838\u9839\u983a\u983b\u983c\u983d\u983e\u983f\u9840\u9841\u9842\u9843\u9844\u9845\u9846\u9847\u9848\u9849\u984a\u984b\u984c\u984d\u984e\u984f\u9850\u9851\u9852\u9853\u9854\u9855\u9856\u9857\u9858\u9859\u985a\u985b\u985c\u985d\u985e\u985f\u9860\u9861\u9862\u9863\u9864\u9865\u9866\u9867\u9868\u9869\u986a\u986b\u986c\u986d\u986e\u986f\u9870\u9871\u9872\u9873\u9874\u9875\u9876\u9877\u9878\u9879\u987a\u987b\u987c\u987d\u987e\u987f\u9880\u9881\u9882\u9883\u9884\u9885\u9886\u9887\u9888\u9889\u988a\u988b\u988c\u988d\u988e\u988f\u9890\u9891\u9892\u9893\u9894\u9895\u9896\u9897\u9898\u9899\u989a\u989b\u989c\u989d\u989e\u989f\u98a0\u98a1\u98a2\u98a3\u98a4\u98a5\u98a6\u98a7\u98a8\u98a9\u98aa\u98ab\u98ac\u98ad\u98ae\u98af\u98b0\u98b1\u98b2\u98b3\u98b4\u98b5\u98b6\u98b7\u98b8\u98b9\u98ba\u98bb\u98bc\u98bd\u98be\u98bf\u98c0\u98c1\u98c2\u98c3\u98c4\u98c5\u98c6\u98c7\u98c8\u98c9\u98ca\u98cb\u98cc\u98cd\u98ce\u98cf\u98d0\u98d1\u98d2\u98d3\u98d4\u98d5\u98d6\u98d7\u98d8\u98d9\u98da\u98db\u98dc\u98dd\u98de\u98df\u98e0\u98e1\u98e2\u98e3\u98e4\u98e5\u98e6\u98e7\u98e8\u98e9\u98ea\u98eb\u98ec\u98ed\u98ee\u98ef\u98f0\u98f1\u98f2\u98f3\u98f4\u98f5\u98f6\u98f7\u98f8\u98f9\u98fa\u98fb\u98fc\u98fd\u98fe\u98ff\u9900\u9901\u9902\u9903\u9904\u9905\u9906\u9907\u9908\u9909\u990a\u990b\u990c\u990d\u990e\u990f\u9910\u9911\u9912\u9913\u9914\u9915\u9916\u9917\u9918\u9919\u991a\u991b\u991c\u991d\u991e\u991f\u9920\u9921\u9922\u9923\u9924\u9925\u9926\u9927\u9928\u9929\u992a\u992b\u992c\u992d\u992e\u992f\u9930\u9931\u9932\u9933\u9934\u9935\u9936\u9937\u9938\u9939\u993a\u993b\u993c\u993d\u993e\u993f\u9940\u9941\u9942\u9943\u9944\u9945\u9946\u9947\u9948\u9949\u994a\u994b\u994c\u994d\u994e\u994f\u9950\u9951\u9952\u9953\u9954\u9955\u9956\u9957\u9958\u9959\u995a\u995b\u995c\u995d\u995e\u995f\u9960\u9961\u9962\u9963\u9964\u9965\u9966\u9967\u9968\u9969\u996a\u996b\u996c\u996d\u996e\u996f\u9970\u9971\u9972\u9973\u9974\u9975\u9976\u9977\u9978\u9979\u997a\u997b\u997c\u997d\u997e\u997f\u9980\u9981\u9982\u9983\u9984\u9985\u9986\u9987\u9988\u9989\u998a\u998b\u998c\u998d\u998e\u998f\u9990\u9991\u9992\u9993\u9994\u9995\u9996\u9997\u9998\u9999\u999a\u999b\u999c\u999d\u999e\u999f\u99a0\u99a1\u99a2\u99a3\u99a4\u99a5\u99a6\u99a7\u99a8\u99a9\u99aa\u99ab\u99ac\u99ad\u99ae\u99af\u99b0\u99b1\u99b2\u99b3\u99b4\u99b5\u99b6\u99b7\u99b8\u99b9\u99ba\u99bb\u99bc\u99bd\u99be\u99bf\u99c0\u99c1\u99c2\u99c3\u99c4\u99c5\u99c6\u99c7\u99c8\u99c9\u99ca\u99cb\u99cc\u99cd\u99ce\u99cf\u99d0\u99d1\u99d2\u99d3\u99d4\u99d5\u99d6\u99d7\u99d8\u99d9\u99da\u99db\u99dc\u99dd\u99de\u99df\u99e0\u99e1\u99e2\u99e3\u99e4\u99e5\u99e6\u99e7\u99e8\u99e9\u99ea\u99eb\u99ec\u99ed\u99ee\u99ef\u99f0\u99f1\u99f2\u99f3\u99f4\u99f5\u99f6\u99f7\u99f8\u99f9\u99fa\u99fb\u99fc\u99fd\u99fe\u99ff\u9a00\u9a01\u9a02\u9a03\u9a04\u9a05\u9a06\u9a07\u9a08\u9a09\u9a0a\u9a0b\u9a0c\u9a0d\u9a0e\u9a0f\u9a10\u9a11\u9a12\u9a13\u9a14\u9a15\u9a16\u9a17\u9a18\u9a19\u9a1a\u9a1b\u9a1c\u9a1d\u9a1e\u9a1f\u9a20\u9a21\u9a22\u9a23\u9a24\u9a25\u9a26\u9a27\u9a28\u9a29\u9a2a\u9a2b\u9a2c\u9a2d\u9a2e\u9a2f\u9a30\u9a31\u9a32\u9a33\u9a34\u9a35\u9a36\u9a37\u9a38\u9a39\u9a3a\u9a3b\u9a3c\u9a3d\u9a3e\u9a3f\u9a40\u9a41\u9a42\u9a43\u9a44\u9a45\u9a46\u9a47\u9a48\u9a49\u9a4a\u9a4b\u9a4c\u9a4d\u9a4e\u9a4f\u9a50\u9a51\u9a52\u9a53\u9a54\u9a55\u9a56\u9a57\u9a58\u9a59\u9a5a\u9a5b\u9a5c\u9a5d\u9a5e\u9a5f\u9a60\u9a61\u9a62\u9a63\u9a64\u9a65\u9a66\u9a67\u9a68\u9a69\u9a6a\u9a6b\u9a6c\u9a6d\u9a6e\u9a6f\u9a70\u9a71\u9a72\u9a73\u9a74\u9a75\u9a76\u9a77\u9a78\u9a79\u9a7a\u9a7b\u9a7c\u9a7d\u9a7e\u9a7f\u9a80\u9a81\u9a82\u9a83\u9a84\u9a85\u9a86\u9a87\u9a88\u9a89\u9a8a\u9a8b\u9a8c\u9a8d\u9a8e\u9a8f\u9a90\u9a91\u9a92\u9a93\u9a94\u9a95\u9a96\u9a97\u9a98\u9a99\u9a9a\u9a9b\u9a9c\u9a9d\u9a9e\u9a9f\u9aa0\u9aa1\u9aa2\u9aa3\u9aa4\u9aa5\u9aa6\u9aa7\u9aa8\u9aa9\u9aaa\u9aab\u9aac\u9aad\u9aae\u9aaf\u9ab0\u9ab1\u9ab2\u9ab3\u9ab4\u9ab5\u9ab6\u9ab7\u9ab8\u9ab9\u9aba\u9abb\u9abc\u9abd\u9abe\u9abf\u9ac0\u9ac1\u9ac2\u9ac3\u9ac4\u9ac5\u9ac6\u9ac7\u9ac8\u9ac9\u9aca\u9acb\u9acc\u9acd\u9ace\u9acf\u9ad0\u9ad1\u9ad2\u9ad3\u9ad4\u9ad5\u9ad6\u9ad7\u9ad8\u9ad9\u9ada\u9adb\u9adc\u9add\u9ade\u9adf\u9ae0\u9ae1\u9ae2\u9ae3\u9ae4\u9ae5\u9ae6\u9ae7\u9ae8\u9ae9\u9aea\u9aeb\u9aec\u9aed\u9aee\u9aef\u9af0\u9af1\u9af2\u9af3\u9af4\u9af5\u9af6\u9af7\u9af8\u9af9\u9afa\u9afb\u9afc\u9afd\u9afe\u9aff\u9b00\u9b01\u9b02\u9b03\u9b04\u9b05\u9b06\u9b07\u9b08\u9b09\u9b0a\u9b0b\u9b0c\u9b0d\u9b0e\u9b0f\u9b10\u9b11\u9b12\u9b13\u9b14\u9b15\u9b16\u9b17\u9b18\u9b19\u9b1a\u9b1b\u9b1c\u9b1d\u9b1e\u9b1f\u9b20\u9b21\u9b22\u9b23\u9b24\u9b25\u9b26\u9b27\u9b28\u9b29\u9b2a\u9b2b\u9b2c\u9b2d\u9b2e\u9b2f\u9b30\u9b31\u9b32\u9b33\u9b34\u9b35\u9b36\u9b37\u9b38\u9b39\u9b3a\u9b3b\u9b3c\u9b3d\u9b3e\u9b3f\u9b40\u9b41\u9b42\u9b43\u9b44\u9b45\u9b46\u9b47\u9b48\u9b49\u9b4a\u9b4b\u9b4c\u9b4d\u9b4e\u9b4f\u9b50\u9b51\u9b52\u9b53\u9b54\u9b55\u9b56\u9b57\u9b58\u9b59\u9b5a\u9b5b\u9b5c\u9b5d\u9b5e\u9b5f\u9b60\u9b61\u9b62\u9b63\u9b64\u9b65\u9b66\u9b67\u9b68\u9b69\u9b6a\u9b6b\u9b6c\u9b6d\u9b6e\u9b6f\u9b70\u9b71\u9b72\u9b73\u9b74\u9b75\u9b76\u9b77\u9b78\u9b79\u9b7a\u9b7b\u9b7c\u9b7d\u9b7e\u9b7f\u9b80\u9b81\u9b82\u9b83\u9b84\u9b85\u9b86\u9b87\u9b88\u9b89\u9b8a\u9b8b\u9b8c\u9b8d\u9b8e\u9b8f\u9b90\u9b91\u9b92\u9b93\u9b94\u9b95\u9b96\u9b97\u9b98\u9b99\u9b9a\u9b9b\u9b9c\u9b9d\u9b9e\u9b9f\u9ba0\u9ba1\u9ba2\u9ba3\u9ba4\u9ba5\u9ba6\u9ba7\u9ba8\u9ba9\u9baa\u9bab\u9bac\u9bad\u9bae\u9baf\u9bb0\u9bb1\u9bb2\u9bb3\u9bb4\u9bb5\u9bb6\u9bb7\u9bb8\u9bb9\u9bba\u9bbb\u9bbc\u9bbd\u9bbe\u9bbf\u9bc0\u9bc1\u9bc2\u9bc3\u9bc4\u9bc5\u9bc6\u9bc7\u9bc8\u9bc9\u9bca\u9bcb\u9bcc\u9bcd\u9bce\u9bcf\u9bd0\u9bd1\u9bd2\u9bd3\u9bd4\u9bd5\u9bd6\u9bd7\u9bd8\u9bd9\u9bda\u9bdb\u9bdc\u9bdd\u9bde\u9bdf\u9be0\u9be1\u9be2\u9be3\u9be4\u9be5\u9be6\u9be7\u9be8\u9be9\u9bea\u9beb\u9bec\u9bed\u9bee\u9bef\u9bf0\u9bf1\u9bf2\u9bf3\u9bf4\u9bf5\u9bf6\u9bf7\u9bf8\u9bf9\u9bfa\u9bfb\u9bfc\u9bfd\u9bfe\u9bff\u9c00\u9c01\u9c02\u9c03\u9c04\u9c05\u9c06\u9c07\u9c08\u9c09\u9c0a\u9c0b\u9c0c\u9c0d\u9c0e\u9c0f\u9c10\u9c11\u9c12\u9c13\u9c14\u9c15\u9c16\u9c17\u9c18\u9c19\u9c1a\u9c1b\u9c1c\u9c1d\u9c1e\u9c1f\u9c20\u9c21\u9c22\u9c23\u9c24\u9c25\u9c26\u9c27\u9c28\u9c29\u9c2a\u9c2b\u9c2c\u9c2d\u9c2e\u9c2f\u9c30\u9c31\u9c32\u9c33\u9c34\u9c35\u9c36\u9c37\u9c38\u9c39\u9c3a\u9c3b\u9c3c\u9c3d\u9c3e\u9c3f\u9c40\u9c41\u9c42\u9c43\u9c44\u9c45\u9c46\u9c47\u9c48\u9c49\u9c4a\u9c4b\u9c4c\u9c4d\u9c4e\u9c4f\u9c50\u9c51\u9c52\u9c53\u9c54\u9c55\u9c56\u9c57\u9c58\u9c59\u9c5a\u9c5b\u9c5c\u9c5d\u9c5e\u9c5f\u9c60\u9c61\u9c62\u9c63\u9c64\u9c65\u9c66\u9c67\u9c68\u9c69\u9c6a\u9c6b\u9c6c\u9c6d\u9c6e\u9c6f\u9c70\u9c71\u9c72\u9c73\u9c74\u9c75\u9c76\u9c77\u9c78\u9c79\u9c7a\u9c7b\u9c7c\u9c7d\u9c7e\u9c7f\u9c80\u9c81\u9c82\u9c83\u9c84\u9c85\u9c86\u9c87\u9c88\u9c89\u9c8a\u9c8b\u9c8c\u9c8d\u9c8e\u9c8f\u9c90\u9c91\u9c92\u9c93\u9c94\u9c95\u9c96\u9c97\u9c98\u9c99\u9c9a\u9c9b\u9c9c\u9c9d\u9c9e\u9c9f\u9ca0\u9ca1\u9ca2\u9ca3\u9ca4\u9ca5\u9ca6\u9ca7\u9ca8\u9ca9\u9caa\u9cab\u9cac\u9cad\u9cae\u9caf\u9cb0\u9cb1\u9cb2\u9cb3\u9cb4\u9cb5\u9cb6\u9cb7\u9cb8\u9cb9\u9cba\u9cbb\u9cbc\u9cbd\u9cbe\u9cbf\u9cc0\u9cc1\u9cc2\u9cc3\u9cc4\u9cc5\u9cc6\u9cc7\u9cc8\u9cc9\u9cca\u9ccb\u9ccc\u9ccd\u9cce\u9ccf\u9cd0\u9cd1\u9cd2\u9cd3\u9cd4\u9cd5\u9cd6\u9cd7\u9cd8\u9cd9\u9cda\u9cdb\u9cdc\u9cdd\u9cde\u9cdf\u9ce0\u9ce1\u9ce2\u9ce3\u9ce4\u9ce5\u9ce6\u9ce7\u9ce8\u9ce9\u9cea\u9ceb\u9cec\u9ced\u9cee\u9cef\u9cf0\u9cf1\u9cf2\u9cf3\u9cf4\u9cf5\u9cf6\u9cf7\u9cf8\u9cf9\u9cfa\u9cfb\u9cfc\u9cfd\u9cfe\u9cff\u9d00\u9d01\u9d02\u9d03\u9d04\u9d05\u9d06\u9d07\u9d08\u9d09\u9d0a\u9d0b\u9d0c\u9d0d\u9d0e\u9d0f\u9d10\u9d11\u9d12\u9d13\u9d14\u9d15\u9d16\u9d17\u9d18\u9d19\u9d1a\u9d1b\u9d1c\u9d1d\u9d1e\u9d1f\u9d20\u9d21\u9d22\u9d23\u9d24\u9d25\u9d26\u9d27\u9d28\u9d29\u9d2a\u9d2b\u9d2c\u9d2d\u9d2e\u9d2f\u9d30\u9d31\u9d32\u9d33\u9d34\u9d35\u9d36\u9d37\u9d38\u9d39\u9d3a\u9d3b\u9d3c\u9d3d\u9d3e\u9d3f\u9d40\u9d41\u9d42\u9d43\u9d44\u9d45\u9d46\u9d47\u9d48\u9d49\u9d4a\u9d4b\u9d4c\u9d4d\u9d4e\u9d4f\u9d50\u9d51\u9d52\u9d53\u9d54\u9d55\u9d56\u9d57\u9d58\u9d59\u9d5a\u9d5b\u9d5c\u9d5d\u9d5e\u9d5f\u9d60\u9d61\u9d62\u9d63\u9d64\u9d65\u9d66\u9d67\u9d68\u9d69\u9d6a\u9d6b\u9d6c\u9d6d\u9d6e\u9d6f\u9d70\u9d71\u9d72\u9d73\u9d74\u9d75\u9d76\u9d77\u9d78\u9d79\u9d7a\u9d7b\u9d7c\u9d7d\u9d7e\u9d7f\u9d80\u9d81\u9d82\u9d83\u9d84\u9d85\u9d86\u9d87\u9d88\u9d89\u9d8a\u9d8b\u9d8c\u9d8d\u9d8e\u9d8f\u9d90\u9d91\u9d92\u9d93\u9d94\u9d95\u9d96\u9d97\u9d98\u9d99\u9d9a\u9d9b\u9d9c\u9d9d\u9d9e\u9d9f\u9da0\u9da1\u9da2\u9da3\u9da4\u9da5\u9da6\u9da7\u9da8\u9da9\u9daa\u9dab\u9dac\u9dad\u9dae\u9daf\u9db0\u9db1\u9db2\u9db3\u9db4\u9db5\u9db6\u9db7\u9db8\u9db9\u9dba\u9dbb\u9dbc\u9dbd\u9dbe\u9dbf\u9dc0\u9dc1\u9dc2\u9dc3\u9dc4\u9dc5\u9dc6\u9dc7\u9dc8\u9dc9\u9dca\u9dcb\u9dcc\u9dcd\u9dce\u9dcf\u9dd0\u9dd1\u9dd2\u9dd3\u9dd4\u9dd5\u9dd6\u9dd7\u9dd8\u9dd9\u9dda\u9ddb\u9ddc\u9ddd\u9dde\u9ddf\u9de0\u9de1\u9de2\u9de3\u9de4\u9de5\u9de6\u9de7\u9de8\u9de9\u9dea\u9deb\u9dec\u9ded\u9dee\u9def\u9df0\u9df1\u9df2\u9df3\u9df4\u9df5\u9df6\u9df7\u9df8\u9df9\u9dfa\u9dfb\u9dfc\u9dfd\u9dfe\u9dff\u9e00\u9e01\u9e02\u9e03\u9e04\u9e05\u9e06\u9e07\u9e08\u9e09\u9e0a\u9e0b\u9e0c\u9e0d\u9e0e\u9e0f\u9e10\u9e11\u9e12\u9e13\u9e14\u9e15\u9e16\u9e17\u9e18\u9e19\u9e1a\u9e1b\u9e1c\u9e1d\u9e1e\u9e1f\u9e20\u9e21\u9e22\u9e23\u9e24\u9e25\u9e26\u9e27\u9e28\u9e29\u9e2a\u9e2b\u9e2c\u9e2d\u9e2e\u9e2f\u9e30\u9e31\u9e32\u9e33\u9e34\u9e35\u9e36\u9e37\u9e38\u9e39\u9e3a\u9e3b\u9e3c\u9e3d\u9e3e\u9e3f\u9e40\u9e41\u9e42\u9e43\u9e44\u9e45\u9e46\u9e47\u9e48\u9e49\u9e4a\u9e4b\u9e4c\u9e4d\u9e4e\u9e4f\u9e50\u9e51\u9e52\u9e53\u9e54\u9e55\u9e56\u9e57\u9e58\u9e59\u9e5a\u9e5b\u9e5c\u9e5d\u9e5e\u9e5f\u9e60\u9e61\u9e62\u9e63\u9e64\u9e65\u9e66\u9e67\u9e68\u9e69\u9e6a\u9e6b\u9e6c\u9e6d\u9e6e\u9e6f\u9e70\u9e71\u9e72\u9e73\u9e74\u9e75\u9e76\u9e77\u9e78\u9e79\u9e7a\u9e7b\u9e7c\u9e7d\u9e7e\u9e7f\u9e80\u9e81\u9e82\u9e83\u9e84\u9e85\u9e86\u9e87\u9e88\u9e89\u9e8a\u9e8b\u9e8c\u9e8d\u9e8e\u9e8f\u9e90\u9e91\u9e92\u9e93\u9e94\u9e95\u9e96\u9e97\u9e98\u9e99\u9e9a\u9e9b\u9e9c\u9e9d\u9e9e\u9e9f\u9ea0\u9ea1\u9ea2\u9ea3\u9ea4\u9ea5\u9ea6\u9ea7\u9ea8\u9ea9\u9eaa\u9eab\u9eac\u9ead\u9eae\u9eaf\u9eb0\u9eb1\u9eb2\u9eb3\u9eb4\u9eb5\u9eb6\u9eb7\u9eb8\u9eb9\u9eba\u9ebb\u9ebc\u9ebd\u9ebe\u9ebf\u9ec0\u9ec1\u9ec2\u9ec3\u9ec4\u9ec5\u9ec6\u9ec7\u9ec8\u9ec9\u9eca\u9ecb\u9ecc\u9ecd\u9ece\u9ecf\u9ed0\u9ed1\u9ed2\u9ed3\u9ed4\u9ed5\u9ed6\u9ed7\u9ed8\u9ed9\u9eda\u9edb\u9edc\u9edd\u9ede\u9edf\u9ee0\u9ee1\u9ee2\u9ee3\u9ee4\u9ee5\u9ee6\u9ee7\u9ee8\u9ee9\u9eea\u9eeb\u9eec\u9eed\u9eee\u9eef\u9ef0\u9ef1\u9ef2\u9ef3\u9ef4\u9ef5\u9ef6\u9ef7\u9ef8\u9ef9\u9efa\u9efb\u9efc\u9efd\u9efe\u9eff\u9f00\u9f01\u9f02\u9f03\u9f04\u9f05\u9f06\u9f07\u9f08\u9f09\u9f0a\u9f0b\u9f0c\u9f0d\u9f0e\u9f0f\u9f10\u9f11\u9f12\u9f13\u9f14\u9f15\u9f16\u9f17\u9f18\u9f19\u9f1a\u9f1b\u9f1c\u9f1d\u9f1e\u9f1f\u9f20\u9f21\u9f22\u9f23\u9f24\u9f25\u9f26\u9f27\u9f28\u9f29\u9f2a\u9f2b\u9f2c\u9f2d\u9f2e\u9f2f\u9f30\u9f31\u9f32\u9f33\u9f34\u9f35\u9f36\u9f37\u9f38\u9f39\u9f3a\u9f3b\u9f3c\u9f3d\u9f3e\u9f3f\u9f40\u9f41\u9f42\u9f43\u9f44\u9f45\u9f46\u9f47\u9f48\u9f49\u9f4a\u9f4b\u9f4c\u9f4d\u9f4e\u9f4f\u9f50\u9f51\u9f52\u9f53\u9f54\u9f55\u9f56\u9f57\u9f58\u9f59\u9f5a\u9f5b\u9f5c\u9f5d\u9f5e\u9f5f\u9f60\u9f61\u9f62\u9f63\u9f64\u9f65\u9f66\u9f67\u9f68\u9f69\u9f6a\u9f6b\u9f6c\u9f6d\u9f6e\u9f6f\u9f70\u9f71\u9f72\u9f73\u9f74\u9f75\u9f76\u9f77\u9f78\u9f79\u9f7a\u9f7b\u9f7c\u9f7d\u9f7e\u9f7f\u9f80\u9f81\u9f82\u9f83\u9f84\u9f85\u9f86\u9f87\u9f88\u9f89\u9f8a\u9f8b\u9f8c\u9f8d\u9f8e\u9f8f\u9f90\u9f91\u9f92\u9f93\u9f94\u9f95\u9f96\u9f97\u9f98\u9f99\u9f9a\u9f9b\u9f9c\u9f9d\u9f9e\u9f9f\u9fa0\u9fa1\u9fa2\u9fa3\u9fa4\u9fa5\u9fa6\u9fa7\u9fa8\u9fa9\u9faa\u9fab\u9fac\u9fad\u9fae\u9faf\u9fb0\u9fb1\u9fb2\u9fb3\u9fb4\u9fb5\u9fb6\u9fb7\u9fb8\u9fb9\u9fba\u9fbb\ua000\ua001\ua002\ua003\ua004\ua005\ua006\ua007\ua008\ua009\ua00a\ua00b\ua00c\ua00d\ua00e\ua00f\ua010\ua011\ua012\ua013\ua014\ua016\ua017\ua018\ua019\ua01a\ua01b\ua01c\ua01d\ua01e\ua01f\ua020\ua021\ua022\ua023\ua024\ua025\ua026\ua027\ua028\ua029\ua02a\ua02b\ua02c\ua02d\ua02e\ua02f\ua030\ua031\ua032\ua033\ua034\ua035\ua036\ua037\ua038\ua039\ua03a\ua03b\ua03c\ua03d\ua03e\ua03f\ua040\ua041\ua042\ua043\ua044\ua045\ua046\ua047\ua048\ua049\ua04a\ua04b\ua04c\ua04d\ua04e\ua04f\ua050\ua051\ua052\ua053\ua054\ua055\ua056\ua057\ua058\ua059\ua05a\ua05b\ua05c\ua05d\ua05e\ua05f\ua060\ua061\ua062\ua063\ua064\ua065\ua066\ua067\ua068\ua069\ua06a\ua06b\ua06c\ua06d\ua06e\ua06f\ua070\ua071\ua072\ua073\ua074\ua075\ua076\ua077\ua078\ua079\ua07a\ua07b\ua07c\ua07d\ua07e\ua07f\ua080\ua081\ua082\ua083\ua084\ua085\ua086\ua087\ua088\ua089\ua08a\ua08b\ua08c\ua08d\ua08e\ua08f\ua090\ua091\ua092\ua093\ua094\ua095\ua096\ua097\ua098\ua099\ua09a\ua09b\ua09c\ua09d\ua09e\ua09f\ua0a0\ua0a1\ua0a2\ua0a3\ua0a4\ua0a5\ua0a6\ua0a7\ua0a8\ua0a9\ua0aa\ua0ab\ua0ac\ua0ad\ua0ae\ua0af\ua0b0\ua0b1\ua0b2\ua0b3\ua0b4\ua0b5\ua0b6\ua0b7\ua0b8\ua0b9\ua0ba\ua0bb\ua0bc\ua0bd\ua0be\ua0bf\ua0c0\ua0c1\ua0c2\ua0c3\ua0c4\ua0c5\ua0c6\ua0c7\ua0c8\ua0c9\ua0ca\ua0cb\ua0cc\ua0cd\ua0ce\ua0cf\ua0d0\ua0d1\ua0d2\ua0d3\ua0d4\ua0d5\ua0d6\ua0d7\ua0d8\ua0d9\ua0da\ua0db\ua0dc\ua0dd\ua0de\ua0df\ua0e0\ua0e1\ua0e2\ua0e3\ua0e4\ua0e5\ua0e6\ua0e7\ua0e8\ua0e9\ua0ea\ua0eb\ua0ec\ua0ed\ua0ee\ua0ef\ua0f0\ua0f1\ua0f2\ua0f3\ua0f4\ua0f5\ua0f6\ua0f7\ua0f8\ua0f9\ua0fa\ua0fb\ua0fc\ua0fd\ua0fe\ua0ff\ua100\ua101\ua102\ua103\ua104\ua105\ua106\ua107\ua108\ua109\ua10a\ua10b\ua10c\ua10d\ua10e\ua10f\ua110\ua111\ua112\ua113\ua114\ua115\ua116\ua117\ua118\ua119\ua11a\ua11b\ua11c\ua11d\ua11e\ua11f\ua120\ua121\ua122\ua123\ua124\ua125\ua126\ua127\ua128\ua129\ua12a\ua12b\ua12c\ua12d\ua12e\ua12f\ua130\ua131\ua132\ua133\ua134\ua135\ua136\ua137\ua138\ua139\ua13a\ua13b\ua13c\ua13d\ua13e\ua13f\ua140\ua141\ua142\ua143\ua144\ua145\ua146\ua147\ua148\ua149\ua14a\ua14b\ua14c\ua14d\ua14e\ua14f\ua150\ua151\ua152\ua153\ua154\ua155\ua156\ua157\ua158\ua159\ua15a\ua15b\ua15c\ua15d\ua15e\ua15f\ua160\ua161\ua162\ua163\ua164\ua165\ua166\ua167\ua168\ua169\ua16a\ua16b\ua16c\ua16d\ua16e\ua16f\ua170\ua171\ua172\ua173\ua174\ua175\ua176\ua177\ua178\ua179\ua17a\ua17b\ua17c\ua17d\ua17e\ua17f\ua180\ua181\ua182\ua183\ua184\ua185\ua186\ua187\ua188\ua189\ua18a\ua18b\ua18c\ua18d\ua18e\ua18f\ua190\ua191\ua192\ua193\ua194\ua195\ua196\ua197\ua198\ua199\ua19a\ua19b\ua19c\ua19d\ua19e\ua19f\ua1a0\ua1a1\ua1a2\ua1a3\ua1a4\ua1a5\ua1a6\ua1a7\ua1a8\ua1a9\ua1aa\ua1ab\ua1ac\ua1ad\ua1ae\ua1af\ua1b0\ua1b1\ua1b2\ua1b3\ua1b4\ua1b5\ua1b6\ua1b7\ua1b8\ua1b9\ua1ba\ua1bb\ua1bc\ua1bd\ua1be\ua1bf\ua1c0\ua1c1\ua1c2\ua1c3\ua1c4\ua1c5\ua1c6\ua1c7\ua1c8\ua1c9\ua1ca\ua1cb\ua1cc\ua1cd\ua1ce\ua1cf\ua1d0\ua1d1\ua1d2\ua1d3\ua1d4\ua1d5\ua1d6\ua1d7\ua1d8\ua1d9\ua1da\ua1db\ua1dc\ua1dd\ua1de\ua1df\ua1e0\ua1e1\ua1e2\ua1e3\ua1e4\ua1e5\ua1e6\ua1e7\ua1e8\ua1e9\ua1ea\ua1eb\ua1ec\ua1ed\ua1ee\ua1ef\ua1f0\ua1f1\ua1f2\ua1f3\ua1f4\ua1f5\ua1f6\ua1f7\ua1f8\ua1f9\ua1fa\ua1fb\ua1fc\ua1fd\ua1fe\ua1ff\ua200\ua201\ua202\ua203\ua204\ua205\ua206\ua207\ua208\ua209\ua20a\ua20b\ua20c\ua20d\ua20e\ua20f\ua210\ua211\ua212\ua213\ua214\ua215\ua216\ua217\ua218\ua219\ua21a\ua21b\ua21c\ua21d\ua21e\ua21f\ua220\ua221\ua222\ua223\ua224\ua225\ua226\ua227\ua228\ua229\ua22a\ua22b\ua22c\ua22d\ua22e\ua22f\ua230\ua231\ua232\ua233\ua234\ua235\ua236\ua237\ua238\ua239\ua23a\ua23b\ua23c\ua23d\ua23e\ua23f\ua240\ua241\ua242\ua243\ua244\ua245\ua246\ua247\ua248\ua249\ua24a\ua24b\ua24c\ua24d\ua24e\ua24f\ua250\ua251\ua252\ua253\ua254\ua255\ua256\ua257\ua258\ua259\ua25a\ua25b\ua25c\ua25d\ua25e\ua25f\ua260\ua261\ua262\ua263\ua264\ua265\ua266\ua267\ua268\ua269\ua26a\ua26b\ua26c\ua26d\ua26e\ua26f\ua270\ua271\ua272\ua273\ua274\ua275\ua276\ua277\ua278\ua279\ua27a\ua27b\ua27c\ua27d\ua27e\ua27f\ua280\ua281\ua282\ua283\ua284\ua285\ua286\ua287\ua288\ua289\ua28a\ua28b\ua28c\ua28d\ua28e\ua28f\ua290\ua291\ua292\ua293\ua294\ua295\ua296\ua297\ua298\ua299\ua29a\ua29b\ua29c\ua29d\ua29e\ua29f\ua2a0\ua2a1\ua2a2\ua2a3\ua2a4\ua2a5\ua2a6\ua2a7\ua2a8\ua2a9\ua2aa\ua2ab\ua2ac\ua2ad\ua2ae\ua2af\ua2b0\ua2b1\ua2b2\ua2b3\ua2b4\ua2b5\ua2b6\ua2b7\ua2b8\ua2b9\ua2ba\ua2bb\ua2bc\ua2bd\ua2be\ua2bf\ua2c0\ua2c1\ua2c2\ua2c3\ua2c4\ua2c5\ua2c6\ua2c7\ua2c8\ua2c9\ua2ca\ua2cb\ua2cc\ua2cd\ua2ce\ua2cf\ua2d0\ua2d1\ua2d2\ua2d3\ua2d4\ua2d5\ua2d6\ua2d7\ua2d8\ua2d9\ua2da\ua2db\ua2dc\ua2dd\ua2de\ua2df\ua2e0\ua2e1\ua2e2\ua2e3\ua2e4\ua2e5\ua2e6\ua2e7\ua2e8\ua2e9\ua2ea\ua2eb\ua2ec\ua2ed\ua2ee\ua2ef\ua2f0\ua2f1\ua2f2\ua2f3\ua2f4\ua2f5\ua2f6\ua2f7\ua2f8\ua2f9\ua2fa\ua2fb\ua2fc\ua2fd\ua2fe\ua2ff\ua300\ua301\ua302\ua303\ua304\ua305\ua306\ua307\ua308\ua309\ua30a\ua30b\ua30c\ua30d\ua30e\ua30f\ua310\ua311\ua312\ua313\ua314\ua315\ua316\ua317\ua318\ua319\ua31a\ua31b\ua31c\ua31d\ua31e\ua31f\ua320\ua321\ua322\ua323\ua324\ua325\ua326\ua327\ua328\ua329\ua32a\ua32b\ua32c\ua32d\ua32e\ua32f\ua330\ua331\ua332\ua333\ua334\ua335\ua336\ua337\ua338\ua339\ua33a\ua33b\ua33c\ua33d\ua33e\ua33f\ua340\ua341\ua342\ua343\ua344\ua345\ua346\ua347\ua348\ua349\ua34a\ua34b\ua34c\ua34d\ua34e\ua34f\ua350\ua351\ua352\ua353\ua354\ua355\ua356\ua357\ua358\ua359\ua35a\ua35b\ua35c\ua35d\ua35e\ua35f\ua360\ua361\ua362\ua363\ua364\ua365\ua366\ua367\ua368\ua369\ua36a\ua36b\ua36c\ua36d\ua36e\ua36f\ua370\ua371\ua372\ua373\ua374\ua375\ua376\ua377\ua378\ua379\ua37a\ua37b\ua37c\ua37d\ua37e\ua37f\ua380\ua381\ua382\ua383\ua384\ua385\ua386\ua387\ua388\ua389\ua38a\ua38b\ua38c\ua38d\ua38e\ua38f\ua390\ua391\ua392\ua393\ua394\ua395\ua396\ua397\ua398\ua399\ua39a\ua39b\ua39c\ua39d\ua39e\ua39f\ua3a0\ua3a1\ua3a2\ua3a3\ua3a4\ua3a5\ua3a6\ua3a7\ua3a8\ua3a9\ua3aa\ua3ab\ua3ac\ua3ad\ua3ae\ua3af\ua3b0\ua3b1\ua3b2\ua3b3\ua3b4\ua3b5\ua3b6\ua3b7\ua3b8\ua3b9\ua3ba\ua3bb\ua3bc\ua3bd\ua3be\ua3bf\ua3c0\ua3c1\ua3c2\ua3c3\ua3c4\ua3c5\ua3c6\ua3c7\ua3c8\ua3c9\ua3ca\ua3cb\ua3cc\ua3cd\ua3ce\ua3cf\ua3d0\ua3d1\ua3d2\ua3d3\ua3d4\ua3d5\ua3d6\ua3d7\ua3d8\ua3d9\ua3da\ua3db\ua3dc\ua3dd\ua3de\ua3df\ua3e0\ua3e1\ua3e2\ua3e3\ua3e4\ua3e5\ua3e6\ua3e7\ua3e8\ua3e9\ua3ea\ua3eb\ua3ec\ua3ed\ua3ee\ua3ef\ua3f0\ua3f1\ua3f2\ua3f3\ua3f4\ua3f5\ua3f6\ua3f7\ua3f8\ua3f9\ua3fa\ua3fb\ua3fc\ua3fd\ua3fe\ua3ff\ua400\ua401\ua402\ua403\ua404\ua405\ua406\ua407\ua408\ua409\ua40a\ua40b\ua40c\ua40d\ua40e\ua40f\ua410\ua411\ua412\ua413\ua414\ua415\ua416\ua417\ua418\ua419\ua41a\ua41b\ua41c\ua41d\ua41e\ua41f\ua420\ua421\ua422\ua423\ua424\ua425\ua426\ua427\ua428\ua429\ua42a\ua42b\ua42c\ua42d\ua42e\ua42f\ua430\ua431\ua432\ua433\ua434\ua435\ua436\ua437\ua438\ua439\ua43a\ua43b\ua43c\ua43d\ua43e\ua43f\ua440\ua441\ua442\ua443\ua444\ua445\ua446\ua447\ua448\ua449\ua44a\ua44b\ua44c\ua44d\ua44e\ua44f\ua450\ua451\ua452\ua453\ua454\ua455\ua456\ua457\ua458\ua459\ua45a\ua45b\ua45c\ua45d\ua45e\ua45f\ua460\ua461\ua462\ua463\ua464\ua465\ua466\ua467\ua468\ua469\ua46a\ua46b\ua46c\ua46d\ua46e\ua46f\ua470\ua471\ua472\ua473\ua474\ua475\ua476\ua477\ua478\ua479\ua47a\ua47b\ua47c\ua47d\ua47e\ua47f\ua480\ua481\ua482\ua483\ua484\ua485\ua486\ua487\ua488\ua489\ua48a\ua48b\ua48c\ua800\ua801\ua803\ua804\ua805\ua807\ua808\ua809\ua80a\ua80c\ua80d\ua80e\ua80f\ua810\ua811\ua812\ua813\ua814\ua815\ua816\ua817\ua818\ua819\ua81a\ua81b\ua81c\ua81d\ua81e\ua81f\ua820\ua821\ua822\uac00\uac01\uac02\uac03\uac04\uac05\uac06\uac07\uac08\uac09\uac0a\uac0b\uac0c\uac0d\uac0e\uac0f\uac10\uac11\uac12\uac13\uac14\uac15\uac16\uac17\uac18\uac19\uac1a\uac1b\uac1c\uac1d\uac1e\uac1f\uac20\uac21\uac22\uac23\uac24\uac25\uac26\uac27\uac28\uac29\uac2a\uac2b\uac2c\uac2d\uac2e\uac2f\uac30\uac31\uac32\uac33\uac34\uac35\uac36\uac37\uac38\uac39\uac3a\uac3b\uac3c\uac3d\uac3e\uac3f\uac40\uac41\uac42\uac43\uac44\uac45\uac46\uac47\uac48\uac49\uac4a\uac4b\uac4c\uac4d\uac4e\uac4f\uac50\uac51\uac52\uac53\uac54\uac55\uac56\uac57\uac58\uac59\uac5a\uac5b\uac5c\uac5d\uac5e\uac5f\uac60\uac61\uac62\uac63\uac64\uac65\uac66\uac67\uac68\uac69\uac6a\uac6b\uac6c\uac6d\uac6e\uac6f\uac70\uac71\uac72\uac73\uac74\uac75\uac76\uac77\uac78\uac79\uac7a\uac7b\uac7c\uac7d\uac7e\uac7f\uac80\uac81\uac82\uac83\uac84\uac85\uac86\uac87\uac88\uac89\uac8a\uac8b\uac8c\uac8d\uac8e\uac8f\uac90\uac91\uac92\uac93\uac94\uac95\uac96\uac97\uac98\uac99\uac9a\uac9b\uac9c\uac9d\uac9e\uac9f\uaca0\uaca1\uaca2\uaca3\uaca4\uaca5\uaca6\uaca7\uaca8\uaca9\uacaa\uacab\uacac\uacad\uacae\uacaf\uacb0\uacb1\uacb2\uacb3\uacb4\uacb5\uacb6\uacb7\uacb8\uacb9\uacba\uacbb\uacbc\uacbd\uacbe\uacbf\uacc0\uacc1\uacc2\uacc3\uacc4\uacc5\uacc6\uacc7\uacc8\uacc9\uacca\uaccb\uaccc\uaccd\uacce\uaccf\uacd0\uacd1\uacd2\uacd3\uacd4\uacd5\uacd6\uacd7\uacd8\uacd9\uacda\uacdb\uacdc\uacdd\uacde\uacdf\uace0\uace1\uace2\uace3\uace4\uace5\uace6\uace7\uace8\uace9\uacea\uaceb\uacec\uaced\uacee\uacef\uacf0\uacf1\uacf2\uacf3\uacf4\uacf5\uacf6\uacf7\uacf8\uacf9\uacfa\uacfb\uacfc\uacfd\uacfe\uacff\uad00\uad01\uad02\uad03\uad04\uad05\uad06\uad07\uad08\uad09\uad0a\uad0b\uad0c\uad0d\uad0e\uad0f\uad10\uad11\uad12\uad13\uad14\uad15\uad16\uad17\uad18\uad19\uad1a\uad1b\uad1c\uad1d\uad1e\uad1f\uad20\uad21\uad22\uad23\uad24\uad25\uad26\uad27\uad28\uad29\uad2a\uad2b\uad2c\uad2d\uad2e\uad2f\uad30\uad31\uad32\uad33\uad34\uad35\uad36\uad37\uad38\uad39\uad3a\uad3b\uad3c\uad3d\uad3e\uad3f\uad40\uad41\uad42\uad43\uad44\uad45\uad46\uad47\uad48\uad49\uad4a\uad4b\uad4c\uad4d\uad4e\uad4f\uad50\uad51\uad52\uad53\uad54\uad55\uad56\uad57\uad58\uad59\uad5a\uad5b\uad5c\uad5d\uad5e\uad5f\uad60\uad61\uad62\uad63\uad64\uad65\uad66\uad67\uad68\uad69\uad6a\uad6b\uad6c\uad6d\uad6e\uad6f\uad70\uad71\uad72\uad73\uad74\uad75\uad76\uad77\uad78\uad79\uad7a\uad7b\uad7c\uad7d\uad7e\uad7f\uad80\uad81\uad82\uad83\uad84\uad85\uad86\uad87\uad88\uad89\uad8a\uad8b\uad8c\uad8d\uad8e\uad8f\uad90\uad91\uad92\uad93\uad94\uad95\uad96\uad97\uad98\uad99\uad9a\uad9b\uad9c\uad9d\uad9e\uad9f\uada0\uada1\uada2\uada3\uada4\uada5\uada6\uada7\uada8\uada9\uadaa\uadab\uadac\uadad\uadae\uadaf\uadb0\uadb1\uadb2\uadb3\uadb4\uadb5\uadb6\uadb7\uadb8\uadb9\uadba\uadbb\uadbc\uadbd\uadbe\uadbf\uadc0\uadc1\uadc2\uadc3\uadc4\uadc5\uadc6\uadc7\uadc8\uadc9\uadca\uadcb\uadcc\uadcd\uadce\uadcf\uadd0\uadd1\uadd2\uadd3\uadd4\uadd5\uadd6\uadd7\uadd8\uadd9\uadda\uaddb\uaddc\uaddd\uadde\uaddf\uade0\uade1\uade2\uade3\uade4\uade5\uade6\uade7\uade8\uade9\uadea\uadeb\uadec\uaded\uadee\uadef\uadf0\uadf1\uadf2\uadf3\uadf4\uadf5\uadf6\uadf7\uadf8\uadf9\uadfa\uadfb\uadfc\uadfd\uadfe\uadff\uae00\uae01\uae02\uae03\uae04\uae05\uae06\uae07\uae08\uae09\uae0a\uae0b\uae0c\uae0d\uae0e\uae0f\uae10\uae11\uae12\uae13\uae14\uae15\uae16\uae17\uae18\uae19\uae1a\uae1b\uae1c\uae1d\uae1e\uae1f\uae20\uae21\uae22\uae23\uae24\uae25\uae26\uae27\uae28\uae29\uae2a\uae2b\uae2c\uae2d\uae2e\uae2f\uae30\uae31\uae32\uae33\uae34\uae35\uae36\uae37\uae38\uae39\uae3a\uae3b\uae3c\uae3d\uae3e\uae3f\uae40\uae41\uae42\uae43\uae44\uae45\uae46\uae47\uae48\uae49\uae4a\uae4b\uae4c\uae4d\uae4e\uae4f\uae50\uae51\uae52\uae53\uae54\uae55\uae56\uae57\uae58\uae59\uae5a\uae5b\uae5c\uae5d\uae5e\uae5f\uae60\uae61\uae62\uae63\uae64\uae65\uae66\uae67\uae68\uae69\uae6a\uae6b\uae6c\uae6d\uae6e\uae6f\uae70\uae71\uae72\uae73\uae74\uae75\uae76\uae77\uae78\uae79\uae7a\uae7b\uae7c\uae7d\uae7e\uae7f\uae80\uae81\uae82\uae83\uae84\uae85\uae86\uae87\uae88\uae89\uae8a\uae8b\uae8c\uae8d\uae8e\uae8f\uae90\uae91\uae92\uae93\uae94\uae95\uae96\uae97\uae98\uae99\uae9a\uae9b\uae9c\uae9d\uae9e\uae9f\uaea0\uaea1\uaea2\uaea3\uaea4\uaea5\uaea6\uaea7\uaea8\uaea9\uaeaa\uaeab\uaeac\uaead\uaeae\uaeaf\uaeb0\uaeb1\uaeb2\uaeb3\uaeb4\uaeb5\uaeb6\uaeb7\uaeb8\uaeb9\uaeba\uaebb\uaebc\uaebd\uaebe\uaebf\uaec0\uaec1\uaec2\uaec3\uaec4\uaec5\uaec6\uaec7\uaec8\uaec9\uaeca\uaecb\uaecc\uaecd\uaece\uaecf\uaed0\uaed1\uaed2\uaed3\uaed4\uaed5\uaed6\uaed7\uaed8\uaed9\uaeda\uaedb\uaedc\uaedd\uaede\uaedf\uaee0\uaee1\uaee2\uaee3\uaee4\uaee5\uaee6\uaee7\uaee8\uaee9\uaeea\uaeeb\uaeec\uaeed\uaeee\uaeef\uaef0\uaef1\uaef2\uaef3\uaef4\uaef5\uaef6\uaef7\uaef8\uaef9\uaefa\uaefb\uaefc\uaefd\uaefe\uaeff\uaf00\uaf01\uaf02\uaf03\uaf04\uaf05\uaf06\uaf07\uaf08\uaf09\uaf0a\uaf0b\uaf0c\uaf0d\uaf0e\uaf0f\uaf10\uaf11\uaf12\uaf13\uaf14\uaf15\uaf16\uaf17\uaf18\uaf19\uaf1a\uaf1b\uaf1c\uaf1d\uaf1e\uaf1f\uaf20\uaf21\uaf22\uaf23\uaf24\uaf25\uaf26\uaf27\uaf28\uaf29\uaf2a\uaf2b\uaf2c\uaf2d\uaf2e\uaf2f\uaf30\uaf31\uaf32\uaf33\uaf34\uaf35\uaf36\uaf37\uaf38\uaf39\uaf3a\uaf3b\uaf3c\uaf3d\uaf3e\uaf3f\uaf40\uaf41\uaf42\uaf43\uaf44\uaf45\uaf46\uaf47\uaf48\uaf49\uaf4a\uaf4b\uaf4c\uaf4d\uaf4e\uaf4f\uaf50\uaf51\uaf52\uaf53\uaf54\uaf55\uaf56\uaf57\uaf58\uaf59\uaf5a\uaf5b\uaf5c\uaf5d\uaf5e\uaf5f\uaf60\uaf61\uaf62\uaf63\uaf64\uaf65\uaf66\uaf67\uaf68\uaf69\uaf6a\uaf6b\uaf6c\uaf6d\uaf6e\uaf6f\uaf70\uaf71\uaf72\uaf73\uaf74\uaf75\uaf76\uaf77\uaf78\uaf79\uaf7a\uaf7b\uaf7c\uaf7d\uaf7e\uaf7f\uaf80\uaf81\uaf82\uaf83\uaf84\uaf85\uaf86\uaf87\uaf88\uaf89\uaf8a\uaf8b\uaf8c\uaf8d\uaf8e\uaf8f\uaf90\uaf91\uaf92\uaf93\uaf94\uaf95\uaf96\uaf97\uaf98\uaf99\uaf9a\uaf9b\uaf9c\uaf9d\uaf9e\uaf9f\uafa0\uafa1\uafa2\uafa3\uafa4\uafa5\uafa6\uafa7\uafa8\uafa9\uafaa\uafab\uafac\uafad\uafae\uafaf\uafb0\uafb1\uafb2\uafb3\uafb4\uafb5\uafb6\uafb7\uafb8\uafb9\uafba\uafbb\uafbc\uafbd\uafbe\uafbf\uafc0\uafc1\uafc2\uafc3\uafc4\uafc5\uafc6\uafc7\uafc8\uafc9\uafca\uafcb\uafcc\uafcd\uafce\uafcf\uafd0\uafd1\uafd2\uafd3\uafd4\uafd5\uafd6\uafd7\uafd8\uafd9\uafda\uafdb\uafdc\uafdd\uafde\uafdf\uafe0\uafe1\uafe2\uafe3\uafe4\uafe5\uafe6\uafe7\uafe8\uafe9\uafea\uafeb\uafec\uafed\uafee\uafef\uaff0\uaff1\uaff2\uaff3\uaff4\uaff5\uaff6\uaff7\uaff8\uaff9\uaffa\uaffb\uaffc\uaffd\uaffe\uafff\ub000\ub001\ub002\ub003\ub004\ub005\ub006\ub007\ub008\ub009\ub00a\ub00b\ub00c\ub00d\ub00e\ub00f\ub010\ub011\ub012\ub013\ub014\ub015\ub016\ub017\ub018\ub019\ub01a\ub01b\ub01c\ub01d\ub01e\ub01f\ub020\ub021\ub022\ub023\ub024\ub025\ub026\ub027\ub028\ub029\ub02a\ub02b\ub02c\ub02d\ub02e\ub02f\ub030\ub031\ub032\ub033\ub034\ub035\ub036\ub037\ub038\ub039\ub03a\ub03b\ub03c\ub03d\ub03e\ub03f\ub040\ub041\ub042\ub043\ub044\ub045\ub046\ub047\ub048\ub049\ub04a\ub04b\ub04c\ub04d\ub04e\ub04f\ub050\ub051\ub052\ub053\ub054\ub055\ub056\ub057\ub058\ub059\ub05a\ub05b\ub05c\ub05d\ub05e\ub05f\ub060\ub061\ub062\ub063\ub064\ub065\ub066\ub067\ub068\ub069\ub06a\ub06b\ub06c\ub06d\ub06e\ub06f\ub070\ub071\ub072\ub073\ub074\ub075\ub076\ub077\ub078\ub079\ub07a\ub07b\ub07c\ub07d\ub07e\ub07f\ub080\ub081\ub082\ub083\ub084\ub085\ub086\ub087\ub088\ub089\ub08a\ub08b\ub08c\ub08d\ub08e\ub08f\ub090\ub091\ub092\ub093\ub094\ub095\ub096\ub097\ub098\ub099\ub09a\ub09b\ub09c\ub09d\ub09e\ub09f\ub0a0\ub0a1\ub0a2\ub0a3\ub0a4\ub0a5\ub0a6\ub0a7\ub0a8\ub0a9\ub0aa\ub0ab\ub0ac\ub0ad\ub0ae\ub0af\ub0b0\ub0b1\ub0b2\ub0b3\ub0b4\ub0b5\ub0b6\ub0b7\ub0b8\ub0b9\ub0ba\ub0bb\ub0bc\ub0bd\ub0be\ub0bf\ub0c0\ub0c1\ub0c2\ub0c3\ub0c4\ub0c5\ub0c6\ub0c7\ub0c8\ub0c9\ub0ca\ub0cb\ub0cc\ub0cd\ub0ce\ub0cf\ub0d0\ub0d1\ub0d2\ub0d3\ub0d4\ub0d5\ub0d6\ub0d7\ub0d8\ub0d9\ub0da\ub0db\ub0dc\ub0dd\ub0de\ub0df\ub0e0\ub0e1\ub0e2\ub0e3\ub0e4\ub0e5\ub0e6\ub0e7\ub0e8\ub0e9\ub0ea\ub0eb\ub0ec\ub0ed\ub0ee\ub0ef\ub0f0\ub0f1\ub0f2\ub0f3\ub0f4\ub0f5\ub0f6\ub0f7\ub0f8\ub0f9\ub0fa\ub0fb\ub0fc\ub0fd\ub0fe\ub0ff\ub100\ub101\ub102\ub103\ub104\ub105\ub106\ub107\ub108\ub109\ub10a\ub10b\ub10c\ub10d\ub10e\ub10f\ub110\ub111\ub112\ub113\ub114\ub115\ub116\ub117\ub118\ub119\ub11a\ub11b\ub11c\ub11d\ub11e\ub11f\ub120\ub121\ub122\ub123\ub124\ub125\ub126\ub127\ub128\ub129\ub12a\ub12b\ub12c\ub12d\ub12e\ub12f\ub130\ub131\ub132\ub133\ub134\ub135\ub136\ub137\ub138\ub139\ub13a\ub13b\ub13c\ub13d\ub13e\ub13f\ub140\ub141\ub142\ub143\ub144\ub145\ub146\ub147\ub148\ub149\ub14a\ub14b\ub14c\ub14d\ub14e\ub14f\ub150\ub151\ub152\ub153\ub154\ub155\ub156\ub157\ub158\ub159\ub15a\ub15b\ub15c\ub15d\ub15e\ub15f\ub160\ub161\ub162\ub163\ub164\ub165\ub166\ub167\ub168\ub169\ub16a\ub16b\ub16c\ub16d\ub16e\ub16f\ub170\ub171\ub172\ub173\ub174\ub175\ub176\ub177\ub178\ub179\ub17a\ub17b\ub17c\ub17d\ub17e\ub17f\ub180\ub181\ub182\ub183\ub184\ub185\ub186\ub187\ub188\ub189\ub18a\ub18b\ub18c\ub18d\ub18e\ub18f\ub190\ub191\ub192\ub193\ub194\ub195\ub196\ub197\ub198\ub199\ub19a\ub19b\ub19c\ub19d\ub19e\ub19f\ub1a0\ub1a1\ub1a2\ub1a3\ub1a4\ub1a5\ub1a6\ub1a7\ub1a8\ub1a9\ub1aa\ub1ab\ub1ac\ub1ad\ub1ae\ub1af\ub1b0\ub1b1\ub1b2\ub1b3\ub1b4\ub1b5\ub1b6\ub1b7\ub1b8\ub1b9\ub1ba\ub1bb\ub1bc\ub1bd\ub1be\ub1bf\ub1c0\ub1c1\ub1c2\ub1c3\ub1c4\ub1c5\ub1c6\ub1c7\ub1c8\ub1c9\ub1ca\ub1cb\ub1cc\ub1cd\ub1ce\ub1cf\ub1d0\ub1d1\ub1d2\ub1d3\ub1d4\ub1d5\ub1d6\ub1d7\ub1d8\ub1d9\ub1da\ub1db\ub1dc\ub1dd\ub1de\ub1df\ub1e0\ub1e1\ub1e2\ub1e3\ub1e4\ub1e5\ub1e6\ub1e7\ub1e8\ub1e9\ub1ea\ub1eb\ub1ec\ub1ed\ub1ee\ub1ef\ub1f0\ub1f1\ub1f2\ub1f3\ub1f4\ub1f5\ub1f6\ub1f7\ub1f8\ub1f9\ub1fa\ub1fb\ub1fc\ub1fd\ub1fe\ub1ff\ub200\ub201\ub202\ub203\ub204\ub205\ub206\ub207\ub208\ub209\ub20a\ub20b\ub20c\ub20d\ub20e\ub20f\ub210\ub211\ub212\ub213\ub214\ub215\ub216\ub217\ub218\ub219\ub21a\ub21b\ub21c\ub21d\ub21e\ub21f\ub220\ub221\ub222\ub223\ub224\ub225\ub226\ub227\ub228\ub229\ub22a\ub22b\ub22c\ub22d\ub22e\ub22f\ub230\ub231\ub232\ub233\ub234\ub235\ub236\ub237\ub238\ub239\ub23a\ub23b\ub23c\ub23d\ub23e\ub23f\ub240\ub241\ub242\ub243\ub244\ub245\ub246\ub247\ub248\ub249\ub24a\ub24b\ub24c\ub24d\ub24e\ub24f\ub250\ub251\ub252\ub253\ub254\ub255\ub256\ub257\ub258\ub259\ub25a\ub25b\ub25c\ub25d\ub25e\ub25f\ub260\ub261\ub262\ub263\ub264\ub265\ub266\ub267\ub268\ub269\ub26a\ub26b\ub26c\ub26d\ub26e\ub26f\ub270\ub271\ub272\ub273\ub274\ub275\ub276\ub277\ub278\ub279\ub27a\ub27b\ub27c\ub27d\ub27e\ub27f\ub280\ub281\ub282\ub283\ub284\ub285\ub286\ub287\ub288\ub289\ub28a\ub28b\ub28c\ub28d\ub28e\ub28f\ub290\ub291\ub292\ub293\ub294\ub295\ub296\ub297\ub298\ub299\ub29a\ub29b\ub29c\ub29d\ub29e\ub29f\ub2a0\ub2a1\ub2a2\ub2a3\ub2a4\ub2a5\ub2a6\ub2a7\ub2a8\ub2a9\ub2aa\ub2ab\ub2ac\ub2ad\ub2ae\ub2af\ub2b0\ub2b1\ub2b2\ub2b3\ub2b4\ub2b5\ub2b6\ub2b7\ub2b8\ub2b9\ub2ba\ub2bb\ub2bc\ub2bd\ub2be\ub2bf\ub2c0\ub2c1\ub2c2\ub2c3\ub2c4\ub2c5\ub2c6\ub2c7\ub2c8\ub2c9\ub2ca\ub2cb\ub2cc\ub2cd\ub2ce\ub2cf\ub2d0\ub2d1\ub2d2\ub2d3\ub2d4\ub2d5\ub2d6\ub2d7\ub2d8\ub2d9\ub2da\ub2db\ub2dc\ub2dd\ub2de\ub2df\ub2e0\ub2e1\ub2e2\ub2e3\ub2e4\ub2e5\ub2e6\ub2e7\ub2e8\ub2e9\ub2ea\ub2eb\ub2ec\ub2ed\ub2ee\ub2ef\ub2f0\ub2f1\ub2f2\ub2f3\ub2f4\ub2f5\ub2f6\ub2f7\ub2f8\ub2f9\ub2fa\ub2fb\ub2fc\ub2fd\ub2fe\ub2ff\ub300\ub301\ub302\ub303\ub304\ub305\ub306\ub307\ub308\ub309\ub30a\ub30b\ub30c\ub30d\ub30e\ub30f\ub310\ub311\ub312\ub313\ub314\ub315\ub316\ub317\ub318\ub319\ub31a\ub31b\ub31c\ub31d\ub31e\ub31f\ub320\ub321\ub322\ub323\ub324\ub325\ub326\ub327\ub328\ub329\ub32a\ub32b\ub32c\ub32d\ub32e\ub32f\ub330\ub331\ub332\ub333\ub334\ub335\ub336\ub337\ub338\ub339\ub33a\ub33b\ub33c\ub33d\ub33e\ub33f\ub340\ub341\ub342\ub343\ub344\ub345\ub346\ub347\ub348\ub349\ub34a\ub34b\ub34c\ub34d\ub34e\ub34f\ub350\ub351\ub352\ub353\ub354\ub355\ub356\ub357\ub358\ub359\ub35a\ub35b\ub35c\ub35d\ub35e\ub35f\ub360\ub361\ub362\ub363\ub364\ub365\ub366\ub367\ub368\ub369\ub36a\ub36b\ub36c\ub36d\ub36e\ub36f\ub370\ub371\ub372\ub373\ub374\ub375\ub376\ub377\ub378\ub379\ub37a\ub37b\ub37c\ub37d\ub37e\ub37f\ub380\ub381\ub382\ub383\ub384\ub385\ub386\ub387\ub388\ub389\ub38a\ub38b\ub38c\ub38d\ub38e\ub38f\ub390\ub391\ub392\ub393\ub394\ub395\ub396\ub397\ub398\ub399\ub39a\ub39b\ub39c\ub39d\ub39e\ub39f\ub3a0\ub3a1\ub3a2\ub3a3\ub3a4\ub3a5\ub3a6\ub3a7\ub3a8\ub3a9\ub3aa\ub3ab\ub3ac\ub3ad\ub3ae\ub3af\ub3b0\ub3b1\ub3b2\ub3b3\ub3b4\ub3b5\ub3b6\ub3b7\ub3b8\ub3b9\ub3ba\ub3bb\ub3bc\ub3bd\ub3be\ub3bf\ub3c0\ub3c1\ub3c2\ub3c3\ub3c4\ub3c5\ub3c6\ub3c7\ub3c8\ub3c9\ub3ca\ub3cb\ub3cc\ub3cd\ub3ce\ub3cf\ub3d0\ub3d1\ub3d2\ub3d3\ub3d4\ub3d5\ub3d6\ub3d7\ub3d8\ub3d9\ub3da\ub3db\ub3dc\ub3dd\ub3de\ub3df\ub3e0\ub3e1\ub3e2\ub3e3\ub3e4\ub3e5\ub3e6\ub3e7\ub3e8\ub3e9\ub3ea\ub3eb\ub3ec\ub3ed\ub3ee\ub3ef\ub3f0\ub3f1\ub3f2\ub3f3\ub3f4\ub3f5\ub3f6\ub3f7\ub3f8\ub3f9\ub3fa\ub3fb\ub3fc\ub3fd\ub3fe\ub3ff\ub400\ub401\ub402\ub403\ub404\ub405\ub406\ub407\ub408\ub409\ub40a\ub40b\ub40c\ub40d\ub40e\ub40f\ub410\ub411\ub412\ub413\ub414\ub415\ub416\ub417\ub418\ub419\ub41a\ub41b\ub41c\ub41d\ub41e\ub41f\ub420\ub421\ub422\ub423\ub424\ub425\ub426\ub427\ub428\ub429\ub42a\ub42b\ub42c\ub42d\ub42e\ub42f\ub430\ub431\ub432\ub433\ub434\ub435\ub436\ub437\ub438\ub439\ub43a\ub43b\ub43c\ub43d\ub43e\ub43f\ub440\ub441\ub442\ub443\ub444\ub445\ub446\ub447\ub448\ub449\ub44a\ub44b\ub44c\ub44d\ub44e\ub44f\ub450\ub451\ub452\ub453\ub454\ub455\ub456\ub457\ub458\ub459\ub45a\ub45b\ub45c\ub45d\ub45e\ub45f\ub460\ub461\ub462\ub463\ub464\ub465\ub466\ub467\ub468\ub469\ub46a\ub46b\ub46c\ub46d\ub46e\ub46f\ub470\ub471\ub472\ub473\ub474\ub475\ub476\ub477\ub478\ub479\ub47a\ub47b\ub47c\ub47d\ub47e\ub47f\ub480\ub481\ub482\ub483\ub484\ub485\ub486\ub487\ub488\ub489\ub48a\ub48b\ub48c\ub48d\ub48e\ub48f\ub490\ub491\ub492\ub493\ub494\ub495\ub496\ub497\ub498\ub499\ub49a\ub49b\ub49c\ub49d\ub49e\ub49f\ub4a0\ub4a1\ub4a2\ub4a3\ub4a4\ub4a5\ub4a6\ub4a7\ub4a8\ub4a9\ub4aa\ub4ab\ub4ac\ub4ad\ub4ae\ub4af\ub4b0\ub4b1\ub4b2\ub4b3\ub4b4\ub4b5\ub4b6\ub4b7\ub4b8\ub4b9\ub4ba\ub4bb\ub4bc\ub4bd\ub4be\ub4bf\ub4c0\ub4c1\ub4c2\ub4c3\ub4c4\ub4c5\ub4c6\ub4c7\ub4c8\ub4c9\ub4ca\ub4cb\ub4cc\ub4cd\ub4ce\ub4cf\ub4d0\ub4d1\ub4d2\ub4d3\ub4d4\ub4d5\ub4d6\ub4d7\ub4d8\ub4d9\ub4da\ub4db\ub4dc\ub4dd\ub4de\ub4df\ub4e0\ub4e1\ub4e2\ub4e3\ub4e4\ub4e5\ub4e6\ub4e7\ub4e8\ub4e9\ub4ea\ub4eb\ub4ec\ub4ed\ub4ee\ub4ef\ub4f0\ub4f1\ub4f2\ub4f3\ub4f4\ub4f5\ub4f6\ub4f7\ub4f8\ub4f9\ub4fa\ub4fb\ub4fc\ub4fd\ub4fe\ub4ff\ub500\ub501\ub502\ub503\ub504\ub505\ub506\ub507\ub508\ub509\ub50a\ub50b\ub50c\ub50d\ub50e\ub50f\ub510\ub511\ub512\ub513\ub514\ub515\ub516\ub517\ub518\ub519\ub51a\ub51b\ub51c\ub51d\ub51e\ub51f\ub520\ub521\ub522\ub523\ub524\ub525\ub526\ub527\ub528\ub529\ub52a\ub52b\ub52c\ub52d\ub52e\ub52f\ub530\ub531\ub532\ub533\ub534\ub535\ub536\ub537\ub538\ub539\ub53a\ub53b\ub53c\ub53d\ub53e\ub53f\ub540\ub541\ub542\ub543\ub544\ub545\ub546\ub547\ub548\ub549\ub54a\ub54b\ub54c\ub54d\ub54e\ub54f\ub550\ub551\ub552\ub553\ub554\ub555\ub556\ub557\ub558\ub559\ub55a\ub55b\ub55c\ub55d\ub55e\ub55f\ub560\ub561\ub562\ub563\ub564\ub565\ub566\ub567\ub568\ub569\ub56a\ub56b\ub56c\ub56d\ub56e\ub56f\ub570\ub571\ub572\ub573\ub574\ub575\ub576\ub577\ub578\ub579\ub57a\ub57b\ub57c\ub57d\ub57e\ub57f\ub580\ub581\ub582\ub583\ub584\ub585\ub586\ub587\ub588\ub589\ub58a\ub58b\ub58c\ub58d\ub58e\ub58f\ub590\ub591\ub592\ub593\ub594\ub595\ub596\ub597\ub598\ub599\ub59a\ub59b\ub59c\ub59d\ub59e\ub59f\ub5a0\ub5a1\ub5a2\ub5a3\ub5a4\ub5a5\ub5a6\ub5a7\ub5a8\ub5a9\ub5aa\ub5ab\ub5ac\ub5ad\ub5ae\ub5af\ub5b0\ub5b1\ub5b2\ub5b3\ub5b4\ub5b5\ub5b6\ub5b7\ub5b8\ub5b9\ub5ba\ub5bb\ub5bc\ub5bd\ub5be\ub5bf\ub5c0\ub5c1\ub5c2\ub5c3\ub5c4\ub5c5\ub5c6\ub5c7\ub5c8\ub5c9\ub5ca\ub5cb\ub5cc\ub5cd\ub5ce\ub5cf\ub5d0\ub5d1\ub5d2\ub5d3\ub5d4\ub5d5\ub5d6\ub5d7\ub5d8\ub5d9\ub5da\ub5db\ub5dc\ub5dd\ub5de\ub5df\ub5e0\ub5e1\ub5e2\ub5e3\ub5e4\ub5e5\ub5e6\ub5e7\ub5e8\ub5e9\ub5ea\ub5eb\ub5ec\ub5ed\ub5ee\ub5ef\ub5f0\ub5f1\ub5f2\ub5f3\ub5f4\ub5f5\ub5f6\ub5f7\ub5f8\ub5f9\ub5fa\ub5fb\ub5fc\ub5fd\ub5fe\ub5ff\ub600\ub601\ub602\ub603\ub604\ub605\ub606\ub607\ub608\ub609\ub60a\ub60b\ub60c\ub60d\ub60e\ub60f\ub610\ub611\ub612\ub613\ub614\ub615\ub616\ub617\ub618\ub619\ub61a\ub61b\ub61c\ub61d\ub61e\ub61f\ub620\ub621\ub622\ub623\ub624\ub625\ub626\ub627\ub628\ub629\ub62a\ub62b\ub62c\ub62d\ub62e\ub62f\ub630\ub631\ub632\ub633\ub634\ub635\ub636\ub637\ub638\ub639\ub63a\ub63b\ub63c\ub63d\ub63e\ub63f\ub640\ub641\ub642\ub643\ub644\ub645\ub646\ub647\ub648\ub649\ub64a\ub64b\ub64c\ub64d\ub64e\ub64f\ub650\ub651\ub652\ub653\ub654\ub655\ub656\ub657\ub658\ub659\ub65a\ub65b\ub65c\ub65d\ub65e\ub65f\ub660\ub661\ub662\ub663\ub664\ub665\ub666\ub667\ub668\ub669\ub66a\ub66b\ub66c\ub66d\ub66e\ub66f\ub670\ub671\ub672\ub673\ub674\ub675\ub676\ub677\ub678\ub679\ub67a\ub67b\ub67c\ub67d\ub67e\ub67f\ub680\ub681\ub682\ub683\ub684\ub685\ub686\ub687\ub688\ub689\ub68a\ub68b\ub68c\ub68d\ub68e\ub68f\ub690\ub691\ub692\ub693\ub694\ub695\ub696\ub697\ub698\ub699\ub69a\ub69b\ub69c\ub69d\ub69e\ub69f\ub6a0\ub6a1\ub6a2\ub6a3\ub6a4\ub6a5\ub6a6\ub6a7\ub6a8\ub6a9\ub6aa\ub6ab\ub6ac\ub6ad\ub6ae\ub6af\ub6b0\ub6b1\ub6b2\ub6b3\ub6b4\ub6b5\ub6b6\ub6b7\ub6b8\ub6b9\ub6ba\ub6bb\ub6bc\ub6bd\ub6be\ub6bf\ub6c0\ub6c1\ub6c2\ub6c3\ub6c4\ub6c5\ub6c6\ub6c7\ub6c8\ub6c9\ub6ca\ub6cb\ub6cc\ub6cd\ub6ce\ub6cf\ub6d0\ub6d1\ub6d2\ub6d3\ub6d4\ub6d5\ub6d6\ub6d7\ub6d8\ub6d9\ub6da\ub6db\ub6dc\ub6dd\ub6de\ub6df\ub6e0\ub6e1\ub6e2\ub6e3\ub6e4\ub6e5\ub6e6\ub6e7\ub6e8\ub6e9\ub6ea\ub6eb\ub6ec\ub6ed\ub6ee\ub6ef\ub6f0\ub6f1\ub6f2\ub6f3\ub6f4\ub6f5\ub6f6\ub6f7\ub6f8\ub6f9\ub6fa\ub6fb\ub6fc\ub6fd\ub6fe\ub6ff\ub700\ub701\ub702\ub703\ub704\ub705\ub706\ub707\ub708\ub709\ub70a\ub70b\ub70c\ub70d\ub70e\ub70f\ub710\ub711\ub712\ub713\ub714\ub715\ub716\ub717\ub718\ub719\ub71a\ub71b\ub71c\ub71d\ub71e\ub71f\ub720\ub721\ub722\ub723\ub724\ub725\ub726\ub727\ub728\ub729\ub72a\ub72b\ub72c\ub72d\ub72e\ub72f\ub730\ub731\ub732\ub733\ub734\ub735\ub736\ub737\ub738\ub739\ub73a\ub73b\ub73c\ub73d\ub73e\ub73f\ub740\ub741\ub742\ub743\ub744\ub745\ub746\ub747\ub748\ub749\ub74a\ub74b\ub74c\ub74d\ub74e\ub74f\ub750\ub751\ub752\ub753\ub754\ub755\ub756\ub757\ub758\ub759\ub75a\ub75b\ub75c\ub75d\ub75e\ub75f\ub760\ub761\ub762\ub763\ub764\ub765\ub766\ub767\ub768\ub769\ub76a\ub76b\ub76c\ub76d\ub76e\ub76f\ub770\ub771\ub772\ub773\ub774\ub775\ub776\ub777\ub778\ub779\ub77a\ub77b\ub77c\ub77d\ub77e\ub77f\ub780\ub781\ub782\ub783\ub784\ub785\ub786\ub787\ub788\ub789\ub78a\ub78b\ub78c\ub78d\ub78e\ub78f\ub790\ub791\ub792\ub793\ub794\ub795\ub796\ub797\ub798\ub799\ub79a\ub79b\ub79c\ub79d\ub79e\ub79f\ub7a0\ub7a1\ub7a2\ub7a3\ub7a4\ub7a5\ub7a6\ub7a7\ub7a8\ub7a9\ub7aa\ub7ab\ub7ac\ub7ad\ub7ae\ub7af\ub7b0\ub7b1\ub7b2\ub7b3\ub7b4\ub7b5\ub7b6\ub7b7\ub7b8\ub7b9\ub7ba\ub7bb\ub7bc\ub7bd\ub7be\ub7bf\ub7c0\ub7c1\ub7c2\ub7c3\ub7c4\ub7c5\ub7c6\ub7c7\ub7c8\ub7c9\ub7ca\ub7cb\ub7cc\ub7cd\ub7ce\ub7cf\ub7d0\ub7d1\ub7d2\ub7d3\ub7d4\ub7d5\ub7d6\ub7d7\ub7d8\ub7d9\ub7da\ub7db\ub7dc\ub7dd\ub7de\ub7df\ub7e0\ub7e1\ub7e2\ub7e3\ub7e4\ub7e5\ub7e6\ub7e7\ub7e8\ub7e9\ub7ea\ub7eb\ub7ec\ub7ed\ub7ee\ub7ef\ub7f0\ub7f1\ub7f2\ub7f3\ub7f4\ub7f5\ub7f6\ub7f7\ub7f8\ub7f9\ub7fa\ub7fb\ub7fc\ub7fd\ub7fe\ub7ff\ub800\ub801\ub802\ub803\ub804\ub805\ub806\ub807\ub808\ub809\ub80a\ub80b\ub80c\ub80d\ub80e\ub80f\ub810\ub811\ub812\ub813\ub814\ub815\ub816\ub817\ub818\ub819\ub81a\ub81b\ub81c\ub81d\ub81e\ub81f\ub820\ub821\ub822\ub823\ub824\ub825\ub826\ub827\ub828\ub829\ub82a\ub82b\ub82c\ub82d\ub82e\ub82f\ub830\ub831\ub832\ub833\ub834\ub835\ub836\ub837\ub838\ub839\ub83a\ub83b\ub83c\ub83d\ub83e\ub83f\ub840\ub841\ub842\ub843\ub844\ub845\ub846\ub847\ub848\ub849\ub84a\ub84b\ub84c\ub84d\ub84e\ub84f\ub850\ub851\ub852\ub853\ub854\ub855\ub856\ub857\ub858\ub859\ub85a\ub85b\ub85c\ub85d\ub85e\ub85f\ub860\ub861\ub862\ub863\ub864\ub865\ub866\ub867\ub868\ub869\ub86a\ub86b\ub86c\ub86d\ub86e\ub86f\ub870\ub871\ub872\ub873\ub874\ub875\ub876\ub877\ub878\ub879\ub87a\ub87b\ub87c\ub87d\ub87e\ub87f\ub880\ub881\ub882\ub883\ub884\ub885\ub886\ub887\ub888\ub889\ub88a\ub88b\ub88c\ub88d\ub88e\ub88f\ub890\ub891\ub892\ub893\ub894\ub895\ub896\ub897\ub898\ub899\ub89a\ub89b\ub89c\ub89d\ub89e\ub89f\ub8a0\ub8a1\ub8a2\ub8a3\ub8a4\ub8a5\ub8a6\ub8a7\ub8a8\ub8a9\ub8aa\ub8ab\ub8ac\ub8ad\ub8ae\ub8af\ub8b0\ub8b1\ub8b2\ub8b3\ub8b4\ub8b5\ub8b6\ub8b7\ub8b8\ub8b9\ub8ba\ub8bb\ub8bc\ub8bd\ub8be\ub8bf\ub8c0\ub8c1\ub8c2\ub8c3\ub8c4\ub8c5\ub8c6\ub8c7\ub8c8\ub8c9\ub8ca\ub8cb\ub8cc\ub8cd\ub8ce\ub8cf\ub8d0\ub8d1\ub8d2\ub8d3\ub8d4\ub8d5\ub8d6\ub8d7\ub8d8\ub8d9\ub8da\ub8db\ub8dc\ub8dd\ub8de\ub8df\ub8e0\ub8e1\ub8e2\ub8e3\ub8e4\ub8e5\ub8e6\ub8e7\ub8e8\ub8e9\ub8ea\ub8eb\ub8ec\ub8ed\ub8ee\ub8ef\ub8f0\ub8f1\ub8f2\ub8f3\ub8f4\ub8f5\ub8f6\ub8f7\ub8f8\ub8f9\ub8fa\ub8fb\ub8fc\ub8fd\ub8fe\ub8ff\ub900\ub901\ub902\ub903\ub904\ub905\ub906\ub907\ub908\ub909\ub90a\ub90b\ub90c\ub90d\ub90e\ub90f\ub910\ub911\ub912\ub913\ub914\ub915\ub916\ub917\ub918\ub919\ub91a\ub91b\ub91c\ub91d\ub91e\ub91f\ub920\ub921\ub922\ub923\ub924\ub925\ub926\ub927\ub928\ub929\ub92a\ub92b\ub92c\ub92d\ub92e\ub92f\ub930\ub931\ub932\ub933\ub934\ub935\ub936\ub937\ub938\ub939\ub93a\ub93b\ub93c\ub93d\ub93e\ub93f\ub940\ub941\ub942\ub943\ub944\ub945\ub946\ub947\ub948\ub949\ub94a\ub94b\ub94c\ub94d\ub94e\ub94f\ub950\ub951\ub952\ub953\ub954\ub955\ub956\ub957\ub958\ub959\ub95a\ub95b\ub95c\ub95d\ub95e\ub95f\ub960\ub961\ub962\ub963\ub964\ub965\ub966\ub967\ub968\ub969\ub96a\ub96b\ub96c\ub96d\ub96e\ub96f\ub970\ub971\ub972\ub973\ub974\ub975\ub976\ub977\ub978\ub979\ub97a\ub97b\ub97c\ub97d\ub97e\ub97f\ub980\ub981\ub982\ub983\ub984\ub985\ub986\ub987\ub988\ub989\ub98a\ub98b\ub98c\ub98d\ub98e\ub98f\ub990\ub991\ub992\ub993\ub994\ub995\ub996\ub997\ub998\ub999\ub99a\ub99b\ub99c\ub99d\ub99e\ub99f\ub9a0\ub9a1\ub9a2\ub9a3\ub9a4\ub9a5\ub9a6\ub9a7\ub9a8\ub9a9\ub9aa\ub9ab\ub9ac\ub9ad\ub9ae\ub9af\ub9b0\ub9b1\ub9b2\ub9b3\ub9b4\ub9b5\ub9b6\ub9b7\ub9b8\ub9b9\ub9ba\ub9bb\ub9bc\ub9bd\ub9be\ub9bf\ub9c0\ub9c1\ub9c2\ub9c3\ub9c4\ub9c5\ub9c6\ub9c7\ub9c8\ub9c9\ub9ca\ub9cb\ub9cc\ub9cd\ub9ce\ub9cf\ub9d0\ub9d1\ub9d2\ub9d3\ub9d4\ub9d5\ub9d6\ub9d7\ub9d8\ub9d9\ub9da\ub9db\ub9dc\ub9dd\ub9de\ub9df\ub9e0\ub9e1\ub9e2\ub9e3\ub9e4\ub9e5\ub9e6\ub9e7\ub9e8\ub9e9\ub9ea\ub9eb\ub9ec\ub9ed\ub9ee\ub9ef\ub9f0\ub9f1\ub9f2\ub9f3\ub9f4\ub9f5\ub9f6\ub9f7\ub9f8\ub9f9\ub9fa\ub9fb\ub9fc\ub9fd\ub9fe\ub9ff\uba00\uba01\uba02\uba03\uba04\uba05\uba06\uba07\uba08\uba09\uba0a\uba0b\uba0c\uba0d\uba0e\uba0f\uba10\uba11\uba12\uba13\uba14\uba15\uba16\uba17\uba18\uba19\uba1a\uba1b\uba1c\uba1d\uba1e\uba1f\uba20\uba21\uba22\uba23\uba24\uba25\uba26\uba27\uba28\uba29\uba2a\uba2b\uba2c\uba2d\uba2e\uba2f\uba30\uba31\uba32\uba33\uba34\uba35\uba36\uba37\uba38\uba39\uba3a\uba3b\uba3c\uba3d\uba3e\uba3f\uba40\uba41\uba42\uba43\uba44\uba45\uba46\uba47\uba48\uba49\uba4a\uba4b\uba4c\uba4d\uba4e\uba4f\uba50\uba51\uba52\uba53\uba54\uba55\uba56\uba57\uba58\uba59\uba5a\uba5b\uba5c\uba5d\uba5e\uba5f\uba60\uba61\uba62\uba63\uba64\uba65\uba66\uba67\uba68\uba69\uba6a\uba6b\uba6c\uba6d\uba6e\uba6f\uba70\uba71\uba72\uba73\uba74\uba75\uba76\uba77\uba78\uba79\uba7a\uba7b\uba7c\uba7d\uba7e\uba7f\uba80\uba81\uba82\uba83\uba84\uba85\uba86\uba87\uba88\uba89\uba8a\uba8b\uba8c\uba8d\uba8e\uba8f\uba90\uba91\uba92\uba93\uba94\uba95\uba96\uba97\uba98\uba99\uba9a\uba9b\uba9c\uba9d\uba9e\uba9f\ubaa0\ubaa1\ubaa2\ubaa3\ubaa4\ubaa5\ubaa6\ubaa7\ubaa8\ubaa9\ubaaa\ubaab\ubaac\ubaad\ubaae\ubaaf\ubab0\ubab1\ubab2\ubab3\ubab4\ubab5\ubab6\ubab7\ubab8\ubab9\ubaba\ubabb\ubabc\ubabd\ubabe\ubabf\ubac0\ubac1\ubac2\ubac3\ubac4\ubac5\ubac6\ubac7\ubac8\ubac9\ubaca\ubacb\ubacc\ubacd\ubace\ubacf\ubad0\ubad1\ubad2\ubad3\ubad4\ubad5\ubad6\ubad7\ubad8\ubad9\ubada\ubadb\ubadc\ubadd\ubade\ubadf\ubae0\ubae1\ubae2\ubae3\ubae4\ubae5\ubae6\ubae7\ubae8\ubae9\ubaea\ubaeb\ubaec\ubaed\ubaee\ubaef\ubaf0\ubaf1\ubaf2\ubaf3\ubaf4\ubaf5\ubaf6\ubaf7\ubaf8\ubaf9\ubafa\ubafb\ubafc\ubafd\ubafe\ubaff\ubb00\ubb01\ubb02\ubb03\ubb04\ubb05\ubb06\ubb07\ubb08\ubb09\ubb0a\ubb0b\ubb0c\ubb0d\ubb0e\ubb0f\ubb10\ubb11\ubb12\ubb13\ubb14\ubb15\ubb16\ubb17\ubb18\ubb19\ubb1a\ubb1b\ubb1c\ubb1d\ubb1e\ubb1f\ubb20\ubb21\ubb22\ubb23\ubb24\ubb25\ubb26\ubb27\ubb28\ubb29\ubb2a\ubb2b\ubb2c\ubb2d\ubb2e\ubb2f\ubb30\ubb31\ubb32\ubb33\ubb34\ubb35\ubb36\ubb37\ubb38\ubb39\ubb3a\ubb3b\ubb3c\ubb3d\ubb3e\ubb3f\ubb40\ubb41\ubb42\ubb43\ubb44\ubb45\ubb46\ubb47\ubb48\ubb49\ubb4a\ubb4b\ubb4c\ubb4d\ubb4e\ubb4f\ubb50\ubb51\ubb52\ubb53\ubb54\ubb55\ubb56\ubb57\ubb58\ubb59\ubb5a\ubb5b\ubb5c\ubb5d\ubb5e\ubb5f\ubb60\ubb61\ubb62\ubb63\ubb64\ubb65\ubb66\ubb67\ubb68\ubb69\ubb6a\ubb6b\ubb6c\ubb6d\ubb6e\ubb6f\ubb70\ubb71\ubb72\ubb73\ubb74\ubb75\ubb76\ubb77\ubb78\ubb79\ubb7a\ubb7b\ubb7c\ubb7d\ubb7e\ubb7f\ubb80\ubb81\ubb82\ubb83\ubb84\ubb85\ubb86\ubb87\ubb88\ubb89\ubb8a\ubb8b\ubb8c\ubb8d\ubb8e\ubb8f\ubb90\ubb91\ubb92\ubb93\ubb94\ubb95\ubb96\ubb97\ubb98\ubb99\ubb9a\ubb9b\ubb9c\ubb9d\ubb9e\ubb9f\ubba0\ubba1\ubba2\ubba3\ubba4\ubba5\ubba6\ubba7\ubba8\ubba9\ubbaa\ubbab\ubbac\ubbad\ubbae\ubbaf\ubbb0\ubbb1\ubbb2\ubbb3\ubbb4\ubbb5\ubbb6\ubbb7\ubbb8\ubbb9\ubbba\ubbbb\ubbbc\ubbbd\ubbbe\ubbbf\ubbc0\ubbc1\ubbc2\ubbc3\ubbc4\ubbc5\ubbc6\ubbc7\ubbc8\ubbc9\ubbca\ubbcb\ubbcc\ubbcd\ubbce\ubbcf\ubbd0\ubbd1\ubbd2\ubbd3\ubbd4\ubbd5\ubbd6\ubbd7\ubbd8\ubbd9\ubbda\ubbdb\ubbdc\ubbdd\ubbde\ubbdf\ubbe0\ubbe1\ubbe2\ubbe3\ubbe4\ubbe5\ubbe6\ubbe7\ubbe8\ubbe9\ubbea\ubbeb\ubbec\ubbed\ubbee\ubbef\ubbf0\ubbf1\ubbf2\ubbf3\ubbf4\ubbf5\ubbf6\ubbf7\ubbf8\ubbf9\ubbfa\ubbfb\ubbfc\ubbfd\ubbfe\ubbff\ubc00\ubc01\ubc02\ubc03\ubc04\ubc05\ubc06\ubc07\ubc08\ubc09\ubc0a\ubc0b\ubc0c\ubc0d\ubc0e\ubc0f\ubc10\ubc11\ubc12\ubc13\ubc14\ubc15\ubc16\ubc17\ubc18\ubc19\ubc1a\ubc1b\ubc1c\ubc1d\ubc1e\ubc1f\ubc20\ubc21\ubc22\ubc23\ubc24\ubc25\ubc26\ubc27\ubc28\ubc29\ubc2a\ubc2b\ubc2c\ubc2d\ubc2e\ubc2f\ubc30\ubc31\ubc32\ubc33\ubc34\ubc35\ubc36\ubc37\ubc38\ubc39\ubc3a\ubc3b\ubc3c\ubc3d\ubc3e\ubc3f\ubc40\ubc41\ubc42\ubc43\ubc44\ubc45\ubc46\ubc47\ubc48\ubc49\ubc4a\ubc4b\ubc4c\ubc4d\ubc4e\ubc4f\ubc50\ubc51\ubc52\ubc53\ubc54\ubc55\ubc56\ubc57\ubc58\ubc59\ubc5a\ubc5b\ubc5c\ubc5d\ubc5e\ubc5f\ubc60\ubc61\ubc62\ubc63\ubc64\ubc65\ubc66\ubc67\ubc68\ubc69\ubc6a\ubc6b\ubc6c\ubc6d\ubc6e\ubc6f\ubc70\ubc71\ubc72\ubc73\ubc74\ubc75\ubc76\ubc77\ubc78\ubc79\ubc7a\ubc7b\ubc7c\ubc7d\ubc7e\ubc7f\ubc80\ubc81\ubc82\ubc83\ubc84\ubc85\ubc86\ubc87\ubc88\ubc89\ubc8a\ubc8b\ubc8c\ubc8d\ubc8e\ubc8f\ubc90\ubc91\ubc92\ubc93\ubc94\ubc95\ubc96\ubc97\ubc98\ubc99\ubc9a\ubc9b\ubc9c\ubc9d\ubc9e\ubc9f\ubca0\ubca1\ubca2\ubca3\ubca4\ubca5\ubca6\ubca7\ubca8\ubca9\ubcaa\ubcab\ubcac\ubcad\ubcae\ubcaf\ubcb0\ubcb1\ubcb2\ubcb3\ubcb4\ubcb5\ubcb6\ubcb7\ubcb8\ubcb9\ubcba\ubcbb\ubcbc\ubcbd\ubcbe\ubcbf\ubcc0\ubcc1\ubcc2\ubcc3\ubcc4\ubcc5\ubcc6\ubcc7\ubcc8\ubcc9\ubcca\ubccb\ubccc\ubccd\ubcce\ubccf\ubcd0\ubcd1\ubcd2\ubcd3\ubcd4\ubcd5\ubcd6\ubcd7\ubcd8\ubcd9\ubcda\ubcdb\ubcdc\ubcdd\ubcde\ubcdf\ubce0\ubce1\ubce2\ubce3\ubce4\ubce5\ubce6\ubce7\ubce8\ubce9\ubcea\ubceb\ubcec\ubced\ubcee\ubcef\ubcf0\ubcf1\ubcf2\ubcf3\ubcf4\ubcf5\ubcf6\ubcf7\ubcf8\ubcf9\ubcfa\ubcfb\ubcfc\ubcfd\ubcfe\ubcff\ubd00\ubd01\ubd02\ubd03\ubd04\ubd05\ubd06\ubd07\ubd08\ubd09\ubd0a\ubd0b\ubd0c\ubd0d\ubd0e\ubd0f\ubd10\ubd11\ubd12\ubd13\ubd14\ubd15\ubd16\ubd17\ubd18\ubd19\ubd1a\ubd1b\ubd1c\ubd1d\ubd1e\ubd1f\ubd20\ubd21\ubd22\ubd23\ubd24\ubd25\ubd26\ubd27\ubd28\ubd29\ubd2a\ubd2b\ubd2c\ubd2d\ubd2e\ubd2f\ubd30\ubd31\ubd32\ubd33\ubd34\ubd35\ubd36\ubd37\ubd38\ubd39\ubd3a\ubd3b\ubd3c\ubd3d\ubd3e\ubd3f\ubd40\ubd41\ubd42\ubd43\ubd44\ubd45\ubd46\ubd47\ubd48\ubd49\ubd4a\ubd4b\ubd4c\ubd4d\ubd4e\ubd4f\ubd50\ubd51\ubd52\ubd53\ubd54\ubd55\ubd56\ubd57\ubd58\ubd59\ubd5a\ubd5b\ubd5c\ubd5d\ubd5e\ubd5f\ubd60\ubd61\ubd62\ubd63\ubd64\ubd65\ubd66\ubd67\ubd68\ubd69\ubd6a\ubd6b\ubd6c\ubd6d\ubd6e\ubd6f\ubd70\ubd71\ubd72\ubd73\ubd74\ubd75\ubd76\ubd77\ubd78\ubd79\ubd7a\ubd7b\ubd7c\ubd7d\ubd7e\ubd7f\ubd80\ubd81\ubd82\ubd83\ubd84\ubd85\ubd86\ubd87\ubd88\ubd89\ubd8a\ubd8b\ubd8c\ubd8d\ubd8e\ubd8f\ubd90\ubd91\ubd92\ubd93\ubd94\ubd95\ubd96\ubd97\ubd98\ubd99\ubd9a\ubd9b\ubd9c\ubd9d\ubd9e\ubd9f\ubda0\ubda1\ubda2\ubda3\ubda4\ubda5\ubda6\ubda7\ubda8\ubda9\ubdaa\ubdab\ubdac\ubdad\ubdae\ubdaf\ubdb0\ubdb1\ubdb2\ubdb3\ubdb4\ubdb5\ubdb6\ubdb7\ubdb8\ubdb9\ubdba\ubdbb\ubdbc\ubdbd\ubdbe\ubdbf\ubdc0\ubdc1\ubdc2\ubdc3\ubdc4\ubdc5\ubdc6\ubdc7\ubdc8\ubdc9\ubdca\ubdcb\ubdcc\ubdcd\ubdce\ubdcf\ubdd0\ubdd1\ubdd2\ubdd3\ubdd4\ubdd5\ubdd6\ubdd7\ubdd8\ubdd9\ubdda\ubddb\ubddc\ubddd\ubdde\ubddf\ubde0\ubde1\ubde2\ubde3\ubde4\ubde5\ubde6\ubde7\ubde8\ubde9\ubdea\ubdeb\ubdec\ubded\ubdee\ubdef\ubdf0\ubdf1\ubdf2\ubdf3\ubdf4\ubdf5\ubdf6\ubdf7\ubdf8\ubdf9\ubdfa\ubdfb\ubdfc\ubdfd\ubdfe\ubdff\ube00\ube01\ube02\ube03\ube04\ube05\ube06\ube07\ube08\ube09\ube0a\ube0b\ube0c\ube0d\ube0e\ube0f\ube10\ube11\ube12\ube13\ube14\ube15\ube16\ube17\ube18\ube19\ube1a\ube1b\ube1c\ube1d\ube1e\ube1f\ube20\ube21\ube22\ube23\ube24\ube25\ube26\ube27\ube28\ube29\ube2a\ube2b\ube2c\ube2d\ube2e\ube2f\ube30\ube31\ube32\ube33\ube34\ube35\ube36\ube37\ube38\ube39\ube3a\ube3b\ube3c\ube3d\ube3e\ube3f\ube40\ube41\ube42\ube43\ube44\ube45\ube46\ube47\ube48\ube49\ube4a\ube4b\ube4c\ube4d\ube4e\ube4f\ube50\ube51\ube52\ube53\ube54\ube55\ube56\ube57\ube58\ube59\ube5a\ube5b\ube5c\ube5d\ube5e\ube5f\ube60\ube61\ube62\ube63\ube64\ube65\ube66\ube67\ube68\ube69\ube6a\ube6b\ube6c\ube6d\ube6e\ube6f\ube70\ube71\ube72\ube73\ube74\ube75\ube76\ube77\ube78\ube79\ube7a\ube7b\ube7c\ube7d\ube7e\ube7f\ube80\ube81\ube82\ube83\ube84\ube85\ube86\ube87\ube88\ube89\ube8a\ube8b\ube8c\ube8d\ube8e\ube8f\ube90\ube91\ube92\ube93\ube94\ube95\ube96\ube97\ube98\ube99\ube9a\ube9b\ube9c\ube9d\ube9e\ube9f\ubea0\ubea1\ubea2\ubea3\ubea4\ubea5\ubea6\ubea7\ubea8\ubea9\ubeaa\ubeab\ubeac\ubead\ubeae\ubeaf\ubeb0\ubeb1\ubeb2\ubeb3\ubeb4\ubeb5\ubeb6\ubeb7\ubeb8\ubeb9\ubeba\ubebb\ubebc\ubebd\ubebe\ubebf\ubec0\ubec1\ubec2\ubec3\ubec4\ubec5\ubec6\ubec7\ubec8\ubec9\ubeca\ubecb\ubecc\ubecd\ubece\ubecf\ubed0\ubed1\ubed2\ubed3\ubed4\ubed5\ubed6\ubed7\ubed8\ubed9\ubeda\ubedb\ubedc\ubedd\ubede\ubedf\ubee0\ubee1\ubee2\ubee3\ubee4\ubee5\ubee6\ubee7\ubee8\ubee9\ubeea\ubeeb\ubeec\ubeed\ubeee\ubeef\ubef0\ubef1\ubef2\ubef3\ubef4\ubef5\ubef6\ubef7\ubef8\ubef9\ubefa\ubefb\ubefc\ubefd\ubefe\ubeff\ubf00\ubf01\ubf02\ubf03\ubf04\ubf05\ubf06\ubf07\ubf08\ubf09\ubf0a\ubf0b\ubf0c\ubf0d\ubf0e\ubf0f\ubf10\ubf11\ubf12\ubf13\ubf14\ubf15\ubf16\ubf17\ubf18\ubf19\ubf1a\ubf1b\ubf1c\ubf1d\ubf1e\ubf1f\ubf20\ubf21\ubf22\ubf23\ubf24\ubf25\ubf26\ubf27\ubf28\ubf29\ubf2a\ubf2b\ubf2c\ubf2d\ubf2e\ubf2f\ubf30\ubf31\ubf32\ubf33\ubf34\ubf35\ubf36\ubf37\ubf38\ubf39\ubf3a\ubf3b\ubf3c\ubf3d\ubf3e\ubf3f\ubf40\ubf41\ubf42\ubf43\ubf44\ubf45\ubf46\ubf47\ubf48\ubf49\ubf4a\ubf4b\ubf4c\ubf4d\ubf4e\ubf4f\ubf50\ubf51\ubf52\ubf53\ubf54\ubf55\ubf56\ubf57\ubf58\ubf59\ubf5a\ubf5b\ubf5c\ubf5d\ubf5e\ubf5f\ubf60\ubf61\ubf62\ubf63\ubf64\ubf65\ubf66\ubf67\ubf68\ubf69\ubf6a\ubf6b\ubf6c\ubf6d\ubf6e\ubf6f\ubf70\ubf71\ubf72\ubf73\ubf74\ubf75\ubf76\ubf77\ubf78\ubf79\ubf7a\ubf7b\ubf7c\ubf7d\ubf7e\ubf7f\ubf80\ubf81\ubf82\ubf83\ubf84\ubf85\ubf86\ubf87\ubf88\ubf89\ubf8a\ubf8b\ubf8c\ubf8d\ubf8e\ubf8f\ubf90\ubf91\ubf92\ubf93\ubf94\ubf95\ubf96\ubf97\ubf98\ubf99\ubf9a\ubf9b\ubf9c\ubf9d\ubf9e\ubf9f\ubfa0\ubfa1\ubfa2\ubfa3\ubfa4\ubfa5\ubfa6\ubfa7\ubfa8\ubfa9\ubfaa\ubfab\ubfac\ubfad\ubfae\ubfaf\ubfb0\ubfb1\ubfb2\ubfb3\ubfb4\ubfb5\ubfb6\ubfb7\ubfb8\ubfb9\ubfba\ubfbb\ubfbc\ubfbd\ubfbe\ubfbf\ubfc0\ubfc1\ubfc2\ubfc3\ubfc4\ubfc5\ubfc6\ubfc7\ubfc8\ubfc9\ubfca\ubfcb\ubfcc\ubfcd\ubfce\ubfcf\ubfd0\ubfd1\ubfd2\ubfd3\ubfd4\ubfd5\ubfd6\ubfd7\ubfd8\ubfd9\ubfda\ubfdb\ubfdc\ubfdd\ubfde\ubfdf\ubfe0\ubfe1\ubfe2\ubfe3\ubfe4\ubfe5\ubfe6\ubfe7\ubfe8\ubfe9\ubfea\ubfeb\ubfec\ubfed\ubfee\ubfef\ubff0\ubff1\ubff2\ubff3\ubff4\ubff5\ubff6\ubff7\ubff8\ubff9\ubffa\ubffb\ubffc\ubffd\ubffe\ubfff\uc000\uc001\uc002\uc003\uc004\uc005\uc006\uc007\uc008\uc009\uc00a\uc00b\uc00c\uc00d\uc00e\uc00f\uc010\uc011\uc012\uc013\uc014\uc015\uc016\uc017\uc018\uc019\uc01a\uc01b\uc01c\uc01d\uc01e\uc01f\uc020\uc021\uc022\uc023\uc024\uc025\uc026\uc027\uc028\uc029\uc02a\uc02b\uc02c\uc02d\uc02e\uc02f\uc030\uc031\uc032\uc033\uc034\uc035\uc036\uc037\uc038\uc039\uc03a\uc03b\uc03c\uc03d\uc03e\uc03f\uc040\uc041\uc042\uc043\uc044\uc045\uc046\uc047\uc048\uc049\uc04a\uc04b\uc04c\uc04d\uc04e\uc04f\uc050\uc051\uc052\uc053\uc054\uc055\uc056\uc057\uc058\uc059\uc05a\uc05b\uc05c\uc05d\uc05e\uc05f\uc060\uc061\uc062\uc063\uc064\uc065\uc066\uc067\uc068\uc069\uc06a\uc06b\uc06c\uc06d\uc06e\uc06f\uc070\uc071\uc072\uc073\uc074\uc075\uc076\uc077\uc078\uc079\uc07a\uc07b\uc07c\uc07d\uc07e\uc07f\uc080\uc081\uc082\uc083\uc084\uc085\uc086\uc087\uc088\uc089\uc08a\uc08b\uc08c\uc08d\uc08e\uc08f\uc090\uc091\uc092\uc093\uc094\uc095\uc096\uc097\uc098\uc099\uc09a\uc09b\uc09c\uc09d\uc09e\uc09f\uc0a0\uc0a1\uc0a2\uc0a3\uc0a4\uc0a5\uc0a6\uc0a7\uc0a8\uc0a9\uc0aa\uc0ab\uc0ac\uc0ad\uc0ae\uc0af\uc0b0\uc0b1\uc0b2\uc0b3\uc0b4\uc0b5\uc0b6\uc0b7\uc0b8\uc0b9\uc0ba\uc0bb\uc0bc\uc0bd\uc0be\uc0bf\uc0c0\uc0c1\uc0c2\uc0c3\uc0c4\uc0c5\uc0c6\uc0c7\uc0c8\uc0c9\uc0ca\uc0cb\uc0cc\uc0cd\uc0ce\uc0cf\uc0d0\uc0d1\uc0d2\uc0d3\uc0d4\uc0d5\uc0d6\uc0d7\uc0d8\uc0d9\uc0da\uc0db\uc0dc\uc0dd\uc0de\uc0df\uc0e0\uc0e1\uc0e2\uc0e3\uc0e4\uc0e5\uc0e6\uc0e7\uc0e8\uc0e9\uc0ea\uc0eb\uc0ec\uc0ed\uc0ee\uc0ef\uc0f0\uc0f1\uc0f2\uc0f3\uc0f4\uc0f5\uc0f6\uc0f7\uc0f8\uc0f9\uc0fa\uc0fb\uc0fc\uc0fd\uc0fe\uc0ff\uc100\uc101\uc102\uc103\uc104\uc105\uc106\uc107\uc108\uc109\uc10a\uc10b\uc10c\uc10d\uc10e\uc10f\uc110\uc111\uc112\uc113\uc114\uc115\uc116\uc117\uc118\uc119\uc11a\uc11b\uc11c\uc11d\uc11e\uc11f\uc120\uc121\uc122\uc123\uc124\uc125\uc126\uc127\uc128\uc129\uc12a\uc12b\uc12c\uc12d\uc12e\uc12f\uc130\uc131\uc132\uc133\uc134\uc135\uc136\uc137\uc138\uc139\uc13a\uc13b\uc13c\uc13d\uc13e\uc13f\uc140\uc141\uc142\uc143\uc144\uc145\uc146\uc147\uc148\uc149\uc14a\uc14b\uc14c\uc14d\uc14e\uc14f\uc150\uc151\uc152\uc153\uc154\uc155\uc156\uc157\uc158\uc159\uc15a\uc15b\uc15c\uc15d\uc15e\uc15f\uc160\uc161\uc162\uc163\uc164\uc165\uc166\uc167\uc168\uc169\uc16a\uc16b\uc16c\uc16d\uc16e\uc16f\uc170\uc171\uc172\uc173\uc174\uc175\uc176\uc177\uc178\uc179\uc17a\uc17b\uc17c\uc17d\uc17e\uc17f\uc180\uc181\uc182\uc183\uc184\uc185\uc186\uc187\uc188\uc189\uc18a\uc18b\uc18c\uc18d\uc18e\uc18f\uc190\uc191\uc192\uc193\uc194\uc195\uc196\uc197\uc198\uc199\uc19a\uc19b\uc19c\uc19d\uc19e\uc19f\uc1a0\uc1a1\uc1a2\uc1a3\uc1a4\uc1a5\uc1a6\uc1a7\uc1a8\uc1a9\uc1aa\uc1ab\uc1ac\uc1ad\uc1ae\uc1af\uc1b0\uc1b1\uc1b2\uc1b3\uc1b4\uc1b5\uc1b6\uc1b7\uc1b8\uc1b9\uc1ba\uc1bb\uc1bc\uc1bd\uc1be\uc1bf\uc1c0\uc1c1\uc1c2\uc1c3\uc1c4\uc1c5\uc1c6\uc1c7\uc1c8\uc1c9\uc1ca\uc1cb\uc1cc\uc1cd\uc1ce\uc1cf\uc1d0\uc1d1\uc1d2\uc1d3\uc1d4\uc1d5\uc1d6\uc1d7\uc1d8\uc1d9\uc1da\uc1db\uc1dc\uc1dd\uc1de\uc1df\uc1e0\uc1e1\uc1e2\uc1e3\uc1e4\uc1e5\uc1e6\uc1e7\uc1e8\uc1e9\uc1ea\uc1eb\uc1ec\uc1ed\uc1ee\uc1ef\uc1f0\uc1f1\uc1f2\uc1f3\uc1f4\uc1f5\uc1f6\uc1f7\uc1f8\uc1f9\uc1fa\uc1fb\uc1fc\uc1fd\uc1fe\uc1ff\uc200\uc201\uc202\uc203\uc204\uc205\uc206\uc207\uc208\uc209\uc20a\uc20b\uc20c\uc20d\uc20e\uc20f\uc210\uc211\uc212\uc213\uc214\uc215\uc216\uc217\uc218\uc219\uc21a\uc21b\uc21c\uc21d\uc21e\uc21f\uc220\uc221\uc222\uc223\uc224\uc225\uc226\uc227\uc228\uc229\uc22a\uc22b\uc22c\uc22d\uc22e\uc22f\uc230\uc231\uc232\uc233\uc234\uc235\uc236\uc237\uc238\uc239\uc23a\uc23b\uc23c\uc23d\uc23e\uc23f\uc240\uc241\uc242\uc243\uc244\uc245\uc246\uc247\uc248\uc249\uc24a\uc24b\uc24c\uc24d\uc24e\uc24f\uc250\uc251\uc252\uc253\uc254\uc255\uc256\uc257\uc258\uc259\uc25a\uc25b\uc25c\uc25d\uc25e\uc25f\uc260\uc261\uc262\uc263\uc264\uc265\uc266\uc267\uc268\uc269\uc26a\uc26b\uc26c\uc26d\uc26e\uc26f\uc270\uc271\uc272\uc273\uc274\uc275\uc276\uc277\uc278\uc279\uc27a\uc27b\uc27c\uc27d\uc27e\uc27f\uc280\uc281\uc282\uc283\uc284\uc285\uc286\uc287\uc288\uc289\uc28a\uc28b\uc28c\uc28d\uc28e\uc28f\uc290\uc291\uc292\uc293\uc294\uc295\uc296\uc297\uc298\uc299\uc29a\uc29b\uc29c\uc29d\uc29e\uc29f\uc2a0\uc2a1\uc2a2\uc2a3\uc2a4\uc2a5\uc2a6\uc2a7\uc2a8\uc2a9\uc2aa\uc2ab\uc2ac\uc2ad\uc2ae\uc2af\uc2b0\uc2b1\uc2b2\uc2b3\uc2b4\uc2b5\uc2b6\uc2b7\uc2b8\uc2b9\uc2ba\uc2bb\uc2bc\uc2bd\uc2be\uc2bf\uc2c0\uc2c1\uc2c2\uc2c3\uc2c4\uc2c5\uc2c6\uc2c7\uc2c8\uc2c9\uc2ca\uc2cb\uc2cc\uc2cd\uc2ce\uc2cf\uc2d0\uc2d1\uc2d2\uc2d3\uc2d4\uc2d5\uc2d6\uc2d7\uc2d8\uc2d9\uc2da\uc2db\uc2dc\uc2dd\uc2de\uc2df\uc2e0\uc2e1\uc2e2\uc2e3\uc2e4\uc2e5\uc2e6\uc2e7\uc2e8\uc2e9\uc2ea\uc2eb\uc2ec\uc2ed\uc2ee\uc2ef\uc2f0\uc2f1\uc2f2\uc2f3\uc2f4\uc2f5\uc2f6\uc2f7\uc2f8\uc2f9\uc2fa\uc2fb\uc2fc\uc2fd\uc2fe\uc2ff\uc300\uc301\uc302\uc303\uc304\uc305\uc306\uc307\uc308\uc309\uc30a\uc30b\uc30c\uc30d\uc30e\uc30f\uc310\uc311\uc312\uc313\uc314\uc315\uc316\uc317\uc318\uc319\uc31a\uc31b\uc31c\uc31d\uc31e\uc31f\uc320\uc321\uc322\uc323\uc324\uc325\uc326\uc327\uc328\uc329\uc32a\uc32b\uc32c\uc32d\uc32e\uc32f\uc330\uc331\uc332\uc333\uc334\uc335\uc336\uc337\uc338\uc339\uc33a\uc33b\uc33c\uc33d\uc33e\uc33f\uc340\uc341\uc342\uc343\uc344\uc345\uc346\uc347\uc348\uc349\uc34a\uc34b\uc34c\uc34d\uc34e\uc34f\uc350\uc351\uc352\uc353\uc354\uc355\uc356\uc357\uc358\uc359\uc35a\uc35b\uc35c\uc35d\uc35e\uc35f\uc360\uc361\uc362\uc363\uc364\uc365\uc366\uc367\uc368\uc369\uc36a\uc36b\uc36c\uc36d\uc36e\uc36f\uc370\uc371\uc372\uc373\uc374\uc375\uc376\uc377\uc378\uc379\uc37a\uc37b\uc37c\uc37d\uc37e\uc37f\uc380\uc381\uc382\uc383\uc384\uc385\uc386\uc387\uc388\uc389\uc38a\uc38b\uc38c\uc38d\uc38e\uc38f\uc390\uc391\uc392\uc393\uc394\uc395\uc396\uc397\uc398\uc399\uc39a\uc39b\uc39c\uc39d\uc39e\uc39f\uc3a0\uc3a1\uc3a2\uc3a3\uc3a4\uc3a5\uc3a6\uc3a7\uc3a8\uc3a9\uc3aa\uc3ab\uc3ac\uc3ad\uc3ae\uc3af\uc3b0\uc3b1\uc3b2\uc3b3\uc3b4\uc3b5\uc3b6\uc3b7\uc3b8\uc3b9\uc3ba\uc3bb\uc3bc\uc3bd\uc3be\uc3bf\uc3c0\uc3c1\uc3c2\uc3c3\uc3c4\uc3c5\uc3c6\uc3c7\uc3c8\uc3c9\uc3ca\uc3cb\uc3cc\uc3cd\uc3ce\uc3cf\uc3d0\uc3d1\uc3d2\uc3d3\uc3d4\uc3d5\uc3d6\uc3d7\uc3d8\uc3d9\uc3da\uc3db\uc3dc\uc3dd\uc3de\uc3df\uc3e0\uc3e1\uc3e2\uc3e3\uc3e4\uc3e5\uc3e6\uc3e7\uc3e8\uc3e9\uc3ea\uc3eb\uc3ec\uc3ed\uc3ee\uc3ef\uc3f0\uc3f1\uc3f2\uc3f3\uc3f4\uc3f5\uc3f6\uc3f7\uc3f8\uc3f9\uc3fa\uc3fb\uc3fc\uc3fd\uc3fe\uc3ff\uc400\uc401\uc402\uc403\uc404\uc405\uc406\uc407\uc408\uc409\uc40a\uc40b\uc40c\uc40d\uc40e\uc40f\uc410\uc411\uc412\uc413\uc414\uc415\uc416\uc417\uc418\uc419\uc41a\uc41b\uc41c\uc41d\uc41e\uc41f\uc420\uc421\uc422\uc423\uc424\uc425\uc426\uc427\uc428\uc429\uc42a\uc42b\uc42c\uc42d\uc42e\uc42f\uc430\uc431\uc432\uc433\uc434\uc435\uc436\uc437\uc438\uc439\uc43a\uc43b\uc43c\uc43d\uc43e\uc43f\uc440\uc441\uc442\uc443\uc444\uc445\uc446\uc447\uc448\uc449\uc44a\uc44b\uc44c\uc44d\uc44e\uc44f\uc450\uc451\uc452\uc453\uc454\uc455\uc456\uc457\uc458\uc459\uc45a\uc45b\uc45c\uc45d\uc45e\uc45f\uc460\uc461\uc462\uc463\uc464\uc465\uc466\uc467\uc468\uc469\uc46a\uc46b\uc46c\uc46d\uc46e\uc46f\uc470\uc471\uc472\uc473\uc474\uc475\uc476\uc477\uc478\uc479\uc47a\uc47b\uc47c\uc47d\uc47e\uc47f\uc480\uc481\uc482\uc483\uc484\uc485\uc486\uc487\uc488\uc489\uc48a\uc48b\uc48c\uc48d\uc48e\uc48f\uc490\uc491\uc492\uc493\uc494\uc495\uc496\uc497\uc498\uc499\uc49a\uc49b\uc49c\uc49d\uc49e\uc49f\uc4a0\uc4a1\uc4a2\uc4a3\uc4a4\uc4a5\uc4a6\uc4a7\uc4a8\uc4a9\uc4aa\uc4ab\uc4ac\uc4ad\uc4ae\uc4af\uc4b0\uc4b1\uc4b2\uc4b3\uc4b4\uc4b5\uc4b6\uc4b7\uc4b8\uc4b9\uc4ba\uc4bb\uc4bc\uc4bd\uc4be\uc4bf\uc4c0\uc4c1\uc4c2\uc4c3\uc4c4\uc4c5\uc4c6\uc4c7\uc4c8\uc4c9\uc4ca\uc4cb\uc4cc\uc4cd\uc4ce\uc4cf\uc4d0\uc4d1\uc4d2\uc4d3\uc4d4\uc4d5\uc4d6\uc4d7\uc4d8\uc4d9\uc4da\uc4db\uc4dc\uc4dd\uc4de\uc4df\uc4e0\uc4e1\uc4e2\uc4e3\uc4e4\uc4e5\uc4e6\uc4e7\uc4e8\uc4e9\uc4ea\uc4eb\uc4ec\uc4ed\uc4ee\uc4ef\uc4f0\uc4f1\uc4f2\uc4f3\uc4f4\uc4f5\uc4f6\uc4f7\uc4f8\uc4f9\uc4fa\uc4fb\uc4fc\uc4fd\uc4fe\uc4ff\uc500\uc501\uc502\uc503\uc504\uc505\uc506\uc507\uc508\uc509\uc50a\uc50b\uc50c\uc50d\uc50e\uc50f\uc510\uc511\uc512\uc513\uc514\uc515\uc516\uc517\uc518\uc519\uc51a\uc51b\uc51c\uc51d\uc51e\uc51f\uc520\uc521\uc522\uc523\uc524\uc525\uc526\uc527\uc528\uc529\uc52a\uc52b\uc52c\uc52d\uc52e\uc52f\uc530\uc531\uc532\uc533\uc534\uc535\uc536\uc537\uc538\uc539\uc53a\uc53b\uc53c\uc53d\uc53e\uc53f\uc540\uc541\uc542\uc543\uc544\uc545\uc546\uc547\uc548\uc549\uc54a\uc54b\uc54c\uc54d\uc54e\uc54f\uc550\uc551\uc552\uc553\uc554\uc555\uc556\uc557\uc558\uc559\uc55a\uc55b\uc55c\uc55d\uc55e\uc55f\uc560\uc561\uc562\uc563\uc564\uc565\uc566\uc567\uc568\uc569\uc56a\uc56b\uc56c\uc56d\uc56e\uc56f\uc570\uc571\uc572\uc573\uc574\uc575\uc576\uc577\uc578\uc579\uc57a\uc57b\uc57c\uc57d\uc57e\uc57f\uc580\uc581\uc582\uc583\uc584\uc585\uc586\uc587\uc588\uc589\uc58a\uc58b\uc58c\uc58d\uc58e\uc58f\uc590\uc591\uc592\uc593\uc594\uc595\uc596\uc597\uc598\uc599\uc59a\uc59b\uc59c\uc59d\uc59e\uc59f\uc5a0\uc5a1\uc5a2\uc5a3\uc5a4\uc5a5\uc5a6\uc5a7\uc5a8\uc5a9\uc5aa\uc5ab\uc5ac\uc5ad\uc5ae\uc5af\uc5b0\uc5b1\uc5b2\uc5b3\uc5b4\uc5b5\uc5b6\uc5b7\uc5b8\uc5b9\uc5ba\uc5bb\uc5bc\uc5bd\uc5be\uc5bf\uc5c0\uc5c1\uc5c2\uc5c3\uc5c4\uc5c5\uc5c6\uc5c7\uc5c8\uc5c9\uc5ca\uc5cb\uc5cc\uc5cd\uc5ce\uc5cf\uc5d0\uc5d1\uc5d2\uc5d3\uc5d4\uc5d5\uc5d6\uc5d7\uc5d8\uc5d9\uc5da\uc5db\uc5dc\uc5dd\uc5de\uc5df\uc5e0\uc5e1\uc5e2\uc5e3\uc5e4\uc5e5\uc5e6\uc5e7\uc5e8\uc5e9\uc5ea\uc5eb\uc5ec\uc5ed\uc5ee\uc5ef\uc5f0\uc5f1\uc5f2\uc5f3\uc5f4\uc5f5\uc5f6\uc5f7\uc5f8\uc5f9\uc5fa\uc5fb\uc5fc\uc5fd\uc5fe\uc5ff\uc600\uc601\uc602\uc603\uc604\uc605\uc606\uc607\uc608\uc609\uc60a\uc60b\uc60c\uc60d\uc60e\uc60f\uc610\uc611\uc612\uc613\uc614\uc615\uc616\uc617\uc618\uc619\uc61a\uc61b\uc61c\uc61d\uc61e\uc61f\uc620\uc621\uc622\uc623\uc624\uc625\uc626\uc627\uc628\uc629\uc62a\uc62b\uc62c\uc62d\uc62e\uc62f\uc630\uc631\uc632\uc633\uc634\uc635\uc636\uc637\uc638\uc639\uc63a\uc63b\uc63c\uc63d\uc63e\uc63f\uc640\uc641\uc642\uc643\uc644\uc645\uc646\uc647\uc648\uc649\uc64a\uc64b\uc64c\uc64d\uc64e\uc64f\uc650\uc651\uc652\uc653\uc654\uc655\uc656\uc657\uc658\uc659\uc65a\uc65b\uc65c\uc65d\uc65e\uc65f\uc660\uc661\uc662\uc663\uc664\uc665\uc666\uc667\uc668\uc669\uc66a\uc66b\uc66c\uc66d\uc66e\uc66f\uc670\uc671\uc672\uc673\uc674\uc675\uc676\uc677\uc678\uc679\uc67a\uc67b\uc67c\uc67d\uc67e\uc67f\uc680\uc681\uc682\uc683\uc684\uc685\uc686\uc687\uc688\uc689\uc68a\uc68b\uc68c\uc68d\uc68e\uc68f\uc690\uc691\uc692\uc693\uc694\uc695\uc696\uc697\uc698\uc699\uc69a\uc69b\uc69c\uc69d\uc69e\uc69f\uc6a0\uc6a1\uc6a2\uc6a3\uc6a4\uc6a5\uc6a6\uc6a7\uc6a8\uc6a9\uc6aa\uc6ab\uc6ac\uc6ad\uc6ae\uc6af\uc6b0\uc6b1\uc6b2\uc6b3\uc6b4\uc6b5\uc6b6\uc6b7\uc6b8\uc6b9\uc6ba\uc6bb\uc6bc\uc6bd\uc6be\uc6bf\uc6c0\uc6c1\uc6c2\uc6c3\uc6c4\uc6c5\uc6c6\uc6c7\uc6c8\uc6c9\uc6ca\uc6cb\uc6cc\uc6cd\uc6ce\uc6cf\uc6d0\uc6d1\uc6d2\uc6d3\uc6d4\uc6d5\uc6d6\uc6d7\uc6d8\uc6d9\uc6da\uc6db\uc6dc\uc6dd\uc6de\uc6df\uc6e0\uc6e1\uc6e2\uc6e3\uc6e4\uc6e5\uc6e6\uc6e7\uc6e8\uc6e9\uc6ea\uc6eb\uc6ec\uc6ed\uc6ee\uc6ef\uc6f0\uc6f1\uc6f2\uc6f3\uc6f4\uc6f5\uc6f6\uc6f7\uc6f8\uc6f9\uc6fa\uc6fb\uc6fc\uc6fd\uc6fe\uc6ff\uc700\uc701\uc702\uc703\uc704\uc705\uc706\uc707\uc708\uc709\uc70a\uc70b\uc70c\uc70d\uc70e\uc70f\uc710\uc711\uc712\uc713\uc714\uc715\uc716\uc717\uc718\uc719\uc71a\uc71b\uc71c\uc71d\uc71e\uc71f\uc720\uc721\uc722\uc723\uc724\uc725\uc726\uc727\uc728\uc729\uc72a\uc72b\uc72c\uc72d\uc72e\uc72f\uc730\uc731\uc732\uc733\uc734\uc735\uc736\uc737\uc738\uc739\uc73a\uc73b\uc73c\uc73d\uc73e\uc73f\uc740\uc741\uc742\uc743\uc744\uc745\uc746\uc747\uc748\uc749\uc74a\uc74b\uc74c\uc74d\uc74e\uc74f\uc750\uc751\uc752\uc753\uc754\uc755\uc756\uc757\uc758\uc759\uc75a\uc75b\uc75c\uc75d\uc75e\uc75f\uc760\uc761\uc762\uc763\uc764\uc765\uc766\uc767\uc768\uc769\uc76a\uc76b\uc76c\uc76d\uc76e\uc76f\uc770\uc771\uc772\uc773\uc774\uc775\uc776\uc777\uc778\uc779\uc77a\uc77b\uc77c\uc77d\uc77e\uc77f\uc780\uc781\uc782\uc783\uc784\uc785\uc786\uc787\uc788\uc789\uc78a\uc78b\uc78c\uc78d\uc78e\uc78f\uc790\uc791\uc792\uc793\uc794\uc795\uc796\uc797\uc798\uc799\uc79a\uc79b\uc79c\uc79d\uc79e\uc79f\uc7a0\uc7a1\uc7a2\uc7a3\uc7a4\uc7a5\uc7a6\uc7a7\uc7a8\uc7a9\uc7aa\uc7ab\uc7ac\uc7ad\uc7ae\uc7af\uc7b0\uc7b1\uc7b2\uc7b3\uc7b4\uc7b5\uc7b6\uc7b7\uc7b8\uc7b9\uc7ba\uc7bb\uc7bc\uc7bd\uc7be\uc7bf\uc7c0\uc7c1\uc7c2\uc7c3\uc7c4\uc7c5\uc7c6\uc7c7\uc7c8\uc7c9\uc7ca\uc7cb\uc7cc\uc7cd\uc7ce\uc7cf\uc7d0\uc7d1\uc7d2\uc7d3\uc7d4\uc7d5\uc7d6\uc7d7\uc7d8\uc7d9\uc7da\uc7db\uc7dc\uc7dd\uc7de\uc7df\uc7e0\uc7e1\uc7e2\uc7e3\uc7e4\uc7e5\uc7e6\uc7e7\uc7e8\uc7e9\uc7ea\uc7eb\uc7ec\uc7ed\uc7ee\uc7ef\uc7f0\uc7f1\uc7f2\uc7f3\uc7f4\uc7f5\uc7f6\uc7f7\uc7f8\uc7f9\uc7fa\uc7fb\uc7fc\uc7fd\uc7fe\uc7ff\uc800\uc801\uc802\uc803\uc804\uc805\uc806\uc807\uc808\uc809\uc80a\uc80b\uc80c\uc80d\uc80e\uc80f\uc810\uc811\uc812\uc813\uc814\uc815\uc816\uc817\uc818\uc819\uc81a\uc81b\uc81c\uc81d\uc81e\uc81f\uc820\uc821\uc822\uc823\uc824\uc825\uc826\uc827\uc828\uc829\uc82a\uc82b\uc82c\uc82d\uc82e\uc82f\uc830\uc831\uc832\uc833\uc834\uc835\uc836\uc837\uc838\uc839\uc83a\uc83b\uc83c\uc83d\uc83e\uc83f\uc840\uc841\uc842\uc843\uc844\uc845\uc846\uc847\uc848\uc849\uc84a\uc84b\uc84c\uc84d\uc84e\uc84f\uc850\uc851\uc852\uc853\uc854\uc855\uc856\uc857\uc858\uc859\uc85a\uc85b\uc85c\uc85d\uc85e\uc85f\uc860\uc861\uc862\uc863\uc864\uc865\uc866\uc867\uc868\uc869\uc86a\uc86b\uc86c\uc86d\uc86e\uc86f\uc870\uc871\uc872\uc873\uc874\uc875\uc876\uc877\uc878\uc879\uc87a\uc87b\uc87c\uc87d\uc87e\uc87f\uc880\uc881\uc882\uc883\uc884\uc885\uc886\uc887\uc888\uc889\uc88a\uc88b\uc88c\uc88d\uc88e\uc88f\uc890\uc891\uc892\uc893\uc894\uc895\uc896\uc897\uc898\uc899\uc89a\uc89b\uc89c\uc89d\uc89e\uc89f\uc8a0\uc8a1\uc8a2\uc8a3\uc8a4\uc8a5\uc8a6\uc8a7\uc8a8\uc8a9\uc8aa\uc8ab\uc8ac\uc8ad\uc8ae\uc8af\uc8b0\uc8b1\uc8b2\uc8b3\uc8b4\uc8b5\uc8b6\uc8b7\uc8b8\uc8b9\uc8ba\uc8bb\uc8bc\uc8bd\uc8be\uc8bf\uc8c0\uc8c1\uc8c2\uc8c3\uc8c4\uc8c5\uc8c6\uc8c7\uc8c8\uc8c9\uc8ca\uc8cb\uc8cc\uc8cd\uc8ce\uc8cf\uc8d0\uc8d1\uc8d2\uc8d3\uc8d4\uc8d5\uc8d6\uc8d7\uc8d8\uc8d9\uc8da\uc8db\uc8dc\uc8dd\uc8de\uc8df\uc8e0\uc8e1\uc8e2\uc8e3\uc8e4\uc8e5\uc8e6\uc8e7\uc8e8\uc8e9\uc8ea\uc8eb\uc8ec\uc8ed\uc8ee\uc8ef\uc8f0\uc8f1\uc8f2\uc8f3\uc8f4\uc8f5\uc8f6\uc8f7\uc8f8\uc8f9\uc8fa\uc8fb\uc8fc\uc8fd\uc8fe\uc8ff\uc900\uc901\uc902\uc903\uc904\uc905\uc906\uc907\uc908\uc909\uc90a\uc90b\uc90c\uc90d\uc90e\uc90f\uc910\uc911\uc912\uc913\uc914\uc915\uc916\uc917\uc918\uc919\uc91a\uc91b\uc91c\uc91d\uc91e\uc91f\uc920\uc921\uc922\uc923\uc924\uc925\uc926\uc927\uc928\uc929\uc92a\uc92b\uc92c\uc92d\uc92e\uc92f\uc930\uc931\uc932\uc933\uc934\uc935\uc936\uc937\uc938\uc939\uc93a\uc93b\uc93c\uc93d\uc93e\uc93f\uc940\uc941\uc942\uc943\uc944\uc945\uc946\uc947\uc948\uc949\uc94a\uc94b\uc94c\uc94d\uc94e\uc94f\uc950\uc951\uc952\uc953\uc954\uc955\uc956\uc957\uc958\uc959\uc95a\uc95b\uc95c\uc95d\uc95e\uc95f\uc960\uc961\uc962\uc963\uc964\uc965\uc966\uc967\uc968\uc969\uc96a\uc96b\uc96c\uc96d\uc96e\uc96f\uc970\uc971\uc972\uc973\uc974\uc975\uc976\uc977\uc978\uc979\uc97a\uc97b\uc97c\uc97d\uc97e\uc97f\uc980\uc981\uc982\uc983\uc984\uc985\uc986\uc987\uc988\uc989\uc98a\uc98b\uc98c\uc98d\uc98e\uc98f\uc990\uc991\uc992\uc993\uc994\uc995\uc996\uc997\uc998\uc999\uc99a\uc99b\uc99c\uc99d\uc99e\uc99f\uc9a0\uc9a1\uc9a2\uc9a3\uc9a4\uc9a5\uc9a6\uc9a7\uc9a8\uc9a9\uc9aa\uc9ab\uc9ac\uc9ad\uc9ae\uc9af\uc9b0\uc9b1\uc9b2\uc9b3\uc9b4\uc9b5\uc9b6\uc9b7\uc9b8\uc9b9\uc9ba\uc9bb\uc9bc\uc9bd\uc9be\uc9bf\uc9c0\uc9c1\uc9c2\uc9c3\uc9c4\uc9c5\uc9c6\uc9c7\uc9c8\uc9c9\uc9ca\uc9cb\uc9cc\uc9cd\uc9ce\uc9cf\uc9d0\uc9d1\uc9d2\uc9d3\uc9d4\uc9d5\uc9d6\uc9d7\uc9d8\uc9d9\uc9da\uc9db\uc9dc\uc9dd\uc9de\uc9df\uc9e0\uc9e1\uc9e2\uc9e3\uc9e4\uc9e5\uc9e6\uc9e7\uc9e8\uc9e9\uc9ea\uc9eb\uc9ec\uc9ed\uc9ee\uc9ef\uc9f0\uc9f1\uc9f2\uc9f3\uc9f4\uc9f5\uc9f6\uc9f7\uc9f8\uc9f9\uc9fa\uc9fb\uc9fc\uc9fd\uc9fe\uc9ff\uca00\uca01\uca02\uca03\uca04\uca05\uca06\uca07\uca08\uca09\uca0a\uca0b\uca0c\uca0d\uca0e\uca0f\uca10\uca11\uca12\uca13\uca14\uca15\uca16\uca17\uca18\uca19\uca1a\uca1b\uca1c\uca1d\uca1e\uca1f\uca20\uca21\uca22\uca23\uca24\uca25\uca26\uca27\uca28\uca29\uca2a\uca2b\uca2c\uca2d\uca2e\uca2f\uca30\uca31\uca32\uca33\uca34\uca35\uca36\uca37\uca38\uca39\uca3a\uca3b\uca3c\uca3d\uca3e\uca3f\uca40\uca41\uca42\uca43\uca44\uca45\uca46\uca47\uca48\uca49\uca4a\uca4b\uca4c\uca4d\uca4e\uca4f\uca50\uca51\uca52\uca53\uca54\uca55\uca56\uca57\uca58\uca59\uca5a\uca5b\uca5c\uca5d\uca5e\uca5f\uca60\uca61\uca62\uca63\uca64\uca65\uca66\uca67\uca68\uca69\uca6a\uca6b\uca6c\uca6d\uca6e\uca6f\uca70\uca71\uca72\uca73\uca74\uca75\uca76\uca77\uca78\uca79\uca7a\uca7b\uca7c\uca7d\uca7e\uca7f\uca80\uca81\uca82\uca83\uca84\uca85\uca86\uca87\uca88\uca89\uca8a\uca8b\uca8c\uca8d\uca8e\uca8f\uca90\uca91\uca92\uca93\uca94\uca95\uca96\uca97\uca98\uca99\uca9a\uca9b\uca9c\uca9d\uca9e\uca9f\ucaa0\ucaa1\ucaa2\ucaa3\ucaa4\ucaa5\ucaa6\ucaa7\ucaa8\ucaa9\ucaaa\ucaab\ucaac\ucaad\ucaae\ucaaf\ucab0\ucab1\ucab2\ucab3\ucab4\ucab5\ucab6\ucab7\ucab8\ucab9\ucaba\ucabb\ucabc\ucabd\ucabe\ucabf\ucac0\ucac1\ucac2\ucac3\ucac4\ucac5\ucac6\ucac7\ucac8\ucac9\ucaca\ucacb\ucacc\ucacd\ucace\ucacf\ucad0\ucad1\ucad2\ucad3\ucad4\ucad5\ucad6\ucad7\ucad8\ucad9\ucada\ucadb\ucadc\ucadd\ucade\ucadf\ucae0\ucae1\ucae2\ucae3\ucae4\ucae5\ucae6\ucae7\ucae8\ucae9\ucaea\ucaeb\ucaec\ucaed\ucaee\ucaef\ucaf0\ucaf1\ucaf2\ucaf3\ucaf4\ucaf5\ucaf6\ucaf7\ucaf8\ucaf9\ucafa\ucafb\ucafc\ucafd\ucafe\ucaff\ucb00\ucb01\ucb02\ucb03\ucb04\ucb05\ucb06\ucb07\ucb08\ucb09\ucb0a\ucb0b\ucb0c\ucb0d\ucb0e\ucb0f\ucb10\ucb11\ucb12\ucb13\ucb14\ucb15\ucb16\ucb17\ucb18\ucb19\ucb1a\ucb1b\ucb1c\ucb1d\ucb1e\ucb1f\ucb20\ucb21\ucb22\ucb23\ucb24\ucb25\ucb26\ucb27\ucb28\ucb29\ucb2a\ucb2b\ucb2c\ucb2d\ucb2e\ucb2f\ucb30\ucb31\ucb32\ucb33\ucb34\ucb35\ucb36\ucb37\ucb38\ucb39\ucb3a\ucb3b\ucb3c\ucb3d\ucb3e\ucb3f\ucb40\ucb41\ucb42\ucb43\ucb44\ucb45\ucb46\ucb47\ucb48\ucb49\ucb4a\ucb4b\ucb4c\ucb4d\ucb4e\ucb4f\ucb50\ucb51\ucb52\ucb53\ucb54\ucb55\ucb56\ucb57\ucb58\ucb59\ucb5a\ucb5b\ucb5c\ucb5d\ucb5e\ucb5f\ucb60\ucb61\ucb62\ucb63\ucb64\ucb65\ucb66\ucb67\ucb68\ucb69\ucb6a\ucb6b\ucb6c\ucb6d\ucb6e\ucb6f\ucb70\ucb71\ucb72\ucb73\ucb74\ucb75\ucb76\ucb77\ucb78\ucb79\ucb7a\ucb7b\ucb7c\ucb7d\ucb7e\ucb7f\ucb80\ucb81\ucb82\ucb83\ucb84\ucb85\ucb86\ucb87\ucb88\ucb89\ucb8a\ucb8b\ucb8c\ucb8d\ucb8e\ucb8f\ucb90\ucb91\ucb92\ucb93\ucb94\ucb95\ucb96\ucb97\ucb98\ucb99\ucb9a\ucb9b\ucb9c\ucb9d\ucb9e\ucb9f\ucba0\ucba1\ucba2\ucba3\ucba4\ucba5\ucba6\ucba7\ucba8\ucba9\ucbaa\ucbab\ucbac\ucbad\ucbae\ucbaf\ucbb0\ucbb1\ucbb2\ucbb3\ucbb4\ucbb5\ucbb6\ucbb7\ucbb8\ucbb9\ucbba\ucbbb\ucbbc\ucbbd\ucbbe\ucbbf\ucbc0\ucbc1\ucbc2\ucbc3\ucbc4\ucbc5\ucbc6\ucbc7\ucbc8\ucbc9\ucbca\ucbcb\ucbcc\ucbcd\ucbce\ucbcf\ucbd0\ucbd1\ucbd2\ucbd3\ucbd4\ucbd5\ucbd6\ucbd7\ucbd8\ucbd9\ucbda\ucbdb\ucbdc\ucbdd\ucbde\ucbdf\ucbe0\ucbe1\ucbe2\ucbe3\ucbe4\ucbe5\ucbe6\ucbe7\ucbe8\ucbe9\ucbea\ucbeb\ucbec\ucbed\ucbee\ucbef\ucbf0\ucbf1\ucbf2\ucbf3\ucbf4\ucbf5\ucbf6\ucbf7\ucbf8\ucbf9\ucbfa\ucbfb\ucbfc\ucbfd\ucbfe\ucbff\ucc00\ucc01\ucc02\ucc03\ucc04\ucc05\ucc06\ucc07\ucc08\ucc09\ucc0a\ucc0b\ucc0c\ucc0d\ucc0e\ucc0f\ucc10\ucc11\ucc12\ucc13\ucc14\ucc15\ucc16\ucc17\ucc18\ucc19\ucc1a\ucc1b\ucc1c\ucc1d\ucc1e\ucc1f\ucc20\ucc21\ucc22\ucc23\ucc24\ucc25\ucc26\ucc27\ucc28\ucc29\ucc2a\ucc2b\ucc2c\ucc2d\ucc2e\ucc2f\ucc30\ucc31\ucc32\ucc33\ucc34\ucc35\ucc36\ucc37\ucc38\ucc39\ucc3a\ucc3b\ucc3c\ucc3d\ucc3e\ucc3f\ucc40\ucc41\ucc42\ucc43\ucc44\ucc45\ucc46\ucc47\ucc48\ucc49\ucc4a\ucc4b\ucc4c\ucc4d\ucc4e\ucc4f\ucc50\ucc51\ucc52\ucc53\ucc54\ucc55\ucc56\ucc57\ucc58\ucc59\ucc5a\ucc5b\ucc5c\ucc5d\ucc5e\ucc5f\ucc60\ucc61\ucc62\ucc63\ucc64\ucc65\ucc66\ucc67\ucc68\ucc69\ucc6a\ucc6b\ucc6c\ucc6d\ucc6e\ucc6f\ucc70\ucc71\ucc72\ucc73\ucc74\ucc75\ucc76\ucc77\ucc78\ucc79\ucc7a\ucc7b\ucc7c\ucc7d\ucc7e\ucc7f\ucc80\ucc81\ucc82\ucc83\ucc84\ucc85\ucc86\ucc87\ucc88\ucc89\ucc8a\ucc8b\ucc8c\ucc8d\ucc8e\ucc8f\ucc90\ucc91\ucc92\ucc93\ucc94\ucc95\ucc96\ucc97\ucc98\ucc99\ucc9a\ucc9b\ucc9c\ucc9d\ucc9e\ucc9f\ucca0\ucca1\ucca2\ucca3\ucca4\ucca5\ucca6\ucca7\ucca8\ucca9\uccaa\uccab\uccac\uccad\uccae\uccaf\uccb0\uccb1\uccb2\uccb3\uccb4\uccb5\uccb6\uccb7\uccb8\uccb9\uccba\uccbb\uccbc\uccbd\uccbe\uccbf\uccc0\uccc1\uccc2\uccc3\uccc4\uccc5\uccc6\uccc7\uccc8\uccc9\uccca\ucccb\ucccc\ucccd\uccce\ucccf\uccd0\uccd1\uccd2\uccd3\uccd4\uccd5\uccd6\uccd7\uccd8\uccd9\uccda\uccdb\uccdc\uccdd\uccde\uccdf\ucce0\ucce1\ucce2\ucce3\ucce4\ucce5\ucce6\ucce7\ucce8\ucce9\uccea\ucceb\uccec\ucced\uccee\uccef\uccf0\uccf1\uccf2\uccf3\uccf4\uccf5\uccf6\uccf7\uccf8\uccf9\uccfa\uccfb\uccfc\uccfd\uccfe\uccff\ucd00\ucd01\ucd02\ucd03\ucd04\ucd05\ucd06\ucd07\ucd08\ucd09\ucd0a\ucd0b\ucd0c\ucd0d\ucd0e\ucd0f\ucd10\ucd11\ucd12\ucd13\ucd14\ucd15\ucd16\ucd17\ucd18\ucd19\ucd1a\ucd1b\ucd1c\ucd1d\ucd1e\ucd1f\ucd20\ucd21\ucd22\ucd23\ucd24\ucd25\ucd26\ucd27\ucd28\ucd29\ucd2a\ucd2b\ucd2c\ucd2d\ucd2e\ucd2f\ucd30\ucd31\ucd32\ucd33\ucd34\ucd35\ucd36\ucd37\ucd38\ucd39\ucd3a\ucd3b\ucd3c\ucd3d\ucd3e\ucd3f\ucd40\ucd41\ucd42\ucd43\ucd44\ucd45\ucd46\ucd47\ucd48\ucd49\ucd4a\ucd4b\ucd4c\ucd4d\ucd4e\ucd4f\ucd50\ucd51\ucd52\ucd53\ucd54\ucd55\ucd56\ucd57\ucd58\ucd59\ucd5a\ucd5b\ucd5c\ucd5d\ucd5e\ucd5f\ucd60\ucd61\ucd62\ucd63\ucd64\ucd65\ucd66\ucd67\ucd68\ucd69\ucd6a\ucd6b\ucd6c\ucd6d\ucd6e\ucd6f\ucd70\ucd71\ucd72\ucd73\ucd74\ucd75\ucd76\ucd77\ucd78\ucd79\ucd7a\ucd7b\ucd7c\ucd7d\ucd7e\ucd7f\ucd80\ucd81\ucd82\ucd83\ucd84\ucd85\ucd86\ucd87\ucd88\ucd89\ucd8a\ucd8b\ucd8c\ucd8d\ucd8e\ucd8f\ucd90\ucd91\ucd92\ucd93\ucd94\ucd95\ucd96\ucd97\ucd98\ucd99\ucd9a\ucd9b\ucd9c\ucd9d\ucd9e\ucd9f\ucda0\ucda1\ucda2\ucda3\ucda4\ucda5\ucda6\ucda7\ucda8\ucda9\ucdaa\ucdab\ucdac\ucdad\ucdae\ucdaf\ucdb0\ucdb1\ucdb2\ucdb3\ucdb4\ucdb5\ucdb6\ucdb7\ucdb8\ucdb9\ucdba\ucdbb\ucdbc\ucdbd\ucdbe\ucdbf\ucdc0\ucdc1\ucdc2\ucdc3\ucdc4\ucdc5\ucdc6\ucdc7\ucdc8\ucdc9\ucdca\ucdcb\ucdcc\ucdcd\ucdce\ucdcf\ucdd0\ucdd1\ucdd2\ucdd3\ucdd4\ucdd5\ucdd6\ucdd7\ucdd8\ucdd9\ucdda\ucddb\ucddc\ucddd\ucdde\ucddf\ucde0\ucde1\ucde2\ucde3\ucde4\ucde5\ucde6\ucde7\ucde8\ucde9\ucdea\ucdeb\ucdec\ucded\ucdee\ucdef\ucdf0\ucdf1\ucdf2\ucdf3\ucdf4\ucdf5\ucdf6\ucdf7\ucdf8\ucdf9\ucdfa\ucdfb\ucdfc\ucdfd\ucdfe\ucdff\uce00\uce01\uce02\uce03\uce04\uce05\uce06\uce07\uce08\uce09\uce0a\uce0b\uce0c\uce0d\uce0e\uce0f\uce10\uce11\uce12\uce13\uce14\uce15\uce16\uce17\uce18\uce19\uce1a\uce1b\uce1c\uce1d\uce1e\uce1f\uce20\uce21\uce22\uce23\uce24\uce25\uce26\uce27\uce28\uce29\uce2a\uce2b\uce2c\uce2d\uce2e\uce2f\uce30\uce31\uce32\uce33\uce34\uce35\uce36\uce37\uce38\uce39\uce3a\uce3b\uce3c\uce3d\uce3e\uce3f\uce40\uce41\uce42\uce43\uce44\uce45\uce46\uce47\uce48\uce49\uce4a\uce4b\uce4c\uce4d\uce4e\uce4f\uce50\uce51\uce52\uce53\uce54\uce55\uce56\uce57\uce58\uce59\uce5a\uce5b\uce5c\uce5d\uce5e\uce5f\uce60\uce61\uce62\uce63\uce64\uce65\uce66\uce67\uce68\uce69\uce6a\uce6b\uce6c\uce6d\uce6e\uce6f\uce70\uce71\uce72\uce73\uce74\uce75\uce76\uce77\uce78\uce79\uce7a\uce7b\uce7c\uce7d\uce7e\uce7f\uce80\uce81\uce82\uce83\uce84\uce85\uce86\uce87\uce88\uce89\uce8a\uce8b\uce8c\uce8d\uce8e\uce8f\uce90\uce91\uce92\uce93\uce94\uce95\uce96\uce97\uce98\uce99\uce9a\uce9b\uce9c\uce9d\uce9e\uce9f\ucea0\ucea1\ucea2\ucea3\ucea4\ucea5\ucea6\ucea7\ucea8\ucea9\uceaa\uceab\uceac\ucead\uceae\uceaf\uceb0\uceb1\uceb2\uceb3\uceb4\uceb5\uceb6\uceb7\uceb8\uceb9\uceba\ucebb\ucebc\ucebd\ucebe\ucebf\ucec0\ucec1\ucec2\ucec3\ucec4\ucec5\ucec6\ucec7\ucec8\ucec9\uceca\ucecb\ucecc\ucecd\ucece\ucecf\uced0\uced1\uced2\uced3\uced4\uced5\uced6\uced7\uced8\uced9\uceda\ucedb\ucedc\ucedd\ucede\ucedf\ucee0\ucee1\ucee2\ucee3\ucee4\ucee5\ucee6\ucee7\ucee8\ucee9\uceea\uceeb\uceec\uceed\uceee\uceef\ucef0\ucef1\ucef2\ucef3\ucef4\ucef5\ucef6\ucef7\ucef8\ucef9\ucefa\ucefb\ucefc\ucefd\ucefe\uceff\ucf00\ucf01\ucf02\ucf03\ucf04\ucf05\ucf06\ucf07\ucf08\ucf09\ucf0a\ucf0b\ucf0c\ucf0d\ucf0e\ucf0f\ucf10\ucf11\ucf12\ucf13\ucf14\ucf15\ucf16\ucf17\ucf18\ucf19\ucf1a\ucf1b\ucf1c\ucf1d\ucf1e\ucf1f\ucf20\ucf21\ucf22\ucf23\ucf24\ucf25\ucf26\ucf27\ucf28\ucf29\ucf2a\ucf2b\ucf2c\ucf2d\ucf2e\ucf2f\ucf30\ucf31\ucf32\ucf33\ucf34\ucf35\ucf36\ucf37\ucf38\ucf39\ucf3a\ucf3b\ucf3c\ucf3d\ucf3e\ucf3f\ucf40\ucf41\ucf42\ucf43\ucf44\ucf45\ucf46\ucf47\ucf48\ucf49\ucf4a\ucf4b\ucf4c\ucf4d\ucf4e\ucf4f\ucf50\ucf51\ucf52\ucf53\ucf54\ucf55\ucf56\ucf57\ucf58\ucf59\ucf5a\ucf5b\ucf5c\ucf5d\ucf5e\ucf5f\ucf60\ucf61\ucf62\ucf63\ucf64\ucf65\ucf66\ucf67\ucf68\ucf69\ucf6a\ucf6b\ucf6c\ucf6d\ucf6e\ucf6f\ucf70\ucf71\ucf72\ucf73\ucf74\ucf75\ucf76\ucf77\ucf78\ucf79\ucf7a\ucf7b\ucf7c\ucf7d\ucf7e\ucf7f\ucf80\ucf81\ucf82\ucf83\ucf84\ucf85\ucf86\ucf87\ucf88\ucf89\ucf8a\ucf8b\ucf8c\ucf8d\ucf8e\ucf8f\ucf90\ucf91\ucf92\ucf93\ucf94\ucf95\ucf96\ucf97\ucf98\ucf99\ucf9a\ucf9b\ucf9c\ucf9d\ucf9e\ucf9f\ucfa0\ucfa1\ucfa2\ucfa3\ucfa4\ucfa5\ucfa6\ucfa7\ucfa8\ucfa9\ucfaa\ucfab\ucfac\ucfad\ucfae\ucfaf\ucfb0\ucfb1\ucfb2\ucfb3\ucfb4\ucfb5\ucfb6\ucfb7\ucfb8\ucfb9\ucfba\ucfbb\ucfbc\ucfbd\ucfbe\ucfbf\ucfc0\ucfc1\ucfc2\ucfc3\ucfc4\ucfc5\ucfc6\ucfc7\ucfc8\ucfc9\ucfca\ucfcb\ucfcc\ucfcd\ucfce\ucfcf\ucfd0\ucfd1\ucfd2\ucfd3\ucfd4\ucfd5\ucfd6\ucfd7\ucfd8\ucfd9\ucfda\ucfdb\ucfdc\ucfdd\ucfde\ucfdf\ucfe0\ucfe1\ucfe2\ucfe3\ucfe4\ucfe5\ucfe6\ucfe7\ucfe8\ucfe9\ucfea\ucfeb\ucfec\ucfed\ucfee\ucfef\ucff0\ucff1\ucff2\ucff3\ucff4\ucff5\ucff6\ucff7\ucff8\ucff9\ucffa\ucffb\ucffc\ucffd\ucffe\ucfff\ud000\ud001\ud002\ud003\ud004\ud005\ud006\ud007\ud008\ud009\ud00a\ud00b\ud00c\ud00d\ud00e\ud00f\ud010\ud011\ud012\ud013\ud014\ud015\ud016\ud017\ud018\ud019\ud01a\ud01b\ud01c\ud01d\ud01e\ud01f\ud020\ud021\ud022\ud023\ud024\ud025\ud026\ud027\ud028\ud029\ud02a\ud02b\ud02c\ud02d\ud02e\ud02f\ud030\ud031\ud032\ud033\ud034\ud035\ud036\ud037\ud038\ud039\ud03a\ud03b\ud03c\ud03d\ud03e\ud03f\ud040\ud041\ud042\ud043\ud044\ud045\ud046\ud047\ud048\ud049\ud04a\ud04b\ud04c\ud04d\ud04e\ud04f\ud050\ud051\ud052\ud053\ud054\ud055\ud056\ud057\ud058\ud059\ud05a\ud05b\ud05c\ud05d\ud05e\ud05f\ud060\ud061\ud062\ud063\ud064\ud065\ud066\ud067\ud068\ud069\ud06a\ud06b\ud06c\ud06d\ud06e\ud06f\ud070\ud071\ud072\ud073\ud074\ud075\ud076\ud077\ud078\ud079\ud07a\ud07b\ud07c\ud07d\ud07e\ud07f\ud080\ud081\ud082\ud083\ud084\ud085\ud086\ud087\ud088\ud089\ud08a\ud08b\ud08c\ud08d\ud08e\ud08f\ud090\ud091\ud092\ud093\ud094\ud095\ud096\ud097\ud098\ud099\ud09a\ud09b\ud09c\ud09d\ud09e\ud09f\ud0a0\ud0a1\ud0a2\ud0a3\ud0a4\ud0a5\ud0a6\ud0a7\ud0a8\ud0a9\ud0aa\ud0ab\ud0ac\ud0ad\ud0ae\ud0af\ud0b0\ud0b1\ud0b2\ud0b3\ud0b4\ud0b5\ud0b6\ud0b7\ud0b8\ud0b9\ud0ba\ud0bb\ud0bc\ud0bd\ud0be\ud0bf\ud0c0\ud0c1\ud0c2\ud0c3\ud0c4\ud0c5\ud0c6\ud0c7\ud0c8\ud0c9\ud0ca\ud0cb\ud0cc\ud0cd\ud0ce\ud0cf\ud0d0\ud0d1\ud0d2\ud0d3\ud0d4\ud0d5\ud0d6\ud0d7\ud0d8\ud0d9\ud0da\ud0db\ud0dc\ud0dd\ud0de\ud0df\ud0e0\ud0e1\ud0e2\ud0e3\ud0e4\ud0e5\ud0e6\ud0e7\ud0e8\ud0e9\ud0ea\ud0eb\ud0ec\ud0ed\ud0ee\ud0ef\ud0f0\ud0f1\ud0f2\ud0f3\ud0f4\ud0f5\ud0f6\ud0f7\ud0f8\ud0f9\ud0fa\ud0fb\ud0fc\ud0fd\ud0fe\ud0ff\ud100\ud101\ud102\ud103\ud104\ud105\ud106\ud107\ud108\ud109\ud10a\ud10b\ud10c\ud10d\ud10e\ud10f\ud110\ud111\ud112\ud113\ud114\ud115\ud116\ud117\ud118\ud119\ud11a\ud11b\ud11c\ud11d\ud11e\ud11f\ud120\ud121\ud122\ud123\ud124\ud125\ud126\ud127\ud128\ud129\ud12a\ud12b\ud12c\ud12d\ud12e\ud12f\ud130\ud131\ud132\ud133\ud134\ud135\ud136\ud137\ud138\ud139\ud13a\ud13b\ud13c\ud13d\ud13e\ud13f\ud140\ud141\ud142\ud143\ud144\ud145\ud146\ud147\ud148\ud149\ud14a\ud14b\ud14c\ud14d\ud14e\ud14f\ud150\ud151\ud152\ud153\ud154\ud155\ud156\ud157\ud158\ud159\ud15a\ud15b\ud15c\ud15d\ud15e\ud15f\ud160\ud161\ud162\ud163\ud164\ud165\ud166\ud167\ud168\ud169\ud16a\ud16b\ud16c\ud16d\ud16e\ud16f\ud170\ud171\ud172\ud173\ud174\ud175\ud176\ud177\ud178\ud179\ud17a\ud17b\ud17c\ud17d\ud17e\ud17f\ud180\ud181\ud182\ud183\ud184\ud185\ud186\ud187\ud188\ud189\ud18a\ud18b\ud18c\ud18d\ud18e\ud18f\ud190\ud191\ud192\ud193\ud194\ud195\ud196\ud197\ud198\ud199\ud19a\ud19b\ud19c\ud19d\ud19e\ud19f\ud1a0\ud1a1\ud1a2\ud1a3\ud1a4\ud1a5\ud1a6\ud1a7\ud1a8\ud1a9\ud1aa\ud1ab\ud1ac\ud1ad\ud1ae\ud1af\ud1b0\ud1b1\ud1b2\ud1b3\ud1b4\ud1b5\ud1b6\ud1b7\ud1b8\ud1b9\ud1ba\ud1bb\ud1bc\ud1bd\ud1be\ud1bf\ud1c0\ud1c1\ud1c2\ud1c3\ud1c4\ud1c5\ud1c6\ud1c7\ud1c8\ud1c9\ud1ca\ud1cb\ud1cc\ud1cd\ud1ce\ud1cf\ud1d0\ud1d1\ud1d2\ud1d3\ud1d4\ud1d5\ud1d6\ud1d7\ud1d8\ud1d9\ud1da\ud1db\ud1dc\ud1dd\ud1de\ud1df\ud1e0\ud1e1\ud1e2\ud1e3\ud1e4\ud1e5\ud1e6\ud1e7\ud1e8\ud1e9\ud1ea\ud1eb\ud1ec\ud1ed\ud1ee\ud1ef\ud1f0\ud1f1\ud1f2\ud1f3\ud1f4\ud1f5\ud1f6\ud1f7\ud1f8\ud1f9\ud1fa\ud1fb\ud1fc\ud1fd\ud1fe\ud1ff\ud200\ud201\ud202\ud203\ud204\ud205\ud206\ud207\ud208\ud209\ud20a\ud20b\ud20c\ud20d\ud20e\ud20f\ud210\ud211\ud212\ud213\ud214\ud215\ud216\ud217\ud218\ud219\ud21a\ud21b\ud21c\ud21d\ud21e\ud21f\ud220\ud221\ud222\ud223\ud224\ud225\ud226\ud227\ud228\ud229\ud22a\ud22b\ud22c\ud22d\ud22e\ud22f\ud230\ud231\ud232\ud233\ud234\ud235\ud236\ud237\ud238\ud239\ud23a\ud23b\ud23c\ud23d\ud23e\ud23f\ud240\ud241\ud242\ud243\ud244\ud245\ud246\ud247\ud248\ud249\ud24a\ud24b\ud24c\ud24d\ud24e\ud24f\ud250\ud251\ud252\ud253\ud254\ud255\ud256\ud257\ud258\ud259\ud25a\ud25b\ud25c\ud25d\ud25e\ud25f\ud260\ud261\ud262\ud263\ud264\ud265\ud266\ud267\ud268\ud269\ud26a\ud26b\ud26c\ud26d\ud26e\ud26f\ud270\ud271\ud272\ud273\ud274\ud275\ud276\ud277\ud278\ud279\ud27a\ud27b\ud27c\ud27d\ud27e\ud27f\ud280\ud281\ud282\ud283\ud284\ud285\ud286\ud287\ud288\ud289\ud28a\ud28b\ud28c\ud28d\ud28e\ud28f\ud290\ud291\ud292\ud293\ud294\ud295\ud296\ud297\ud298\ud299\ud29a\ud29b\ud29c\ud29d\ud29e\ud29f\ud2a0\ud2a1\ud2a2\ud2a3\ud2a4\ud2a5\ud2a6\ud2a7\ud2a8\ud2a9\ud2aa\ud2ab\ud2ac\ud2ad\ud2ae\ud2af\ud2b0\ud2b1\ud2b2\ud2b3\ud2b4\ud2b5\ud2b6\ud2b7\ud2b8\ud2b9\ud2ba\ud2bb\ud2bc\ud2bd\ud2be\ud2bf\ud2c0\ud2c1\ud2c2\ud2c3\ud2c4\ud2c5\ud2c6\ud2c7\ud2c8\ud2c9\ud2ca\ud2cb\ud2cc\ud2cd\ud2ce\ud2cf\ud2d0\ud2d1\ud2d2\ud2d3\ud2d4\ud2d5\ud2d6\ud2d7\ud2d8\ud2d9\ud2da\ud2db\ud2dc\ud2dd\ud2de\ud2df\ud2e0\ud2e1\ud2e2\ud2e3\ud2e4\ud2e5\ud2e6\ud2e7\ud2e8\ud2e9\ud2ea\ud2eb\ud2ec\ud2ed\ud2ee\ud2ef\ud2f0\ud2f1\ud2f2\ud2f3\ud2f4\ud2f5\ud2f6\ud2f7\ud2f8\ud2f9\ud2fa\ud2fb\ud2fc\ud2fd\ud2fe\ud2ff\ud300\ud301\ud302\ud303\ud304\ud305\ud306\ud307\ud308\ud309\ud30a\ud30b\ud30c\ud30d\ud30e\ud30f\ud310\ud311\ud312\ud313\ud314\ud315\ud316\ud317\ud318\ud319\ud31a\ud31b\ud31c\ud31d\ud31e\ud31f\ud320\ud321\ud322\ud323\ud324\ud325\ud326\ud327\ud328\ud329\ud32a\ud32b\ud32c\ud32d\ud32e\ud32f\ud330\ud331\ud332\ud333\ud334\ud335\ud336\ud337\ud338\ud339\ud33a\ud33b\ud33c\ud33d\ud33e\ud33f\ud340\ud341\ud342\ud343\ud344\ud345\ud346\ud347\ud348\ud349\ud34a\ud34b\ud34c\ud34d\ud34e\ud34f\ud350\ud351\ud352\ud353\ud354\ud355\ud356\ud357\ud358\ud359\ud35a\ud35b\ud35c\ud35d\ud35e\ud35f\ud360\ud361\ud362\ud363\ud364\ud365\ud366\ud367\ud368\ud369\ud36a\ud36b\ud36c\ud36d\ud36e\ud36f\ud370\ud371\ud372\ud373\ud374\ud375\ud376\ud377\ud378\ud379\ud37a\ud37b\ud37c\ud37d\ud37e\ud37f\ud380\ud381\ud382\ud383\ud384\ud385\ud386\ud387\ud388\ud389\ud38a\ud38b\ud38c\ud38d\ud38e\ud38f\ud390\ud391\ud392\ud393\ud394\ud395\ud396\ud397\ud398\ud399\ud39a\ud39b\ud39c\ud39d\ud39e\ud39f\ud3a0\ud3a1\ud3a2\ud3a3\ud3a4\ud3a5\ud3a6\ud3a7\ud3a8\ud3a9\ud3aa\ud3ab\ud3ac\ud3ad\ud3ae\ud3af\ud3b0\ud3b1\ud3b2\ud3b3\ud3b4\ud3b5\ud3b6\ud3b7\ud3b8\ud3b9\ud3ba\ud3bb\ud3bc\ud3bd\ud3be\ud3bf\ud3c0\ud3c1\ud3c2\ud3c3\ud3c4\ud3c5\ud3c6\ud3c7\ud3c8\ud3c9\ud3ca\ud3cb\ud3cc\ud3cd\ud3ce\ud3cf\ud3d0\ud3d1\ud3d2\ud3d3\ud3d4\ud3d5\ud3d6\ud3d7\ud3d8\ud3d9\ud3da\ud3db\ud3dc\ud3dd\ud3de\ud3df\ud3e0\ud3e1\ud3e2\ud3e3\ud3e4\ud3e5\ud3e6\ud3e7\ud3e8\ud3e9\ud3ea\ud3eb\ud3ec\ud3ed\ud3ee\ud3ef\ud3f0\ud3f1\ud3f2\ud3f3\ud3f4\ud3f5\ud3f6\ud3f7\ud3f8\ud3f9\ud3fa\ud3fb\ud3fc\ud3fd\ud3fe\ud3ff\ud400\ud401\ud402\ud403\ud404\ud405\ud406\ud407\ud408\ud409\ud40a\ud40b\ud40c\ud40d\ud40e\ud40f\ud410\ud411\ud412\ud413\ud414\ud415\ud416\ud417\ud418\ud419\ud41a\ud41b\ud41c\ud41d\ud41e\ud41f\ud420\ud421\ud422\ud423\ud424\ud425\ud426\ud427\ud428\ud429\ud42a\ud42b\ud42c\ud42d\ud42e\ud42f\ud430\ud431\ud432\ud433\ud434\ud435\ud436\ud437\ud438\ud439\ud43a\ud43b\ud43c\ud43d\ud43e\ud43f\ud440\ud441\ud442\ud443\ud444\ud445\ud446\ud447\ud448\ud449\ud44a\ud44b\ud44c\ud44d\ud44e\ud44f\ud450\ud451\ud452\ud453\ud454\ud455\ud456\ud457\ud458\ud459\ud45a\ud45b\ud45c\ud45d\ud45e\ud45f\ud460\ud461\ud462\ud463\ud464\ud465\ud466\ud467\ud468\ud469\ud46a\ud46b\ud46c\ud46d\ud46e\ud46f\ud470\ud471\ud472\ud473\ud474\ud475\ud476\ud477\ud478\ud479\ud47a\ud47b\ud47c\ud47d\ud47e\ud47f\ud480\ud481\ud482\ud483\ud484\ud485\ud486\ud487\ud488\ud489\ud48a\ud48b\ud48c\ud48d\ud48e\ud48f\ud490\ud491\ud492\ud493\ud494\ud495\ud496\ud497\ud498\ud499\ud49a\ud49b\ud49c\ud49d\ud49e\ud49f\ud4a0\ud4a1\ud4a2\ud4a3\ud4a4\ud4a5\ud4a6\ud4a7\ud4a8\ud4a9\ud4aa\ud4ab\ud4ac\ud4ad\ud4ae\ud4af\ud4b0\ud4b1\ud4b2\ud4b3\ud4b4\ud4b5\ud4b6\ud4b7\ud4b8\ud4b9\ud4ba\ud4bb\ud4bc\ud4bd\ud4be\ud4bf\ud4c0\ud4c1\ud4c2\ud4c3\ud4c4\ud4c5\ud4c6\ud4c7\ud4c8\ud4c9\ud4ca\ud4cb\ud4cc\ud4cd\ud4ce\ud4cf\ud4d0\ud4d1\ud4d2\ud4d3\ud4d4\ud4d5\ud4d6\ud4d7\ud4d8\ud4d9\ud4da\ud4db\ud4dc\ud4dd\ud4de\ud4df\ud4e0\ud4e1\ud4e2\ud4e3\ud4e4\ud4e5\ud4e6\ud4e7\ud4e8\ud4e9\ud4ea\ud4eb\ud4ec\ud4ed\ud4ee\ud4ef\ud4f0\ud4f1\ud4f2\ud4f3\ud4f4\ud4f5\ud4f6\ud4f7\ud4f8\ud4f9\ud4fa\ud4fb\ud4fc\ud4fd\ud4fe\ud4ff\ud500\ud501\ud502\ud503\ud504\ud505\ud506\ud507\ud508\ud509\ud50a\ud50b\ud50c\ud50d\ud50e\ud50f\ud510\ud511\ud512\ud513\ud514\ud515\ud516\ud517\ud518\ud519\ud51a\ud51b\ud51c\ud51d\ud51e\ud51f\ud520\ud521\ud522\ud523\ud524\ud525\ud526\ud527\ud528\ud529\ud52a\ud52b\ud52c\ud52d\ud52e\ud52f\ud530\ud531\ud532\ud533\ud534\ud535\ud536\ud537\ud538\ud539\ud53a\ud53b\ud53c\ud53d\ud53e\ud53f\ud540\ud541\ud542\ud543\ud544\ud545\ud546\ud547\ud548\ud549\ud54a\ud54b\ud54c\ud54d\ud54e\ud54f\ud550\ud551\ud552\ud553\ud554\ud555\ud556\ud557\ud558\ud559\ud55a\ud55b\ud55c\ud55d\ud55e\ud55f\ud560\ud561\ud562\ud563\ud564\ud565\ud566\ud567\ud568\ud569\ud56a\ud56b\ud56c\ud56d\ud56e\ud56f\ud570\ud571\ud572\ud573\ud574\ud575\ud576\ud577\ud578\ud579\ud57a\ud57b\ud57c\ud57d\ud57e\ud57f\ud580\ud581\ud582\ud583\ud584\ud585\ud586\ud587\ud588\ud589\ud58a\ud58b\ud58c\ud58d\ud58e\ud58f\ud590\ud591\ud592\ud593\ud594\ud595\ud596\ud597\ud598\ud599\ud59a\ud59b\ud59c\ud59d\ud59e\ud59f\ud5a0\ud5a1\ud5a2\ud5a3\ud5a4\ud5a5\ud5a6\ud5a7\ud5a8\ud5a9\ud5aa\ud5ab\ud5ac\ud5ad\ud5ae\ud5af\ud5b0\ud5b1\ud5b2\ud5b3\ud5b4\ud5b5\ud5b6\ud5b7\ud5b8\ud5b9\ud5ba\ud5bb\ud5bc\ud5bd\ud5be\ud5bf\ud5c0\ud5c1\ud5c2\ud5c3\ud5c4\ud5c5\ud5c6\ud5c7\ud5c8\ud5c9\ud5ca\ud5cb\ud5cc\ud5cd\ud5ce\ud5cf\ud5d0\ud5d1\ud5d2\ud5d3\ud5d4\ud5d5\ud5d6\ud5d7\ud5d8\ud5d9\ud5da\ud5db\ud5dc\ud5dd\ud5de\ud5df\ud5e0\ud5e1\ud5e2\ud5e3\ud5e4\ud5e5\ud5e6\ud5e7\ud5e8\ud5e9\ud5ea\ud5eb\ud5ec\ud5ed\ud5ee\ud5ef\ud5f0\ud5f1\ud5f2\ud5f3\ud5f4\ud5f5\ud5f6\ud5f7\ud5f8\ud5f9\ud5fa\ud5fb\ud5fc\ud5fd\ud5fe\ud5ff\ud600\ud601\ud602\ud603\ud604\ud605\ud606\ud607\ud608\ud609\ud60a\ud60b\ud60c\ud60d\ud60e\ud60f\ud610\ud611\ud612\ud613\ud614\ud615\ud616\ud617\ud618\ud619\ud61a\ud61b\ud61c\ud61d\ud61e\ud61f\ud620\ud621\ud622\ud623\ud624\ud625\ud626\ud627\ud628\ud629\ud62a\ud62b\ud62c\ud62d\ud62e\ud62f\ud630\ud631\ud632\ud633\ud634\ud635\ud636\ud637\ud638\ud639\ud63a\ud63b\ud63c\ud63d\ud63e\ud63f\ud640\ud641\ud642\ud643\ud644\ud645\ud646\ud647\ud648\ud649\ud64a\ud64b\ud64c\ud64d\ud64e\ud64f\ud650\ud651\ud652\ud653\ud654\ud655\ud656\ud657\ud658\ud659\ud65a\ud65b\ud65c\ud65d\ud65e\ud65f\ud660\ud661\ud662\ud663\ud664\ud665\ud666\ud667\ud668\ud669\ud66a\ud66b\ud66c\ud66d\ud66e\ud66f\ud670\ud671\ud672\ud673\ud674\ud675\ud676\ud677\ud678\ud679\ud67a\ud67b\ud67c\ud67d\ud67e\ud67f\ud680\ud681\ud682\ud683\ud684\ud685\ud686\ud687\ud688\ud689\ud68a\ud68b\ud68c\ud68d\ud68e\ud68f\ud690\ud691\ud692\ud693\ud694\ud695\ud696\ud697\ud698\ud699\ud69a\ud69b\ud69c\ud69d\ud69e\ud69f\ud6a0\ud6a1\ud6a2\ud6a3\ud6a4\ud6a5\ud6a6\ud6a7\ud6a8\ud6a9\ud6aa\ud6ab\ud6ac\ud6ad\ud6ae\ud6af\ud6b0\ud6b1\ud6b2\ud6b3\ud6b4\ud6b5\ud6b6\ud6b7\ud6b8\ud6b9\ud6ba\ud6bb\ud6bc\ud6bd\ud6be\ud6bf\ud6c0\ud6c1\ud6c2\ud6c3\ud6c4\ud6c5\ud6c6\ud6c7\ud6c8\ud6c9\ud6ca\ud6cb\ud6cc\ud6cd\ud6ce\ud6cf\ud6d0\ud6d1\ud6d2\ud6d3\ud6d4\ud6d5\ud6d6\ud6d7\ud6d8\ud6d9\ud6da\ud6db\ud6dc\ud6dd\ud6de\ud6df\ud6e0\ud6e1\ud6e2\ud6e3\ud6e4\ud6e5\ud6e6\ud6e7\ud6e8\ud6e9\ud6ea\ud6eb\ud6ec\ud6ed\ud6ee\ud6ef\ud6f0\ud6f1\ud6f2\ud6f3\ud6f4\ud6f5\ud6f6\ud6f7\ud6f8\ud6f9\ud6fa\ud6fb\ud6fc\ud6fd\ud6fe\ud6ff\ud700\ud701\ud702\ud703\ud704\ud705\ud706\ud707\ud708\ud709\ud70a\ud70b\ud70c\ud70d\ud70e\ud70f\ud710\ud711\ud712\ud713\ud714\ud715\ud716\ud717\ud718\ud719\ud71a\ud71b\ud71c\ud71d\ud71e\ud71f\ud720\ud721\ud722\ud723\ud724\ud725\ud726\ud727\ud728\ud729\ud72a\ud72b\ud72c\ud72d\ud72e\ud72f\ud730\ud731\ud732\ud733\ud734\ud735\ud736\ud737\ud738\ud739\ud73a\ud73b\ud73c\ud73d\ud73e\ud73f\ud740\ud741\ud742\ud743\ud744\ud745\ud746\ud747\ud748\ud749\ud74a\ud74b\ud74c\ud74d\ud74e\ud74f\ud750\ud751\ud752\ud753\ud754\ud755\ud756\ud757\ud758\ud759\ud75a\ud75b\ud75c\ud75d\ud75e\ud75f\ud760\ud761\ud762\ud763\ud764\ud765\ud766\ud767\ud768\ud769\ud76a\ud76b\ud76c\ud76d\ud76e\ud76f\ud770\ud771\ud772\ud773\ud774\ud775\ud776\ud777\ud778\ud779\ud77a\ud77b\ud77c\ud77d\ud77e\ud77f\ud780\ud781\ud782\ud783\ud784\ud785\ud786\ud787\ud788\ud789\ud78a\ud78b\ud78c\ud78d\ud78e\ud78f\ud790\ud791\ud792\ud793\ud794\ud795\ud796\ud797\ud798\ud799\ud79a\ud79b\ud79c\ud79d\ud79e\ud79f\ud7a0\ud7a1\ud7a2\ud7a3\uf900\uf901\uf902\uf903\uf904\uf905\uf906\uf907\uf908\uf909\uf90a\uf90b\uf90c\uf90d\uf90e\uf90f\uf910\uf911\uf912\uf913\uf914\uf915\uf916\uf917\uf918\uf919\uf91a\uf91b\uf91c\uf91d\uf91e\uf91f\uf920\uf921\uf922\uf923\uf924\uf925\uf926\uf927\uf928\uf929\uf92a\uf92b\uf92c\uf92d\uf92e\uf92f\uf930\uf931\uf932\uf933\uf934\uf935\uf936\uf937\uf938\uf939\uf93a\uf93b\uf93c\uf93d\uf93e\uf93f\uf940\uf941\uf942\uf943\uf944\uf945\uf946\uf947\uf948\uf949\uf94a\uf94b\uf94c\uf94d\uf94e\uf94f\uf950\uf951\uf952\uf953\uf954\uf955\uf956\uf957\uf958\uf959\uf95a\uf95b\uf95c\uf95d\uf95e\uf95f\uf960\uf961\uf962\uf963\uf964\uf965\uf966\uf967\uf968\uf969\uf96a\uf96b\uf96c\uf96d\uf96e\uf96f\uf970\uf971\uf972\uf973\uf974\uf975\uf976\uf977\uf978\uf979\uf97a\uf97b\uf97c\uf97d\uf97e\uf97f\uf980\uf981\uf982\uf983\uf984\uf985\uf986\uf987\uf988\uf989\uf98a\uf98b\uf98c\uf98d\uf98e\uf98f\uf990\uf991\uf992\uf993\uf994\uf995\uf996\uf997\uf998\uf999\uf99a\uf99b\uf99c\uf99d\uf99e\uf99f\uf9a0\uf9a1\uf9a2\uf9a3\uf9a4\uf9a5\uf9a6\uf9a7\uf9a8\uf9a9\uf9aa\uf9ab\uf9ac\uf9ad\uf9ae\uf9af\uf9b0\uf9b1\uf9b2\uf9b3\uf9b4\uf9b5\uf9b6\uf9b7\uf9b8\uf9b9\uf9ba\uf9bb\uf9bc\uf9bd\uf9be\uf9bf\uf9c0\uf9c1\uf9c2\uf9c3\uf9c4\uf9c5\uf9c6\uf9c7\uf9c8\uf9c9\uf9ca\uf9cb\uf9cc\uf9cd\uf9ce\uf9cf\uf9d0\uf9d1\uf9d2\uf9d3\uf9d4\uf9d5\uf9d6\uf9d7\uf9d8\uf9d9\uf9da\uf9db\uf9dc\uf9dd\uf9de\uf9df\uf9e0\uf9e1\uf9e2\uf9e3\uf9e4\uf9e5\uf9e6\uf9e7\uf9e8\uf9e9\uf9ea\uf9eb\uf9ec\uf9ed\uf9ee\uf9ef\uf9f0\uf9f1\uf9f2\uf9f3\uf9f4\uf9f5\uf9f6\uf9f7\uf9f8\uf9f9\uf9fa\uf9fb\uf9fc\uf9fd\uf9fe\uf9ff\ufa00\ufa01\ufa02\ufa03\ufa04\ufa05\ufa06\ufa07\ufa08\ufa09\ufa0a\ufa0b\ufa0c\ufa0d\ufa0e\ufa0f\ufa10\ufa11\ufa12\ufa13\ufa14\ufa15\ufa16\ufa17\ufa18\ufa19\ufa1a\ufa1b\ufa1c\ufa1d\ufa1e\ufa1f\ufa20\ufa21\ufa22\ufa23\ufa24\ufa25\ufa26\ufa27\ufa28\ufa29\ufa2a\ufa2b\ufa2c\ufa2d\ufa30\ufa31\ufa32\ufa33\ufa34\ufa35\ufa36\ufa37\ufa38\ufa39\ufa3a\ufa3b\ufa3c\ufa3d\ufa3e\ufa3f\ufa40\ufa41\ufa42\ufa43\ufa44\ufa45\ufa46\ufa47\ufa48\ufa49\ufa4a\ufa4b\ufa4c\ufa4d\ufa4e\ufa4f\ufa50\ufa51\ufa52\ufa53\ufa54\ufa55\ufa56\ufa57\ufa58\ufa59\ufa5a\ufa5b\ufa5c\ufa5d\ufa5e\ufa5f\ufa60\ufa61\ufa62\ufa63\ufa64\ufa65\ufa66\ufa67\ufa68\ufa69\ufa6a\ufa70\ufa71\ufa72\ufa73\ufa74\ufa75\ufa76\ufa77\ufa78\ufa79\ufa7a\ufa7b\ufa7c\ufa7d\ufa7e\ufa7f\ufa80\ufa81\ufa82\ufa83\ufa84\ufa85\ufa86\ufa87\ufa88\ufa89\ufa8a\ufa8b\ufa8c\ufa8d\ufa8e\ufa8f\ufa90\ufa91\ufa92\ufa93\ufa94\ufa95\ufa96\ufa97\ufa98\ufa99\ufa9a\ufa9b\ufa9c\ufa9d\ufa9e\ufa9f\ufaa0\ufaa1\ufaa2\ufaa3\ufaa4\ufaa5\ufaa6\ufaa7\ufaa8\ufaa9\ufaaa\ufaab\ufaac\ufaad\ufaae\ufaaf\ufab0\ufab1\ufab2\ufab3\ufab4\ufab5\ufab6\ufab7\ufab8\ufab9\ufaba\ufabb\ufabc\ufabd\ufabe\ufabf\ufac0\ufac1\ufac2\ufac3\ufac4\ufac5\ufac6\ufac7\ufac8\ufac9\ufaca\ufacb\ufacc\ufacd\uface\ufacf\ufad0\ufad1\ufad2\ufad3\ufad4\ufad5\ufad6\ufad7\ufad8\ufad9\ufb1d\ufb1f\ufb20\ufb21\ufb22\ufb23\ufb24\ufb25\ufb26\ufb27\ufb28\ufb2a\ufb2b\ufb2c\ufb2d\ufb2e\ufb2f\ufb30\ufb31\ufb32\ufb33\ufb34\ufb35\ufb36\ufb38\ufb39\ufb3a\ufb3b\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46\ufb47\ufb48\ufb49\ufb4a\ufb4b\ufb4c\ufb4d\ufb4e\ufb4f\ufb50\ufb51\ufb52\ufb53\ufb54\ufb55\ufb56\ufb57\ufb58\ufb59\ufb5a\ufb5b\ufb5c\ufb5d\ufb5e\ufb5f\ufb60\ufb61\ufb62\ufb63\ufb64\ufb65\ufb66\ufb67\ufb68\ufb69\ufb6a\ufb6b\ufb6c\ufb6d\ufb6e\ufb6f\ufb70\ufb71\ufb72\ufb73\ufb74\ufb75\ufb76\ufb77\ufb78\ufb79\ufb7a\ufb7b\ufb7c\ufb7d\ufb7e\ufb7f\ufb80\ufb81\ufb82\ufb83\ufb84\ufb85\ufb86\ufb87\ufb88\ufb89\ufb8a\ufb8b\ufb8c\ufb8d\ufb8e\ufb8f\ufb90\ufb91\ufb92\ufb93\ufb94\ufb95\ufb96\ufb97\ufb98\ufb99\ufb9a\ufb9b\ufb9c\ufb9d\ufb9e\ufb9f\ufba0\ufba1\ufba2\ufba3\ufba4\ufba5\ufba6\ufba7\ufba8\ufba9\ufbaa\ufbab\ufbac\ufbad\ufbae\ufbaf\ufbb0\ufbb1\ufbd3\ufbd4\ufbd5\ufbd6\ufbd7\ufbd8\ufbd9\ufbda\ufbdb\ufbdc\ufbdd\ufbde\ufbdf\ufbe0\ufbe1\ufbe2\ufbe3\ufbe4\ufbe5\ufbe6\ufbe7\ufbe8\ufbe9\ufbea\ufbeb\ufbec\ufbed\ufbee\ufbef\ufbf0\ufbf1\ufbf2\ufbf3\ufbf4\ufbf5\ufbf6\ufbf7\ufbf8\ufbf9\ufbfa\ufbfb\ufbfc\ufbfd\ufbfe\ufbff\ufc00\ufc01\ufc02\ufc03\ufc04\ufc05\ufc06\ufc07\ufc08\ufc09\ufc0a\ufc0b\ufc0c\ufc0d\ufc0e\ufc0f\ufc10\ufc11\ufc12\ufc13\ufc14\ufc15\ufc16\ufc17\ufc18\ufc19\ufc1a\ufc1b\ufc1c\ufc1d\ufc1e\ufc1f\ufc20\ufc21\ufc22\ufc23\ufc24\ufc25\ufc26\ufc27\ufc28\ufc29\ufc2a\ufc2b\ufc2c\ufc2d\ufc2e\ufc2f\ufc30\ufc31\ufc32\ufc33\ufc34\ufc35\ufc36\ufc37\ufc38\ufc39\ufc3a\ufc3b\ufc3c\ufc3d\ufc3e\ufc3f\ufc40\ufc41\ufc42\ufc43\ufc44\ufc45\ufc46\ufc47\ufc48\ufc49\ufc4a\ufc4b\ufc4c\ufc4d\ufc4e\ufc4f\ufc50\ufc51\ufc52\ufc53\ufc54\ufc55\ufc56\ufc57\ufc58\ufc59\ufc5a\ufc5b\ufc5c\ufc5d\ufc5e\ufc5f\ufc60\ufc61\ufc62\ufc63\ufc64\ufc65\ufc66\ufc67\ufc68\ufc69\ufc6a\ufc6b\ufc6c\ufc6d\ufc6e\ufc6f\ufc70\ufc71\ufc72\ufc73\ufc74\ufc75\ufc76\ufc77\ufc78\ufc79\ufc7a\ufc7b\ufc7c\ufc7d\ufc7e\ufc7f\ufc80\ufc81\ufc82\ufc83\ufc84\ufc85\ufc86\ufc87\ufc88\ufc89\ufc8a\ufc8b\ufc8c\ufc8d\ufc8e\ufc8f\ufc90\ufc91\ufc92\ufc93\ufc94\ufc95\ufc96\ufc97\ufc98\ufc99\ufc9a\ufc9b\ufc9c\ufc9d\ufc9e\ufc9f\ufca0\ufca1\ufca2\ufca3\ufca4\ufca5\ufca6\ufca7\ufca8\ufca9\ufcaa\ufcab\ufcac\ufcad\ufcae\ufcaf\ufcb0\ufcb1\ufcb2\ufcb3\ufcb4\ufcb5\ufcb6\ufcb7\ufcb8\ufcb9\ufcba\ufcbb\ufcbc\ufcbd\ufcbe\ufcbf\ufcc0\ufcc1\ufcc2\ufcc3\ufcc4\ufcc5\ufcc6\ufcc7\ufcc8\ufcc9\ufcca\ufccb\ufccc\ufccd\ufcce\ufccf\ufcd0\ufcd1\ufcd2\ufcd3\ufcd4\ufcd5\ufcd6\ufcd7\ufcd8\ufcd9\ufcda\ufcdb\ufcdc\ufcdd\ufcde\ufcdf\ufce0\ufce1\ufce2\ufce3\ufce4\ufce5\ufce6\ufce7\ufce8\ufce9\ufcea\ufceb\ufcec\ufced\ufcee\ufcef\ufcf0\ufcf1\ufcf2\ufcf3\ufcf4\ufcf5\ufcf6\ufcf7\ufcf8\ufcf9\ufcfa\ufcfb\ufcfc\ufcfd\ufcfe\ufcff\ufd00\ufd01\ufd02\ufd03\ufd04\ufd05\ufd06\ufd07\ufd08\ufd09\ufd0a\ufd0b\ufd0c\ufd0d\ufd0e\ufd0f\ufd10\ufd11\ufd12\ufd13\ufd14\ufd15\ufd16\ufd17\ufd18\ufd19\ufd1a\ufd1b\ufd1c\ufd1d\ufd1e\ufd1f\ufd20\ufd21\ufd22\ufd23\ufd24\ufd25\ufd26\ufd27\ufd28\ufd29\ufd2a\ufd2b\ufd2c\ufd2d\ufd2e\ufd2f\ufd30\ufd31\ufd32\ufd33\ufd34\ufd35\ufd36\ufd37\ufd38\ufd39\ufd3a\ufd3b\ufd3c\ufd3d\ufd50\ufd51\ufd52\ufd53\ufd54\ufd55\ufd56\ufd57\ufd58\ufd59\ufd5a\ufd5b\ufd5c\ufd5d\ufd5e\ufd5f\ufd60\ufd61\ufd62\ufd63\ufd64\ufd65\ufd66\ufd67\ufd68\ufd69\ufd6a\ufd6b\ufd6c\ufd6d\ufd6e\ufd6f\ufd70\ufd71\ufd72\ufd73\ufd74\ufd75\ufd76\ufd77\ufd78\ufd79\ufd7a\ufd7b\ufd7c\ufd7d\ufd7e\ufd7f\ufd80\ufd81\ufd82\ufd83\ufd84\ufd85\ufd86\ufd87\ufd88\ufd89\ufd8a\ufd8b\ufd8c\ufd8d\ufd8e\ufd8f\ufd92\ufd93\ufd94\ufd95\ufd96\ufd97\ufd98\ufd99\ufd9a\ufd9b\ufd9c\ufd9d\ufd9e\ufd9f\ufda0\ufda1\ufda2\ufda3\ufda4\ufda5\ufda6\ufda7\ufda8\ufda9\ufdaa\ufdab\ufdac\ufdad\ufdae\ufdaf\ufdb0\ufdb1\ufdb2\ufdb3\ufdb4\ufdb5\ufdb6\ufdb7\ufdb8\ufdb9\ufdba\ufdbb\ufdbc\ufdbd\ufdbe\ufdbf\ufdc0\ufdc1\ufdc2\ufdc3\ufdc4\ufdc5\ufdc6\ufdc7\ufdf0\ufdf1\ufdf2\ufdf3\ufdf4\ufdf5\ufdf6\ufdf7\ufdf8\ufdf9\ufdfa\ufdfb\ufe70\ufe71\ufe72\ufe73\ufe74\ufe76\ufe77\ufe78\ufe79\ufe7a\ufe7b\ufe7c\ufe7d\ufe7e\ufe7f\ufe80\ufe81\ufe82\ufe83\ufe84\ufe85\ufe86\ufe87\ufe88\ufe89\ufe8a\ufe8b\ufe8c\ufe8d\ufe8e\ufe8f\ufe90\ufe91\ufe92\ufe93\ufe94\ufe95\ufe96\ufe97\ufe98\ufe99\ufe9a\ufe9b\ufe9c\ufe9d\ufe9e\ufe9f\ufea0\ufea1\ufea2\ufea3\ufea4\ufea5\ufea6\ufea7\ufea8\ufea9\ufeaa\ufeab\ufeac\ufead\ufeae\ufeaf\ufeb0\ufeb1\ufeb2\ufeb3\ufeb4\ufeb5\ufeb6\ufeb7\ufeb8\ufeb9\ufeba\ufebb\ufebc\ufebd\ufebe\ufebf\ufec0\ufec1\ufec2\ufec3\ufec4\ufec5\ufec6\ufec7\ufec8\ufec9\ufeca\ufecb\ufecc\ufecd\ufece\ufecf\ufed0\ufed1\ufed2\ufed3\ufed4\ufed5\ufed6\ufed7\ufed8\ufed9\ufeda\ufedb\ufedc\ufedd\ufede\ufedf\ufee0\ufee1\ufee2\ufee3\ufee4\ufee5\ufee6\ufee7\ufee8\ufee9\ufeea\ufeeb\ufeec\ufeed\ufeee\ufeef\ufef0\ufef1\ufef2\ufef3\ufef4\ufef5\ufef6\ufef7\ufef8\ufef9\ufefa\ufefb\ufefc\uff66\uff67\uff68\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\uff8a\uff8b\uff8c\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d\uffa0\uffa1\uffa2\uffa3\uffa4\uffa5\uffa6\uffa7\uffa8\uffa9\uffaa\uffab\uffac\uffad\uffae\uffaf\uffb0\uffb1\uffb2\uffb3\uffb4\uffb5\uffb6\uffb7\uffb8\uffb9\uffba\uffbb\uffbc\uffbd\uffbe\uffc2\uffc3\uffc4\uffc5\uffc6\uffc7\uffca\uffcb\uffcc\uffcd\uffce\uffcf\uffd2\uffd3\uffd4\uffd5\uffd6\uffd7\uffda\uffdb\uffdc'
+
+Lt = u'\u01c5\u01c8\u01cb\u01f2\u1f88\u1f89\u1f8a\u1f8b\u1f8c\u1f8d\u1f8e\u1f8f\u1f98\u1f99\u1f9a\u1f9b\u1f9c\u1f9d\u1f9e\u1f9f\u1fa8\u1fa9\u1faa\u1fab\u1fac\u1fad\u1fae\u1faf\u1fbc\u1fcc\u1ffc'
+
+Lu = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd8\xd9\xda\xdb\xdc\xdd\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a\u023b\u023d\u023e\u0241\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9\u03fa\u03fd\u03fe\u03ff\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\u2c00\u2c01\u2c02\u2c03\u2c04\u2c05\u2c06\u2c07\u2c08\u2c09\u2c0a\u2c0b\u2c0c\u2c0d\u2c0e\u2c0f\u2c10\u2c11\u2c12\u2c13\u2c14\u2c15\u2c16\u2c17\u2c18\u2c19\u2c1a\u2c1b\u2c1c\u2c1d\u2c1e\u2c1f\u2c20\u2c21\u2c22\u2c23\u2c24\u2c25\u2c26\u2c27\u2c28\u2c29\u2c2a\u2c2b\u2c2c\u2c2d\u2c2e\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a'
+
+Mc = u'\u0903\u093e\u093f\u0940\u0949\u094a\u094b\u094c\u0982\u0983\u09be\u09bf\u09c0\u09c7\u09c8\u09cb\u09cc\u09d7\u0a03\u0a3e\u0a3f\u0a40\u0a83\u0abe\u0abf\u0ac0\u0ac9\u0acb\u0acc\u0b02\u0b03\u0b3e\u0b40\u0b47\u0b48\u0b4b\u0b4c\u0b57\u0bbe\u0bbf\u0bc1\u0bc2\u0bc6\u0bc7\u0bc8\u0bca\u0bcb\u0bcc\u0bd7\u0c01\u0c02\u0c03\u0c41\u0c42\u0c43\u0c44\u0c82\u0c83\u0cbe\u0cc0\u0cc1\u0cc2\u0cc3\u0cc4\u0cc7\u0cc8\u0cca\u0ccb\u0cd5\u0cd6\u0d02\u0d03\u0d3e\u0d3f\u0d40\u0d46\u0d47\u0d48\u0d4a\u0d4b\u0d4c\u0d57\u0d82\u0d83\u0dcf\u0dd0\u0dd1\u0dd8\u0dd9\u0dda\u0ddb\u0ddc\u0ddd\u0dde\u0ddf\u0df2\u0df3\u0f3e\u0f3f\u0f7f\u102c\u1031\u1038\u1056\u1057\u17b6\u17be\u17bf\u17c0\u17c1\u17c2\u17c3\u17c4\u17c5\u17c7\u17c8\u1923\u1924\u1925\u1926\u1929\u192a\u192b\u1930\u1931\u1933\u1934\u1935\u1936\u1937\u1938\u19b0\u19b1\u19b2\u19b3\u19b4\u19b5\u19b6\u19b7\u19b8\u19b9\u19ba\u19bb\u19bc\u19bd\u19be\u19bf\u19c0\u19c8\u19c9\u1a19\u1a1a\u1a1b\ua802\ua823\ua824\ua827'
+
+Me = u'\u0488\u0489\u06de\u20dd\u20de\u20df\u20e0\u20e2\u20e3\u20e4'
+
+Mn = u'\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030a\u030b\u030c\u030d\u030e\u030f\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031a\u031b\u031c\u031d\u031e\u031f\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033a\u033b\u033c\u033d\u033e\u033f\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034a\u034b\u034c\u034d\u034e\u034f\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035a\u035b\u035c\u035d\u035e\u035f\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u036f\u0483\u0484\u0485\u0486\u0591\u0592\u0593\u0594\u0595\u0596\u0597\u0598\u0599\u059a\u059b\u059c\u059d\u059e\u059f\u05a0\u05a1\u05a2\u05a3\u05a4\u05a5\u05a6\u05a7\u05a8\u05a9\u05aa\u05ab\u05ac\u05ad\u05ae\u05af\u05b0\u05b1\u05b2\u05b3\u05b4\u05b5\u05b6\u05b7\u05b8\u05b9\u05bb\u05bc\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610\u0611\u0612\u0613\u0614\u0615\u064b\u064c\u064d\u064e\u064f\u0650\u0651\u0652\u0653\u0654\u0655\u0656\u0657\u0658\u0659\u065a\u065b\u065c\u065d\u065e\u0670\u06d6\u06d7\u06d8\u06d9\u06da\u06db\u06dc\u06df\u06e0\u06e1\u06e2\u06e3\u06e4\u06e7\u06e8\u06ea\u06eb\u06ec\u06ed\u0711\u0730\u0731\u0732\u0733\u0734\u0735\u0736\u0737\u0738\u0739\u073a\u073b\u073c\u073d\u073e\u073f\u0740\u0741\u0742\u0743\u0744\u0745\u0746\u0747\u0748\u0749\u074a\u07a6\u07a7\u07a8\u07a9\u07aa\u07ab\u07ac\u07ad\u07ae\u07af\u07b0\u0901\u0902\u093c\u0941\u0942\u0943\u0944\u0945\u0946\u0947\u0948\u094d\u0951\u0952\u0953\u0954\u0962\u0963\u0981\u09bc\u09c1\u09c2\u09c3\u09c4\u09cd\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b\u0a4c\u0a4d\u0a70\u0a71\u0a81\u0a82\u0abc\u0ac1\u0ac2\u0ac3\u0ac4\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3f\u0b41\u0b42\u0b43\u0b4d\u0b56\u0b82\u0bc0\u0bcd\u0c3e\u0c3f\u0c40\u0c46\u0c47\u0c48\u0c4a\u0c4b\u0c4c\u0c4d\u0c55\u0c56\u0cbc\u0cbf\u0cc6\u0ccc\u0ccd\u0d41\u0d42\u0d43\u0d4d\u0dca\u0dd2\u0dd3\u0dd4\u0dd6\u0e31\u0e34\u0e35\u0e36\u0e37\u0e38\u0e39\u0e3a\u0e47\u0e48\u0e49\u0e4a\u0e4b\u0e4c\u0e4d\u0e4e\u0eb1\u0eb4\u0eb5\u0eb6\u0eb7\u0eb8\u0eb9\u0ebb\u0ebc\u0ec8\u0ec9\u0eca\u0ecb\u0ecc\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71\u0f72\u0f73\u0f74\u0f75\u0f76\u0f77\u0f78\u0f79\u0f7a\u0f7b\u0f7c\u0f7d\u0f7e\u0f80\u0f81\u0f82\u0f83\u0f84\u0f86\u0f87\u0f90\u0f91\u0f92\u0f93\u0f94\u0f95\u0f96\u0f97\u0f99\u0f9a\u0f9b\u0f9c\u0f9d\u0f9e\u0f9f\u0fa0\u0fa1\u0fa2\u0fa3\u0fa4\u0fa5\u0fa6\u0fa7\u0fa8\u0fa9\u0faa\u0fab\u0fac\u0fad\u0fae\u0faf\u0fb0\u0fb1\u0fb2\u0fb3\u0fb4\u0fb5\u0fb6\u0fb7\u0fb8\u0fb9\u0fba\u0fbb\u0fbc\u0fc6\u102d\u102e\u102f\u1030\u1032\u1036\u1037\u1039\u1058\u1059\u135f\u1712\u1713\u1714\u1732\u1733\u1734\u1752\u1753\u1772\u1773\u17b7\u17b8\u17b9\u17ba\u17bb\u17bc\u17bd\u17c6\u17c9\u17ca\u17cb\u17cc\u17cd\u17ce\u17cf\u17d0\u17d1\u17d2\u17d3\u17dd\u180b\u180c\u180d\u18a9\u1920\u1921\u1922\u1927\u1928\u1932\u1939\u193a\u193b\u1a17\u1a18\u1dc0\u1dc1\u1dc2\u1dc3\u20d0\u20d1\u20d2\u20d3\u20d4\u20d5\u20d6\u20d7\u20d8\u20d9\u20da\u20db\u20dc\u20e1\u20e5\u20e6\u20e7\u20e8\u20e9\u20ea\u20eb\u302a\u302b\u302c\u302d\u302e\u302f\u3099\u309a\ua806\ua80b\ua825\ua826\ufb1e\ufe00\ufe01\ufe02\ufe03\ufe04\ufe05\ufe06\ufe07\ufe08\ufe09\ufe0a\ufe0b\ufe0c\ufe0d\ufe0e\ufe0f\ufe20\ufe21\ufe22\ufe23'
+
+Nd = u'0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u1946\u1947\u1948\u1949\u194a\u194b\u194c\u194d\u194e\u194f\u19d0\u19d1\u19d2\u19d3\u19d4\u19d5\u19d6\u19d7\u19d8\u19d9\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19'
+
+Nl = u'\u16ee\u16ef\u16f0\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216a\u216b\u216c\u216d\u216e\u216f\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217a\u217b\u217c\u217d\u217e\u217f\u2180\u2181\u2182\u2183\u3007\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u3038\u3039\u303a'
+
+No = u'\xb2\xb3\xb9\xbc\xbd\xbe\u09f4\u09f5\u09f6\u09f7\u09f8\u09f9\u0bf0\u0bf1\u0bf2\u0f2a\u0f2b\u0f2c\u0f2d\u0f2e\u0f2f\u0f30\u0f31\u0f32\u0f33\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u1372\u1373\u1374\u1375\u1376\u1377\u1378\u1379\u137a\u137b\u137c\u17f0\u17f1\u17f2\u17f3\u17f4\u17f5\u17f6\u17f7\u17f8\u17f9\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215a\u215b\u215c\u215d\u215e\u215f\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u247d\u247e\u247f\u2480\u2481\u2482\u2483\u2484\u2485\u2486\u2487\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u2491\u2492\u2493\u2494\u2495\u2496\u2497\u2498\u2499\u249a\u249b\u24ea\u24eb\u24ec\u24ed\u24ee\u24ef\u24f0\u24f1\u24f2\u24f3\u24f4\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u24fe\u24ff\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u277f\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u2789\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\u2793\u2cfd\u3192\u3193\u3194\u3195\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325a\u325b\u325c\u325d\u325e\u325f\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u32b1\u32b2\u32b3\u32b4\u32b5\u32b6\u32b7\u32b8\u32b9\u32ba\u32bb\u32bc\u32bd\u32be\u32bf'
+
+Pc = u'_\u203f\u2040\u2054\ufe33\ufe34\ufe4d\ufe4e\ufe4f\uff3f'
+
+Pd = u'-\u058a\u1806\u2010\u2011\u2012\u2013\u2014\u2015\u2e17\u301c\u3030\u30a0\ufe31\ufe32\ufe58\ufe63\uff0d'
+
+Pe = u')]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u23b5\u2769\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992\u2994\u2996\u2998\u29d9\u29db\u29fd\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63'
+
+Pf = u'\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d'
+
+Pi = u'\xab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c'
+
+Po = u'!"#%&\'*,./:;?@\\\xa1\xb7\xbf\u037e\u0387\u055a\u055b\u055c\u055d\u055e\u055f\u0589\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u060c\u060d\u061b\u061e\u061f\u066a\u066b\u066c\u066d\u06d4\u0700\u0701\u0702\u0703\u0704\u0705\u0706\u0707\u0708\u0709\u070a\u070b\u070c\u070d\u0964\u0965\u0970\u0df4\u0e4f\u0e5a\u0e5b\u0f04\u0f05\u0f06\u0f07\u0f08\u0f09\u0f0a\u0f0b\u0f0c\u0f0d\u0f0e\u0f0f\u0f10\u0f11\u0f12\u0f85\u0fd0\u0fd1\u104a\u104b\u104c\u104d\u104e\u104f\u10fb\u1361\u1362\u1363\u1364\u1365\u1366\u1367\u1368\u166d\u166e\u16eb\u16ec\u16ed\u1735\u1736\u17d4\u17d5\u17d6\u17d8\u17d9\u17da\u1800\u1801\u1802\u1803\u1804\u1805\u1807\u1808\u1809\u180a\u1944\u1945\u19de\u19df\u1a1e\u1a1f\u2016\u2017\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u203b\u203c\u203d\u203e\u2041\u2042\u2043\u2047\u2048\u2049\u204a\u204b\u204c\u204d\u204e\u204f\u2050\u2051\u2053\u2055\u2056\u2057\u2058\u2059\u205a\u205b\u205c\u205d\u205e\u23b6\u2cf9\u2cfa\u2cfb\u2cfc\u2cfe\u2cff\u2e00\u2e01\u2e06\u2e07\u2e08\u2e0b\u2e0e\u2e0f\u2e10\u2e11\u2e12\u2e13\u2e14\u2e15\u2e16\u3001\u3002\u3003\u303d\u30fb\ufe10\ufe11\ufe12\ufe13\ufe14\ufe15\ufe16\ufe19\ufe30\ufe45\ufe46\ufe49\ufe4a\ufe4b\ufe4c\ufe50\ufe51\ufe52\ufe54\ufe55\ufe56\ufe57\ufe5f\ufe60\ufe61\ufe68\ufe6a\ufe6b\uff01\uff02\uff03\uff05\uff06\uff07\uff0a\uff0c\uff0e\uff0f\uff1a\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65'
+
+Ps = u'([{\u0f3a\u0f3c\u169b\u201a\u201e\u2045\u207d\u208d\u2329\u23b4\u2768\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991\u2993\u2995\u2997\u29d8\u29da\u29fc\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62'
+
+Sc = u'$\xa2\xa3\xa4\xa5\u060b\u09f2\u09f3\u0af1\u0bf9\u0e3f\u17db\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5\u20a6\u20a7\u20a8\u20a9\u20aa\u20ab\u20ac\u20ad\u20ae\u20af\u20b0\u20b1\u20b2\u20b3\u20b4\u20b5\ufdfc\ufe69\uff04\uffe0\uffe1\uffe5\uffe6'
+
+Sk = u'^`\xa8\xaf\xb4\xb8\u02c2\u02c3\u02c4\u02c5\u02d2\u02d3\u02d4\u02d5\u02d6\u02d7\u02d8\u02d9\u02da\u02db\u02dc\u02dd\u02de\u02df\u02e5\u02e6\u02e7\u02e8\u02e9\u02ea\u02eb\u02ec\u02ed\u02ef\u02f0\u02f1\u02f2\u02f3\u02f4\u02f5\u02f6\u02f7\u02f8\u02f9\u02fa\u02fb\u02fc\u02fd\u02fe\u02ff\u0374\u0375\u0384\u0385\u1fbd\u1fbf\u1fc0\u1fc1\u1fcd\u1fce\u1fcf\u1fdd\u1fde\u1fdf\u1fed\u1fee\u1fef\u1ffd\u1ffe\u309b\u309c\ua700\ua701\ua702\ua703\ua704\ua705\ua706\ua707\ua708\ua709\ua70a\ua70b\ua70c\ua70d\ua70e\ua70f\ua710\ua711\ua712\ua713\ua714\ua715\ua716\uff3e\uff40\uffe3'
+
+Sm = u'+<=>|~\xac\xb1\xd7\xf7\u03f6\u2044\u2052\u207a\u207b\u207c\u208a\u208b\u208c\u2140\u2141\u2142\u2143\u2144\u214b\u2190\u2191\u2192\u2193\u2194\u219a\u219b\u21a0\u21a3\u21a6\u21ae\u21ce\u21cf\u21d2\u21d4\u21f4\u21f5\u21f6\u21f7\u21f8\u21f9\u21fa\u21fb\u21fc\u21fd\u21fe\u21ff\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220a\u220b\u220c\u220d\u220e\u220f\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221a\u221b\u221c\u221d\u221e\u221f\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222a\u222b\u222c\u222d\u222e\u222f\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223a\u223b\u223c\u223d\u223e\u223f\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224a\u224b\u224c\u224d\u224e\u224f\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225a\u225b\u225c\u225d\u225e\u225f\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226a\u226b\u226c\u226d\u226e\u226f\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227a\u227b\u227c\u227d\u227e\u227f\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228a\u228b\u228c\u228d\u228e\u228f\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229a\u229b\u229c\u229d\u229e\u229f\u22a0\u22a1\u22a2\u22a3\u22a4\u22a5\u22a6\u22a7\u22a8\u22a9\u22aa\u22ab\u22ac\u22ad\u22ae\u22af\u22b0\u22b1\u22b2\u22b3\u22b4\u22b5\u22b6\u22b7\u22b8\u22b9\u22ba\u22bb\u22bc\u22bd\u22be\u22bf\u22c0\u22c1\u22c2\u22c3\u22c4\u22c5\u22c6\u22c7\u22c8\u22c9\u22ca\u22cb\u22cc\u22cd\u22ce\u22cf\u22d0\u22d1\u22d2\u22d3\u22d4\u22d5\u22d6\u22d7\u22d8\u22d9\u22da\u22db\u22dc\u22dd\u22de\u22df\u22e0\u22e1\u22e2\u22e3\u22e4\u22e5\u22e6\u22e7\u22e8\u22e9\u22ea\u22eb\u22ec\u22ed\u22ee\u22ef\u22f0\u22f1\u22f2\u22f3\u22f4\u22f5\u22f6\u22f7\u22f8\u22f9\u22fa\u22fb\u22fc\u22fd\u22fe\u22ff\u2308\u2309\u230a\u230b\u2320\u2321\u237c\u239b\u239c\u239d\u239e\u239f\u23a0\u23a1\u23a2\u23a3\u23a4\u23a5\u23a6\u23a7\u23a8\u23a9\u23aa\u23ab\u23ac\u23ad\u23ae\u23af\u23b0\u23b1\u23b2\u23b3\u25b7\u25c1\u25f8\u25f9\u25fa\u25fb\u25fc\u25fd\u25fe\u25ff\u266f\u27c0\u27c1\u27c2\u27c3\u27c4\u27d0\u27d1\u27d2\u27d3\u27d4\u27d5\u27d6\u27d7\u27d8\u27d9\u27da\u27db\u27dc\u27dd\u27de\u27df\u27e0\u27e1\u27e2\u27e3\u27e4\u27e5\u27f0\u27f1\u27f2\u27f3\u27f4\u27f5\u27f6\u27f7\u27f8\u27f9\u27fa\u27fb\u27fc\u27fd\u27fe\u27ff\u2900\u2901\u2902\u2903\u2904\u2905\u2906\u2907\u2908\u2909\u290a\u290b\u290c\u290d\u290e\u290f\u2910\u2911\u2912\u2913\u2914\u2915\u2916\u2917\u2918\u2919\u291a\u291b\u291c\u291d\u291e\u291f\u2920\u2921\u2922\u2923\u2924\u2925\u2926\u2927\u2928\u2929\u292a\u292b\u292c\u292d\u292e\u292f\u2930\u2931\u2932\u2933\u2934\u2935\u2936\u2937\u2938\u2939\u293a\u293b\u293c\u293d\u293e\u293f\u2940\u2941\u2942\u2943\u2944\u2945\u2946\u2947\u2948\u2949\u294a\u294b\u294c\u294d\u294e\u294f\u2950\u2951\u2952\u2953\u2954\u2955\u2956\u2957\u2958\u2959\u295a\u295b\u295c\u295d\u295e\u295f\u2960\u2961\u2962\u2963\u2964\u2965\u2966\u2967\u2968\u2969\u296a\u296b\u296c\u296d\u296e\u296f\u2970\u2971\u2972\u2973\u2974\u2975\u2976\u2977\u2978\u2979\u297a\u297b\u297c\u297d\u297e\u297f\u2980\u2981\u2982\u2999\u299a\u299b\u299c\u299d\u299e\u299f\u29a0\u29a1\u29a2\u29a3\u29a4\u29a5\u29a6\u29a7\u29a8\u29a9\u29aa\u29ab\u29ac\u29ad\u29ae\u29af\u29b0\u29b1\u29b2\u29b3\u29b4\u29b5\u29b6\u29b7\u29b8\u29b9\u29ba\u29bb\u29bc\u29bd\u29be\u29bf\u29c0\u29c1\u29c2\u29c3\u29c4\u29c5\u29c6\u29c7\u29c8\u29c9\u29ca\u29cb\u29cc\u29cd\u29ce\u29cf\u29d0\u29d1\u29d2\u29d3\u29d4\u29d5\u29d6\u29d7\u29dc\u29dd\u29de\u29df\u29e0\u29e1\u29e2\u29e3\u29e4\u29e5\u29e6\u29e7\u29e8\u29e9\u29ea\u29eb\u29ec\u29ed\u29ee\u29ef\u29f0\u29f1\u29f2\u29f3\u29f4\u29f5\u29f6\u29f7\u29f8\u29f9\u29fa\u29fb\u29fe\u29ff\u2a00\u2a01\u2a02\u2a03\u2a04\u2a05\u2a06\u2a07\u2a08\u2a09\u2a0a\u2a0b\u2a0c\u2a0d\u2a0e\u2a0f\u2a10\u2a11\u2a12\u2a13\u2a14\u2a15\u2a16\u2a17\u2a18\u2a19\u2a1a\u2a1b\u2a1c\u2a1d\u2a1e\u2a1f\u2a20\u2a21\u2a22\u2a23\u2a24\u2a25\u2a26\u2a27\u2a28\u2a29\u2a2a\u2a2b\u2a2c\u2a2d\u2a2e\u2a2f\u2a30\u2a31\u2a32\u2a33\u2a34\u2a35\u2a36\u2a37\u2a38\u2a39\u2a3a\u2a3b\u2a3c\u2a3d\u2a3e\u2a3f\u2a40\u2a41\u2a42\u2a43\u2a44\u2a45\u2a46\u2a47\u2a48\u2a49\u2a4a\u2a4b\u2a4c\u2a4d\u2a4e\u2a4f\u2a50\u2a51\u2a52\u2a53\u2a54\u2a55\u2a56\u2a57\u2a58\u2a59\u2a5a\u2a5b\u2a5c\u2a5d\u2a5e\u2a5f\u2a60\u2a61\u2a62\u2a63\u2a64\u2a65\u2a66\u2a67\u2a68\u2a69\u2a6a\u2a6b\u2a6c\u2a6d\u2a6e\u2a6f\u2a70\u2a71\u2a72\u2a73\u2a74\u2a75\u2a76\u2a77\u2a78\u2a79\u2a7a\u2a7b\u2a7c\u2a7d\u2a7e\u2a7f\u2a80\u2a81\u2a82\u2a83\u2a84\u2a85\u2a86\u2a87\u2a88\u2a89\u2a8a\u2a8b\u2a8c\u2a8d\u2a8e\u2a8f\u2a90\u2a91\u2a92\u2a93\u2a94\u2a95\u2a96\u2a97\u2a98\u2a99\u2a9a\u2a9b\u2a9c\u2a9d\u2a9e\u2a9f\u2aa0\u2aa1\u2aa2\u2aa3\u2aa4\u2aa5\u2aa6\u2aa7\u2aa8\u2aa9\u2aaa\u2aab\u2aac\u2aad\u2aae\u2aaf\u2ab0\u2ab1\u2ab2\u2ab3\u2ab4\u2ab5\u2ab6\u2ab7\u2ab8\u2ab9\u2aba\u2abb\u2abc\u2abd\u2abe\u2abf\u2ac0\u2ac1\u2ac2\u2ac3\u2ac4\u2ac5\u2ac6\u2ac7\u2ac8\u2ac9\u2aca\u2acb\u2acc\u2acd\u2ace\u2acf\u2ad0\u2ad1\u2ad2\u2ad3\u2ad4\u2ad5\u2ad6\u2ad7\u2ad8\u2ad9\u2ada\u2adb\u2adc\u2add\u2ade\u2adf\u2ae0\u2ae1\u2ae2\u2ae3\u2ae4\u2ae5\u2ae6\u2ae7\u2ae8\u2ae9\u2aea\u2aeb\u2aec\u2aed\u2aee\u2aef\u2af0\u2af1\u2af2\u2af3\u2af4\u2af5\u2af6\u2af7\u2af8\u2af9\u2afa\u2afb\u2afc\u2afd\u2afe\u2aff\ufb29\ufe62\ufe64\ufe65\ufe66\uff0b\uff1c\uff1d\uff1e\uff5c\uff5e\uffe2\uffe9\uffea\uffeb\uffec'
+
+So = u'\xa6\xa7\xa9\xae\xb0\xb6\u0482\u060e\u060f\u06e9\u06fd\u06fe\u09fa\u0b70\u0bf3\u0bf4\u0bf5\u0bf6\u0bf7\u0bf8\u0bfa\u0f01\u0f02\u0f03\u0f13\u0f14\u0f15\u0f16\u0f17\u0f1a\u0f1b\u0f1c\u0f1d\u0f1e\u0f1f\u0f34\u0f36\u0f38\u0fbe\u0fbf\u0fc0\u0fc1\u0fc2\u0fc3\u0fc4\u0fc5\u0fc7\u0fc8\u0fc9\u0fca\u0fcb\u0fcc\u0fcf\u1360\u1390\u1391\u1392\u1393\u1394\u1395\u1396\u1397\u1398\u1399\u1940\u19e0\u19e1\u19e2\u19e3\u19e4\u19e5\u19e6\u19e7\u19e8\u19e9\u19ea\u19eb\u19ec\u19ed\u19ee\u19ef\u19f0\u19f1\u19f2\u19f3\u19f4\u19f5\u19f6\u19f7\u19f8\u19f9\u19fa\u19fb\u19fc\u19fd\u19fe\u19ff\u2100\u2101\u2103\u2104\u2105\u2106\u2108\u2109\u2114\u2116\u2117\u2118\u211e\u211f\u2120\u2121\u2122\u2123\u2125\u2127\u2129\u212e\u2132\u213a\u213b\u214a\u214c\u2195\u2196\u2197\u2198\u2199\u219c\u219d\u219e\u219f\u21a1\u21a2\u21a4\u21a5\u21a7\u21a8\u21a9\u21aa\u21ab\u21ac\u21ad\u21af\u21b0\u21b1\u21b2\u21b3\u21b4\u21b5\u21b6\u21b7\u21b8\u21b9\u21ba\u21bb\u21bc\u21bd\u21be\u21bf\u21c0\u21c1\u21c2\u21c3\u21c4\u21c5\u21c6\u21c7\u21c8\u21c9\u21ca\u21cb\u21cc\u21cd\u21d0\u21d1\u21d3\u21d5\u21d6\u21d7\u21d8\u21d9\u21da\u21db\u21dc\u21dd\u21de\u21df\u21e0\u21e1\u21e2\u21e3\u21e4\u21e5\u21e6\u21e7\u21e8\u21e9\u21ea\u21eb\u21ec\u21ed\u21ee\u21ef\u21f0\u21f1\u21f2\u21f3\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u230c\u230d\u230e\u230f\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231a\u231b\u231c\u231d\u231e\u231f\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u232b\u232c\u232d\u232e\u232f\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233a\u233b\u233c\u233d\u233e\u233f\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234a\u234b\u234c\u234d\u234e\u234f\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235a\u235b\u235c\u235d\u235e\u235f\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236a\u236b\u236c\u236d\u236e\u236f\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237a\u237b\u237d\u237e\u237f\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238a\u238b\u238c\u238d\u238e\u238f\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239a\u23b7\u23b8\u23b9\u23ba\u23bb\u23bc\u23bd\u23be\u23bf\u23c0\u23c1\u23c2\u23c3\u23c4\u23c5\u23c6\u23c7\u23c8\u23c9\u23ca\u23cb\u23cc\u23cd\u23ce\u23cf\u23d0\u23d1\u23d2\u23d3\u23d4\u23d5\u23d6\u23d7\u23d8\u23d9\u23da\u23db\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240a\u240b\u240c\u240d\u240e\u240f\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241a\u241b\u241c\u241d\u241e\u241f\u2420\u2421\u2422\u2423\u2424\u2425\u2426\u2440\u2441\u2442\u2443\u2444\u2445\u2446\u2447\u2448\u2449\u244a\u249c\u249d\u249e\u249f\u24a0\u24a1\u24a2\u24a3\u24a4\u24a5\u24a6\u24a7\u24a8\u24a9\u24aa\u24ab\u24ac\u24ad\u24ae\u24af\u24b0\u24b1\u24b2\u24b3\u24b4\u24b5\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u2500\u2501\u2502\u2503\u2504\u2505\u2506\u2507\u2508\u2509\u250a\u250b\u250c\u250d\u250e\u250f\u2510\u2511\u2512\u2513\u2514\u2515\u2516\u2517\u2518\u2519\u251a\u251b\u251c\u251d\u251e\u251f\u2520\u2521\u2522\u2523\u2524\u2525\u2526\u2527\u2528\u2529\u252a\u252b\u252c\u252d\u252e\u252f\u2530\u2531\u2532\u2533\u2534\u2535\u2536\u2537\u2538\u2539\u253a\u253b\u253c\u253d\u253e\u253f\u2540\u2541\u2542\u2543\u2544\u2545\u2546\u2547\u2548\u2549\u254a\u254b\u254c\u254d\u254e\u254f\u2550\u2551\u2552\u2553\u2554\u2555\u2556\u2557\u2558\u2559\u255a\u255b\u255c\u255d\u255e\u255f\u2560\u2561\u2562\u2563\u2564\u2565\u2566\u2567\u2568\u2569\u256a\u256b\u256c\u256d\u256e\u256f\u2570\u2571\u2572\u2573\u2574\u2575\u2576\u2577\u2578\u2579\u257a\u257b\u257c\u257d\u257e\u257f\u2580\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2589\u258a\u258b\u258c\u258d\u258e\u258f\u2590\u2591\u2592\u2593\u2594\u2595\u2596\u2597\u2598\u2599\u259a\u259b\u259c\u259d\u259e\u259f\u25a0\u25a1\u25a2\u25a3\u25a4\u25a5\u25a6\u25a7\u25a8\u25a9\u25aa\u25ab\u25ac\u25ad\u25ae\u25af\u25b0\u25b1\u25b2\u25b3\u25b4\u25b5\u25b6\u25b8\u25b9\u25ba\u25bb\u25bc\u25bd\u25be\u25bf\u25c0\u25c2\u25c3\u25c4\u25c5\u25c6\u25c7\u25c8\u25c9\u25ca\u25cb\u25cc\u25cd\u25ce\u25cf\u25d0\u25d1\u25d2\u25d3\u25d4\u25d5\u25d6\u25d7\u25d8\u25d9\u25da\u25db\u25dc\u25dd\u25de\u25df\u25e0\u25e1\u25e2\u25e3\u25e4\u25e5\u25e6\u25e7\u25e8\u25e9\u25ea\u25eb\u25ec\u25ed\u25ee\u25ef\u25f0\u25f1\u25f2\u25f3\u25f4\u25f5\u25f6\u25f7\u2600\u2601\u2602\u2603\u2604\u2605\u2606\u2607\u2608\u2609\u260a\u260b\u260c\u260d\u260e\u260f\u2610\u2611\u2612\u2613\u2614\u2615\u2616\u2617\u2618\u2619\u261a\u261b\u261c\u261d\u261e\u261f\u2620\u2621\u2622\u2623\u2624\u2625\u2626\u2627\u2628\u2629\u262a\u262b\u262c\u262d\u262e\u262f\u2630\u2631\u2632\u2633\u2634\u2635\u2636\u2637\u2638\u2639\u263a\u263b\u263c\u263d\u263e\u263f\u2640\u2641\u2642\u2643\u2644\u2645\u2646\u2647\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2654\u2655\u2656\u2657\u2658\u2659\u265a\u265b\u265c\u265d\u265e\u265f\u2660\u2661\u2662\u2663\u2664\u2665\u2666\u2667\u2668\u2669\u266a\u266b\u266c\u266d\u266e\u2670\u2671\u2672\u2673\u2674\u2675\u2676\u2677\u2678\u2679\u267a\u267b\u267c\u267d\u267e\u267f\u2680\u2681\u2682\u2683\u2684\u2685\u2686\u2687\u2688\u2689\u268a\u268b\u268c\u268d\u268e\u268f\u2690\u2691\u2692\u2693\u2694\u2695\u2696\u2697\u2698\u2699\u269a\u269b\u269c\u26a0\u26a1\u26a2\u26a3\u26a4\u26a5\u26a6\u26a7\u26a8\u26a9\u26aa\u26ab\u26ac\u26ad\u26ae\u26af\u26b0\u26b1\u2701\u2702\u2703\u2704\u2706\u2707\u2708\u2709\u270c\u270d\u270e\u270f\u2710\u2711\u2712\u2713\u2714\u2715\u2716\u2717\u2718\u2719\u271a\u271b\u271c\u271d\u271e\u271f\u2720\u2721\u2722\u2723\u2724\u2725\u2726\u2727\u2729\u272a\u272b\u272c\u272d\u272e\u272f\u2730\u2731\u2732\u2733\u2734\u2735\u2736\u2737\u2738\u2739\u273a\u273b\u273c\u273d\u273e\u273f\u2740\u2741\u2742\u2743\u2744\u2745\u2746\u2747\u2748\u2749\u274a\u274b\u274d\u274f\u2750\u2751\u2752\u2756\u2758\u2759\u275a\u275b\u275c\u275d\u275e\u2761\u2762\u2763\u2764\u2765\u2766\u2767\u2794\u2798\u2799\u279a\u279b\u279c\u279d\u279e\u279f\u27a0\u27a1\u27a2\u27a3\u27a4\u27a5\u27a6\u27a7\u27a8\u27a9\u27aa\u27ab\u27ac\u27ad\u27ae\u27af\u27b1\u27b2\u27b3\u27b4\u27b5\u27b6\u27b7\u27b8\u27b9\u27ba\u27bb\u27bc\u27bd\u27be\u2800\u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280a\u280b\u280c\u280d\u280e\u280f\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281a\u281b\u281c\u281d\u281e\u281f\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282a\u282b\u282c\u282d\u282e\u282f\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283a\u283b\u283c\u283d\u283e\u283f\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u2848\u2849\u284a\u284b\u284c\u284d\u284e\u284f\u2850\u2851\u2852\u2853\u2854\u2855\u2856\u2857\u2858\u2859\u285a\u285b\u285c\u285d\u285e\u285f\u2860\u2861\u2862\u2863\u2864\u2865\u2866\u2867\u2868\u2869\u286a\u286b\u286c\u286d\u286e\u286f\u2870\u2871\u2872\u2873\u2874\u2875\u2876\u2877\u2878\u2879\u287a\u287b\u287c\u287d\u287e\u287f\u2880\u2881\u2882\u2883\u2884\u2885\u2886\u2887\u2888\u2889\u288a\u288b\u288c\u288d\u288e\u288f\u2890\u2891\u2892\u2893\u2894\u2895\u2896\u2897\u2898\u2899\u289a\u289b\u289c\u289d\u289e\u289f\u28a0\u28a1\u28a2\u28a3\u28a4\u28a5\u28a6\u28a7\u28a8\u28a9\u28aa\u28ab\u28ac\u28ad\u28ae\u28af\u28b0\u28b1\u28b2\u28b3\u28b4\u28b5\u28b6\u28b7\u28b8\u28b9\u28ba\u28bb\u28bc\u28bd\u28be\u28bf\u28c0\u28c1\u28c2\u28c3\u28c4\u28c5\u28c6\u28c7\u28c8\u28c9\u28ca\u28cb\u28cc\u28cd\u28ce\u28cf\u28d0\u28d1\u28d2\u28d3\u28d4\u28d5\u28d6\u28d7\u28d8\u28d9\u28da\u28db\u28dc\u28dd\u28de\u28df\u28e0\u28e1\u28e2\u28e3\u28e4\u28e5\u28e6\u28e7\u28e8\u28e9\u28ea\u28eb\u28ec\u28ed\u28ee\u28ef\u28f0\u28f1\u28f2\u28f3\u28f4\u28f5\u28f6\u28f7\u28f8\u28f9\u28fa\u28fb\u28fc\u28fd\u28fe\u28ff\u2b00\u2b01\u2b02\u2b03\u2b04\u2b05\u2b06\u2b07\u2b08\u2b09\u2b0a\u2b0b\u2b0c\u2b0d\u2b0e\u2b0f\u2b10\u2b11\u2b12\u2b13\u2ce5\u2ce6\u2ce7\u2ce8\u2ce9\u2cea\u2e80\u2e81\u2e82\u2e83\u2e84\u2e85\u2e86\u2e87\u2e88\u2e89\u2e8a\u2e8b\u2e8c\u2e8d\u2e8e\u2e8f\u2e90\u2e91\u2e92\u2e93\u2e94\u2e95\u2e96\u2e97\u2e98\u2e99\u2e9b\u2e9c\u2e9d\u2e9e\u2e9f\u2ea0\u2ea1\u2ea2\u2ea3\u2ea4\u2ea5\u2ea6\u2ea7\u2ea8\u2ea9\u2eaa\u2eab\u2eac\u2ead\u2eae\u2eaf\u2eb0\u2eb1\u2eb2\u2eb3\u2eb4\u2eb5\u2eb6\u2eb7\u2eb8\u2eb9\u2eba\u2ebb\u2ebc\u2ebd\u2ebe\u2ebf\u2ec0\u2ec1\u2ec2\u2ec3\u2ec4\u2ec5\u2ec6\u2ec7\u2ec8\u2ec9\u2eca\u2ecb\u2ecc\u2ecd\u2ece\u2ecf\u2ed0\u2ed1\u2ed2\u2ed3\u2ed4\u2ed5\u2ed6\u2ed7\u2ed8\u2ed9\u2eda\u2edb\u2edc\u2edd\u2ede\u2edf\u2ee0\u2ee1\u2ee2\u2ee3\u2ee4\u2ee5\u2ee6\u2ee7\u2ee8\u2ee9\u2eea\u2eeb\u2eec\u2eed\u2eee\u2eef\u2ef0\u2ef1\u2ef2\u2ef3\u2f00\u2f01\u2f02\u2f03\u2f04\u2f05\u2f06\u2f07\u2f08\u2f09\u2f0a\u2f0b\u2f0c\u2f0d\u2f0e\u2f0f\u2f10\u2f11\u2f12\u2f13\u2f14\u2f15\u2f16\u2f17\u2f18\u2f19\u2f1a\u2f1b\u2f1c\u2f1d\u2f1e\u2f1f\u2f20\u2f21\u2f22\u2f23\u2f24\u2f25\u2f26\u2f27\u2f28\u2f29\u2f2a\u2f2b\u2f2c\u2f2d\u2f2e\u2f2f\u2f30\u2f31\u2f32\u2f33\u2f34\u2f35\u2f36\u2f37\u2f38\u2f39\u2f3a\u2f3b\u2f3c\u2f3d\u2f3e\u2f3f\u2f40\u2f41\u2f42\u2f43\u2f44\u2f45\u2f46\u2f47\u2f48\u2f49\u2f4a\u2f4b\u2f4c\u2f4d\u2f4e\u2f4f\u2f50\u2f51\u2f52\u2f53\u2f54\u2f55\u2f56\u2f57\u2f58\u2f59\u2f5a\u2f5b\u2f5c\u2f5d\u2f5e\u2f5f\u2f60\u2f61\u2f62\u2f63\u2f64\u2f65\u2f66\u2f67\u2f68\u2f69\u2f6a\u2f6b\u2f6c\u2f6d\u2f6e\u2f6f\u2f70\u2f71\u2f72\u2f73\u2f74\u2f75\u2f76\u2f77\u2f78\u2f79\u2f7a\u2f7b\u2f7c\u2f7d\u2f7e\u2f7f\u2f80\u2f81\u2f82\u2f83\u2f84\u2f85\u2f86\u2f87\u2f88\u2f89\u2f8a\u2f8b\u2f8c\u2f8d\u2f8e\u2f8f\u2f90\u2f91\u2f92\u2f93\u2f94\u2f95\u2f96\u2f97\u2f98\u2f99\u2f9a\u2f9b\u2f9c\u2f9d\u2f9e\u2f9f\u2fa0\u2fa1\u2fa2\u2fa3\u2fa4\u2fa5\u2fa6\u2fa7\u2fa8\u2fa9\u2faa\u2fab\u2fac\u2fad\u2fae\u2faf\u2fb0\u2fb1\u2fb2\u2fb3\u2fb4\u2fb5\u2fb6\u2fb7\u2fb8\u2fb9\u2fba\u2fbb\u2fbc\u2fbd\u2fbe\u2fbf\u2fc0\u2fc1\u2fc2\u2fc3\u2fc4\u2fc5\u2fc6\u2fc7\u2fc8\u2fc9\u2fca\u2fcb\u2fcc\u2fcd\u2fce\u2fcf\u2fd0\u2fd1\u2fd2\u2fd3\u2fd4\u2fd5\u2ff0\u2ff1\u2ff2\u2ff3\u2ff4\u2ff5\u2ff6\u2ff7\u2ff8\u2ff9\u2ffa\u2ffb\u3004\u3012\u3013\u3020\u3036\u3037\u303e\u303f\u3190\u3191\u3196\u3197\u3198\u3199\u319a\u319b\u319c\u319d\u319e\u319f\u31c0\u31c1\u31c2\u31c3\u31c4\u31c5\u31c6\u31c7\u31c8\u31c9\u31ca\u31cb\u31cc\u31cd\u31ce\u31cf\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320a\u320b\u320c\u320d\u320e\u320f\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321a\u321b\u321c\u321d\u321e\u322a\u322b\u322c\u322d\u322e\u322f\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323a\u323b\u323c\u323d\u323e\u323f\u3240\u3241\u3242\u3243\u3250\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326a\u326b\u326c\u326d\u326e\u326f\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327a\u327b\u327c\u327d\u327e\u327f\u328a\u328b\u328c\u328d\u328e\u328f\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329a\u329b\u329c\u329d\u329e\u329f\u32a0\u32a1\u32a2\u32a3\u32a4\u32a5\u32a6\u32a7\u32a8\u32a9\u32aa\u32ab\u32ac\u32ad\u32ae\u32af\u32b0\u32c0\u32c1\u32c2\u32c3\u32c4\u32c5\u32c6\u32c7\u32c8\u32c9\u32ca\u32cb\u32cc\u32cd\u32ce\u32cf\u32d0\u32d1\u32d2\u32d3\u32d4\u32d5\u32d6\u32d7\u32d8\u32d9\u32da\u32db\u32dc\u32dd\u32de\u32df\u32e0\u32e1\u32e2\u32e3\u32e4\u32e5\u32e6\u32e7\u32e8\u32e9\u32ea\u32eb\u32ec\u32ed\u32ee\u32ef\u32f0\u32f1\u32f2\u32f3\u32f4\u32f5\u32f6\u32f7\u32f8\u32f9\u32fa\u32fb\u32fc\u32fd\u32fe\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330a\u330b\u330c\u330d\u330e\u330f\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331a\u331b\u331c\u331d\u331e\u331f\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332a\u332b\u332c\u332d\u332e\u332f\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333a\u333b\u333c\u333d\u333e\u333f\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334a\u334b\u334c\u334d\u334e\u334f\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335a\u335b\u335c\u335d\u335e\u335f\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336a\u336b\u336c\u336d\u336e\u336f\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337a\u337b\u337c\u337d\u337e\u337f\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338a\u338b\u338c\u338d\u338e\u338f\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339a\u339b\u339c\u339d\u339e\u339f\u33a0\u33a1\u33a2\u33a3\u33a4\u33a5\u33a6\u33a7\u33a8\u33a9\u33aa\u33ab\u33ac\u33ad\u33ae\u33af\u33b0\u33b1\u33b2\u33b3\u33b4\u33b5\u33b6\u33b7\u33b8\u33b9\u33ba\u33bb\u33bc\u33bd\u33be\u33bf\u33c0\u33c1\u33c2\u33c3\u33c4\u33c5\u33c6\u33c7\u33c8\u33c9\u33ca\u33cb\u33cc\u33cd\u33ce\u33cf\u33d0\u33d1\u33d2\u33d3\u33d4\u33d5\u33d6\u33d7\u33d8\u33d9\u33da\u33db\u33dc\u33dd\u33de\u33df\u33e0\u33e1\u33e2\u33e3\u33e4\u33e5\u33e6\u33e7\u33e8\u33e9\u33ea\u33eb\u33ec\u33ed\u33ee\u33ef\u33f0\u33f1\u33f2\u33f3\u33f4\u33f5\u33f6\u33f7\u33f8\u33f9\u33fa\u33fb\u33fc\u33fd\u33fe\u33ff\u4dc0\u4dc1\u4dc2\u4dc3\u4dc4\u4dc5\u4dc6\u4dc7\u4dc8\u4dc9\u4dca\u4dcb\u4dcc\u4dcd\u4dce\u4dcf\u4dd0\u4dd1\u4dd2\u4dd3\u4dd4\u4dd5\u4dd6\u4dd7\u4dd8\u4dd9\u4dda\u4ddb\u4ddc\u4ddd\u4dde\u4ddf\u4de0\u4de1\u4de2\u4de3\u4de4\u4de5\u4de6\u4de7\u4de8\u4de9\u4dea\u4deb\u4dec\u4ded\u4dee\u4def\u4df0\u4df1\u4df2\u4df3\u4df4\u4df5\u4df6\u4df7\u4df8\u4df9\u4dfa\u4dfb\u4dfc\u4dfd\u4dfe\u4dff\ua490\ua491\ua492\ua493\ua494\ua495\ua496\ua497\ua498\ua499\ua49a\ua49b\ua49c\ua49d\ua49e\ua49f\ua4a0\ua4a1\ua4a2\ua4a3\ua4a4\ua4a5\ua4a6\ua4a7\ua4a8\ua4a9\ua4aa\ua4ab\ua4ac\ua4ad\ua4ae\ua4af\ua4b0\ua4b1\ua4b2\ua4b3\ua4b4\ua4b5\ua4b6\ua4b7\ua4b8\ua4b9\ua4ba\ua4bb\ua4bc\ua4bd\ua4be\ua4bf\ua4c0\ua4c1\ua4c2\ua4c3\ua4c4\ua4c5\ua4c6\ua828\ua829\ua82a\ua82b\ufdfd\uffe4\uffe8\uffed\uffee\ufffc\ufffd'
+
+Zl = u'\u2028'
+
+Zp = u'\u2029'
+
+Zs = u' \xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+
+cats = ['Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu', 'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf', 'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs']
+
+def combine(*args):
+ return u''.join([globals()[cat] for cat in args])
+
+xid_start = u'\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0640\u0641-\u064A\u066E-\u066F\u0671-\u06D3\u06D5\u06E5-\u06E6\u06EE-\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u076D\u0780-\u07A5\u07B1\u0904-\u0939\u093D\u0950\u0958-\u0961\u097D\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0-\u0AE1\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E40-\u0E45\u0E46\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB2\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6A\u0F88-\u0F8B\u1000-\u1021\u1023-\u1027\u1029-\u102A\u1050-\u1055\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19C1-\u19C7\u1A00-\u1A16\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
+
+xid_continue = u'\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00AA\u00B5\u00B7\u00BA\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u01BA\u01BB\u01BC-\u01BF\u01C0-\u01C3\u01C4-\u0241\u0250-\u02AF\u02B0-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EE\u0300-\u036F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03F5\u03F7-\u0481\u0483-\u0486\u048A-\u04CE\u04D0-\u04F9\u0500-\u050F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u0615\u0621-\u063A\u0640\u0641-\u064A\u064B-\u065E\u0660-\u0669\u066E-\u066F\u0670\u0671-\u06D3\u06D5\u06D6-\u06DC\u06DF-\u06E4\u06E5-\u06E6\u06E7-\u06E8\u06EA-\u06ED\u06EE-\u06EF\u06F0-\u06F9\u06FA-\u06FC\u06FF\u0710\u0711\u0712-\u072F\u0730-\u074A\u074D-\u076D\u0780-\u07A5\u07A6-\u07B0\u07B1\u0901-\u0902\u0903\u0904-\u0939\u093C\u093D\u093E-\u0940\u0941-\u0948\u0949-\u094C\u094D\u0950\u0951-\u0954\u0958-\u0961\u0962-\u0963\u0966-\u096F\u097D\u0981\u0982-\u0983\u0985-\u098C\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC\u09BD\u09BE-\u09C0\u09C1-\u09C4\u09C7-\u09C8\u09CB-\u09CC\u09CD\u09CE\u09D7\u09DC-\u09DD\u09DF-\u09E1\u09E2-\u09E3\u09E6-\u09EF\u09F0-\u09F1\u0A01-\u0A02\u0A03\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39\u0A3C\u0A3E-\u0A40\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A70-\u0A71\u0A72-\u0A74\u0A81-\u0A82\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABC\u0ABD\u0ABE-\u0AC0\u0AC1-\u0AC5\u0AC7-\u0AC8\u0AC9\u0ACB-\u0ACC\u0ACD\u0AD0\u0AE0-\u0AE1\u0AE2-\u0AE3\u0AE6-\u0AEF\u0B01\u0B02-\u0B03\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B35-\u0B39\u0B3C\u0B3D\u0B3E\u0B3F\u0B40\u0B41-\u0B43\u0B47-\u0B48\u0B4B-\u0B4C\u0B4D\u0B56\u0B57\u0B5C-\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BBF\u0BC0\u0BC1-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BCD\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C40\u0C41-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C60-\u0C61\u0C66-\u0C6F\u0C82-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC\u0CBD\u0CBE\u0CBF\u0CC0-\u0CC4\u0CC6\u0CC7-\u0CC8\u0CCA-\u0CCB\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CDE\u0CE0-\u0CE1\u0CE6-\u0CEF\u0D02-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D40\u0D41-\u0D43\u0D46-\u0D48\u0D4A-\u0D4C\u0D4D\u0D57\u0D60-\u0D61\u0D66-\u0D6F\u0D82-\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD1\u0DD2-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF3\u0E01-\u0E30\u0E31\u0E32-\u0E33\u0E34-\u0E3A\u0E40-\u0E45\u0E46\u0E47-\u0E4E\u0E50-\u0E59\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EB0\u0EB1\u0EB2-\u0EB3\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDD\u0F00\u0F18-\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F3F\u0F40-\u0F47\u0F49-\u0F6A\u0F71-\u0F7E\u0F7F\u0F80-\u0F84\u0F86-\u0F87\u0F88-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1021\u1023-\u1027\u1029-\u102A\u102C\u102D-\u1030\u1031\u1032\u1036-\u1037\u1038\u1039\u1040-\u1049\u1050-\u1055\u1056-\u1057\u1058-\u1059\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1159\u115F-\u11A2\u11A8-\u11F9\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u1676\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1712-\u1714\u1720-\u1731\u1732-\u1734\u1740-\u1751\u1752-\u1753\u1760-\u176C\u176E-\u1770\u1772-\u1773\u1780-\u17B3\u17B6\u17B7-\u17BD\u17BE-\u17C5\u17C6\u17C7-\u17C8\u17C9-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1842\u1843\u1844-\u1877\u1880-\u18A8\u18A9\u1900-\u191C\u1920-\u1922\u1923-\u1926\u1927-\u1928\u1929-\u192B\u1930-\u1931\u1932\u1933-\u1938\u1939-\u193B\u1946-\u194F\u1950-\u196D\u1970-\u1974\u1980-\u19A9\u19B0-\u19C0\u19C1-\u19C7\u19C8-\u19C9\u19D0-\u19D9\u1A00-\u1A16\u1A17-\u1A18\u1A19-\u1A1B\u1D00-\u1D2B\u1D2C-\u1D61\u1D62-\u1D77\u1D78\u1D79-\u1D9A\u1D9B-\u1DBF\u1DC0-\u1DC3\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F-\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20EB\u2102\u2107\u210A-\u2113\u2115\u2118\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212E\u212F-\u2131\u2133-\u2134\u2135-\u2138\u2139\u213C-\u213F\u2145-\u2149\u2160-\u2183\u2C00-\u2C2E\u2C30-\u2C5E\u2C80-\u2CE4\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005\u3006\u3007\u3021-\u3029\u302A-\u302F\u3031-\u3035\u3038-\u303A\u303B\u303C\u3041-\u3096\u3099-\u309A\u309D-\u309E\u309F\u30A1-\u30FA\u30FC-\u30FE\u30FF\u3105-\u312C\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FBB\uA000-\uA014\uA015\uA016-\uA48C\uA800-\uA801\uA802\uA803-\uA805\uA806\uA807-\uA80A\uA80B\uA80C-\uA822\uA823-\uA824\uA825-\uA826\uA827\uAC00-\uD7A3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1E\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40-\uFB41\uFB43-\uFB44\uFB46-\uFBB1\uFBD3-\uFC5D\uFC64-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDF9\uFE00-\uFE0F\uFE20-\uFE23\uFE33-\uFE34\uFE4D-\uFE4F\uFE71\uFE73\uFE77\uFE79\uFE7B\uFE7D\uFE7F-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFF6F\uFF70\uFF71-\uFF9D\uFF9E-\uFF9F\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC'
+
+def allexcept(*args):
+ newcats = cats[:]
+ for arg in args:
+ newcats.remove(arg)
+ return u''.join([globals()[cat] for cat in newcats])
+
+if __name__ == '__main__':
+ import unicodedata
+
+ categories = {}
+
+ f = open(__file__.rstrip('co'))
+ try:
+ content = f.read()
+ finally:
+ f.close()
+
+ header = content[:content.find('Cc =')]
+ footer = content[content.find("def combine("):]
+
+ for code in range(65535):
+ c = unichr(code)
+ cat = unicodedata.category(c)
+ categories.setdefault(cat, []).append(c)
+
+ f = open(__file__, 'w')
+ f.write(header)
+
+ for cat in sorted(categories):
+ val = u''.join(categories[cat])
+ if cat == 'Cs':
+ # Jython can't handle isolated surrogates
+ f.write("""\
+try:
+ Cs = eval(r"%r")
+except UnicodeDecodeError:
+ Cs = '' # Jython can't handle isolated surrogates\n\n""" % val)
+ else:
+ f.write('%s = %r\n\n' % (cat, val))
+ f.write('cats = %r\n\n' % sorted(categories.keys()))
+
+ f.write(footer)
+ f.close()
diff --git a/pyload/lib/jinja2/bccache.py b/pyload/lib/jinja2/bccache.py
new file mode 100644
index 000000000..2d28ab8b2
--- /dev/null
+++ b/pyload/lib/jinja2/bccache.py
@@ -0,0 +1,344 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.bccache
+ ~~~~~~~~~~~~~~
+
+ This module implements the bytecode cache system Jinja is optionally
+ using. This is useful if you have very complex template situations and
+ the compiliation of all those templates slow down your application too
+ much.
+
+ Situations where this is useful are often forking web applications that
+ are initialized on the first request.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
+"""
+from os import path, listdir
+import os
+import stat
+import sys
+import errno
+import marshal
+import tempfile
+import fnmatch
+from hashlib import sha1
+from jinja2.utils import open_if_exists
+from jinja2._compat import BytesIO, pickle, PY2, text_type
+
+
+# marshal works better on 3.x, one hack less required
+if not PY2:
+ marshal_dump = marshal.dump
+ marshal_load = marshal.load
+else:
+
+ def marshal_dump(code, f):
+ if isinstance(f, file):
+ marshal.dump(code, f)
+ else:
+ f.write(marshal.dumps(code))
+
+ def marshal_load(f):
+ if isinstance(f, file):
+ return marshal.load(f)
+ return marshal.loads(f.read())
+
+
+bc_version = 2
+
+# magic version used to only change with new jinja versions. With 2.6
+# we change this to also take Python version changes into account. The
+# reason for this is that Python tends to segfault if fed earlier bytecode
+# versions because someone thought it would be a good idea to reuse opcodes
+# or make Python incompatible with earlier versions.
+bc_magic = 'j2'.encode('ascii') + \
+ pickle.dumps(bc_version, 2) + \
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
+
+
+class Bucket(object):
+ """Buckets are used to store the bytecode for one template. It's created
+ and initialized by the bytecode cache and passed to the loading functions.
+
+ The buckets get an internal checksum from the cache assigned and use this
+ to automatically reject outdated cache material. Individual bytecode
+ cache subclasses don't have to care about cache invalidation.
+ """
+
+ def __init__(self, environment, key, checksum):
+ self.environment = environment
+ self.key = key
+ self.checksum = checksum
+ self.reset()
+
+ def reset(self):
+ """Resets the bucket (unloads the bytecode)."""
+ self.code = None
+
+ def load_bytecode(self, f):
+ """Loads bytecode from a file or file like object."""
+ # make sure the magic header is correct
+ magic = f.read(len(bc_magic))
+ if magic != bc_magic:
+ self.reset()
+ return
+ # the source code of the file changed, we need to reload
+ checksum = pickle.load(f)
+ if self.checksum != checksum:
+ self.reset()
+ return
+ self.code = marshal_load(f)
+
+ def write_bytecode(self, f):
+ """Dump the bytecode into the file or file like object passed."""
+ if self.code is None:
+ raise TypeError('can\'t write empty bucket')
+ f.write(bc_magic)
+ pickle.dump(self.checksum, f, 2)
+ marshal_dump(self.code, f)
+
+ def bytecode_from_string(self, string):
+ """Load bytecode from a string."""
+ self.load_bytecode(BytesIO(string))
+
+ def bytecode_to_string(self):
+ """Return the bytecode as string."""
+ out = BytesIO()
+ self.write_bytecode(out)
+ return out.getvalue()
+
+
+class BytecodeCache(object):
+ """To implement your own bytecode cache you have to subclass this class
+ and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
+ these methods are passed a :class:`~jinja2.bccache.Bucket`.
+
+ A very basic bytecode cache that saves the bytecode on the file system::
+
+ from os import path
+
+ class MyCache(BytecodeCache):
+
+ def __init__(self, directory):
+ self.directory = directory
+
+ def load_bytecode(self, bucket):
+ filename = path.join(self.directory, bucket.key)
+ if path.exists(filename):
+ with open(filename, 'rb') as f:
+ bucket.load_bytecode(f)
+
+ def dump_bytecode(self, bucket):
+ filename = path.join(self.directory, bucket.key)
+ with open(filename, 'wb') as f:
+ bucket.write_bytecode(f)
+
+ A more advanced version of a filesystem based bytecode cache is part of
+ Jinja2.
+ """
+
+ def load_bytecode(self, bucket):
+ """Subclasses have to override this method to load bytecode into a
+ bucket. If they are not able to find code in the cache for the
+ bucket, it must not do anything.
+ """
+ raise NotImplementedError()
+
+ def dump_bytecode(self, bucket):
+ """Subclasses have to override this method to write the bytecode
+ from a bucket back to the cache. If it unable to do so it must not
+ fail silently but raise an exception.
+ """
+ raise NotImplementedError()
+
+ def clear(self):
+ """Clears the cache. This method is not used by Jinja2 but should be
+ implemented to allow applications to clear the bytecode cache used
+ by a particular environment.
+ """
+
+ def get_cache_key(self, name, filename=None):
+ """Returns the unique hash key for this template name."""
+ hash = sha1(name.encode('utf-8'))
+ if filename is not None:
+ filename = '|' + filename
+ if isinstance(filename, text_type):
+ filename = filename.encode('utf-8')
+ hash.update(filename)
+ return hash.hexdigest()
+
+ def get_source_checksum(self, source):
+ """Returns a checksum for the source."""
+ return sha1(source.encode('utf-8')).hexdigest()
+
+ def get_bucket(self, environment, name, filename, source):
+ """Return a cache bucket for the given template. All arguments are
+ mandatory but filename may be `None`.
+ """
+ key = self.get_cache_key(name, filename)
+ checksum = self.get_source_checksum(source)
+ bucket = Bucket(environment, key, checksum)
+ self.load_bytecode(bucket)
+ return bucket
+
+ def set_bucket(self, bucket):
+ """Put the bucket into the cache."""
+ self.dump_bytecode(bucket)
+
+
+class FileSystemBytecodeCache(BytecodeCache):
+ """A bytecode cache that stores bytecode on the filesystem. It accepts
+ two arguments: The directory where the cache items are stored and a
+ pattern string that is used to build the filename.
+
+ If no directory is specified a default cache directory is selected. On
+ Windows the user's temp directory is used, on UNIX systems a directory
+ is created for the user in the system temp directory.
+
+ The pattern can be used to have multiple separate caches operate on the
+ same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
+ is replaced with the cache key.
+
+ >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
+
+ This bytecode cache supports clearing of the cache using the clear method.
+ """
+
+ def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
+ if directory is None:
+ directory = self._get_default_cache_dir()
+ self.directory = directory
+ self.pattern = pattern
+
+ def _get_default_cache_dir(self):
+ tmpdir = tempfile.gettempdir()
+
+ # On windows the temporary directory is used specific unless
+ # explicitly forced otherwise. We can just use that.
+ if os.name == 'nt':
+ return tmpdir
+ if not hasattr(os, 'getuid'):
+ raise RuntimeError('Cannot determine safe temp directory. You '
+ 'need to explicitly provide one.')
+
+ dirname = '_jinja2-cache-%d' % os.getuid()
+ actual_dir = os.path.join(tmpdir, dirname)
+ try:
+ os.mkdir(actual_dir, stat.S_IRWXU) # 0o700
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ actual_dir_stat = os.lstat(actual_dir)
+ if actual_dir_stat.st_uid != os.getuid() \
+ or not stat.S_ISDIR(actual_dir_stat.st_mode) \
+ or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
+ raise RuntimeError('Temporary directory \'%s\' has an incorrect '
+ 'owner, permissions, or type.' % actual_dir)
+
+ return actual_dir
+
+ def _get_cache_filename(self, bucket):
+ return path.join(self.directory, self.pattern % bucket.key)
+
+ def load_bytecode(self, bucket):
+ f = open_if_exists(self._get_cache_filename(bucket), 'rb')
+ if f is not None:
+ try:
+ bucket.load_bytecode(f)
+ finally:
+ f.close()
+
+ def dump_bytecode(self, bucket):
+ f = open(self._get_cache_filename(bucket), 'wb')
+ try:
+ bucket.write_bytecode(f)
+ finally:
+ f.close()
+
+ def clear(self):
+ # imported lazily here because google app-engine doesn't support
+ # write access on the file system and the function does not exist
+ # normally.
+ from os import remove
+ files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
+ for filename in files:
+ try:
+ remove(path.join(self.directory, filename))
+ except OSError:
+ pass
+
+
+class MemcachedBytecodeCache(BytecodeCache):
+ """This class implements a bytecode cache that uses a memcache cache for
+ storing the information. It does not enforce a specific memcache library
+ (tummy's memcache or cmemcache) but will accept any class that provides
+ the minimal interface required.
+
+ Libraries compatible with this class:
+
+ - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
+ - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
+ - `cmemcache <http://gijsbert.org/cmemcache/>`_
+
+ (Unfortunately the django cache interface is not compatible because it
+ does not support storing binary data, only unicode. You can however pass
+ the underlying cache client to the bytecode cache which is available
+ as `django.core.cache.cache._client`.)
+
+ The minimal interface for the client passed to the constructor is this:
+
+ .. class:: MinimalClientInterface
+
+ .. method:: set(key, value[, timeout])
+
+ Stores the bytecode in the cache. `value` is a string and
+ `timeout` the timeout of the key. If timeout is not provided
+ a default timeout or no timeout should be assumed, if it's
+ provided it's an integer with the number of seconds the cache
+ item should exist.
+
+ .. method:: get(key)
+
+ Returns the value for the cache key. If the item does not
+ exist in the cache the return value must be `None`.
+
+ The other arguments to the constructor are the prefix for all keys that
+ is added before the actual cache key and the timeout for the bytecode in
+ the cache system. We recommend a high (or no) timeout.
+
+ This bytecode cache does not support clearing of used items in the cache.
+ The clear method is a no-operation function.
+
+ .. versionadded:: 2.7
+ Added support for ignoring memcache errors through the
+ `ignore_memcache_errors` parameter.
+ """
+
+ def __init__(self, client, prefix='jinja2/bytecode/', timeout=None,
+ ignore_memcache_errors=True):
+ self.client = client
+ self.prefix = prefix
+ self.timeout = timeout
+ self.ignore_memcache_errors = ignore_memcache_errors
+
+ def load_bytecode(self, bucket):
+ try:
+ code = self.client.get(self.prefix + bucket.key)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+ code = None
+ if code is not None:
+ bucket.bytecode_from_string(code)
+
+ def dump_bytecode(self, bucket):
+ args = (self.prefix + bucket.key, bucket.bytecode_to_string())
+ if self.timeout is not None:
+ args += (self.timeout,)
+ try:
+ self.client.set(*args)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
diff --git a/pyload/lib/jinja2/compiler.py b/pyload/lib/jinja2/compiler.py
new file mode 100644
index 000000000..75a60b8d2
--- /dev/null
+++ b/pyload/lib/jinja2/compiler.py
@@ -0,0 +1,1640 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.compiler
+ ~~~~~~~~~~~~~~~
+
+ Compiles nodes into python code.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+from itertools import chain
+from copy import deepcopy
+from keyword import iskeyword as is_python_keyword
+from jinja2 import nodes
+from jinja2.nodes import EvalContext
+from jinja2.visitor import NodeVisitor
+from jinja2.exceptions import TemplateAssertionError
+from jinja2.utils import Markup, concat, escape
+from jinja2._compat import range_type, next, text_type, string_types, \
+ iteritems, NativeStringIO, imap
+
+
+operators = {
+ 'eq': '==',
+ 'ne': '!=',
+ 'gt': '>',
+ 'gteq': '>=',
+ 'lt': '<',
+ 'lteq': '<=',
+ 'in': 'in',
+ 'notin': 'not in'
+}
+
+# what method to iterate over items do we want to use for dict iteration
+# in generated code? on 2.x let's go with iteritems, on 3.x with items
+if hasattr(dict, 'iteritems'):
+ dict_item_iter = 'iteritems'
+else:
+ dict_item_iter = 'items'
+
+
+# does if 0: dummy(x) get us x into the scope?
+def unoptimize_before_dead_code():
+ x = 42
+ def f():
+ if 0: dummy(x)
+ return f
+
+# The getattr is necessary for pypy which does not set this attribute if
+# no closure is on the function
+unoptimize_before_dead_code = bool(
+ getattr(unoptimize_before_dead_code(), '__closure__', None))
+
+
+def generate(node, environment, name, filename, stream=None,
+ defer_init=False):
+ """Generate the python source for a node tree."""
+ if not isinstance(node, nodes.Template):
+ raise TypeError('Can\'t compile non template nodes')
+ generator = CodeGenerator(environment, name, filename, stream, defer_init)
+ generator.visit(node)
+ if stream is None:
+ return generator.stream.getvalue()
+
+
+def has_safe_repr(value):
+ """Does the node have a safe representation?"""
+ if value is None or value is NotImplemented or value is Ellipsis:
+ return True
+ if isinstance(value, (bool, int, float, complex, range_type,
+ Markup) + string_types):
+ return True
+ if isinstance(value, (tuple, list, set, frozenset)):
+ for item in value:
+ if not has_safe_repr(item):
+ return False
+ return True
+ elif isinstance(value, dict):
+ for key, value in iteritems(value):
+ if not has_safe_repr(key):
+ return False
+ if not has_safe_repr(value):
+ return False
+ return True
+ return False
+
+
+def find_undeclared(nodes, names):
+ """Check if the names passed are accessed undeclared. The return value
+ is a set of all the undeclared names from the sequence of names found.
+ """
+ visitor = UndeclaredNameVisitor(names)
+ try:
+ for node in nodes:
+ visitor.visit(node)
+ except VisitorExit:
+ pass
+ return visitor.undeclared
+
+
+class Identifiers(object):
+ """Tracks the status of identifiers in frames."""
+
+ def __init__(self):
+ # variables that are known to be declared (probably from outer
+ # frames or because they are special for the frame)
+ self.declared = set()
+
+ # undeclared variables from outer scopes
+ self.outer_undeclared = set()
+
+ # names that are accessed without being explicitly declared by
+ # this one or any of the outer scopes. Names can appear both in
+ # declared and undeclared.
+ self.undeclared = set()
+
+ # names that are declared locally
+ self.declared_locally = set()
+
+ # names that are declared by parameters
+ self.declared_parameter = set()
+
+ def add_special(self, name):
+ """Register a special name like `loop`."""
+ self.undeclared.discard(name)
+ self.declared.add(name)
+
+ def is_declared(self, name):
+ """Check if a name is declared in this or an outer scope."""
+ if name in self.declared_locally or name in self.declared_parameter:
+ return True
+ return name in self.declared
+
+ def copy(self):
+ return deepcopy(self)
+
+
+class Frame(object):
+ """Holds compile time information for us."""
+
+ def __init__(self, eval_ctx, parent=None):
+ self.eval_ctx = eval_ctx
+ self.identifiers = Identifiers()
+
+ # a toplevel frame is the root + soft frames such as if conditions.
+ self.toplevel = False
+
+ # the root frame is basically just the outermost frame, so no if
+ # conditions. This information is used to optimize inheritance
+ # situations.
+ self.rootlevel = False
+
+ # in some dynamic inheritance situations the compiler needs to add
+ # write tests around output statements.
+ self.require_output_check = parent and parent.require_output_check
+
+ # inside some tags we are using a buffer rather than yield statements.
+ # this for example affects {% filter %} or {% macro %}. If a frame
+ # is buffered this variable points to the name of the list used as
+ # buffer.
+ self.buffer = None
+
+ # the name of the block we're in, otherwise None.
+ self.block = parent and parent.block or None
+
+ # a set of actually assigned names
+ self.assigned_names = set()
+
+ # the parent of this frame
+ self.parent = parent
+
+ if parent is not None:
+ self.identifiers.declared.update(
+ parent.identifiers.declared |
+ parent.identifiers.declared_parameter |
+ parent.assigned_names
+ )
+ self.identifiers.outer_undeclared.update(
+ parent.identifiers.undeclared -
+ self.identifiers.declared
+ )
+ self.buffer = parent.buffer
+
+ def copy(self):
+ """Create a copy of the current one."""
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.identifiers = object.__new__(self.identifiers.__class__)
+ rv.identifiers.__dict__.update(self.identifiers.__dict__)
+ return rv
+
+ def inspect(self, nodes):
+ """Walk the node and check for identifiers. If the scope is hard (eg:
+ enforce on a python level) overrides from outer scopes are tracked
+ differently.
+ """
+ visitor = FrameIdentifierVisitor(self.identifiers)
+ for node in nodes:
+ visitor.visit(node)
+
+ def find_shadowed(self, extra=()):
+ """Find all the shadowed names. extra is an iterable of variables
+ that may be defined with `add_special` which may occour scoped.
+ """
+ i = self.identifiers
+ return (i.declared | i.outer_undeclared) & \
+ (i.declared_locally | i.declared_parameter) | \
+ set(x for x in extra if i.is_declared(x))
+
+ def inner(self):
+ """Return an inner frame."""
+ return Frame(self.eval_ctx, self)
+
+ def soft(self):
+ """Return a soft frame. A soft frame may not be modified as
+ standalone thing as it shares the resources with the frame it
+ was created of, but it's not a rootlevel frame any longer.
+ """
+ rv = self.copy()
+ rv.rootlevel = False
+ return rv
+
+ __copy__ = copy
+
+
+class VisitorExit(RuntimeError):
+ """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
+
+
+class DependencyFinderVisitor(NodeVisitor):
+ """A visitor that collects filter and test calls."""
+
+ def __init__(self):
+ self.filters = set()
+ self.tests = set()
+
+ def visit_Filter(self, node):
+ self.generic_visit(node)
+ self.filters.add(node.name)
+
+ def visit_Test(self, node):
+ self.generic_visit(node)
+ self.tests.add(node.name)
+
+ def visit_Block(self, node):
+ """Stop visiting at blocks."""
+
+
+class UndeclaredNameVisitor(NodeVisitor):
+ """A visitor that checks if a name is accessed without being
+ declared. This is different from the frame visitor as it will
+ not stop at closure frames.
+ """
+
+ def __init__(self, names):
+ self.names = set(names)
+ self.undeclared = set()
+
+ def visit_Name(self, node):
+ if node.ctx == 'load' and node.name in self.names:
+ self.undeclared.add(node.name)
+ if self.undeclared == self.names:
+ raise VisitorExit()
+ else:
+ self.names.discard(node.name)
+
+ def visit_Block(self, node):
+ """Stop visiting a blocks."""
+
+
+class FrameIdentifierVisitor(NodeVisitor):
+ """A visitor for `Frame.inspect`."""
+
+ def __init__(self, identifiers):
+ self.identifiers = identifiers
+
+ def visit_Name(self, node):
+ """All assignments to names go through this function."""
+ if node.ctx == 'store':
+ self.identifiers.declared_locally.add(node.name)
+ elif node.ctx == 'param':
+ self.identifiers.declared_parameter.add(node.name)
+ elif node.ctx == 'load' and not \
+ self.identifiers.is_declared(node.name):
+ self.identifiers.undeclared.add(node.name)
+
+ def visit_If(self, node):
+ self.visit(node.test)
+ real_identifiers = self.identifiers
+
+ old_names = real_identifiers.declared_locally | \
+ real_identifiers.declared_parameter
+
+ def inner_visit(nodes):
+ if not nodes:
+ return set()
+ self.identifiers = real_identifiers.copy()
+ for subnode in nodes:
+ self.visit(subnode)
+ rv = self.identifiers.declared_locally - old_names
+ # we have to remember the undeclared variables of this branch
+ # because we will have to pull them.
+ real_identifiers.undeclared.update(self.identifiers.undeclared)
+ self.identifiers = real_identifiers
+ return rv
+
+ body = inner_visit(node.body)
+ else_ = inner_visit(node.else_ or ())
+
+ # the differences between the two branches are also pulled as
+ # undeclared variables
+ real_identifiers.undeclared.update(body.symmetric_difference(else_) -
+ real_identifiers.declared)
+
+ # remember those that are declared.
+ real_identifiers.declared_locally.update(body | else_)
+
+ def visit_Macro(self, node):
+ self.identifiers.declared_locally.add(node.name)
+
+ def visit_Import(self, node):
+ self.generic_visit(node)
+ self.identifiers.declared_locally.add(node.target)
+
+ def visit_FromImport(self, node):
+ self.generic_visit(node)
+ for name in node.names:
+ if isinstance(name, tuple):
+ self.identifiers.declared_locally.add(name[1])
+ else:
+ self.identifiers.declared_locally.add(name)
+
+ def visit_Assign(self, node):
+ """Visit assignments in the correct order."""
+ self.visit(node.node)
+ self.visit(node.target)
+
+ def visit_For(self, node):
+ """Visiting stops at for blocks. However the block sequence
+ is visited as part of the outer scope.
+ """
+ self.visit(node.iter)
+
+ def visit_CallBlock(self, node):
+ self.visit(node.call)
+
+ def visit_FilterBlock(self, node):
+ self.visit(node.filter)
+
+ def visit_Scope(self, node):
+ """Stop visiting at scopes."""
+
+ def visit_Block(self, node):
+ """Stop visiting at blocks."""
+
+
+class CompilerExit(Exception):
+ """Raised if the compiler encountered a situation where it just
+ doesn't make sense to further process the code. Any block that
+ raises such an exception is not further processed.
+ """
+
+
+class CodeGenerator(NodeVisitor):
+
+ def __init__(self, environment, name, filename, stream=None,
+ defer_init=False):
+ if stream is None:
+ stream = NativeStringIO()
+ self.environment = environment
+ self.name = name
+ self.filename = filename
+ self.stream = stream
+ self.created_block_context = False
+ self.defer_init = defer_init
+
+ # aliases for imports
+ self.import_aliases = {}
+
+ # a registry for all blocks. Because blocks are moved out
+ # into the global python scope they are registered here
+ self.blocks = {}
+
+ # the number of extends statements so far
+ self.extends_so_far = 0
+
+ # some templates have a rootlevel extends. In this case we
+ # can safely assume that we're a child template and do some
+ # more optimizations.
+ self.has_known_extends = False
+
+ # the current line number
+ self.code_lineno = 1
+
+ # registry of all filters and tests (global, not block local)
+ self.tests = {}
+ self.filters = {}
+
+ # the debug information
+ self.debug_info = []
+ self._write_debug_info = None
+
+ # the number of new lines before the next write()
+ self._new_lines = 0
+
+ # the line number of the last written statement
+ self._last_line = 0
+
+ # true if nothing was written so far.
+ self._first_write = True
+
+ # used by the `temporary_identifier` method to get new
+ # unique, temporary identifier
+ self._last_identifier = 0
+
+ # the current indentation
+ self._indentation = 0
+
+ # -- Various compilation helpers
+
+ def fail(self, msg, lineno):
+ """Fail with a :exc:`TemplateAssertionError`."""
+ raise TemplateAssertionError(msg, lineno, self.name, self.filename)
+
+ def temporary_identifier(self):
+ """Get a new unique identifier."""
+ self._last_identifier += 1
+ return 't_%d' % self._last_identifier
+
+ def buffer(self, frame):
+ """Enable buffering for the frame from that point onwards."""
+ frame.buffer = self.temporary_identifier()
+ self.writeline('%s = []' % frame.buffer)
+
+ def return_buffer_contents(self, frame):
+ """Return the buffer contents of the frame."""
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ elif frame.eval_ctx.autoescape:
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ else:
+ self.writeline('return concat(%s)' % frame.buffer)
+
+ def indent(self):
+ """Indent by one."""
+ self._indentation += 1
+
+ def outdent(self, step=1):
+ """Outdent by step."""
+ self._indentation -= step
+
+ def start_write(self, frame, node=None):
+ """Yield or write into the frame buffer."""
+ if frame.buffer is None:
+ self.writeline('yield ', node)
+ else:
+ self.writeline('%s.append(' % frame.buffer, node)
+
+ def end_write(self, frame):
+ """End the writing process started by `start_write`."""
+ if frame.buffer is not None:
+ self.write(')')
+
+ def simple_write(self, s, frame, node=None):
+ """Simple shortcut for start_write + write + end_write."""
+ self.start_write(frame, node)
+ self.write(s)
+ self.end_write(frame)
+
+ def blockvisit(self, nodes, frame):
+ """Visit a list of nodes as block in a frame. If the current frame
+ is no buffer a dummy ``if 0: yield None`` is written automatically
+ unless the force_generator parameter is set to False.
+ """
+ if frame.buffer is None:
+ self.writeline('if 0: yield None')
+ else:
+ self.writeline('pass')
+ try:
+ for node in nodes:
+ self.visit(node, frame)
+ except CompilerExit:
+ pass
+
+ def write(self, x):
+ """Write a string into the output stream."""
+ if self._new_lines:
+ if not self._first_write:
+ self.stream.write('\n' * self._new_lines)
+ self.code_lineno += self._new_lines
+ if self._write_debug_info is not None:
+ self.debug_info.append((self._write_debug_info,
+ self.code_lineno))
+ self._write_debug_info = None
+ self._first_write = False
+ self.stream.write(' ' * self._indentation)
+ self._new_lines = 0
+ self.stream.write(x)
+
+ def writeline(self, x, node=None, extra=0):
+ """Combination of newline and write."""
+ self.newline(node, extra)
+ self.write(x)
+
+ def newline(self, node=None, extra=0):
+ """Add one or more newlines before the next write."""
+ self._new_lines = max(self._new_lines, 1 + extra)
+ if node is not None and node.lineno != self._last_line:
+ self._write_debug_info = node.lineno
+ self._last_line = node.lineno
+
+ def signature(self, node, frame, extra_kwargs=None):
+ """Writes a function call to the stream for the current node.
+ A leading comma is added automatically. The extra keyword
+ arguments may not include python keywords otherwise a syntax
+ error could occour. The extra keyword arguments should be given
+ as python dict.
+ """
+ # if any of the given keyword arguments is a python keyword
+ # we have to make sure that no invalid call is created.
+ kwarg_workaround = False
+ for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
+ if is_python_keyword(kwarg):
+ kwarg_workaround = True
+ break
+
+ for arg in node.args:
+ self.write(', ')
+ self.visit(arg, frame)
+
+ if not kwarg_workaround:
+ for kwarg in node.kwargs:
+ self.write(', ')
+ self.visit(kwarg, frame)
+ if extra_kwargs is not None:
+ for key, value in iteritems(extra_kwargs):
+ self.write(', %s=%s' % (key, value))
+ if node.dyn_args:
+ self.write(', *')
+ self.visit(node.dyn_args, frame)
+
+ if kwarg_workaround:
+ if node.dyn_kwargs is not None:
+ self.write(', **dict({')
+ else:
+ self.write(', **{')
+ for kwarg in node.kwargs:
+ self.write('%r: ' % kwarg.key)
+ self.visit(kwarg.value, frame)
+ self.write(', ')
+ if extra_kwargs is not None:
+ for key, value in iteritems(extra_kwargs):
+ self.write('%r: %s, ' % (key, value))
+ if node.dyn_kwargs is not None:
+ self.write('}, **')
+ self.visit(node.dyn_kwargs, frame)
+ self.write(')')
+ else:
+ self.write('}')
+
+ elif node.dyn_kwargs is not None:
+ self.write(', **')
+ self.visit(node.dyn_kwargs, frame)
+
+ def pull_locals(self, frame):
+ """Pull all the references identifiers into the local scope."""
+ for name in frame.identifiers.undeclared:
+ self.writeline('l_%s = context.resolve(%r)' % (name, name))
+
+ def pull_dependencies(self, nodes):
+ """Pull all the dependencies."""
+ visitor = DependencyFinderVisitor()
+ for node in nodes:
+ visitor.visit(node)
+ for dependency in 'filters', 'tests':
+ mapping = getattr(self, dependency)
+ for name in getattr(visitor, dependency):
+ if name not in mapping:
+ mapping[name] = self.temporary_identifier()
+ self.writeline('%s = environment.%s[%r]' %
+ (mapping[name], dependency, name))
+
+ def unoptimize_scope(self, frame):
+ """Disable Python optimizations for the frame."""
+ # XXX: this is not that nice but it has no real overhead. It
+ # mainly works because python finds the locals before dead code
+ # is removed. If that breaks we have to add a dummy function
+ # that just accepts the arguments and does nothing.
+ if frame.identifiers.declared:
+ self.writeline('%sdummy(%s)' % (
+ unoptimize_before_dead_code and 'if 0: ' or '',
+ ', '.join('l_' + name for name in frame.identifiers.declared)
+ ))
+
+ def push_scope(self, frame, extra_vars=()):
+ """This function returns all the shadowed variables in a dict
+ in the form name: alias and will write the required assignments
+ into the current scope. No indentation takes place.
+
+ This also predefines locally declared variables from the loop
+ body because under some circumstances it may be the case that
+
+ `extra_vars` is passed to `Frame.find_shadowed`.
+ """
+ aliases = {}
+ for name in frame.find_shadowed(extra_vars):
+ aliases[name] = ident = self.temporary_identifier()
+ self.writeline('%s = l_%s' % (ident, name))
+ to_declare = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_declare.add('l_' + name)
+ if to_declare:
+ self.writeline(' = '.join(to_declare) + ' = missing')
+ return aliases
+
+ def pop_scope(self, aliases, frame):
+ """Restore all aliases and delete unused variables."""
+ for name, alias in iteritems(aliases):
+ self.writeline('l_%s = %s' % (name, alias))
+ to_delete = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_delete.add('l_' + name)
+ if to_delete:
+ # we cannot use the del statement here because enclosed
+ # scopes can trigger a SyntaxError:
+ # a = 42; b = lambda: a; del a
+ self.writeline(' = '.join(to_delete) + ' = missing')
+
+ def function_scoping(self, node, frame, children=None,
+ find_special=True):
+ """In Jinja a few statements require the help of anonymous
+ functions. Those are currently macros and call blocks and in
+ the future also recursive loops. As there is currently
+ technical limitation that doesn't allow reading and writing a
+ variable in a scope where the initial value is coming from an
+ outer scope, this function tries to fall back with a common
+ error message. Additionally the frame passed is modified so
+ that the argumetns are collected and callers are looked up.
+
+ This will return the modified frame.
+ """
+ # we have to iterate twice over it, make sure that works
+ if children is None:
+ children = node.iter_child_nodes()
+ children = list(children)
+ func_frame = frame.inner()
+ func_frame.inspect(children)
+
+ # variables that are undeclared (accessed before declaration) and
+ # declared locally *and* part of an outside scope raise a template
+ # assertion error. Reason: we can't generate reasonable code from
+ # it without aliasing all the variables.
+ # this could be fixed in Python 3 where we have the nonlocal
+ # keyword or if we switch to bytecode generation
+ overridden_closure_vars = (
+ func_frame.identifiers.undeclared &
+ func_frame.identifiers.declared &
+ (func_frame.identifiers.declared_locally |
+ func_frame.identifiers.declared_parameter)
+ )
+ if overridden_closure_vars:
+ self.fail('It\'s not possible to set and access variables '
+ 'derived from an outer scope! (affects: %s)' %
+ ', '.join(sorted(overridden_closure_vars)), node.lineno)
+
+ # remove variables from a closure from the frame's undeclared
+ # identifiers.
+ func_frame.identifiers.undeclared -= (
+ func_frame.identifiers.undeclared &
+ func_frame.identifiers.declared
+ )
+
+ # no special variables for this scope, abort early
+ if not find_special:
+ return func_frame
+
+ func_frame.accesses_kwargs = False
+ func_frame.accesses_varargs = False
+ func_frame.accesses_caller = False
+ func_frame.arguments = args = ['l_' + x.name for x in node.args]
+
+ undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
+
+ if 'caller' in undeclared:
+ func_frame.accesses_caller = True
+ func_frame.identifiers.add_special('caller')
+ args.append('l_caller')
+ if 'kwargs' in undeclared:
+ func_frame.accesses_kwargs = True
+ func_frame.identifiers.add_special('kwargs')
+ args.append('l_kwargs')
+ if 'varargs' in undeclared:
+ func_frame.accesses_varargs = True
+ func_frame.identifiers.add_special('varargs')
+ args.append('l_varargs')
+ return func_frame
+
+ def macro_body(self, node, frame, children=None):
+ """Dump the function def of a macro or call block."""
+ frame = self.function_scoping(node, frame, children)
+ # macros are delayed, they never require output checks
+ frame.require_output_check = False
+ args = frame.arguments
+ # XXX: this is an ugly fix for the loop nesting bug
+ # (tests.test_old_bugs.test_loop_call_bug). This works around
+ # a identifier nesting problem we have in general. It's just more
+ # likely to happen in loops which is why we work around it. The
+ # real solution would be "nonlocal" all the identifiers that are
+ # leaking into a new python frame and might be used both unassigned
+ # and assigned.
+ if 'loop' in frame.identifiers.declared:
+ args = args + ['l_loop=l_loop']
+ self.writeline('def macro(%s):' % ', '.join(args), node)
+ self.indent()
+ self.buffer(frame)
+ self.pull_locals(frame)
+ self.blockvisit(node.body, frame)
+ self.return_buffer_contents(frame)
+ self.outdent()
+ return frame
+
+ def macro_def(self, node, frame):
+ """Dump the macro definition for the def created by macro_body."""
+ arg_tuple = ', '.join(repr(x.name) for x in node.args)
+ name = getattr(node, 'name', None)
+ if len(node.args) == 1:
+ arg_tuple += ','
+ self.write('Macro(environment, macro, %r, (%s), (' %
+ (name, arg_tuple))
+ for arg in node.defaults:
+ self.visit(arg, frame)
+ self.write(', ')
+ self.write('), %r, %r, %r)' % (
+ bool(frame.accesses_kwargs),
+ bool(frame.accesses_varargs),
+ bool(frame.accesses_caller)
+ ))
+
+ def position(self, node):
+ """Return a human readable position for the node."""
+ rv = 'line %d' % node.lineno
+ if self.name is not None:
+ rv += ' in ' + repr(self.name)
+ return rv
+
+ # -- Statement Visitors
+
+ def visit_Template(self, node, frame=None):
+ assert frame is None, 'no root frame allowed'
+ eval_ctx = EvalContext(self.environment, self.name)
+
+ from jinja2.runtime import __all__ as exported
+ self.writeline('from __future__ import division')
+ self.writeline('from jinja2.runtime import ' + ', '.join(exported))
+ if not unoptimize_before_dead_code:
+ self.writeline('dummy = lambda *x: None')
+
+ # if we want a deferred initialization we cannot move the
+ # environment into a local name
+ envenv = not self.defer_init and ', environment=environment' or ''
+
+ # do we have an extends tag at all? If not, we can save some
+ # overhead by just not processing any inheritance code.
+ have_extends = node.find(nodes.Extends) is not None
+
+ # find all blocks
+ for block in node.find_all(nodes.Block):
+ if block.name in self.blocks:
+ self.fail('block %r defined twice' % block.name, block.lineno)
+ self.blocks[block.name] = block
+
+ # find all imports and import them
+ for import_ in node.find_all(nodes.ImportedName):
+ if import_.importname not in self.import_aliases:
+ imp = import_.importname
+ self.import_aliases[imp] = alias = self.temporary_identifier()
+ if '.' in imp:
+ module, obj = imp.rsplit('.', 1)
+ self.writeline('from %s import %s as %s' %
+ (module, obj, alias))
+ else:
+ self.writeline('import %s as %s' % (imp, alias))
+
+ # add the load name
+ self.writeline('name = %r' % self.name)
+
+ # generate the root render function.
+ self.writeline('def root(context%s):' % envenv, extra=1)
+
+ # process the root
+ frame = Frame(eval_ctx)
+ frame.inspect(node.body)
+ frame.toplevel = frame.rootlevel = True
+ frame.require_output_check = have_extends and not self.has_known_extends
+ self.indent()
+ if have_extends:
+ self.writeline('parent_template = None')
+ if 'self' in find_undeclared(node.body, ('self',)):
+ frame.identifiers.add_special('self')
+ self.writeline('l_self = TemplateReference(context)')
+ self.pull_locals(frame)
+ self.pull_dependencies(node.body)
+ self.blockvisit(node.body, frame)
+ self.outdent()
+
+ # make sure that the parent root is called.
+ if have_extends:
+ if not self.has_known_extends:
+ self.indent()
+ self.writeline('if parent_template is not None:')
+ self.indent()
+ self.writeline('for event in parent_template.'
+ 'root_render_func(context):')
+ self.indent()
+ self.writeline('yield event')
+ self.outdent(2 + (not self.has_known_extends))
+
+ # at this point we now have the blocks collected and can visit them too.
+ for name, block in iteritems(self.blocks):
+ block_frame = Frame(eval_ctx)
+ block_frame.inspect(block.body)
+ block_frame.block = name
+ self.writeline('def block_%s(context%s):' % (name, envenv),
+ block, 1)
+ self.indent()
+ undeclared = find_undeclared(block.body, ('self', 'super'))
+ if 'self' in undeclared:
+ block_frame.identifiers.add_special('self')
+ self.writeline('l_self = TemplateReference(context)')
+ if 'super' in undeclared:
+ block_frame.identifiers.add_special('super')
+ self.writeline('l_super = context.super(%r, '
+ 'block_%s)' % (name, name))
+ self.pull_locals(block_frame)
+ self.pull_dependencies(block.body)
+ self.blockvisit(block.body, block_frame)
+ self.outdent()
+
+ self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
+ for x in self.blocks),
+ extra=1)
+
+ # add a function that returns the debug info
+ self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
+ in self.debug_info))
+
+ def visit_Block(self, node, frame):
+ """Call a block and register it for the template."""
+ level = 1
+ if frame.toplevel:
+ # if we know that we are a child template, there is no need to
+ # check if we are one
+ if self.has_known_extends:
+ return
+ if self.extends_so_far > 0:
+ self.writeline('if parent_template is None:')
+ self.indent()
+ level += 1
+ context = node.scoped and 'context.derived(locals())' or 'context'
+ self.writeline('for event in context.blocks[%r][0](%s):' % (
+ node.name, context), node)
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent(level)
+
+ def visit_Extends(self, node, frame):
+ """Calls the extender."""
+ if not frame.toplevel:
+ self.fail('cannot use extend from a non top-level scope',
+ node.lineno)
+
+ # if the number of extends statements in general is zero so
+ # far, we don't have to add a check if something extended
+ # the template before this one.
+ if self.extends_so_far > 0:
+
+ # if we have a known extends we just add a template runtime
+ # error into the generated code. We could catch that at compile
+ # time too, but i welcome it not to confuse users by throwing the
+ # same error at different times just "because we can".
+ if not self.has_known_extends:
+ self.writeline('if parent_template is not None:')
+ self.indent()
+ self.writeline('raise TemplateRuntimeError(%r)' %
+ 'extended multiple times')
+
+ # if we have a known extends already we don't need that code here
+ # as we know that the template execution will end here.
+ if self.has_known_extends:
+ raise CompilerExit()
+ else:
+ self.outdent()
+
+ self.writeline('parent_template = environment.get_template(', node)
+ self.visit(node.template, frame)
+ self.write(', %r)' % self.name)
+ self.writeline('for name, parent_block in parent_template.'
+ 'blocks.%s():' % dict_item_iter)
+ self.indent()
+ self.writeline('context.blocks.setdefault(name, []).'
+ 'append(parent_block)')
+ self.outdent()
+
+ # if this extends statement was in the root level we can take
+ # advantage of that information and simplify the generated code
+ # in the top level from this point onwards
+ if frame.rootlevel:
+ self.has_known_extends = True
+
+ # and now we have one more
+ self.extends_so_far += 1
+
+ def visit_Include(self, node, frame):
+ """Handles includes."""
+ if node.with_context:
+ self.unoptimize_scope(frame)
+ if node.ignore_missing:
+ self.writeline('try:')
+ self.indent()
+
+ func_name = 'get_or_select_template'
+ if isinstance(node.template, nodes.Const):
+ if isinstance(node.template.value, string_types):
+ func_name = 'get_template'
+ elif isinstance(node.template.value, (tuple, list)):
+ func_name = 'select_template'
+ elif isinstance(node.template, (nodes.Tuple, nodes.List)):
+ func_name = 'select_template'
+
+ self.writeline('template = environment.%s(' % func_name, node)
+ self.visit(node.template, frame)
+ self.write(', %r)' % self.name)
+ if node.ignore_missing:
+ self.outdent()
+ self.writeline('except TemplateNotFound:')
+ self.indent()
+ self.writeline('pass')
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+
+ if node.with_context:
+ self.writeline('for event in template.root_render_func('
+ 'template.new_context(context.parent, True, '
+ 'locals())):')
+ else:
+ self.writeline('for event in template.module._body_stream:')
+
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent()
+
+ if node.ignore_missing:
+ self.outdent()
+
+ def visit_Import(self, node, frame):
+ """Visit regular imports."""
+ if node.with_context:
+ self.unoptimize_scope(frame)
+ self.writeline('l_%s = ' % node.target, node)
+ if frame.toplevel:
+ self.write('context.vars[%r] = ' % node.target)
+ self.write('environment.get_template(')
+ self.visit(node.template, frame)
+ self.write(', %r).' % self.name)
+ if node.with_context:
+ self.write('make_module(context.parent, True, locals())')
+ else:
+ self.write('module')
+ if frame.toplevel and not node.target.startswith('_'):
+ self.writeline('context.exported_vars.discard(%r)' % node.target)
+ frame.assigned_names.add(node.target)
+
+ def visit_FromImport(self, node, frame):
+ """Visit named imports."""
+ self.newline(node)
+ self.write('included_template = environment.get_template(')
+ self.visit(node.template, frame)
+ self.write(', %r).' % self.name)
+ if node.with_context:
+ self.write('make_module(context.parent, True)')
+ else:
+ self.write('module')
+
+ var_names = []
+ discarded_names = []
+ for name in node.names:
+ if isinstance(name, tuple):
+ name, alias = name
+ else:
+ alias = name
+ self.writeline('l_%s = getattr(included_template, '
+ '%r, missing)' % (alias, name))
+ self.writeline('if l_%s is missing:' % alias)
+ self.indent()
+ self.writeline('l_%s = environment.undefined(%r %% '
+ 'included_template.__name__, '
+ 'name=%r)' %
+ (alias, 'the template %%r (imported on %s) does '
+ 'not export the requested name %s' % (
+ self.position(node),
+ repr(name)
+ ), name))
+ self.outdent()
+ if frame.toplevel:
+ var_names.append(alias)
+ if not alias.startswith('_'):
+ discarded_names.append(alias)
+ frame.assigned_names.add(alias)
+
+ if var_names:
+ if len(var_names) == 1:
+ name = var_names[0]
+ self.writeline('context.vars[%r] = l_%s' % (name, name))
+ else:
+ self.writeline('context.vars.update({%s})' % ', '.join(
+ '%r: l_%s' % (name, name) for name in var_names
+ ))
+ if discarded_names:
+ if len(discarded_names) == 1:
+ self.writeline('context.exported_vars.discard(%r)' %
+ discarded_names[0])
+ else:
+ self.writeline('context.exported_vars.difference_'
+ 'update((%s))' % ', '.join(imap(repr, discarded_names)))
+
+ def visit_For(self, node, frame):
+ # when calculating the nodes for the inner frame we have to exclude
+ # the iterator contents from it
+ children = node.iter_child_nodes(exclude=('iter',))
+ if node.recursive:
+ loop_frame = self.function_scoping(node, frame, children,
+ find_special=False)
+ else:
+ loop_frame = frame.inner()
+ loop_frame.inspect(children)
+
+ # try to figure out if we have an extended loop. An extended loop
+ # is necessary if the loop is in recursive mode if the special loop
+ # variable is accessed in the body.
+ extended_loop = node.recursive or 'loop' in \
+ find_undeclared(node.iter_child_nodes(
+ only=('body',)), ('loop',))
+
+ # if we don't have an recursive loop we have to find the shadowed
+ # variables at that point. Because loops can be nested but the loop
+ # variable is a special one we have to enforce aliasing for it.
+ if not node.recursive:
+ aliases = self.push_scope(loop_frame, ('loop',))
+
+ # otherwise we set up a buffer and add a function def
+ else:
+ self.writeline('def loop(reciter, loop_render_func, depth=0):', node)
+ self.indent()
+ self.buffer(loop_frame)
+ aliases = {}
+
+ # make sure the loop variable is a special one and raise a template
+ # assertion error if a loop tries to write to loop
+ if extended_loop:
+ self.writeline('l_loop = missing')
+ loop_frame.identifiers.add_special('loop')
+ for name in node.find_all(nodes.Name):
+ if name.ctx == 'store' and name.name == 'loop':
+ self.fail('Can\'t assign to special loop variable '
+ 'in for-loop target', name.lineno)
+
+ self.pull_locals(loop_frame)
+ if node.else_:
+ iteration_indicator = self.temporary_identifier()
+ self.writeline('%s = 1' % iteration_indicator)
+
+ # Create a fake parent loop if the else or test section of a
+ # loop is accessing the special loop variable and no parent loop
+ # exists.
+ if 'loop' not in aliases and 'loop' in find_undeclared(
+ node.iter_child_nodes(only=('else_', 'test')), ('loop',)):
+ self.writeline("l_loop = environment.undefined(%r, name='loop')" %
+ ("'loop' is undefined. the filter section of a loop as well "
+ "as the else block don't have access to the special 'loop'"
+ " variable of the current loop. Because there is no parent "
+ "loop it's undefined. Happened in loop on %s" %
+ self.position(node)))
+
+ self.writeline('for ', node)
+ self.visit(node.target, loop_frame)
+ self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
+
+ # if we have an extened loop and a node test, we filter in the
+ # "outer frame".
+ if extended_loop and node.test is not None:
+ self.write('(')
+ self.visit(node.target, loop_frame)
+ self.write(' for ')
+ self.visit(node.target, loop_frame)
+ self.write(' in ')
+ if node.recursive:
+ self.write('reciter')
+ else:
+ self.visit(node.iter, loop_frame)
+ self.write(' if (')
+ test_frame = loop_frame.copy()
+ self.visit(node.test, test_frame)
+ self.write('))')
+
+ elif node.recursive:
+ self.write('reciter')
+ else:
+ self.visit(node.iter, loop_frame)
+
+ if node.recursive:
+ self.write(', loop_render_func, depth):')
+ else:
+ self.write(extended_loop and '):' or ':')
+
+ # tests in not extended loops become a continue
+ if not extended_loop and node.test is not None:
+ self.indent()
+ self.writeline('if not ')
+ self.visit(node.test, loop_frame)
+ self.write(':')
+ self.indent()
+ self.writeline('continue')
+ self.outdent(2)
+
+ self.indent()
+ self.blockvisit(node.body, loop_frame)
+ if node.else_:
+ self.writeline('%s = 0' % iteration_indicator)
+ self.outdent()
+
+ if node.else_:
+ self.writeline('if %s:' % iteration_indicator)
+ self.indent()
+ self.blockvisit(node.else_, loop_frame)
+ self.outdent()
+
+ # reset the aliases if there are any.
+ if not node.recursive:
+ self.pop_scope(aliases, loop_frame)
+
+ # if the node was recursive we have to return the buffer contents
+ # and start the iteration code
+ if node.recursive:
+ self.return_buffer_contents(loop_frame)
+ self.outdent()
+ self.start_write(frame, node)
+ self.write('loop(')
+ self.visit(node.iter, frame)
+ self.write(', loop)')
+ self.end_write(frame)
+
+ def visit_If(self, node, frame):
+ if_frame = frame.soft()
+ self.writeline('if ', node)
+ self.visit(node.test, if_frame)
+ self.write(':')
+ self.indent()
+ self.blockvisit(node.body, if_frame)
+ self.outdent()
+ if node.else_:
+ self.writeline('else:')
+ self.indent()
+ self.blockvisit(node.else_, if_frame)
+ self.outdent()
+
+ def visit_Macro(self, node, frame):
+ macro_frame = self.macro_body(node, frame)
+ self.newline()
+ if frame.toplevel:
+ if not node.name.startswith('_'):
+ self.write('context.exported_vars.add(%r)' % node.name)
+ self.writeline('context.vars[%r] = ' % node.name)
+ self.write('l_%s = ' % node.name)
+ self.macro_def(node, macro_frame)
+ frame.assigned_names.add(node.name)
+
+ def visit_CallBlock(self, node, frame):
+ children = node.iter_child_nodes(exclude=('call',))
+ call_frame = self.macro_body(node, frame, children)
+ self.writeline('caller = ')
+ self.macro_def(node, call_frame)
+ self.start_write(frame, node)
+ self.visit_Call(node.call, call_frame, forward_caller=True)
+ self.end_write(frame)
+
+ def visit_FilterBlock(self, node, frame):
+ filter_frame = frame.inner()
+ filter_frame.inspect(node.iter_child_nodes())
+ aliases = self.push_scope(filter_frame)
+ self.pull_locals(filter_frame)
+ self.buffer(filter_frame)
+ self.blockvisit(node.body, filter_frame)
+ self.start_write(frame, node)
+ self.visit_Filter(node.filter, filter_frame)
+ self.end_write(frame)
+ self.pop_scope(aliases, filter_frame)
+
+ def visit_ExprStmt(self, node, frame):
+ self.newline(node)
+ self.visit(node.node, frame)
+
+ def visit_Output(self, node, frame):
+ # if we have a known extends statement, we don't output anything
+ # if we are in a require_output_check section
+ if self.has_known_extends and frame.require_output_check:
+ return
+
+ if self.environment.finalize:
+ finalize = lambda x: text_type(self.environment.finalize(x))
+ else:
+ finalize = text_type
+
+ # if we are inside a frame that requires output checking, we do so
+ outdent_later = False
+ if frame.require_output_check:
+ self.writeline('if parent_template is None:')
+ self.indent()
+ outdent_later = True
+
+ # try to evaluate as many chunks as possible into a static
+ # string at compile time.
+ body = []
+ for child in node.nodes:
+ try:
+ const = child.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ body.append(child)
+ continue
+ # the frame can't be volatile here, becaus otherwise the
+ # as_const() function would raise an Impossible exception
+ # at that point.
+ try:
+ if frame.eval_ctx.autoescape:
+ if hasattr(const, '__html__'):
+ const = const.__html__()
+ else:
+ const = escape(const)
+ const = finalize(const)
+ except Exception:
+ # if something goes wrong here we evaluate the node
+ # at runtime for easier debugging
+ body.append(child)
+ continue
+ if body and isinstance(body[-1], list):
+ body[-1].append(const)
+ else:
+ body.append([const])
+
+ # if we have less than 3 nodes or a buffer we yield or extend/append
+ if len(body) < 3 or frame.buffer is not None:
+ if frame.buffer is not None:
+ # for one item we append, for more we extend
+ if len(body) == 1:
+ self.writeline('%s.append(' % frame.buffer)
+ else:
+ self.writeline('%s.extend((' % frame.buffer)
+ self.indent()
+ for item in body:
+ if isinstance(item, list):
+ val = repr(concat(item))
+ if frame.buffer is None:
+ self.writeline('yield ' + val)
+ else:
+ self.writeline(val + ', ')
+ else:
+ if frame.buffer is None:
+ self.writeline('yield ', item)
+ else:
+ self.newline(item)
+ close = 1
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ elif frame.eval_ctx.autoescape:
+ self.write('escape(')
+ else:
+ self.write('to_string(')
+ if self.environment.finalize is not None:
+ self.write('environment.finalize(')
+ close += 1
+ self.visit(item, frame)
+ self.write(')' * close)
+ if frame.buffer is not None:
+ self.write(', ')
+ if frame.buffer is not None:
+ # close the open parentheses
+ self.outdent()
+ self.writeline(len(body) == 1 and ')' or '))')
+
+ # otherwise we create a format string as this is faster in that case
+ else:
+ format = []
+ arguments = []
+ for item in body:
+ if isinstance(item, list):
+ format.append(concat(item).replace('%', '%%'))
+ else:
+ format.append('%s')
+ arguments.append(item)
+ self.writeline('yield ')
+ self.write(repr(concat(format)) + ' % (')
+ idx = -1
+ self.indent()
+ for argument in arguments:
+ self.newline(argument)
+ close = 0
+ if frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' escape or to_string)(')
+ close += 1
+ elif frame.eval_ctx.autoescape:
+ self.write('escape(')
+ close += 1
+ if self.environment.finalize is not None:
+ self.write('environment.finalize(')
+ close += 1
+ self.visit(argument, frame)
+ self.write(')' * close + ', ')
+ self.outdent()
+ self.writeline(')')
+
+ if outdent_later:
+ self.outdent()
+
+ def visit_Assign(self, node, frame):
+ self.newline(node)
+ # toplevel assignments however go into the local namespace and
+ # the current template's context. We create a copy of the frame
+ # here and add a set so that the Name visitor can add the assigned
+ # names here.
+ if frame.toplevel:
+ assignment_frame = frame.copy()
+ assignment_frame.toplevel_assignments = set()
+ else:
+ assignment_frame = frame
+ self.visit(node.target, assignment_frame)
+ self.write(' = ')
+ self.visit(node.node, frame)
+
+ # make sure toplevel assignments are added to the context.
+ if frame.toplevel:
+ public_names = [x for x in assignment_frame.toplevel_assignments
+ if not x.startswith('_')]
+ if len(assignment_frame.toplevel_assignments) == 1:
+ name = next(iter(assignment_frame.toplevel_assignments))
+ self.writeline('context.vars[%r] = l_%s' % (name, name))
+ else:
+ self.writeline('context.vars.update({')
+ for idx, name in enumerate(assignment_frame.toplevel_assignments):
+ if idx:
+ self.write(', ')
+ self.write('%r: l_%s' % (name, name))
+ self.write('})')
+ if public_names:
+ if len(public_names) == 1:
+ self.writeline('context.exported_vars.add(%r)' %
+ public_names[0])
+ else:
+ self.writeline('context.exported_vars.update((%s))' %
+ ', '.join(imap(repr, public_names)))
+
+ # -- Expression Visitors
+
+ def visit_Name(self, node, frame):
+ if node.ctx == 'store' and frame.toplevel:
+ frame.toplevel_assignments.add(node.name)
+ self.write('l_' + node.name)
+ frame.assigned_names.add(node.name)
+
+ def visit_Const(self, node, frame):
+ val = node.value
+ if isinstance(val, float):
+ self.write(str(val))
+ else:
+ self.write(repr(val))
+
+ def visit_TemplateData(self, node, frame):
+ try:
+ self.write(repr(node.as_const(frame.eval_ctx)))
+ except nodes.Impossible:
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(%r)'
+ % node.data)
+
+ def visit_Tuple(self, node, frame):
+ self.write('(')
+ idx = -1
+ for idx, item in enumerate(node.items):
+ if idx:
+ self.write(', ')
+ self.visit(item, frame)
+ self.write(idx == 0 and ',)' or ')')
+
+ def visit_List(self, node, frame):
+ self.write('[')
+ for idx, item in enumerate(node.items):
+ if idx:
+ self.write(', ')
+ self.visit(item, frame)
+ self.write(']')
+
+ def visit_Dict(self, node, frame):
+ self.write('{')
+ for idx, item in enumerate(node.items):
+ if idx:
+ self.write(', ')
+ self.visit(item.key, frame)
+ self.write(': ')
+ self.visit(item.value, frame)
+ self.write('}')
+
+ def binop(operator, interceptable=True):
+ def visitor(self, node, frame):
+ if self.environment.sandboxed and \
+ operator in self.environment.intercepted_binops:
+ self.write('environment.call_binop(context, %r, ' % operator)
+ self.visit(node.left, frame)
+ self.write(', ')
+ self.visit(node.right, frame)
+ else:
+ self.write('(')
+ self.visit(node.left, frame)
+ self.write(' %s ' % operator)
+ self.visit(node.right, frame)
+ self.write(')')
+ return visitor
+
+ def uaop(operator, interceptable=True):
+ def visitor(self, node, frame):
+ if self.environment.sandboxed and \
+ operator in self.environment.intercepted_unops:
+ self.write('environment.call_unop(context, %r, ' % operator)
+ self.visit(node.node, frame)
+ else:
+ self.write('(' + operator)
+ self.visit(node.node, frame)
+ self.write(')')
+ return visitor
+
+ visit_Add = binop('+')
+ visit_Sub = binop('-')
+ visit_Mul = binop('*')
+ visit_Div = binop('/')
+ visit_FloorDiv = binop('//')
+ visit_Pow = binop('**')
+ visit_Mod = binop('%')
+ visit_And = binop('and', interceptable=False)
+ visit_Or = binop('or', interceptable=False)
+ visit_Pos = uaop('+')
+ visit_Neg = uaop('-')
+ visit_Not = uaop('not ', interceptable=False)
+ del binop, uaop
+
+ def visit_Concat(self, node, frame):
+ if frame.eval_ctx.volatile:
+ func_name = '(context.eval_ctx.volatile and' \
+ ' markup_join or unicode_join)'
+ elif frame.eval_ctx.autoescape:
+ func_name = 'markup_join'
+ else:
+ func_name = 'unicode_join'
+ self.write('%s((' % func_name)
+ for arg in node.nodes:
+ self.visit(arg, frame)
+ self.write(', ')
+ self.write('))')
+
+ def visit_Compare(self, node, frame):
+ self.visit(node.expr, frame)
+ for op in node.ops:
+ self.visit(op, frame)
+
+ def visit_Operand(self, node, frame):
+ self.write(' %s ' % operators[node.op])
+ self.visit(node.expr, frame)
+
+ def visit_Getattr(self, node, frame):
+ self.write('environment.getattr(')
+ self.visit(node.node, frame)
+ self.write(', %r)' % node.attr)
+
+ def visit_Getitem(self, node, frame):
+ # slices bypass the environment getitem method.
+ if isinstance(node.arg, nodes.Slice):
+ self.visit(node.node, frame)
+ self.write('[')
+ self.visit(node.arg, frame)
+ self.write(']')
+ else:
+ self.write('environment.getitem(')
+ self.visit(node.node, frame)
+ self.write(', ')
+ self.visit(node.arg, frame)
+ self.write(')')
+
+ def visit_Slice(self, node, frame):
+ if node.start is not None:
+ self.visit(node.start, frame)
+ self.write(':')
+ if node.stop is not None:
+ self.visit(node.stop, frame)
+ if node.step is not None:
+ self.write(':')
+ self.visit(node.step, frame)
+
+ def visit_Filter(self, node, frame):
+ self.write(self.filters[node.name] + '(')
+ func = self.environment.filters.get(node.name)
+ if func is None:
+ self.fail('no filter named %r' % node.name, node.lineno)
+ if getattr(func, 'contextfilter', False):
+ self.write('context, ')
+ elif getattr(func, 'evalcontextfilter', False):
+ self.write('context.eval_ctx, ')
+ elif getattr(func, 'environmentfilter', False):
+ self.write('environment, ')
+
+ # if the filter node is None we are inside a filter block
+ # and want to write to the current buffer
+ if node.node is not None:
+ self.visit(node.node, frame)
+ elif frame.eval_ctx.volatile:
+ self.write('(context.eval_ctx.autoescape and'
+ ' Markup(concat(%s)) or concat(%s))' %
+ (frame.buffer, frame.buffer))
+ elif frame.eval_ctx.autoescape:
+ self.write('Markup(concat(%s))' % frame.buffer)
+ else:
+ self.write('concat(%s)' % frame.buffer)
+ self.signature(node, frame)
+ self.write(')')
+
+ def visit_Test(self, node, frame):
+ self.write(self.tests[node.name] + '(')
+ if node.name not in self.environment.tests:
+ self.fail('no test named %r' % node.name, node.lineno)
+ self.visit(node.node, frame)
+ self.signature(node, frame)
+ self.write(')')
+
+ def visit_CondExpr(self, node, frame):
+ def write_expr2():
+ if node.expr2 is not None:
+ return self.visit(node.expr2, frame)
+ self.write('environment.undefined(%r)' % ('the inline if-'
+ 'expression on %s evaluated to false and '
+ 'no else section was defined.' % self.position(node)))
+
+ self.write('(')
+ self.visit(node.expr1, frame)
+ self.write(' if ')
+ self.visit(node.test, frame)
+ self.write(' else ')
+ write_expr2()
+ self.write(')')
+
+ def visit_Call(self, node, frame, forward_caller=False):
+ if self.environment.sandboxed:
+ self.write('environment.call(context, ')
+ else:
+ self.write('context.call(')
+ self.visit(node.node, frame)
+ extra_kwargs = forward_caller and {'caller': 'caller'} or None
+ self.signature(node, frame, extra_kwargs)
+ self.write(')')
+
+ def visit_Keyword(self, node, frame):
+ self.write(node.key + '=')
+ self.visit(node.value, frame)
+
+ # -- Unused nodes for extensions
+
+ def visit_MarkSafe(self, node, frame):
+ self.write('Markup(')
+ self.visit(node.expr, frame)
+ self.write(')')
+
+ def visit_MarkSafeIfAutoescape(self, node, frame):
+ self.write('(context.eval_ctx.autoescape and Markup or identity)(')
+ self.visit(node.expr, frame)
+ self.write(')')
+
+ def visit_EnvironmentAttribute(self, node, frame):
+ self.write('environment.' + node.name)
+
+ def visit_ExtensionAttribute(self, node, frame):
+ self.write('environment.extensions[%r].%s' % (node.identifier, node.name))
+
+ def visit_ImportedName(self, node, frame):
+ self.write(self.import_aliases[node.importname])
+
+ def visit_InternalName(self, node, frame):
+ self.write(node.name)
+
+ def visit_ContextReference(self, node, frame):
+ self.write('context')
+
+ def visit_Continue(self, node, frame):
+ self.writeline('continue', node)
+
+ def visit_Break(self, node, frame):
+ self.writeline('break', node)
+
+ def visit_Scope(self, node, frame):
+ scope_frame = frame.inner()
+ scope_frame.inspect(node.iter_child_nodes())
+ aliases = self.push_scope(scope_frame)
+ self.pull_locals(scope_frame)
+ self.blockvisit(node.body, scope_frame)
+ self.pop_scope(aliases, scope_frame)
+
+ def visit_EvalContextModifier(self, node, frame):
+ for keyword in node.options:
+ self.writeline('context.eval_ctx.%s = ' % keyword.key)
+ self.visit(keyword.value, frame)
+ try:
+ val = keyword.value.as_const(frame.eval_ctx)
+ except nodes.Impossible:
+ frame.eval_ctx.volatile = True
+ else:
+ setattr(frame.eval_ctx, keyword.key, val)
+
+ def visit_ScopedEvalContextModifier(self, node, frame):
+ old_ctx_name = self.temporary_identifier()
+ safed_ctx = frame.eval_ctx.save()
+ self.writeline('%s = context.eval_ctx.save()' % old_ctx_name)
+ self.visit_EvalContextModifier(node, frame)
+ for child in node.body:
+ self.visit(child, frame)
+ frame.eval_ctx.revert(safed_ctx)
+ self.writeline('context.eval_ctx.revert(%s)' % old_ctx_name)
diff --git a/module/lib/jinja2/constants.py b/pyload/lib/jinja2/constants.py
index cab203cc7..cab203cc7 100644
--- a/module/lib/jinja2/constants.py
+++ b/pyload/lib/jinja2/constants.py
diff --git a/pyload/lib/jinja2/debug.py b/pyload/lib/jinja2/debug.py
new file mode 100644
index 000000000..815cc18a4
--- /dev/null
+++ b/pyload/lib/jinja2/debug.py
@@ -0,0 +1,337 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.debug
+ ~~~~~~~~~~~~
+
+ Implements the debug interface for Jinja. This module does some pretty
+ ugly stuff with the Python traceback system in order to achieve tracebacks
+ with correct line numbers, locals and contents.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import sys
+import traceback
+from types import TracebackType
+from jinja2.utils import missing, internal_code
+from jinja2.exceptions import TemplateSyntaxError
+from jinja2._compat import iteritems, reraise, code_type
+
+# on pypy we can take advantage of transparent proxies
+try:
+ from __pypy__ import tproxy
+except ImportError:
+ tproxy = None
+
+
+# how does the raise helper look like?
+try:
+ exec("raise TypeError, 'foo'")
+except SyntaxError:
+ raise_helper = 'raise __jinja_exception__[1]'
+except TypeError:
+ raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
+
+
+class TracebackFrameProxy(object):
+ """Proxies a traceback frame."""
+
+ def __init__(self, tb):
+ self.tb = tb
+ self._tb_next = None
+
+ @property
+ def tb_next(self):
+ return self._tb_next
+
+ def set_next(self, next):
+ if tb_set_next is not None:
+ try:
+ tb_set_next(self.tb, next and next.tb or None)
+ except Exception:
+ # this function can fail due to all the hackery it does
+ # on various python implementations. We just catch errors
+ # down and ignore them if necessary.
+ pass
+ self._tb_next = next
+
+ @property
+ def is_jinja_frame(self):
+ return '__jinja_template__' in self.tb.tb_frame.f_globals
+
+ def __getattr__(self, name):
+ return getattr(self.tb, name)
+
+
+def make_frame_proxy(frame):
+ proxy = TracebackFrameProxy(frame)
+ if tproxy is None:
+ return proxy
+ def operation_handler(operation, *args, **kwargs):
+ if operation in ('__getattribute__', '__getattr__'):
+ return getattr(proxy, args[0])
+ elif operation == '__setattr__':
+ proxy.__setattr__(*args, **kwargs)
+ else:
+ return getattr(proxy, operation)(*args, **kwargs)
+ return tproxy(TracebackType, operation_handler)
+
+
+class ProcessedTraceback(object):
+ """Holds a Jinja preprocessed traceback for printing or reraising."""
+
+ def __init__(self, exc_type, exc_value, frames):
+ assert frames, 'no frames for this traceback?'
+ self.exc_type = exc_type
+ self.exc_value = exc_value
+ self.frames = frames
+
+ # newly concatenate the frames (which are proxies)
+ prev_tb = None
+ for tb in self.frames:
+ if prev_tb is not None:
+ prev_tb.set_next(tb)
+ prev_tb = tb
+ prev_tb.set_next(None)
+
+ def render_as_text(self, limit=None):
+ """Return a string with the traceback."""
+ lines = traceback.format_exception(self.exc_type, self.exc_value,
+ self.frames[0], limit=limit)
+ return ''.join(lines).rstrip()
+
+ def render_as_html(self, full=False):
+ """Return a unicode string with the traceback as rendered HTML."""
+ from jinja2.debugrenderer import render_traceback
+ return u'%s\n\n<!--\n%s\n-->' % (
+ render_traceback(self, full=full),
+ self.render_as_text().decode('utf-8', 'replace')
+ )
+
+ @property
+ def is_template_syntax_error(self):
+ """`True` if this is a template syntax error."""
+ return isinstance(self.exc_value, TemplateSyntaxError)
+
+ @property
+ def exc_info(self):
+ """Exception info tuple with a proxy around the frame objects."""
+ return self.exc_type, self.exc_value, self.frames[0]
+
+ @property
+ def standard_exc_info(self):
+ """Standard python exc_info for re-raising"""
+ tb = self.frames[0]
+ # the frame will be an actual traceback (or transparent proxy) if
+ # we are on pypy or a python implementation with support for tproxy
+ if type(tb) is not TracebackType:
+ tb = tb.tb
+ return self.exc_type, self.exc_value, tb
+
+
+def make_traceback(exc_info, source_hint=None):
+ """Creates a processed traceback object from the exc_info."""
+ exc_type, exc_value, tb = exc_info
+ if isinstance(exc_value, TemplateSyntaxError):
+ exc_info = translate_syntax_error(exc_value, source_hint)
+ initial_skip = 0
+ else:
+ initial_skip = 1
+ return translate_exception(exc_info, initial_skip)
+
+
+def translate_syntax_error(error, source=None):
+ """Rewrites a syntax error to please traceback systems."""
+ error.source = source
+ error.translated = True
+ exc_info = (error.__class__, error, None)
+ filename = error.filename
+ if filename is None:
+ filename = '<unknown>'
+ return fake_exc_info(exc_info, filename, error.lineno)
+
+
+def translate_exception(exc_info, initial_skip=0):
+ """If passed an exc_info it will automatically rewrite the exceptions
+ all the way down to the correct line numbers and frames.
+ """
+ tb = exc_info[2]
+ frames = []
+
+ # skip some internal frames if wanted
+ for x in range(initial_skip):
+ if tb is not None:
+ tb = tb.tb_next
+ initial_tb = tb
+
+ while tb is not None:
+ # skip frames decorated with @internalcode. These are internal
+ # calls we can't avoid and that are useless in template debugging
+ # output.
+ if tb.tb_frame.f_code in internal_code:
+ tb = tb.tb_next
+ continue
+
+ # save a reference to the next frame if we override the current
+ # one with a faked one.
+ next = tb.tb_next
+
+ # fake template exceptions
+ template = tb.tb_frame.f_globals.get('__jinja_template__')
+ if template is not None:
+ lineno = template.get_corresponding_lineno(tb.tb_lineno)
+ tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
+ lineno)[2]
+
+ frames.append(make_frame_proxy(tb))
+ tb = next
+
+ # if we don't have any exceptions in the frames left, we have to
+ # reraise it unchanged.
+ # XXX: can we backup here? when could this happen?
+ if not frames:
+ reraise(exc_info[0], exc_info[1], exc_info[2])
+
+ return ProcessedTraceback(exc_info[0], exc_info[1], frames)
+
+
+def fake_exc_info(exc_info, filename, lineno):
+ """Helper for `translate_exception`."""
+ exc_type, exc_value, tb = exc_info
+
+ # figure the real context out
+ if tb is not None:
+ real_locals = tb.tb_frame.f_locals.copy()
+ ctx = real_locals.get('context')
+ if ctx:
+ locals = ctx.get_all()
+ else:
+ locals = {}
+ for name, value in iteritems(real_locals):
+ if name.startswith('l_') and value is not missing:
+ locals[name[2:]] = value
+
+ # if there is a local called __jinja_exception__, we get
+ # rid of it to not break the debug functionality.
+ locals.pop('__jinja_exception__', None)
+ else:
+ locals = {}
+
+ # assamble fake globals we need
+ globals = {
+ '__name__': filename,
+ '__file__': filename,
+ '__jinja_exception__': exc_info[:2],
+
+ # we don't want to keep the reference to the template around
+ # to not cause circular dependencies, but we mark it as Jinja
+ # frame for the ProcessedTraceback
+ '__jinja_template__': None
+ }
+
+ # and fake the exception
+ code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
+
+ # if it's possible, change the name of the code. This won't work
+ # on some python environments such as google appengine
+ try:
+ if tb is None:
+ location = 'template'
+ else:
+ function = tb.tb_frame.f_code.co_name
+ if function == 'root':
+ location = 'top-level template code'
+ elif function.startswith('block_'):
+ location = 'block "%s"' % function[6:]
+ else:
+ location = 'template'
+ code = code_type(0, code.co_nlocals, code.co_stacksize,
+ code.co_flags, code.co_code, code.co_consts,
+ code.co_names, code.co_varnames, filename,
+ location, code.co_firstlineno,
+ code.co_lnotab, (), ())
+ except:
+ pass
+
+ # execute the code and catch the new traceback
+ try:
+ exec(code, globals, locals)
+ except:
+ exc_info = sys.exc_info()
+ new_tb = exc_info[2].tb_next
+
+ # return without this frame
+ return exc_info[:2] + (new_tb,)
+
+
+def _init_ugly_crap():
+ """This function implements a few ugly things so that we can patch the
+ traceback objects. The function returned allows resetting `tb_next` on
+ any python traceback object. Do not attempt to use this on non cpython
+ interpreters
+ """
+ import ctypes
+ from types import TracebackType
+
+ # figure out side of _Py_ssize_t
+ if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
+ _Py_ssize_t = ctypes.c_int64
+ else:
+ _Py_ssize_t = ctypes.c_int
+
+ # regular python
+ class _PyObject(ctypes.Structure):
+ pass
+ _PyObject._fields_ = [
+ ('ob_refcnt', _Py_ssize_t),
+ ('ob_type', ctypes.POINTER(_PyObject))
+ ]
+
+ # python with trace
+ if hasattr(sys, 'getobjects'):
+ class _PyObject(ctypes.Structure):
+ pass
+ _PyObject._fields_ = [
+ ('_ob_next', ctypes.POINTER(_PyObject)),
+ ('_ob_prev', ctypes.POINTER(_PyObject)),
+ ('ob_refcnt', _Py_ssize_t),
+ ('ob_type', ctypes.POINTER(_PyObject))
+ ]
+
+ class _Traceback(_PyObject):
+ pass
+ _Traceback._fields_ = [
+ ('tb_next', ctypes.POINTER(_Traceback)),
+ ('tb_frame', ctypes.POINTER(_PyObject)),
+ ('tb_lasti', ctypes.c_int),
+ ('tb_lineno', ctypes.c_int)
+ ]
+
+ def tb_set_next(tb, next):
+ """Set the tb_next attribute of a traceback object."""
+ if not (isinstance(tb, TracebackType) and
+ (next is None or isinstance(next, TracebackType))):
+ raise TypeError('tb_set_next arguments must be traceback objects')
+ obj = _Traceback.from_address(id(tb))
+ if tb.tb_next is not None:
+ old = _Traceback.from_address(id(tb.tb_next))
+ old.ob_refcnt -= 1
+ if next is None:
+ obj.tb_next = ctypes.POINTER(_Traceback)()
+ else:
+ next = _Traceback.from_address(id(next))
+ next.ob_refcnt += 1
+ obj.tb_next = ctypes.pointer(next)
+
+ return tb_set_next
+
+
+# try to get a tb_set_next implementation if we don't have transparent
+# proxies.
+tb_set_next = None
+if tproxy is None:
+ try:
+ tb_set_next = _init_ugly_crap()
+ except:
+ pass
+ del _init_ugly_crap
diff --git a/pyload/lib/jinja2/defaults.py b/pyload/lib/jinja2/defaults.py
new file mode 100644
index 000000000..a27cb80cb
--- /dev/null
+++ b/pyload/lib/jinja2/defaults.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.defaults
+ ~~~~~~~~~~~~~~~
+
+ Jinja default filters and tags.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja2._compat import range_type
+from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
+
+
+# defaults for the parser / lexer
+BLOCK_START_STRING = '{%'
+BLOCK_END_STRING = '%}'
+VARIABLE_START_STRING = '{{'
+VARIABLE_END_STRING = '}}'
+COMMENT_START_STRING = '{#'
+COMMENT_END_STRING = '#}'
+LINE_STATEMENT_PREFIX = None
+LINE_COMMENT_PREFIX = None
+TRIM_BLOCKS = False
+LSTRIP_BLOCKS = False
+NEWLINE_SEQUENCE = '\n'
+KEEP_TRAILING_NEWLINE = False
+
+
+# default filters, tests and namespace
+from jinja2.filters import FILTERS as DEFAULT_FILTERS
+from jinja2.tests import TESTS as DEFAULT_TESTS
+DEFAULT_NAMESPACE = {
+ 'range': range_type,
+ 'dict': lambda **kw: kw,
+ 'lipsum': generate_lorem_ipsum,
+ 'cycler': Cycler,
+ 'joiner': Joiner
+}
+
+
+# export all constants
+__all__ = tuple(x for x in locals().keys() if x.isupper())
diff --git a/pyload/lib/jinja2/environment.py b/pyload/lib/jinja2/environment.py
new file mode 100644
index 000000000..45fabada2
--- /dev/null
+++ b/pyload/lib/jinja2/environment.py
@@ -0,0 +1,1191 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.environment
+ ~~~~~~~~~~~~~~~~~~
+
+ Provides a class that holds runtime and parsing time options.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+from jinja2 import nodes
+from jinja2.defaults import BLOCK_START_STRING, \
+ BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
+ COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
+ LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
+ DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE, \
+ KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
+from jinja2.lexer import get_lexer, TokenStream
+from jinja2.parser import Parser
+from jinja2.nodes import EvalContext
+from jinja2.optimizer import optimize
+from jinja2.compiler import generate
+from jinja2.runtime import Undefined, new_context
+from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
+ TemplatesNotFound, TemplateRuntimeError
+from jinja2.utils import import_string, LRUCache, Markup, missing, \
+ concat, consume, internalcode
+from jinja2._compat import imap, ifilter, string_types, iteritems, \
+ text_type, reraise, implements_iterator, implements_to_string, \
+ get_next, encode_filename, PY2, PYPY
+from functools import reduce
+
+
+# for direct template usage we have up to ten living environments
+_spontaneous_environments = LRUCache(10)
+
+# the function to create jinja traceback objects. This is dynamically
+# imported on the first exception in the exception handler.
+_make_traceback = None
+
+
+def get_spontaneous_environment(*args):
+ """Return a new spontaneous environment. A spontaneous environment is an
+ unnamed and unaccessible (in theory) environment that is used for
+ templates generated from a string and not from the file system.
+ """
+ try:
+ env = _spontaneous_environments.get(args)
+ except TypeError:
+ return Environment(*args)
+ if env is not None:
+ return env
+ _spontaneous_environments[args] = env = Environment(*args)
+ env.shared = True
+ return env
+
+
+def create_cache(size):
+ """Return the cache class for the given size."""
+ if size == 0:
+ return None
+ if size < 0:
+ return {}
+ return LRUCache(size)
+
+
+def copy_cache(cache):
+ """Create an empty copy of the given cache."""
+ if cache is None:
+ return None
+ elif type(cache) is dict:
+ return {}
+ return LRUCache(cache.capacity)
+
+
+def load_extensions(environment, extensions):
+ """Load the extensions from the list and bind it to the environment.
+ Returns a dict of instantiated environments.
+ """
+ result = {}
+ for extension in extensions:
+ if isinstance(extension, string_types):
+ extension = import_string(extension)
+ result[extension.identifier] = extension(environment)
+ return result
+
+
+def _environment_sanity_check(environment):
+ """Perform a sanity check on the environment."""
+ assert issubclass(environment.undefined, Undefined), 'undefined must ' \
+ 'be a subclass of undefined because filters depend on it.'
+ assert environment.block_start_string != \
+ environment.variable_start_string != \
+ environment.comment_start_string, 'block, variable and comment ' \
+ 'start strings must be different'
+ assert environment.newline_sequence in ('\r', '\r\n', '\n'), \
+ 'newline_sequence set to unknown line ending string.'
+ return environment
+
+
+class Environment(object):
+ r"""The core component of Jinja is the `Environment`. It contains
+ important shared variables like configuration, filters, tests,
+ globals and others. Instances of this class may be modified if
+ they are not shared and if no template was loaded so far.
+ Modifications on environments after the first template was loaded
+ will lead to surprising effects and undefined behavior.
+
+ Here the possible initialization parameters:
+
+ `block_start_string`
+ The string marking the begin of a block. Defaults to ``'{%'``.
+
+ `block_end_string`
+ The string marking the end of a block. Defaults to ``'%}'``.
+
+ `variable_start_string`
+ The string marking the begin of a print statement.
+ Defaults to ``'{{'``.
+
+ `variable_end_string`
+ The string marking the end of a print statement. Defaults to
+ ``'}}'``.
+
+ `comment_start_string`
+ The string marking the begin of a comment. Defaults to ``'{#'``.
+
+ `comment_end_string`
+ The string marking the end of a comment. Defaults to ``'#}'``.
+
+ `line_statement_prefix`
+ If given and a string, this will be used as prefix for line based
+ statements. See also :ref:`line-statements`.
+
+ `line_comment_prefix`
+ If given and a string, this will be used as prefix for line based
+ based comments. See also :ref:`line-statements`.
+
+ .. versionadded:: 2.2
+
+ `trim_blocks`
+ If this is set to ``True`` the first newline after a block is
+ removed (block, not variable tag!). Defaults to `False`.
+
+ `lstrip_blocks`
+ If this is set to ``True`` leading spaces and tabs are stripped
+ from the start of a line to a block. Defaults to `False`.
+
+ `newline_sequence`
+ The sequence that starts a newline. Must be one of ``'\r'``,
+ ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
+ useful default for Linux and OS X systems as well as web
+ applications.
+
+ `keep_trailing_newline`
+ Preserve the trailing newline when rendering templates.
+ The default is ``False``, which causes a single newline,
+ if present, to be stripped from the end of the template.
+
+ .. versionadded:: 2.7
+
+ `extensions`
+ List of Jinja extensions to use. This can either be import paths
+ as strings or extension classes. For more information have a
+ look at :ref:`the extensions documentation <jinja-extensions>`.
+
+ `optimized`
+ should the optimizer be enabled? Default is `True`.
+
+ `undefined`
+ :class:`Undefined` or a subclass of it that is used to represent
+ undefined values in the template.
+
+ `finalize`
+ A callable that can be used to process the result of a variable
+ expression before it is output. For example one can convert
+ `None` implicitly into an empty string here.
+
+ `autoescape`
+ If set to true the XML/HTML autoescaping feature is enabled by
+ default. For more details about auto escaping see
+ :class:`~jinja2.utils.Markup`. As of Jinja 2.4 this can also
+ be a callable that is passed the template name and has to
+ return `True` or `False` depending on autoescape should be
+ enabled by default.
+
+ .. versionchanged:: 2.4
+ `autoescape` can now be a function
+
+ `loader`
+ The template loader for this environment.
+
+ `cache_size`
+ The size of the cache. Per default this is ``50`` which means
+ that if more than 50 templates are loaded the loader will clean
+ out the least recently used template. If the cache size is set to
+ ``0`` templates are recompiled all the time, if the cache size is
+ ``-1`` the cache will not be cleaned.
+
+ `auto_reload`
+ Some loaders load templates from locations where the template
+ sources may change (ie: file system or database). If
+ `auto_reload` is set to `True` (default) every time a template is
+ requested the loader checks if the source changed and if yes, it
+ will reload the template. For higher performance it's possible to
+ disable that.
+
+ `bytecode_cache`
+ If set to a bytecode cache object, this object will provide a
+ cache for the internal Jinja bytecode so that templates don't
+ have to be parsed if they were not changed.
+
+ See :ref:`bytecode-cache` for more information.
+ """
+
+ #: if this environment is sandboxed. Modifying this variable won't make
+ #: the environment sandboxed though. For a real sandboxed environment
+ #: have a look at jinja2.sandbox. This flag alone controls the code
+ #: generation by the compiler.
+ sandboxed = False
+
+ #: True if the environment is just an overlay
+ overlayed = False
+
+ #: the environment this environment is linked to if it is an overlay
+ linked_to = None
+
+ #: shared environments have this set to `True`. A shared environment
+ #: must not be modified
+ shared = False
+
+ #: these are currently EXPERIMENTAL undocumented features.
+ exception_handler = None
+ exception_formatter = None
+
+ def __init__(self,
+ block_start_string=BLOCK_START_STRING,
+ block_end_string=BLOCK_END_STRING,
+ variable_start_string=VARIABLE_START_STRING,
+ variable_end_string=VARIABLE_END_STRING,
+ comment_start_string=COMMENT_START_STRING,
+ comment_end_string=COMMENT_END_STRING,
+ line_statement_prefix=LINE_STATEMENT_PREFIX,
+ line_comment_prefix=LINE_COMMENT_PREFIX,
+ trim_blocks=TRIM_BLOCKS,
+ lstrip_blocks=LSTRIP_BLOCKS,
+ newline_sequence=NEWLINE_SEQUENCE,
+ keep_trailing_newline=KEEP_TRAILING_NEWLINE,
+ extensions=(),
+ optimized=True,
+ undefined=Undefined,
+ finalize=None,
+ autoescape=False,
+ loader=None,
+ cache_size=50,
+ auto_reload=True,
+ bytecode_cache=None):
+ # !!Important notice!!
+ # The constructor accepts quite a few arguments that should be
+ # passed by keyword rather than position. However it's important to
+ # not change the order of arguments because it's used at least
+ # internally in those cases:
+ # - spontaneous environments (i18n extension and Template)
+ # - unittests
+ # If parameter changes are required only add parameters at the end
+ # and don't change the arguments (or the defaults!) of the arguments
+ # existing already.
+
+ # lexer / parser information
+ self.block_start_string = block_start_string
+ self.block_end_string = block_end_string
+ self.variable_start_string = variable_start_string
+ self.variable_end_string = variable_end_string
+ self.comment_start_string = comment_start_string
+ self.comment_end_string = comment_end_string
+ self.line_statement_prefix = line_statement_prefix
+ self.line_comment_prefix = line_comment_prefix
+ self.trim_blocks = trim_blocks
+ self.lstrip_blocks = lstrip_blocks
+ self.newline_sequence = newline_sequence
+ self.keep_trailing_newline = keep_trailing_newline
+
+ # runtime information
+ self.undefined = undefined
+ self.optimized = optimized
+ self.finalize = finalize
+ self.autoescape = autoescape
+
+ # defaults
+ self.filters = DEFAULT_FILTERS.copy()
+ self.tests = DEFAULT_TESTS.copy()
+ self.globals = DEFAULT_NAMESPACE.copy()
+
+ # set the loader provided
+ self.loader = loader
+ self.cache = create_cache(cache_size)
+ self.bytecode_cache = bytecode_cache
+ self.auto_reload = auto_reload
+
+ # load extensions
+ self.extensions = load_extensions(self, extensions)
+
+ _environment_sanity_check(self)
+
+ def add_extension(self, extension):
+ """Adds an extension after the environment was created.
+
+ .. versionadded:: 2.5
+ """
+ self.extensions.update(load_extensions(self, [extension]))
+
+ def extend(self, **attributes):
+ """Add the items to the instance of the environment if they do not exist
+ yet. This is used by :ref:`extensions <writing-extensions>` to register
+ callbacks and configuration values without breaking inheritance.
+ """
+ for key, value in iteritems(attributes):
+ if not hasattr(self, key):
+ setattr(self, key, value)
+
+ def overlay(self, block_start_string=missing, block_end_string=missing,
+ variable_start_string=missing, variable_end_string=missing,
+ comment_start_string=missing, comment_end_string=missing,
+ line_statement_prefix=missing, line_comment_prefix=missing,
+ trim_blocks=missing, lstrip_blocks=missing,
+ extensions=missing, optimized=missing,
+ undefined=missing, finalize=missing, autoescape=missing,
+ loader=missing, cache_size=missing, auto_reload=missing,
+ bytecode_cache=missing):
+ """Create a new overlay environment that shares all the data with the
+ current environment except of cache and the overridden attributes.
+ Extensions cannot be removed for an overlayed environment. An overlayed
+ environment automatically gets all the extensions of the environment it
+ is linked to plus optional extra extensions.
+
+ Creating overlays should happen after the initial environment was set
+ up completely. Not all attributes are truly linked, some are just
+ copied over so modifications on the original environment may not shine
+ through.
+ """
+ args = dict(locals())
+ del args['self'], args['cache_size'], args['extensions']
+
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.overlayed = True
+ rv.linked_to = self
+
+ for key, value in iteritems(args):
+ if value is not missing:
+ setattr(rv, key, value)
+
+ if cache_size is not missing:
+ rv.cache = create_cache(cache_size)
+ else:
+ rv.cache = copy_cache(self.cache)
+
+ rv.extensions = {}
+ for key, value in iteritems(self.extensions):
+ rv.extensions[key] = value.bind(rv)
+ if extensions is not missing:
+ rv.extensions.update(load_extensions(rv, extensions))
+
+ return _environment_sanity_check(rv)
+
+ lexer = property(get_lexer, doc="The lexer for this environment.")
+
+ def iter_extensions(self):
+ """Iterates over the extensions by priority."""
+ return iter(sorted(self.extensions.values(),
+ key=lambda x: x.priority))
+
+ def getitem(self, obj, argument):
+ """Get an item or attribute of an object but prefer the item."""
+ try:
+ return obj[argument]
+ except (TypeError, LookupError):
+ if isinstance(argument, string_types):
+ try:
+ attr = str(argument)
+ except Exception:
+ pass
+ else:
+ try:
+ return getattr(obj, attr)
+ except AttributeError:
+ pass
+ return self.undefined(obj=obj, name=argument)
+
+ def getattr(self, obj, attribute):
+ """Get an item or attribute of an object but prefer the attribute.
+ Unlike :meth:`getitem` the attribute *must* be a bytestring.
+ """
+ try:
+ return getattr(obj, attribute)
+ except AttributeError:
+ pass
+ try:
+ return obj[attribute]
+ except (TypeError, LookupError, AttributeError):
+ return self.undefined(obj=obj, name=attribute)
+
+ def call_filter(self, name, value, args=None, kwargs=None,
+ context=None, eval_ctx=None):
+ """Invokes a filter on a value the same way the compiler does it.
+
+ .. versionadded:: 2.7
+ """
+ func = self.filters.get(name)
+ if func is None:
+ raise TemplateRuntimeError('no filter named %r' % name)
+ args = [value] + list(args or ())
+ if getattr(func, 'contextfilter', False):
+ if context is None:
+ raise TemplateRuntimeError('Attempted to invoke context '
+ 'filter without context')
+ args.insert(0, context)
+ elif getattr(func, 'evalcontextfilter', False):
+ if eval_ctx is None:
+ if context is not None:
+ eval_ctx = context.eval_ctx
+ else:
+ eval_ctx = EvalContext(self)
+ args.insert(0, eval_ctx)
+ elif getattr(func, 'environmentfilter', False):
+ args.insert(0, self)
+ return func(*args, **(kwargs or {}))
+
+ def call_test(self, name, value, args=None, kwargs=None):
+ """Invokes a test on a value the same way the compiler does it.
+
+ .. versionadded:: 2.7
+ """
+ func = self.tests.get(name)
+ if func is None:
+ raise TemplateRuntimeError('no test named %r' % name)
+ return func(value, *(args or ()), **(kwargs or {}))
+
+ @internalcode
+ def parse(self, source, name=None, filename=None):
+ """Parse the sourcecode and return the abstract syntax tree. This
+ tree of nodes is used by the compiler to convert the template into
+ executable source- or bytecode. This is useful for debugging or to
+ extract information from templates.
+
+ If you are :ref:`developing Jinja2 extensions <writing-extensions>`
+ this gives you a good overview of the node tree generated.
+ """
+ try:
+ return self._parse(source, name, filename)
+ except TemplateSyntaxError:
+ exc_info = sys.exc_info()
+ self.handle_exception(exc_info, source_hint=source)
+
+ def _parse(self, source, name, filename):
+ """Internal parsing function used by `parse` and `compile`."""
+ return Parser(self, source, name, encode_filename(filename)).parse()
+
+ def lex(self, source, name=None, filename=None):
+ """Lex the given sourcecode and return a generator that yields
+ tokens as tuples in the form ``(lineno, token_type, value)``.
+ This can be useful for :ref:`extension development <writing-extensions>`
+ and debugging templates.
+
+ This does not perform preprocessing. If you want the preprocessing
+ of the extensions to be applied you have to filter source through
+ the :meth:`preprocess` method.
+ """
+ source = text_type(source)
+ try:
+ return self.lexer.tokeniter(source, name, filename)
+ except TemplateSyntaxError:
+ exc_info = sys.exc_info()
+ self.handle_exception(exc_info, source_hint=source)
+
+ def preprocess(self, source, name=None, filename=None):
+ """Preprocesses the source with all extensions. This is automatically
+ called for all parsing and compiling methods but *not* for :meth:`lex`
+ because there you usually only want the actual source tokenized.
+ """
+ return reduce(lambda s, e: e.preprocess(s, name, filename),
+ self.iter_extensions(), text_type(source))
+
+ def _tokenize(self, source, name, filename=None, state=None):
+ """Called by the parser to do the preprocessing and filtering
+ for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
+ """
+ source = self.preprocess(source, name, filename)
+ stream = self.lexer.tokenize(source, name, filename, state)
+ for ext in self.iter_extensions():
+ stream = ext.filter_stream(stream)
+ if not isinstance(stream, TokenStream):
+ stream = TokenStream(stream, name, filename)
+ return stream
+
+ def _generate(self, source, name, filename, defer_init=False):
+ """Internal hook that can be overridden to hook a different generate
+ method in.
+
+ .. versionadded:: 2.5
+ """
+ return generate(source, self, name, filename, defer_init=defer_init)
+
+ def _compile(self, source, filename):
+ """Internal hook that can be overridden to hook a different compile
+ method in.
+
+ .. versionadded:: 2.5
+ """
+ return compile(source, filename, 'exec')
+
+ @internalcode
+ def compile(self, source, name=None, filename=None, raw=False,
+ defer_init=False):
+ """Compile a node or template source code. The `name` parameter is
+ the load name of the template after it was joined using
+ :meth:`join_path` if necessary, not the filename on the file system.
+ the `filename` parameter is the estimated filename of the template on
+ the file system. If the template came from a database or memory this
+ can be omitted.
+
+ The return value of this method is a python code object. If the `raw`
+ parameter is `True` the return value will be a string with python
+ code equivalent to the bytecode returned otherwise. This method is
+ mainly used internally.
+
+ `defer_init` is use internally to aid the module code generator. This
+ causes the generated code to be able to import without the global
+ environment variable to be set.
+
+ .. versionadded:: 2.4
+ `defer_init` parameter added.
+ """
+ source_hint = None
+ try:
+ if isinstance(source, string_types):
+ source_hint = source
+ source = self._parse(source, name, filename)
+ if self.optimized:
+ source = optimize(source, self)
+ source = self._generate(source, name, filename,
+ defer_init=defer_init)
+ if raw:
+ return source
+ if filename is None:
+ filename = '<template>'
+ else:
+ filename = encode_filename(filename)
+ return self._compile(source, filename)
+ except TemplateSyntaxError:
+ exc_info = sys.exc_info()
+ self.handle_exception(exc_info, source_hint=source)
+
+ def compile_expression(self, source, undefined_to_none=True):
+ """A handy helper method that returns a callable that accepts keyword
+ arguments that appear as variables in the expression. If called it
+ returns the result of the expression.
+
+ This is useful if applications want to use the same rules as Jinja
+ in template "configuration files" or similar situations.
+
+ Example usage:
+
+ >>> env = Environment()
+ >>> expr = env.compile_expression('foo == 42')
+ >>> expr(foo=23)
+ False
+ >>> expr(foo=42)
+ True
+
+ Per default the return value is converted to `None` if the
+ expression returns an undefined value. This can be changed
+ by setting `undefined_to_none` to `False`.
+
+ >>> env.compile_expression('var')() is None
+ True
+ >>> env.compile_expression('var', undefined_to_none=False)()
+ Undefined
+
+ .. versionadded:: 2.1
+ """
+ parser = Parser(self, source, state='variable')
+ exc_info = None
+ try:
+ expr = parser.parse_expression()
+ if not parser.stream.eos:
+ raise TemplateSyntaxError('chunk after expression',
+ parser.stream.current.lineno,
+ None, None)
+ expr.set_environment(self)
+ except TemplateSyntaxError:
+ exc_info = sys.exc_info()
+ if exc_info is not None:
+ self.handle_exception(exc_info, source_hint=source)
+ body = [nodes.Assign(nodes.Name('result', 'store'), expr, lineno=1)]
+ template = self.from_string(nodes.Template(body, lineno=1))
+ return TemplateExpression(template, undefined_to_none)
+
+ def compile_templates(self, target, extensions=None, filter_func=None,
+ zip='deflated', log_function=None,
+ ignore_errors=True, py_compile=False):
+ """Finds all the templates the loader can find, compiles them
+ and stores them in `target`. If `zip` is `None`, instead of in a
+ zipfile, the templates will be will be stored in a directory.
+ By default a deflate zip algorithm is used, to switch to
+ the stored algorithm, `zip` can be set to ``'stored'``.
+
+ `extensions` and `filter_func` are passed to :meth:`list_templates`.
+ Each template returned will be compiled to the target folder or
+ zipfile.
+
+ By default template compilation errors are ignored. In case a
+ log function is provided, errors are logged. If you want template
+ syntax errors to abort the compilation you can set `ignore_errors`
+ to `False` and you will get an exception on syntax errors.
+
+ If `py_compile` is set to `True` .pyc files will be written to the
+ target instead of standard .py files. This flag does not do anything
+ on pypy and Python 3 where pyc files are not picked up by itself and
+ don't give much benefit.
+
+ .. versionadded:: 2.4
+ """
+ from jinja2.loaders import ModuleLoader
+
+ if log_function is None:
+ log_function = lambda x: None
+
+ if py_compile:
+ if not PY2 or PYPY:
+ from warnings import warn
+ warn(Warning('py_compile has no effect on pypy or Python 3'))
+ py_compile = False
+ else:
+ import imp, marshal
+ py_header = imp.get_magic() + \
+ u'\xff\xff\xff\xff'.encode('iso-8859-15')
+
+ # Python 3.3 added a source filesize to the header
+ if sys.version_info >= (3, 3):
+ py_header += u'\x00\x00\x00\x00'.encode('iso-8859-15')
+
+ def write_file(filename, data, mode):
+ if zip:
+ info = ZipInfo(filename)
+ info.external_attr = 0o755 << 16
+ zip_file.writestr(info, data)
+ else:
+ f = open(os.path.join(target, filename), mode)
+ try:
+ f.write(data)
+ finally:
+ f.close()
+
+ if zip is not None:
+ from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
+ zip_file = ZipFile(target, 'w', dict(deflated=ZIP_DEFLATED,
+ stored=ZIP_STORED)[zip])
+ log_function('Compiling into Zip archive "%s"' % target)
+ else:
+ if not os.path.isdir(target):
+ os.makedirs(target)
+ log_function('Compiling into folder "%s"' % target)
+
+ try:
+ for name in self.list_templates(extensions, filter_func):
+ source, filename, _ = self.loader.get_source(self, name)
+ try:
+ code = self.compile(source, name, filename, True, True)
+ except TemplateSyntaxError as e:
+ if not ignore_errors:
+ raise
+ log_function('Could not compile "%s": %s' % (name, e))
+ continue
+
+ filename = ModuleLoader.get_module_filename(name)
+
+ if py_compile:
+ c = self._compile(code, encode_filename(filename))
+ write_file(filename + 'c', py_header +
+ marshal.dumps(c), 'wb')
+ log_function('Byte-compiled "%s" as %s' %
+ (name, filename + 'c'))
+ else:
+ write_file(filename, code, 'w')
+ log_function('Compiled "%s" as %s' % (name, filename))
+ finally:
+ if zip:
+ zip_file.close()
+
+ log_function('Finished compiling templates')
+
+ def list_templates(self, extensions=None, filter_func=None):
+ """Returns a list of templates for this environment. This requires
+ that the loader supports the loader's
+ :meth:`~BaseLoader.list_templates` method.
+
+ If there are other files in the template folder besides the
+ actual templates, the returned list can be filtered. There are two
+ ways: either `extensions` is set to a list of file extensions for
+ templates, or a `filter_func` can be provided which is a callable that
+ is passed a template name and should return `True` if it should end up
+ in the result list.
+
+ If the loader does not support that, a :exc:`TypeError` is raised.
+
+ .. versionadded:: 2.4
+ """
+ x = self.loader.list_templates()
+ if extensions is not None:
+ if filter_func is not None:
+ raise TypeError('either extensions or filter_func '
+ 'can be passed, but not both')
+ filter_func = lambda x: '.' in x and \
+ x.rsplit('.', 1)[1] in extensions
+ if filter_func is not None:
+ x = ifilter(filter_func, x)
+ return x
+
+ def handle_exception(self, exc_info=None, rendered=False, source_hint=None):
+ """Exception handling helper. This is used internally to either raise
+ rewritten exceptions or return a rendered traceback for the template.
+ """
+ global _make_traceback
+ if exc_info is None:
+ exc_info = sys.exc_info()
+
+ # the debugging module is imported when it's used for the first time.
+ # we're doing a lot of stuff there and for applications that do not
+ # get any exceptions in template rendering there is no need to load
+ # all of that.
+ if _make_traceback is None:
+ from jinja2.debug import make_traceback as _make_traceback
+ traceback = _make_traceback(exc_info, source_hint)
+ if rendered and self.exception_formatter is not None:
+ return self.exception_formatter(traceback)
+ if self.exception_handler is not None:
+ self.exception_handler(traceback)
+ exc_type, exc_value, tb = traceback.standard_exc_info
+ reraise(exc_type, exc_value, tb)
+
+ def join_path(self, template, parent):
+ """Join a template with the parent. By default all the lookups are
+ relative to the loader root so this method returns the `template`
+ parameter unchanged, but if the paths should be relative to the
+ parent template, this function can be used to calculate the real
+ template name.
+
+ Subclasses may override this method and implement template path
+ joining here.
+ """
+ return template
+
+ @internalcode
+ def _load_template(self, name, globals):
+ if self.loader is None:
+ raise TypeError('no loader for this environment specified')
+ if self.cache is not None:
+ template = self.cache.get(name)
+ if template is not None and (not self.auto_reload or \
+ template.is_up_to_date):
+ return template
+ template = self.loader.load(self, name, globals)
+ if self.cache is not None:
+ self.cache[name] = template
+ return template
+
+ @internalcode
+ def get_template(self, name, parent=None, globals=None):
+ """Load a template from the loader. If a loader is configured this
+ method ask the loader for the template and returns a :class:`Template`.
+ If the `parent` parameter is not `None`, :meth:`join_path` is called
+ to get the real template name before loading.
+
+ The `globals` parameter can be used to provide template wide globals.
+ These variables are available in the context at render time.
+
+ If the template does not exist a :exc:`TemplateNotFound` exception is
+ raised.
+
+ .. versionchanged:: 2.4
+ If `name` is a :class:`Template` object it is returned from the
+ function unchanged.
+ """
+ if isinstance(name, Template):
+ return name
+ if parent is not None:
+ name = self.join_path(name, parent)
+ return self._load_template(name, self.make_globals(globals))
+
+ @internalcode
+ def select_template(self, names, parent=None, globals=None):
+ """Works like :meth:`get_template` but tries a number of templates
+ before it fails. If it cannot find any of the templates, it will
+ raise a :exc:`TemplatesNotFound` exception.
+
+ .. versionadded:: 2.3
+
+ .. versionchanged:: 2.4
+ If `names` contains a :class:`Template` object it is returned
+ from the function unchanged.
+ """
+ if not names:
+ raise TemplatesNotFound(message=u'Tried to select from an empty list '
+ u'of templates.')
+ globals = self.make_globals(globals)
+ for name in names:
+ if isinstance(name, Template):
+ return name
+ if parent is not None:
+ name = self.join_path(name, parent)
+ try:
+ return self._load_template(name, globals)
+ except TemplateNotFound:
+ pass
+ raise TemplatesNotFound(names)
+
+ @internalcode
+ def get_or_select_template(self, template_name_or_list,
+ parent=None, globals=None):
+ """Does a typecheck and dispatches to :meth:`select_template`
+ if an iterable of template names is given, otherwise to
+ :meth:`get_template`.
+
+ .. versionadded:: 2.3
+ """
+ if isinstance(template_name_or_list, string_types):
+ return self.get_template(template_name_or_list, parent, globals)
+ elif isinstance(template_name_or_list, Template):
+ return template_name_or_list
+ return self.select_template(template_name_or_list, parent, globals)
+
+ def from_string(self, source, globals=None, template_class=None):
+ """Load a template from a string. This parses the source given and
+ returns a :class:`Template` object.
+ """
+ globals = self.make_globals(globals)
+ cls = template_class or self.template_class
+ return cls.from_code(self, self.compile(source), globals, None)
+
+ def make_globals(self, d):
+ """Return a dict for the globals."""
+ if not d:
+ return self.globals
+ return dict(self.globals, **d)
+
+
+class Template(object):
+ """The central template object. This class represents a compiled template
+ and is used to evaluate it.
+
+ Normally the template object is generated from an :class:`Environment` but
+ it also has a constructor that makes it possible to create a template
+ instance directly using the constructor. It takes the same arguments as
+ the environment constructor but it's not possible to specify a loader.
+
+ Every template object has a few methods and members that are guaranteed
+ to exist. However it's important that a template object should be
+ considered immutable. Modifications on the object are not supported.
+
+ Template objects created from the constructor rather than an environment
+ do have an `environment` attribute that points to a temporary environment
+ that is probably shared with other templates created with the constructor
+ and compatible settings.
+
+ >>> template = Template('Hello {{ name }}!')
+ >>> template.render(name='John Doe')
+ u'Hello John Doe!'
+
+ >>> stream = template.stream(name='John Doe')
+ >>> stream.next()
+ u'Hello John Doe!'
+ >>> stream.next()
+ Traceback (most recent call last):
+ ...
+ StopIteration
+ """
+
+ def __new__(cls, source,
+ block_start_string=BLOCK_START_STRING,
+ block_end_string=BLOCK_END_STRING,
+ variable_start_string=VARIABLE_START_STRING,
+ variable_end_string=VARIABLE_END_STRING,
+ comment_start_string=COMMENT_START_STRING,
+ comment_end_string=COMMENT_END_STRING,
+ line_statement_prefix=LINE_STATEMENT_PREFIX,
+ line_comment_prefix=LINE_COMMENT_PREFIX,
+ trim_blocks=TRIM_BLOCKS,
+ lstrip_blocks=LSTRIP_BLOCKS,
+ newline_sequence=NEWLINE_SEQUENCE,
+ keep_trailing_newline=KEEP_TRAILING_NEWLINE,
+ extensions=(),
+ optimized=True,
+ undefined=Undefined,
+ finalize=None,
+ autoescape=False):
+ env = get_spontaneous_environment(
+ block_start_string, block_end_string, variable_start_string,
+ variable_end_string, comment_start_string, comment_end_string,
+ line_statement_prefix, line_comment_prefix, trim_blocks,
+ lstrip_blocks, newline_sequence, keep_trailing_newline,
+ frozenset(extensions), optimized, undefined, finalize, autoescape,
+ None, 0, False, None)
+ return env.from_string(source, template_class=cls)
+
+ @classmethod
+ def from_code(cls, environment, code, globals, uptodate=None):
+ """Creates a template object from compiled code and the globals. This
+ is used by the loaders and environment to create a template object.
+ """
+ namespace = {
+ 'environment': environment,
+ '__file__': code.co_filename
+ }
+ exec(code, namespace)
+ rv = cls._from_namespace(environment, namespace, globals)
+ rv._uptodate = uptodate
+ return rv
+
+ @classmethod
+ def from_module_dict(cls, environment, module_dict, globals):
+ """Creates a template object from a module. This is used by the
+ module loader to create a template object.
+
+ .. versionadded:: 2.4
+ """
+ return cls._from_namespace(environment, module_dict, globals)
+
+ @classmethod
+ def _from_namespace(cls, environment, namespace, globals):
+ t = object.__new__(cls)
+ t.environment = environment
+ t.globals = globals
+ t.name = namespace['name']
+ t.filename = namespace['__file__']
+ t.blocks = namespace['blocks']
+
+ # render function and module
+ t.root_render_func = namespace['root']
+ t._module = None
+
+ # debug and loader helpers
+ t._debug_info = namespace['debug_info']
+ t._uptodate = None
+
+ # store the reference
+ namespace['environment'] = environment
+ namespace['__jinja_template__'] = t
+
+ return t
+
+ def render(self, *args, **kwargs):
+ """This method accepts the same arguments as the `dict` constructor:
+ A dict, a dict subclass or some keyword arguments. If no arguments
+ are given the context will be empty. These two calls do the same::
+
+ template.render(knights='that say nih')
+ template.render({'knights': 'that say nih'})
+
+ This will return the rendered template as unicode string.
+ """
+ vars = dict(*args, **kwargs)
+ try:
+ return concat(self.root_render_func(self.new_context(vars)))
+ except Exception:
+ exc_info = sys.exc_info()
+ return self.environment.handle_exception(exc_info, True)
+
+ def stream(self, *args, **kwargs):
+ """Works exactly like :meth:`generate` but returns a
+ :class:`TemplateStream`.
+ """
+ return TemplateStream(self.generate(*args, **kwargs))
+
+ def generate(self, *args, **kwargs):
+ """For very large templates it can be useful to not render the whole
+ template at once but evaluate each statement after another and yield
+ piece for piece. This method basically does exactly that and returns
+ a generator that yields one item after another as unicode strings.
+
+ It accepts the same arguments as :meth:`render`.
+ """
+ vars = dict(*args, **kwargs)
+ try:
+ for event in self.root_render_func(self.new_context(vars)):
+ yield event
+ except Exception:
+ exc_info = sys.exc_info()
+ else:
+ return
+ yield self.environment.handle_exception(exc_info, True)
+
+ def new_context(self, vars=None, shared=False, locals=None):
+ """Create a new :class:`Context` for this template. The vars
+ provided will be passed to the template. Per default the globals
+ are added to the context. If shared is set to `True` the data
+ is passed as it to the context without adding the globals.
+
+ `locals` can be a dict of local variables for internal usage.
+ """
+ return new_context(self.environment, self.name, self.blocks,
+ vars, shared, self.globals, locals)
+
+ def make_module(self, vars=None, shared=False, locals=None):
+ """This method works like the :attr:`module` attribute when called
+ without arguments but it will evaluate the template on every call
+ rather than caching it. It's also possible to provide
+ a dict which is then used as context. The arguments are the same
+ as for the :meth:`new_context` method.
+ """
+ return TemplateModule(self, self.new_context(vars, shared, locals))
+
+ @property
+ def module(self):
+ """The template as module. This is used for imports in the
+ template runtime but is also useful if one wants to access
+ exported template variables from the Python layer:
+
+ >>> t = Template('{% macro foo() %}42{% endmacro %}23')
+ >>> unicode(t.module)
+ u'23'
+ >>> t.module.foo()
+ u'42'
+ """
+ if self._module is not None:
+ return self._module
+ self._module = rv = self.make_module()
+ return rv
+
+ def get_corresponding_lineno(self, lineno):
+ """Return the source line number of a line number in the
+ generated bytecode as they are not in sync.
+ """
+ for template_line, code_line in reversed(self.debug_info):
+ if code_line <= lineno:
+ return template_line
+ return 1
+
+ @property
+ def is_up_to_date(self):
+ """If this variable is `False` there is a newer version available."""
+ if self._uptodate is None:
+ return True
+ return self._uptodate()
+
+ @property
+ def debug_info(self):
+ """The debug info mapping."""
+ return [tuple(imap(int, x.split('='))) for x in
+ self._debug_info.split('&')]
+
+ def __repr__(self):
+ if self.name is None:
+ name = 'memory:%x' % id(self)
+ else:
+ name = repr(self.name)
+ return '<%s %s>' % (self.__class__.__name__, name)
+
+
+@implements_to_string
+class TemplateModule(object):
+ """Represents an imported template. All the exported names of the
+ template are available as attributes on this object. Additionally
+ converting it into an unicode- or bytestrings renders the contents.
+ """
+
+ def __init__(self, template, context):
+ self._body_stream = list(template.root_render_func(context))
+ self.__dict__.update(context.get_exported())
+ self.__name__ = template.name
+
+ def __html__(self):
+ return Markup(concat(self._body_stream))
+
+ def __str__(self):
+ return concat(self._body_stream)
+
+ def __repr__(self):
+ if self.__name__ is None:
+ name = 'memory:%x' % id(self)
+ else:
+ name = repr(self.__name__)
+ return '<%s %s>' % (self.__class__.__name__, name)
+
+
+class TemplateExpression(object):
+ """The :meth:`jinja2.Environment.compile_expression` method returns an
+ instance of this object. It encapsulates the expression-like access
+ to the template with an expression it wraps.
+ """
+
+ def __init__(self, template, undefined_to_none):
+ self._template = template
+ self._undefined_to_none = undefined_to_none
+
+ def __call__(self, *args, **kwargs):
+ context = self._template.new_context(dict(*args, **kwargs))
+ consume(self._template.root_render_func(context))
+ rv = context.vars['result']
+ if self._undefined_to_none and isinstance(rv, Undefined):
+ rv = None
+ return rv
+
+
+@implements_iterator
+class TemplateStream(object):
+ """A template stream works pretty much like an ordinary python generator
+ but it can buffer multiple items to reduce the number of total iterations.
+ Per default the output is unbuffered which means that for every unbuffered
+ instruction in the template one unicode string is yielded.
+
+ If buffering is enabled with a buffer size of 5, five items are combined
+ into a new unicode string. This is mainly useful if you are streaming
+ big templates to a client via WSGI which flushes after each iteration.
+ """
+
+ def __init__(self, gen):
+ self._gen = gen
+ self.disable_buffering()
+
+ def dump(self, fp, encoding=None, errors='strict'):
+ """Dump the complete stream into a file or file-like object.
+ Per default unicode strings are written, if you want to encode
+ before writing specify an `encoding`.
+
+ Example usage::
+
+ Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
+ """
+ close = False
+ if isinstance(fp, string_types):
+ fp = open(fp, encoding is None and 'w' or 'wb')
+ close = True
+ try:
+ if encoding is not None:
+ iterable = (x.encode(encoding, errors) for x in self)
+ else:
+ iterable = self
+ if hasattr(fp, 'writelines'):
+ fp.writelines(iterable)
+ else:
+ for item in iterable:
+ fp.write(item)
+ finally:
+ if close:
+ fp.close()
+
+ def disable_buffering(self):
+ """Disable the output buffering."""
+ self._next = get_next(self._gen)
+ self.buffered = False
+
+ def enable_buffering(self, size=5):
+ """Enable buffering. Buffer `size` items before yielding them."""
+ if size <= 1:
+ raise ValueError('buffer size too small')
+
+ def generator(next):
+ buf = []
+ c_size = 0
+ push = buf.append
+
+ while 1:
+ try:
+ while c_size < size:
+ c = next()
+ push(c)
+ if c:
+ c_size += 1
+ except StopIteration:
+ if not c_size:
+ return
+ yield concat(buf)
+ del buf[:]
+ c_size = 0
+
+ self.buffered = True
+ self._next = get_next(generator(get_next(self._gen)))
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return self._next()
+
+
+# hook in default template class. if anyone reads this comment: ignore that
+# it's possible to use custom templates ;-)
+Environment.template_class = Template
diff --git a/pyload/lib/jinja2/exceptions.py b/pyload/lib/jinja2/exceptions.py
new file mode 100644
index 000000000..c9df6dc7c
--- /dev/null
+++ b/pyload/lib/jinja2/exceptions.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.exceptions
+ ~~~~~~~~~~~~~~~~~
+
+ Jinja exceptions.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja2._compat import imap, text_type, PY2, implements_to_string
+
+
+class TemplateError(Exception):
+ """Baseclass for all template errors."""
+
+ if PY2:
+ def __init__(self, message=None):
+ if message is not None:
+ message = text_type(message).encode('utf-8')
+ Exception.__init__(self, message)
+
+ @property
+ def message(self):
+ if self.args:
+ message = self.args[0]
+ if message is not None:
+ return message.decode('utf-8', 'replace')
+
+ def __unicode__(self):
+ return self.message or u''
+ else:
+ def __init__(self, message=None):
+ Exception.__init__(self, message)
+
+ @property
+ def message(self):
+ if self.args:
+ message = self.args[0]
+ if message is not None:
+ return message
+
+
+@implements_to_string
+class TemplateNotFound(IOError, LookupError, TemplateError):
+ """Raised if a template does not exist."""
+
+ # looks weird, but removes the warning descriptor that just
+ # bogusly warns us about message being deprecated
+ message = None
+
+ def __init__(self, name, message=None):
+ IOError.__init__(self)
+ if message is None:
+ message = name
+ self.message = message
+ self.name = name
+ self.templates = [name]
+
+ def __str__(self):
+ return self.message
+
+
+class TemplatesNotFound(TemplateNotFound):
+ """Like :class:`TemplateNotFound` but raised if multiple templates
+ are selected. This is a subclass of :class:`TemplateNotFound`
+ exception, so just catching the base exception will catch both.
+
+ .. versionadded:: 2.2
+ """
+
+ def __init__(self, names=(), message=None):
+ if message is None:
+ message = u'none of the templates given were found: ' + \
+ u', '.join(imap(text_type, names))
+ TemplateNotFound.__init__(self, names and names[-1] or None, message)
+ self.templates = list(names)
+
+
+@implements_to_string
+class TemplateSyntaxError(TemplateError):
+ """Raised to tell the user that there is a problem with the template."""
+
+ def __init__(self, message, lineno, name=None, filename=None):
+ TemplateError.__init__(self, message)
+ self.lineno = lineno
+ self.name = name
+ self.filename = filename
+ self.source = None
+
+ # this is set to True if the debug.translate_syntax_error
+ # function translated the syntax error into a new traceback
+ self.translated = False
+
+ def __str__(self):
+ # for translated errors we only return the message
+ if self.translated:
+ return self.message
+
+ # otherwise attach some stuff
+ location = 'line %d' % self.lineno
+ name = self.filename or self.name
+ if name:
+ location = 'File "%s", %s' % (name, location)
+ lines = [self.message, ' ' + location]
+
+ # if the source is set, add the line to the output
+ if self.source is not None:
+ try:
+ line = self.source.splitlines()[self.lineno - 1]
+ except IndexError:
+ line = None
+ if line:
+ lines.append(' ' + line.strip())
+
+ return u'\n'.join(lines)
+
+
+class TemplateAssertionError(TemplateSyntaxError):
+ """Like a template syntax error, but covers cases where something in the
+ template caused an error at compile time that wasn't necessarily caused
+ by a syntax error. However it's a direct subclass of
+ :exc:`TemplateSyntaxError` and has the same attributes.
+ """
+
+
+class TemplateRuntimeError(TemplateError):
+ """A generic runtime error in the template engine. Under some situations
+ Jinja may raise this exception.
+ """
+
+
+class UndefinedError(TemplateRuntimeError):
+ """Raised if a template tries to operate on :class:`Undefined`."""
+
+
+class SecurityError(TemplateRuntimeError):
+ """Raised if a template tries to do something insecure if the
+ sandbox is enabled.
+ """
+
+
+class FilterArgumentError(TemplateRuntimeError):
+ """This error is raised if a filter was called with inappropriate
+ arguments
+ """
diff --git a/pyload/lib/jinja2/ext.py b/pyload/lib/jinja2/ext.py
new file mode 100644
index 000000000..c2df12d55
--- /dev/null
+++ b/pyload/lib/jinja2/ext.py
@@ -0,0 +1,636 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.ext
+ ~~~~~~~~~~
+
+ Jinja extensions allow to add custom tags similar to the way django custom
+ tags work. By default two example extensions exist: an i18n and a cache
+ extension.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
+"""
+from jinja2 import nodes
+from jinja2.defaults import BLOCK_START_STRING, \
+ BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
+ COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
+ LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
+ KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
+from jinja2.environment import Environment
+from jinja2.runtime import concat
+from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
+from jinja2.utils import contextfunction, import_string, Markup
+from jinja2._compat import next, with_metaclass, string_types, iteritems
+
+
+# the only real useful gettext functions for a Jinja template. Note
+# that ugettext must be assigned to gettext as Jinja doesn't support
+# non unicode strings.
+GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
+
+
+class ExtensionRegistry(type):
+ """Gives the extension an unique identifier."""
+
+ def __new__(cls, name, bases, d):
+ rv = type.__new__(cls, name, bases, d)
+ rv.identifier = rv.__module__ + '.' + rv.__name__
+ return rv
+
+
+class Extension(with_metaclass(ExtensionRegistry, object)):
+ """Extensions can be used to add extra functionality to the Jinja template
+ system at the parser level. Custom extensions are bound to an environment
+ but may not store environment specific data on `self`. The reason for
+ this is that an extension can be bound to another environment (for
+ overlays) by creating a copy and reassigning the `environment` attribute.
+
+ As extensions are created by the environment they cannot accept any
+ arguments for configuration. One may want to work around that by using
+ a factory function, but that is not possible as extensions are identified
+ by their import name. The correct way to configure the extension is
+ storing the configuration values on the environment. Because this way the
+ environment ends up acting as central configuration storage the
+ attributes may clash which is why extensions have to ensure that the names
+ they choose for configuration are not too generic. ``prefix`` for example
+ is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
+ name as includes the name of the extension (fragment cache).
+ """
+
+ #: if this extension parses this is the list of tags it's listening to.
+ tags = set()
+
+ #: the priority of that extension. This is especially useful for
+ #: extensions that preprocess values. A lower value means higher
+ #: priority.
+ #:
+ #: .. versionadded:: 2.4
+ priority = 100
+
+ def __init__(self, environment):
+ self.environment = environment
+
+ def bind(self, environment):
+ """Create a copy of this extension bound to another environment."""
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.environment = environment
+ return rv
+
+ def preprocess(self, source, name, filename=None):
+ """This method is called before the actual lexing and can be used to
+ preprocess the source. The `filename` is optional. The return value
+ must be the preprocessed source.
+ """
+ return source
+
+ def filter_stream(self, stream):
+ """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
+ to filter tokens returned. This method has to return an iterable of
+ :class:`~jinja2.lexer.Token`\s, but it doesn't have to return a
+ :class:`~jinja2.lexer.TokenStream`.
+
+ In the `ext` folder of the Jinja2 source distribution there is a file
+ called `inlinegettext.py` which implements a filter that utilizes this
+ method.
+ """
+ return stream
+
+ def parse(self, parser):
+ """If any of the :attr:`tags` matched this method is called with the
+ parser as first argument. The token the parser stream is pointing at
+ is the name token that matched. This method has to return one or a
+ list of multiple nodes.
+ """
+ raise NotImplementedError()
+
+ def attr(self, name, lineno=None):
+ """Return an attribute node for the current extension. This is useful
+ to pass constants on extensions to generated template code.
+
+ ::
+
+ self.attr('_my_attribute', lineno=lineno)
+ """
+ return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
+
+ def call_method(self, name, args=None, kwargs=None, dyn_args=None,
+ dyn_kwargs=None, lineno=None):
+ """Call a method of the extension. This is a shortcut for
+ :meth:`attr` + :class:`jinja2.nodes.Call`.
+ """
+ if args is None:
+ args = []
+ if kwargs is None:
+ kwargs = []
+ return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
+ dyn_args, dyn_kwargs, lineno=lineno)
+
+
+@contextfunction
+def _gettext_alias(__context, *args, **kwargs):
+ return __context.call(__context.resolve('gettext'), *args, **kwargs)
+
+
+def _make_new_gettext(func):
+ @contextfunction
+ def gettext(__context, __string, **variables):
+ rv = __context.call(func, __string)
+ if __context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv % variables
+ return gettext
+
+
+def _make_new_ngettext(func):
+ @contextfunction
+ def ngettext(__context, __singular, __plural, __num, **variables):
+ variables.setdefault('num', __num)
+ rv = __context.call(func, __singular, __plural, __num)
+ if __context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv % variables
+ return ngettext
+
+
+class InternationalizationExtension(Extension):
+ """This extension adds gettext support to Jinja2."""
+ tags = set(['trans'])
+
+ # TODO: the i18n extension is currently reevaluating values in a few
+ # situations. Take this example:
+ # {% trans count=something() %}{{ count }} foo{% pluralize
+ # %}{{ count }} fooss{% endtrans %}
+ # something is called twice here. One time for the gettext value and
+ # the other time for the n-parameter of the ngettext function.
+
+ def __init__(self, environment):
+ Extension.__init__(self, environment)
+ environment.globals['_'] = _gettext_alias
+ environment.extend(
+ install_gettext_translations=self._install,
+ install_null_translations=self._install_null,
+ install_gettext_callables=self._install_callables,
+ uninstall_gettext_translations=self._uninstall,
+ extract_translations=self._extract,
+ newstyle_gettext=False
+ )
+
+ def _install(self, translations, newstyle=None):
+ gettext = getattr(translations, 'ugettext', None)
+ if gettext is None:
+ gettext = translations.gettext
+ ngettext = getattr(translations, 'ungettext', None)
+ if ngettext is None:
+ ngettext = translations.ngettext
+ self._install_callables(gettext, ngettext, newstyle)
+
+ def _install_null(self, newstyle=None):
+ self._install_callables(
+ lambda x: x,
+ lambda s, p, n: (n != 1 and (p,) or (s,))[0],
+ newstyle
+ )
+
+ def _install_callables(self, gettext, ngettext, newstyle=None):
+ if newstyle is not None:
+ self.environment.newstyle_gettext = newstyle
+ if self.environment.newstyle_gettext:
+ gettext = _make_new_gettext(gettext)
+ ngettext = _make_new_ngettext(ngettext)
+ self.environment.globals.update(
+ gettext=gettext,
+ ngettext=ngettext
+ )
+
+ def _uninstall(self, translations):
+ for key in 'gettext', 'ngettext':
+ self.environment.globals.pop(key, None)
+
+ def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
+ if isinstance(source, string_types):
+ source = self.environment.parse(source)
+ return extract_from_ast(source, gettext_functions)
+
+ def parse(self, parser):
+ """Parse a translatable tag."""
+ lineno = next(parser.stream).lineno
+ num_called_num = False
+
+ # find all the variables referenced. Additionally a variable can be
+ # defined in the body of the trans block too, but this is checked at
+ # a later state.
+ plural_expr = None
+ plural_expr_assignment = None
+ variables = {}
+ while parser.stream.current.type != 'block_end':
+ if variables:
+ parser.stream.expect('comma')
+
+ # skip colon for python compatibility
+ if parser.stream.skip_if('colon'):
+ break
+
+ name = parser.stream.expect('name')
+ if name.value in variables:
+ parser.fail('translatable variable %r defined twice.' %
+ name.value, name.lineno,
+ exc=TemplateAssertionError)
+
+ # expressions
+ if parser.stream.current.type == 'assign':
+ next(parser.stream)
+ variables[name.value] = var = parser.parse_expression()
+ else:
+ variables[name.value] = var = nodes.Name(name.value, 'load')
+
+ if plural_expr is None:
+ if isinstance(var, nodes.Call):
+ plural_expr = nodes.Name('_trans', 'load')
+ variables[name.value] = plural_expr
+ plural_expr_assignment = nodes.Assign(
+ nodes.Name('_trans', 'store'), var)
+ else:
+ plural_expr = var
+ num_called_num = name.value == 'num'
+
+ parser.stream.expect('block_end')
+
+ plural = plural_names = None
+ have_plural = False
+ referenced = set()
+
+ # now parse until endtrans or pluralize
+ singular_names, singular = self._parse_block(parser, True)
+ if singular_names:
+ referenced.update(singular_names)
+ if plural_expr is None:
+ plural_expr = nodes.Name(singular_names[0], 'load')
+ num_called_num = singular_names[0] == 'num'
+
+ # if we have a pluralize block, we parse that too
+ if parser.stream.current.test('name:pluralize'):
+ have_plural = True
+ next(parser.stream)
+ if parser.stream.current.type != 'block_end':
+ name = parser.stream.expect('name')
+ if name.value not in variables:
+ parser.fail('unknown variable %r for pluralization' %
+ name.value, name.lineno,
+ exc=TemplateAssertionError)
+ plural_expr = variables[name.value]
+ num_called_num = name.value == 'num'
+ parser.stream.expect('block_end')
+ plural_names, plural = self._parse_block(parser, False)
+ next(parser.stream)
+ referenced.update(plural_names)
+ else:
+ next(parser.stream)
+
+ # register free names as simple name expressions
+ for var in referenced:
+ if var not in variables:
+ variables[var] = nodes.Name(var, 'load')
+
+ if not have_plural:
+ plural_expr = None
+ elif plural_expr is None:
+ parser.fail('pluralize without variables', lineno)
+
+ node = self._make_node(singular, plural, variables, plural_expr,
+ bool(referenced),
+ num_called_num and have_plural)
+ node.set_lineno(lineno)
+ if plural_expr_assignment is not None:
+ return [plural_expr_assignment, node]
+ else:
+ return node
+
+ def _parse_block(self, parser, allow_pluralize):
+ """Parse until the next block tag with a given name."""
+ referenced = []
+ buf = []
+ while 1:
+ if parser.stream.current.type == 'data':
+ buf.append(parser.stream.current.value.replace('%', '%%'))
+ next(parser.stream)
+ elif parser.stream.current.type == 'variable_begin':
+ next(parser.stream)
+ name = parser.stream.expect('name').value
+ referenced.append(name)
+ buf.append('%%(%s)s' % name)
+ parser.stream.expect('variable_end')
+ elif parser.stream.current.type == 'block_begin':
+ next(parser.stream)
+ if parser.stream.current.test('name:endtrans'):
+ break
+ elif parser.stream.current.test('name:pluralize'):
+ if allow_pluralize:
+ break
+ parser.fail('a translatable section can have only one '
+ 'pluralize section')
+ parser.fail('control structures in translatable sections are '
+ 'not allowed')
+ elif parser.stream.eos:
+ parser.fail('unclosed translation block')
+ else:
+ assert False, 'internal parser error'
+
+ return referenced, concat(buf)
+
+ def _make_node(self, singular, plural, variables, plural_expr,
+ vars_referenced, num_called_num):
+ """Generates a useful node from the data provided."""
+ # no variables referenced? no need to escape for old style
+ # gettext invocations only if there are vars.
+ if not vars_referenced and not self.environment.newstyle_gettext:
+ singular = singular.replace('%%', '%')
+ if plural:
+ plural = plural.replace('%%', '%')
+
+ # singular only:
+ if plural_expr is None:
+ gettext = nodes.Name('gettext', 'load')
+ node = nodes.Call(gettext, [nodes.Const(singular)],
+ [], None, None)
+
+ # singular and plural
+ else:
+ ngettext = nodes.Name('ngettext', 'load')
+ node = nodes.Call(ngettext, [
+ nodes.Const(singular),
+ nodes.Const(plural),
+ plural_expr
+ ], [], None, None)
+
+ # in case newstyle gettext is used, the method is powerful
+ # enough to handle the variable expansion and autoescape
+ # handling itself
+ if self.environment.newstyle_gettext:
+ for key, value in iteritems(variables):
+ # the function adds that later anyways in case num was
+ # called num, so just skip it.
+ if num_called_num and key == 'num':
+ continue
+ node.kwargs.append(nodes.Keyword(key, value))
+
+ # otherwise do that here
+ else:
+ # mark the return value as safe if we are in an
+ # environment with autoescaping turned on
+ node = nodes.MarkSafeIfAutoescape(node)
+ if variables:
+ node = nodes.Mod(node, nodes.Dict([
+ nodes.Pair(nodes.Const(key), value)
+ for key, value in variables.items()
+ ]))
+ return nodes.Output([node])
+
+
+class ExprStmtExtension(Extension):
+ """Adds a `do` tag to Jinja2 that works like the print statement just
+ that it doesn't print the return value.
+ """
+ tags = set(['do'])
+
+ def parse(self, parser):
+ node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
+ node.node = parser.parse_tuple()
+ return node
+
+
+class LoopControlExtension(Extension):
+ """Adds break and continue to the template engine."""
+ tags = set(['break', 'continue'])
+
+ def parse(self, parser):
+ token = next(parser.stream)
+ if token.value == 'break':
+ return nodes.Break(lineno=token.lineno)
+ return nodes.Continue(lineno=token.lineno)
+
+
+class WithExtension(Extension):
+ """Adds support for a django-like with block."""
+ tags = set(['with'])
+
+ def parse(self, parser):
+ node = nodes.Scope(lineno=next(parser.stream).lineno)
+ assignments = []
+ while parser.stream.current.type != 'block_end':
+ lineno = parser.stream.current.lineno
+ if assignments:
+ parser.stream.expect('comma')
+ target = parser.parse_assign_target()
+ parser.stream.expect('assign')
+ expr = parser.parse_expression()
+ assignments.append(nodes.Assign(target, expr, lineno=lineno))
+ node.body = assignments + \
+ list(parser.parse_statements(('name:endwith',),
+ drop_needle=True))
+ return node
+
+
+class AutoEscapeExtension(Extension):
+ """Changes auto escape rules for a scope."""
+ tags = set(['autoescape'])
+
+ def parse(self, parser):
+ node = nodes.ScopedEvalContextModifier(lineno=next(parser.stream).lineno)
+ node.options = [
+ nodes.Keyword('autoescape', parser.parse_expression())
+ ]
+ node.body = parser.parse_statements(('name:endautoescape',),
+ drop_needle=True)
+ return nodes.Scope([node])
+
+
+def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
+ babel_style=True):
+ """Extract localizable strings from the given template node. Per
+ default this function returns matches in babel style that means non string
+ parameters as well as keyword arguments are returned as `None`. This
+ allows Babel to figure out what you really meant if you are using
+ gettext functions that allow keyword arguments for placeholder expansion.
+ If you don't want that behavior set the `babel_style` parameter to `False`
+ which causes only strings to be returned and parameters are always stored
+ in tuples. As a consequence invalid gettext calls (calls without a single
+ string parameter or string parameters after non-string parameters) are
+ skipped.
+
+ This example explains the behavior:
+
+ >>> from jinja2 import Environment
+ >>> env = Environment()
+ >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
+ >>> list(extract_from_ast(node))
+ [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
+ >>> list(extract_from_ast(node, babel_style=False))
+ [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
+
+ For every string found this function yields a ``(lineno, function,
+ message)`` tuple, where:
+
+ * ``lineno`` is the number of the line on which the string was found,
+ * ``function`` is the name of the ``gettext`` function used (if the
+ string was extracted from embedded Python code), and
+ * ``message`` is the string itself (a ``unicode`` object, or a tuple
+ of ``unicode`` objects for functions with multiple string arguments).
+
+ This extraction function operates on the AST and is because of that unable
+ to extract any comments. For comment support you have to use the babel
+ extraction interface or extract comments yourself.
+ """
+ for node in node.find_all(nodes.Call):
+ if not isinstance(node.node, nodes.Name) or \
+ node.node.name not in gettext_functions:
+ continue
+
+ strings = []
+ for arg in node.args:
+ if isinstance(arg, nodes.Const) and \
+ isinstance(arg.value, string_types):
+ strings.append(arg.value)
+ else:
+ strings.append(None)
+
+ for arg in node.kwargs:
+ strings.append(None)
+ if node.dyn_args is not None:
+ strings.append(None)
+ if node.dyn_kwargs is not None:
+ strings.append(None)
+
+ if not babel_style:
+ strings = tuple(x for x in strings if x is not None)
+ if not strings:
+ continue
+ else:
+ if len(strings) == 1:
+ strings = strings[0]
+ else:
+ strings = tuple(strings)
+ yield node.lineno, node.node.name, strings
+
+
+class _CommentFinder(object):
+ """Helper class to find comments in a token stream. Can only
+ find comments for gettext calls forwards. Once the comment
+ from line 4 is found, a comment for line 1 will not return a
+ usable value.
+ """
+
+ def __init__(self, tokens, comment_tags):
+ self.tokens = tokens
+ self.comment_tags = comment_tags
+ self.offset = 0
+ self.last_lineno = 0
+
+ def find_backwards(self, offset):
+ try:
+ for _, token_type, token_value in \
+ reversed(self.tokens[self.offset:offset]):
+ if token_type in ('comment', 'linecomment'):
+ try:
+ prefix, comment = token_value.split(None, 1)
+ except ValueError:
+ continue
+ if prefix in self.comment_tags:
+ return [comment.rstrip()]
+ return []
+ finally:
+ self.offset = offset
+
+ def find_comments(self, lineno):
+ if not self.comment_tags or self.last_lineno > lineno:
+ return []
+ for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
+ if token_lineno > lineno:
+ return self.find_backwards(self.offset + idx)
+ return self.find_backwards(len(self.tokens))
+
+
+def babel_extract(fileobj, keywords, comment_tags, options):
+ """Babel extraction method for Jinja templates.
+
+ .. versionchanged:: 2.3
+ Basic support for translation comments was added. If `comment_tags`
+ is now set to a list of keywords for extraction, the extractor will
+ try to find the best preceeding comment that begins with one of the
+ keywords. For best results, make sure to not have more than one
+ gettext call in one line of code and the matching comment in the
+ same line or the line before.
+
+ .. versionchanged:: 2.5.1
+ The `newstyle_gettext` flag can be set to `True` to enable newstyle
+ gettext calls.
+
+ .. versionchanged:: 2.7
+ A `silent` option can now be provided. If set to `False` template
+ syntax errors are propagated instead of being ignored.
+
+ :param fileobj: the file-like object the messages should be extracted from
+ :param keywords: a list of keywords (i.e. function names) that should be
+ recognized as translation functions
+ :param comment_tags: a list of translator tags to search for and include
+ in the results.
+ :param options: a dictionary of additional options (optional)
+ :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
+ (comments will be empty currently)
+ """
+ extensions = set()
+ for extension in options.get('extensions', '').split(','):
+ extension = extension.strip()
+ if not extension:
+ continue
+ extensions.add(import_string(extension))
+ if InternationalizationExtension not in extensions:
+ extensions.add(InternationalizationExtension)
+
+ def getbool(options, key, default=False):
+ return options.get(key, str(default)).lower() in \
+ ('1', 'on', 'yes', 'true')
+
+ silent = getbool(options, 'silent', True)
+ environment = Environment(
+ options.get('block_start_string', BLOCK_START_STRING),
+ options.get('block_end_string', BLOCK_END_STRING),
+ options.get('variable_start_string', VARIABLE_START_STRING),
+ options.get('variable_end_string', VARIABLE_END_STRING),
+ options.get('comment_start_string', COMMENT_START_STRING),
+ options.get('comment_end_string', COMMENT_END_STRING),
+ options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
+ options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
+ getbool(options, 'trim_blocks', TRIM_BLOCKS),
+ getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
+ NEWLINE_SEQUENCE,
+ getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
+ frozenset(extensions),
+ cache_size=0,
+ auto_reload=False
+ )
+
+ if getbool(options, 'newstyle_gettext'):
+ environment.newstyle_gettext = True
+
+ source = fileobj.read().decode(options.get('encoding', 'utf-8'))
+ try:
+ node = environment.parse(source)
+ tokens = list(environment.lex(environment.preprocess(source)))
+ except TemplateSyntaxError as e:
+ if not silent:
+ raise
+ # skip templates with syntax errors
+ return
+
+ finder = _CommentFinder(tokens, comment_tags)
+ for lineno, func, message in extract_from_ast(node, keywords):
+ yield lineno, func, message, finder.find_comments(lineno)
+
+
+#: nicer import names
+i18n = InternationalizationExtension
+do = ExprStmtExtension
+loopcontrols = LoopControlExtension
+with_ = WithExtension
+autoescape = AutoEscapeExtension
diff --git a/pyload/lib/jinja2/filters.py b/pyload/lib/jinja2/filters.py
new file mode 100644
index 000000000..fd0db04aa
--- /dev/null
+++ b/pyload/lib/jinja2/filters.py
@@ -0,0 +1,987 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.filters
+ ~~~~~~~~~~~~~~
+
+ Bundled jinja filters.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import math
+
+from random import choice
+from operator import itemgetter
+from itertools import groupby
+from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
+ unicode_urlencode
+from jinja2.runtime import Undefined
+from jinja2.exceptions import FilterArgumentError
+from jinja2._compat import next, imap, string_types, text_type, iteritems
+
+
+_word_re = re.compile(r'\w+(?u)')
+
+
+def contextfilter(f):
+ """Decorator for marking context dependent filters. The current
+ :class:`Context` will be passed as first argument.
+ """
+ f.contextfilter = True
+ return f
+
+
+def evalcontextfilter(f):
+ """Decorator for marking eval-context dependent filters. An eval
+ context object is passed as first argument. For more information
+ about the eval context, see :ref:`eval-context`.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfilter = True
+ return f
+
+
+def environmentfilter(f):
+ """Decorator for marking evironment dependent filters. The current
+ :class:`Environment` is passed to the filter as first argument.
+ """
+ f.environmentfilter = True
+ return f
+
+
+def make_attrgetter(environment, attribute):
+ """Returns a callable that looks up the given attribute from a
+ passed object with the rules of the environment. Dots are allowed
+ to access attributes of attributes. Integer parts in paths are
+ looked up as integers.
+ """
+ if not isinstance(attribute, string_types) \
+ or ('.' not in attribute and not attribute.isdigit()):
+ return lambda x: environment.getitem(x, attribute)
+ attribute = attribute.split('.')
+ def attrgetter(item):
+ for part in attribute:
+ if part.isdigit():
+ part = int(part)
+ item = environment.getitem(item, part)
+ return item
+ return attrgetter
+
+
+def do_forceescape(value):
+ """Enforce HTML escaping. This will probably double escape variables."""
+ if hasattr(value, '__html__'):
+ value = value.__html__()
+ return escape(text_type(value))
+
+
+def do_urlencode(value):
+ """Escape strings for use in URLs (uses UTF-8 encoding). It accepts both
+ dictionaries and regular strings as well as pairwise iterables.
+
+ .. versionadded:: 2.7
+ """
+ itemiter = None
+ if isinstance(value, dict):
+ itemiter = iteritems(value)
+ elif not isinstance(value, string_types):
+ try:
+ itemiter = iter(value)
+ except TypeError:
+ pass
+ if itemiter is None:
+ return unicode_urlencode(value)
+ return u'&'.join(unicode_urlencode(k) + '=' +
+ unicode_urlencode(v) for k, v in itemiter)
+
+
+@evalcontextfilter
+def do_replace(eval_ctx, s, old, new, count=None):
+ """Return a copy of the value with all occurrences of a substring
+ replaced with a new one. The first argument is the substring
+ that should be replaced, the second is the replacement string.
+ If the optional third argument ``count`` is given, only the first
+ ``count`` occurrences are replaced:
+
+ .. sourcecode:: jinja
+
+ {{ "Hello World"|replace("Hello", "Goodbye") }}
+ -> Goodbye World
+
+ {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
+ -> d'oh, d'oh, aaargh
+ """
+ if count is None:
+ count = -1
+ if not eval_ctx.autoescape:
+ return text_type(s).replace(text_type(old), text_type(new), count)
+ if hasattr(old, '__html__') or hasattr(new, '__html__') and \
+ not hasattr(s, '__html__'):
+ s = escape(s)
+ else:
+ s = soft_unicode(s)
+ return s.replace(soft_unicode(old), soft_unicode(new), count)
+
+
+def do_upper(s):
+ """Convert a value to uppercase."""
+ return soft_unicode(s).upper()
+
+
+def do_lower(s):
+ """Convert a value to lowercase."""
+ return soft_unicode(s).lower()
+
+
+@evalcontextfilter
+def do_xmlattr(_eval_ctx, d, autospace=True):
+ """Create an SGML/XML attribute string based on the items in a dict.
+ All values that are neither `none` nor `undefined` are automatically
+ escaped:
+
+ .. sourcecode:: html+jinja
+
+ <ul{{ {'class': 'my_list', 'missing': none,
+ 'id': 'list-%d'|format(variable)}|xmlattr }}>
+ ...
+ </ul>
+
+ Results in something like this:
+
+ .. sourcecode:: html
+
+ <ul class="my_list" id="list-42">
+ ...
+ </ul>
+
+ As you can see it automatically prepends a space in front of the item
+ if the filter returned something unless the second parameter is false.
+ """
+ rv = u' '.join(
+ u'%s="%s"' % (escape(key), escape(value))
+ for key, value in iteritems(d)
+ if value is not None and not isinstance(value, Undefined)
+ )
+ if autospace and rv:
+ rv = u' ' + rv
+ if _eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv
+
+
+def do_capitalize(s):
+ """Capitalize a value. The first character will be uppercase, all others
+ lowercase.
+ """
+ return soft_unicode(s).capitalize()
+
+
+def do_title(s):
+ """Return a titlecased version of the value. I.e. words will start with
+ uppercase letters, all remaining characters are lowercase.
+ """
+ rv = []
+ for item in re.compile(r'([-\s]+)(?u)').split(s):
+ if not item:
+ continue
+ rv.append(item[0].upper() + item[1:].lower())
+ return ''.join(rv)
+
+
+def do_dictsort(value, case_sensitive=False, by='key'):
+ """Sort a dict and yield (key, value) pairs. Because python dicts are
+ unsorted you may want to use this function to order them by either
+ key or value:
+
+ .. sourcecode:: jinja
+
+ {% for item in mydict|dictsort %}
+ sort the dict by key, case insensitive
+
+ {% for item in mydict|dictsort(true) %}
+ sort the dict by key, case sensitive
+
+ {% for item in mydict|dictsort(false, 'value') %}
+ sort the dict by key, case insensitive, sorted
+ normally and ordered by value.
+ """
+ if by == 'key':
+ pos = 0
+ elif by == 'value':
+ pos = 1
+ else:
+ raise FilterArgumentError('You can only sort by either '
+ '"key" or "value"')
+ def sort_func(item):
+ value = item[pos]
+ if isinstance(value, string_types) and not case_sensitive:
+ value = value.lower()
+ return value
+
+ return sorted(value.items(), key=sort_func)
+
+
+@environmentfilter
+def do_sort(environment, value, reverse=False, case_sensitive=False,
+ attribute=None):
+ """Sort an iterable. Per default it sorts ascending, if you pass it
+ true as first argument it will reverse the sorting.
+
+ If the iterable is made of strings the third parameter can be used to
+ control the case sensitiveness of the comparison which is disabled by
+ default.
+
+ .. sourcecode:: jinja
+
+ {% for item in iterable|sort %}
+ ...
+ {% endfor %}
+
+ It is also possible to sort by an attribute (for example to sort
+ by the date of an object) by specifying the `attribute` parameter:
+
+ .. sourcecode:: jinja
+
+ {% for item in iterable|sort(attribute='date') %}
+ ...
+ {% endfor %}
+
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added.
+ """
+ if not case_sensitive:
+ def sort_func(item):
+ if isinstance(item, string_types):
+ item = item.lower()
+ return item
+ else:
+ sort_func = None
+ if attribute is not None:
+ getter = make_attrgetter(environment, attribute)
+ def sort_func(item, processor=sort_func or (lambda x: x)):
+ return processor(getter(item))
+ return sorted(value, key=sort_func, reverse=reverse)
+
+
+def do_default(value, default_value=u'', boolean=False):
+ """If the value is undefined it will return the passed default value,
+ otherwise the value of the variable:
+
+ .. sourcecode:: jinja
+
+ {{ my_variable|default('my_variable is not defined') }}
+
+ This will output the value of ``my_variable`` if the variable was
+ defined, otherwise ``'my_variable is not defined'``. If you want
+ to use default with variables that evaluate to false you have to
+ set the second parameter to `true`:
+
+ .. sourcecode:: jinja
+
+ {{ ''|default('the string was empty', true) }}
+ """
+ if isinstance(value, Undefined) or (boolean and not value):
+ return default_value
+ return value
+
+
+@evalcontextfilter
+def do_join(eval_ctx, value, d=u'', attribute=None):
+ """Return a string which is the concatenation of the strings in the
+ sequence. The separator between elements is an empty string per
+ default, you can define it with the optional parameter:
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|join('|') }}
+ -> 1|2|3
+
+ {{ [1, 2, 3]|join }}
+ -> 123
+
+ It is also possible to join certain attributes of an object:
+
+ .. sourcecode:: jinja
+
+ {{ users|join(', ', attribute='username') }}
+
+ .. versionadded:: 2.6
+ The `attribute` parameter was added.
+ """
+ if attribute is not None:
+ value = imap(make_attrgetter(eval_ctx.environment, attribute), value)
+
+ # no automatic escaping? joining is a lot eaiser then
+ if not eval_ctx.autoescape:
+ return text_type(d).join(imap(text_type, value))
+
+ # if the delimiter doesn't have an html representation we check
+ # if any of the items has. If yes we do a coercion to Markup
+ if not hasattr(d, '__html__'):
+ value = list(value)
+ do_escape = False
+ for idx, item in enumerate(value):
+ if hasattr(item, '__html__'):
+ do_escape = True
+ else:
+ value[idx] = text_type(item)
+ if do_escape:
+ d = escape(d)
+ else:
+ d = text_type(d)
+ return d.join(value)
+
+ # no html involved, to normal joining
+ return soft_unicode(d).join(imap(soft_unicode, value))
+
+
+def do_center(value, width=80):
+ """Centers the value in a field of a given width."""
+ return text_type(value).center(width)
+
+
+@environmentfilter
+def do_first(environment, seq):
+ """Return the first item of a sequence."""
+ try:
+ return next(iter(seq))
+ except StopIteration:
+ return environment.undefined('No first item, sequence was empty.')
+
+
+@environmentfilter
+def do_last(environment, seq):
+ """Return the last item of a sequence."""
+ try:
+ return next(iter(reversed(seq)))
+ except StopIteration:
+ return environment.undefined('No last item, sequence was empty.')
+
+
+@environmentfilter
+def do_random(environment, seq):
+ """Return a random item from the sequence."""
+ try:
+ return choice(seq)
+ except IndexError:
+ return environment.undefined('No random item, sequence was empty.')
+
+
+def do_filesizeformat(value, binary=False):
+ """Format the value like a 'human-readable' file size (i.e. 13 kB,
+ 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
+ Giga, etc.), if the second parameter is set to `True` the binary
+ prefixes are used (Mebi, Gibi).
+ """
+ bytes = float(value)
+ base = binary and 1024 or 1000
+ prefixes = [
+ (binary and 'KiB' or 'kB'),
+ (binary and 'MiB' or 'MB'),
+ (binary and 'GiB' or 'GB'),
+ (binary and 'TiB' or 'TB'),
+ (binary and 'PiB' or 'PB'),
+ (binary and 'EiB' or 'EB'),
+ (binary and 'ZiB' or 'ZB'),
+ (binary and 'YiB' or 'YB')
+ ]
+ if bytes == 1:
+ return '1 Byte'
+ elif bytes < base:
+ return '%d Bytes' % bytes
+ else:
+ for i, prefix in enumerate(prefixes):
+ unit = base ** (i + 2)
+ if bytes < unit:
+ return '%.1f %s' % ((base * bytes / unit), prefix)
+ return '%.1f %s' % ((base * bytes / unit), prefix)
+
+
+def do_pprint(value, verbose=False):
+ """Pretty print a variable. Useful for debugging.
+
+ With Jinja 1.2 onwards you can pass it a parameter. If this parameter
+ is truthy the output will be more verbose (this requires `pretty`)
+ """
+ return pformat(value, verbose=verbose)
+
+
+@evalcontextfilter
+def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False):
+ """Converts URLs in plain text into clickable links.
+
+ If you pass the filter an additional integer it will shorten the urls
+ to that number. Also a third argument exists that makes the urls
+ "nofollow":
+
+ .. sourcecode:: jinja
+
+ {{ mytext|urlize(40, true) }}
+ links are shortened to 40 chars and defined with rel="nofollow"
+ """
+ rv = urlize(value, trim_url_limit, nofollow)
+ if eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv
+
+
+def do_indent(s, width=4, indentfirst=False):
+ """Return a copy of the passed string, each line indented by
+ 4 spaces. The first line is not indented. If you want to
+ change the number of spaces or indent the first line too
+ you can pass additional parameters to the filter:
+
+ .. sourcecode:: jinja
+
+ {{ mytext|indent(2, true) }}
+ indent by two spaces and indent the first line too.
+ """
+ indention = u' ' * width
+ rv = (u'\n' + indention).join(s.splitlines())
+ if indentfirst:
+ rv = indention + rv
+ return rv
+
+
+def do_truncate(s, length=255, killwords=False, end='...'):
+ """Return a truncated copy of the string. The length is specified
+ with the first parameter which defaults to ``255``. If the second
+ parameter is ``true`` the filter will cut the text at length. Otherwise
+ it will discard the last word. If the text was in fact
+ truncated it will append an ellipsis sign (``"..."``). If you want a
+ different ellipsis sign than ``"..."`` you can specify it using the
+ third parameter.
+
+ .. sourcecode:: jinja
+
+ {{ "foo bar"|truncate(5) }}
+ -> "foo ..."
+ {{ "foo bar"|truncate(5, True) }}
+ -> "foo b..."
+ """
+ if len(s) <= length:
+ return s
+ elif killwords:
+ return s[:length] + end
+ words = s.split(' ')
+ result = []
+ m = 0
+ for word in words:
+ m += len(word) + 1
+ if m > length:
+ break
+ result.append(word)
+ result.append(end)
+ return u' '.join(result)
+
+@environmentfilter
+def do_wordwrap(environment, s, width=79, break_long_words=True,
+ wrapstring=None):
+ """
+ Return a copy of the string passed to the filter wrapped after
+ ``79`` characters. You can override this default using the first
+ parameter. If you set the second parameter to `false` Jinja will not
+ split words apart if they are longer than `width`. By default, the newlines
+ will be the default newlines for the environment, but this can be changed
+ using the wrapstring keyword argument.
+
+ .. versionadded:: 2.7
+ Added support for the `wrapstring` parameter.
+ """
+ if not wrapstring:
+ wrapstring = environment.newline_sequence
+ import textwrap
+ return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False,
+ replace_whitespace=False,
+ break_long_words=break_long_words))
+
+
+def do_wordcount(s):
+ """Count the words in that string."""
+ return len(_word_re.findall(s))
+
+
+def do_int(value, default=0):
+ """Convert the value into an integer. If the
+ conversion doesn't work it will return ``0``. You can
+ override this default using the first parameter.
+ """
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ # this quirk is necessary so that "42.23"|int gives 42.
+ try:
+ return int(float(value))
+ except (TypeError, ValueError):
+ return default
+
+
+def do_float(value, default=0.0):
+ """Convert the value into a floating point number. If the
+ conversion doesn't work it will return ``0.0``. You can
+ override this default using the first parameter.
+ """
+ try:
+ return float(value)
+ except (TypeError, ValueError):
+ return default
+
+
+def do_format(value, *args, **kwargs):
+ """
+ Apply python string formatting on an object:
+
+ .. sourcecode:: jinja
+
+ {{ "%s - %s"|format("Hello?", "Foo!") }}
+ -> Hello? - Foo!
+ """
+ if args and kwargs:
+ raise FilterArgumentError('can\'t handle positional and keyword '
+ 'arguments at the same time')
+ return soft_unicode(value) % (kwargs or args)
+
+
+def do_trim(value):
+ """Strip leading and trailing whitespace."""
+ return soft_unicode(value).strip()
+
+
+def do_striptags(value):
+ """Strip SGML/XML tags and replace adjacent whitespace by one space.
+ """
+ if hasattr(value, '__html__'):
+ value = value.__html__()
+ return Markup(text_type(value)).striptags()
+
+
+def do_slice(value, slices, fill_with=None):
+ """Slice an iterator and return a list of lists containing
+ those items. Useful if you want to create a div containing
+ three ul tags that represent columns:
+
+ .. sourcecode:: html+jinja
+
+ <div class="columwrapper">
+ {%- for column in items|slice(3) %}
+ <ul class="column-{{ loop.index }}">
+ {%- for item in column %}
+ <li>{{ item }}</li>
+ {%- endfor %}
+ </ul>
+ {%- endfor %}
+ </div>
+
+ If you pass it a second argument it's used to fill missing
+ values on the last iteration.
+ """
+ seq = list(value)
+ length = len(seq)
+ items_per_slice = length // slices
+ slices_with_extra = length % slices
+ offset = 0
+ for slice_number in range(slices):
+ start = offset + slice_number * items_per_slice
+ if slice_number < slices_with_extra:
+ offset += 1
+ end = offset + (slice_number + 1) * items_per_slice
+ tmp = seq[start:end]
+ if fill_with is not None and slice_number >= slices_with_extra:
+ tmp.append(fill_with)
+ yield tmp
+
+
+def do_batch(value, linecount, fill_with=None):
+ """
+ A filter that batches items. It works pretty much like `slice`
+ just the other way round. It returns a list of lists with the
+ given number of items. If you provide a second parameter this
+ is used to fill up missing items. See this example:
+
+ .. sourcecode:: html+jinja
+
+ <table>
+ {%- for row in items|batch(3, '&nbsp;') %}
+ <tr>
+ {%- for column in row %}
+ <td>{{ column }}</td>
+ {%- endfor %}
+ </tr>
+ {%- endfor %}
+ </table>
+ """
+ result = []
+ tmp = []
+ for item in value:
+ if len(tmp) == linecount:
+ yield tmp
+ tmp = []
+ tmp.append(item)
+ if tmp:
+ if fill_with is not None and len(tmp) < linecount:
+ tmp += [fill_with] * (linecount - len(tmp))
+ yield tmp
+
+
+def do_round(value, precision=0, method='common'):
+ """Round the number to a given precision. The first
+ parameter specifies the precision (default is ``0``), the
+ second the rounding method:
+
+ - ``'common'`` rounds either up or down
+ - ``'ceil'`` always rounds up
+ - ``'floor'`` always rounds down
+
+ If you don't specify a method ``'common'`` is used.
+
+ .. sourcecode:: jinja
+
+ {{ 42.55|round }}
+ -> 43.0
+ {{ 42.55|round(1, 'floor') }}
+ -> 42.5
+
+ Note that even if rounded to 0 precision, a float is returned. If
+ you need a real integer, pipe it through `int`:
+
+ .. sourcecode:: jinja
+
+ {{ 42.55|round|int }}
+ -> 43
+ """
+ if not method in ('common', 'ceil', 'floor'):
+ raise FilterArgumentError('method must be common, ceil or floor')
+ if method == 'common':
+ return round(value, precision)
+ func = getattr(math, method)
+ return func(value * (10 ** precision)) / (10 ** precision)
+
+
+@environmentfilter
+def do_groupby(environment, value, attribute):
+ """Group a sequence of objects by a common attribute.
+
+ If you for example have a list of dicts or objects that represent persons
+ with `gender`, `first_name` and `last_name` attributes and you want to
+ group all users by genders you can do something like the following
+ snippet:
+
+ .. sourcecode:: html+jinja
+
+ <ul>
+ {% for group in persons|groupby('gender') %}
+ <li>{{ group.grouper }}<ul>
+ {% for person in group.list %}
+ <li>{{ person.first_name }} {{ person.last_name }}</li>
+ {% endfor %}</ul></li>
+ {% endfor %}
+ </ul>
+
+ Additionally it's possible to use tuple unpacking for the grouper and
+ list:
+
+ .. sourcecode:: html+jinja
+
+ <ul>
+ {% for grouper, list in persons|groupby('gender') %}
+ ...
+ {% endfor %}
+ </ul>
+
+ As you can see the item we're grouping by is stored in the `grouper`
+ attribute and the `list` contains all the objects that have this grouper
+ in common.
+
+ .. versionchanged:: 2.6
+ It's now possible to use dotted notation to group by the child
+ attribute of another attribute.
+ """
+ expr = make_attrgetter(environment, attribute)
+ return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
+
+
+class _GroupTuple(tuple):
+ __slots__ = ()
+ grouper = property(itemgetter(0))
+ list = property(itemgetter(1))
+
+ def __new__(cls, xxx_todo_changeme):
+ (key, value) = xxx_todo_changeme
+ return tuple.__new__(cls, (key, list(value)))
+
+
+@environmentfilter
+def do_sum(environment, iterable, attribute=None, start=0):
+ """Returns the sum of a sequence of numbers plus the value of parameter
+ 'start' (which defaults to 0). When the sequence is empty it returns
+ start.
+
+ It is also possible to sum up only certain attributes:
+
+ .. sourcecode:: jinja
+
+ Total: {{ items|sum(attribute='price') }}
+
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added to allow suming up over
+ attributes. Also the `start` parameter was moved on to the right.
+ """
+ if attribute is not None:
+ iterable = imap(make_attrgetter(environment, attribute), iterable)
+ return sum(iterable, start)
+
+
+def do_list(value):
+ """Convert the value into a list. If it was a string the returned list
+ will be a list of characters.
+ """
+ return list(value)
+
+
+def do_mark_safe(value):
+ """Mark the value as safe which means that in an environment with automatic
+ escaping enabled this variable will not be escaped.
+ """
+ return Markup(value)
+
+
+def do_mark_unsafe(value):
+ """Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
+ return text_type(value)
+
+
+def do_reverse(value):
+ """Reverse the object or return an iterator the iterates over it the other
+ way round.
+ """
+ if isinstance(value, string_types):
+ return value[::-1]
+ try:
+ return reversed(value)
+ except TypeError:
+ try:
+ rv = list(value)
+ rv.reverse()
+ return rv
+ except TypeError:
+ raise FilterArgumentError('argument must be iterable')
+
+
+@environmentfilter
+def do_attr(environment, obj, name):
+ """Get an attribute of an object. ``foo|attr("bar")`` works like
+ ``foo["bar"]`` just that always an attribute is returned and items are not
+ looked up.
+
+ See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
+ """
+ try:
+ name = str(name)
+ except UnicodeError:
+ pass
+ else:
+ try:
+ value = getattr(obj, name)
+ except AttributeError:
+ pass
+ else:
+ if environment.sandboxed and not \
+ environment.is_safe_attribute(obj, name, value):
+ return environment.unsafe_undefined(obj, name)
+ return value
+ return environment.undefined(obj=obj, name=name)
+
+
+@contextfilter
+def do_map(*args, **kwargs):
+ """Applies a filter on a sequence of objects or looks up an attribute.
+ This is useful when dealing with lists of objects but you are really
+ only interested in a certain value of it.
+
+ The basic usage is mapping on an attribute. Imagine you have a list
+ of users but you are only interested in a list of usernames:
+
+ .. sourcecode:: jinja
+
+ Users on this page: {{ users|map(attribute='username')|join(', ') }}
+
+ Alternatively you can let it invoke a filter by passing the name of the
+ filter and the arguments afterwards. A good example would be applying a
+ text conversion filter on a sequence:
+
+ .. sourcecode:: jinja
+
+ Users on this page: {{ titles|map('lower')|join(', ') }}
+
+ .. versionadded:: 2.7
+ """
+ context = args[0]
+ seq = args[1]
+
+ if len(args) == 2 and 'attribute' in kwargs:
+ attribute = kwargs.pop('attribute')
+ if kwargs:
+ raise FilterArgumentError('Unexpected keyword argument %r' %
+ next(iter(kwargs)))
+ func = make_attrgetter(context.environment, attribute)
+ else:
+ try:
+ name = args[2]
+ args = args[3:]
+ except LookupError:
+ raise FilterArgumentError('map requires a filter argument')
+ func = lambda item: context.environment.call_filter(
+ name, item, args, kwargs, context=context)
+
+ if seq:
+ for item in seq:
+ yield func(item)
+
+
+@contextfilter
+def do_select(*args, **kwargs):
+ """Filters a sequence of objects by appying a test to either the object
+ or the attribute and only selecting the ones with the test succeeding.
+
+ Example usage:
+
+ .. sourcecode:: jinja
+
+ {{ numbers|select("odd") }}
+
+ .. versionadded:: 2.7
+ """
+ return _select_or_reject(args, kwargs, lambda x: x, False)
+
+
+@contextfilter
+def do_reject(*args, **kwargs):
+ """Filters a sequence of objects by appying a test to either the object
+ or the attribute and rejecting the ones with the test succeeding.
+
+ Example usage:
+
+ .. sourcecode:: jinja
+
+ {{ numbers|reject("odd") }}
+
+ .. versionadded:: 2.7
+ """
+ return _select_or_reject(args, kwargs, lambda x: not x, False)
+
+
+@contextfilter
+def do_selectattr(*args, **kwargs):
+ """Filters a sequence of objects by appying a test to either the object
+ or the attribute and only selecting the ones with the test succeeding.
+
+ Example usage:
+
+ .. sourcecode:: jinja
+
+ {{ users|selectattr("is_active") }}
+ {{ users|selectattr("email", "none") }}
+
+ .. versionadded:: 2.7
+ """
+ return _select_or_reject(args, kwargs, lambda x: x, True)
+
+
+@contextfilter
+def do_rejectattr(*args, **kwargs):
+ """Filters a sequence of objects by appying a test to either the object
+ or the attribute and rejecting the ones with the test succeeding.
+
+ .. sourcecode:: jinja
+
+ {{ users|rejectattr("is_active") }}
+ {{ users|rejectattr("email", "none") }}
+
+ .. versionadded:: 2.7
+ """
+ return _select_or_reject(args, kwargs, lambda x: not x, True)
+
+
+def _select_or_reject(args, kwargs, modfunc, lookup_attr):
+ context = args[0]
+ seq = args[1]
+ if lookup_attr:
+ try:
+ attr = args[2]
+ except LookupError:
+ raise FilterArgumentError('Missing parameter for attribute name')
+ transfunc = make_attrgetter(context.environment, attr)
+ off = 1
+ else:
+ off = 0
+ transfunc = lambda x: x
+
+ try:
+ name = args[2 + off]
+ args = args[3 + off:]
+ func = lambda item: context.environment.call_test(
+ name, item, args, kwargs)
+ except LookupError:
+ func = bool
+
+ if seq:
+ for item in seq:
+ if modfunc(func(transfunc(item))):
+ yield item
+
+
+FILTERS = {
+ 'attr': do_attr,
+ 'replace': do_replace,
+ 'upper': do_upper,
+ 'lower': do_lower,
+ 'escape': escape,
+ 'e': escape,
+ 'forceescape': do_forceescape,
+ 'capitalize': do_capitalize,
+ 'title': do_title,
+ 'default': do_default,
+ 'd': do_default,
+ 'join': do_join,
+ 'count': len,
+ 'dictsort': do_dictsort,
+ 'sort': do_sort,
+ 'length': len,
+ 'reverse': do_reverse,
+ 'center': do_center,
+ 'indent': do_indent,
+ 'title': do_title,
+ 'capitalize': do_capitalize,
+ 'first': do_first,
+ 'last': do_last,
+ 'map': do_map,
+ 'random': do_random,
+ 'reject': do_reject,
+ 'rejectattr': do_rejectattr,
+ 'filesizeformat': do_filesizeformat,
+ 'pprint': do_pprint,
+ 'truncate': do_truncate,
+ 'wordwrap': do_wordwrap,
+ 'wordcount': do_wordcount,
+ 'int': do_int,
+ 'float': do_float,
+ 'string': soft_unicode,
+ 'list': do_list,
+ 'urlize': do_urlize,
+ 'format': do_format,
+ 'trim': do_trim,
+ 'striptags': do_striptags,
+ 'select': do_select,
+ 'selectattr': do_selectattr,
+ 'slice': do_slice,
+ 'batch': do_batch,
+ 'sum': do_sum,
+ 'abs': abs,
+ 'round': do_round,
+ 'groupby': do_groupby,
+ 'safe': do_mark_safe,
+ 'xmlattr': do_xmlattr,
+ 'urlencode': do_urlencode
+}
diff --git a/pyload/lib/jinja2/lexer.py b/pyload/lib/jinja2/lexer.py
new file mode 100644
index 000000000..a50128507
--- /dev/null
+++ b/pyload/lib/jinja2/lexer.py
@@ -0,0 +1,733 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.lexer
+ ~~~~~~~~~~~~
+
+ This module implements a Jinja / Python combination lexer. The
+ `Lexer` class provided by this module is used to do some preprocessing
+ for Jinja.
+
+ On the one hand it filters out invalid operators like the bitshift
+ operators we don't allow in templates. On the other hand it separates
+ template code and python code in expressions.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+
+from operator import itemgetter
+from collections import deque
+from jinja2.exceptions import TemplateSyntaxError
+from jinja2.utils import LRUCache
+from jinja2._compat import next, iteritems, implements_iterator, text_type, \
+ intern
+
+
+# cache for the lexers. Exists in order to be able to have multiple
+# environments with the same lexer
+_lexer_cache = LRUCache(50)
+
+# static regular expressions
+whitespace_re = re.compile(r'\s+', re.U)
+string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
+integer_re = re.compile(r'\d+')
+
+# we use the unicode identifier rule if this python version is able
+# to handle unicode identifiers, otherwise the standard ASCII one.
+try:
+ compile('föö', '<unknown>', 'eval')
+except SyntaxError:
+ name_re = re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
+else:
+ from jinja2 import _stringdefs
+ name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
+ _stringdefs.xid_continue))
+
+float_re = re.compile(r'(?<!\.)\d+\.\d+')
+newline_re = re.compile(r'(\r\n|\r|\n)')
+
+# internal the tokens and keep references to them
+TOKEN_ADD = intern('add')
+TOKEN_ASSIGN = intern('assign')
+TOKEN_COLON = intern('colon')
+TOKEN_COMMA = intern('comma')
+TOKEN_DIV = intern('div')
+TOKEN_DOT = intern('dot')
+TOKEN_EQ = intern('eq')
+TOKEN_FLOORDIV = intern('floordiv')
+TOKEN_GT = intern('gt')
+TOKEN_GTEQ = intern('gteq')
+TOKEN_LBRACE = intern('lbrace')
+TOKEN_LBRACKET = intern('lbracket')
+TOKEN_LPAREN = intern('lparen')
+TOKEN_LT = intern('lt')
+TOKEN_LTEQ = intern('lteq')
+TOKEN_MOD = intern('mod')
+TOKEN_MUL = intern('mul')
+TOKEN_NE = intern('ne')
+TOKEN_PIPE = intern('pipe')
+TOKEN_POW = intern('pow')
+TOKEN_RBRACE = intern('rbrace')
+TOKEN_RBRACKET = intern('rbracket')
+TOKEN_RPAREN = intern('rparen')
+TOKEN_SEMICOLON = intern('semicolon')
+TOKEN_SUB = intern('sub')
+TOKEN_TILDE = intern('tilde')
+TOKEN_WHITESPACE = intern('whitespace')
+TOKEN_FLOAT = intern('float')
+TOKEN_INTEGER = intern('integer')
+TOKEN_NAME = intern('name')
+TOKEN_STRING = intern('string')
+TOKEN_OPERATOR = intern('operator')
+TOKEN_BLOCK_BEGIN = intern('block_begin')
+TOKEN_BLOCK_END = intern('block_end')
+TOKEN_VARIABLE_BEGIN = intern('variable_begin')
+TOKEN_VARIABLE_END = intern('variable_end')
+TOKEN_RAW_BEGIN = intern('raw_begin')
+TOKEN_RAW_END = intern('raw_end')
+TOKEN_COMMENT_BEGIN = intern('comment_begin')
+TOKEN_COMMENT_END = intern('comment_end')
+TOKEN_COMMENT = intern('comment')
+TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
+TOKEN_LINESTATEMENT_END = intern('linestatement_end')
+TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
+TOKEN_LINECOMMENT_END = intern('linecomment_end')
+TOKEN_LINECOMMENT = intern('linecomment')
+TOKEN_DATA = intern('data')
+TOKEN_INITIAL = intern('initial')
+TOKEN_EOF = intern('eof')
+
+# bind operators to token types
+operators = {
+ '+': TOKEN_ADD,
+ '-': TOKEN_SUB,
+ '/': TOKEN_DIV,
+ '//': TOKEN_FLOORDIV,
+ '*': TOKEN_MUL,
+ '%': TOKEN_MOD,
+ '**': TOKEN_POW,
+ '~': TOKEN_TILDE,
+ '[': TOKEN_LBRACKET,
+ ']': TOKEN_RBRACKET,
+ '(': TOKEN_LPAREN,
+ ')': TOKEN_RPAREN,
+ '{': TOKEN_LBRACE,
+ '}': TOKEN_RBRACE,
+ '==': TOKEN_EQ,
+ '!=': TOKEN_NE,
+ '>': TOKEN_GT,
+ '>=': TOKEN_GTEQ,
+ '<': TOKEN_LT,
+ '<=': TOKEN_LTEQ,
+ '=': TOKEN_ASSIGN,
+ '.': TOKEN_DOT,
+ ':': TOKEN_COLON,
+ '|': TOKEN_PIPE,
+ ',': TOKEN_COMMA,
+ ';': TOKEN_SEMICOLON
+}
+
+reverse_operators = dict([(v, k) for k, v in iteritems(operators)])
+assert len(operators) == len(reverse_operators), 'operators dropped'
+operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
+ sorted(operators, key=lambda x: -len(x))))
+
+ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
+ TOKEN_COMMENT_END, TOKEN_WHITESPACE,
+ TOKEN_WHITESPACE, TOKEN_LINECOMMENT_BEGIN,
+ TOKEN_LINECOMMENT_END, TOKEN_LINECOMMENT])
+ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
+ TOKEN_COMMENT, TOKEN_LINECOMMENT])
+
+
+def _describe_token_type(token_type):
+ if token_type in reverse_operators:
+ return reverse_operators[token_type]
+ return {
+ TOKEN_COMMENT_BEGIN: 'begin of comment',
+ TOKEN_COMMENT_END: 'end of comment',
+ TOKEN_COMMENT: 'comment',
+ TOKEN_LINECOMMENT: 'comment',
+ TOKEN_BLOCK_BEGIN: 'begin of statement block',
+ TOKEN_BLOCK_END: 'end of statement block',
+ TOKEN_VARIABLE_BEGIN: 'begin of print statement',
+ TOKEN_VARIABLE_END: 'end of print statement',
+ TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
+ TOKEN_LINESTATEMENT_END: 'end of line statement',
+ TOKEN_DATA: 'template data / text',
+ TOKEN_EOF: 'end of template'
+ }.get(token_type, token_type)
+
+
+def describe_token(token):
+ """Returns a description of the token."""
+ if token.type == 'name':
+ return token.value
+ return _describe_token_type(token.type)
+
+
+def describe_token_expr(expr):
+ """Like `describe_token` but for token expressions."""
+ if ':' in expr:
+ type, value = expr.split(':', 1)
+ if type == 'name':
+ return value
+ else:
+ type = expr
+ return _describe_token_type(type)
+
+
+def count_newlines(value):
+ """Count the number of newline characters in the string. This is
+ useful for extensions that filter a stream.
+ """
+ return len(newline_re.findall(value))
+
+
+def compile_rules(environment):
+ """Compiles all the rules from the environment into a list of rules."""
+ e = re.escape
+ rules = [
+ (len(environment.comment_start_string), 'comment',
+ e(environment.comment_start_string)),
+ (len(environment.block_start_string), 'block',
+ e(environment.block_start_string)),
+ (len(environment.variable_start_string), 'variable',
+ e(environment.variable_start_string))
+ ]
+
+ if environment.line_statement_prefix is not None:
+ rules.append((len(environment.line_statement_prefix), 'linestatement',
+ r'^[ \t\v]*' + e(environment.line_statement_prefix)))
+ if environment.line_comment_prefix is not None:
+ rules.append((len(environment.line_comment_prefix), 'linecomment',
+ r'(?:^|(?<=\S))[^\S\r\n]*' +
+ e(environment.line_comment_prefix)))
+
+ return [x[1:] for x in sorted(rules, reverse=True)]
+
+
+class Failure(object):
+ """Class that raises a `TemplateSyntaxError` if called.
+ Used by the `Lexer` to specify known errors.
+ """
+
+ def __init__(self, message, cls=TemplateSyntaxError):
+ self.message = message
+ self.error_class = cls
+
+ def __call__(self, lineno, filename):
+ raise self.error_class(self.message, lineno, filename)
+
+
+class Token(tuple):
+ """Token class."""
+ __slots__ = ()
+ lineno, type, value = (property(itemgetter(x)) for x in range(3))
+
+ def __new__(cls, lineno, type, value):
+ return tuple.__new__(cls, (lineno, intern(str(type)), value))
+
+ def __str__(self):
+ if self.type in reverse_operators:
+ return reverse_operators[self.type]
+ elif self.type == 'name':
+ return self.value
+ return self.type
+
+ def test(self, expr):
+ """Test a token against a token expression. This can either be a
+ token type or ``'token_type:token_value'``. This can only test
+ against string values and types.
+ """
+ # here we do a regular string equality check as test_any is usually
+ # passed an iterable of not interned strings.
+ if self.type == expr:
+ return True
+ elif ':' in expr:
+ return expr.split(':', 1) == [self.type, self.value]
+ return False
+
+ def test_any(self, *iterable):
+ """Test against multiple token expressions."""
+ for expr in iterable:
+ if self.test(expr):
+ return True
+ return False
+
+ def __repr__(self):
+ return 'Token(%r, %r, %r)' % (
+ self.lineno,
+ self.type,
+ self.value
+ )
+
+
+@implements_iterator
+class TokenStreamIterator(object):
+ """The iterator for tokenstreams. Iterate over the stream
+ until the eof token is reached.
+ """
+
+ def __init__(self, stream):
+ self.stream = stream
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ token = self.stream.current
+ if token.type is TOKEN_EOF:
+ self.stream.close()
+ raise StopIteration()
+ next(self.stream)
+ return token
+
+
+@implements_iterator
+class TokenStream(object):
+ """A token stream is an iterable that yields :class:`Token`\s. The
+ parser however does not iterate over it but calls :meth:`next` to go
+ one token ahead. The current active token is stored as :attr:`current`.
+ """
+
+ def __init__(self, generator, name, filename):
+ self._iter = iter(generator)
+ self._pushed = deque()
+ self.name = name
+ self.filename = filename
+ self.closed = False
+ self.current = Token(1, TOKEN_INITIAL, '')
+ next(self)
+
+ def __iter__(self):
+ return TokenStreamIterator(self)
+
+ def __bool__(self):
+ return bool(self._pushed) or self.current.type is not TOKEN_EOF
+ __nonzero__ = __bool__ # py2
+
+ eos = property(lambda x: not x, doc="Are we at the end of the stream?")
+
+ def push(self, token):
+ """Push a token back to the stream."""
+ self._pushed.append(token)
+
+ def look(self):
+ """Look at the next token."""
+ old_token = next(self)
+ result = self.current
+ self.push(result)
+ self.current = old_token
+ return result
+
+ def skip(self, n=1):
+ """Got n tokens ahead."""
+ for x in range(n):
+ next(self)
+
+ def next_if(self, expr):
+ """Perform the token test and return the token if it matched.
+ Otherwise the return value is `None`.
+ """
+ if self.current.test(expr):
+ return next(self)
+
+ def skip_if(self, expr):
+ """Like :meth:`next_if` but only returns `True` or `False`."""
+ return self.next_if(expr) is not None
+
+ def __next__(self):
+ """Go one token ahead and return the old one"""
+ rv = self.current
+ if self._pushed:
+ self.current = self._pushed.popleft()
+ elif self.current.type is not TOKEN_EOF:
+ try:
+ self.current = next(self._iter)
+ except StopIteration:
+ self.close()
+ return rv
+
+ def close(self):
+ """Close the stream."""
+ self.current = Token(self.current.lineno, TOKEN_EOF, '')
+ self._iter = None
+ self.closed = True
+
+ def expect(self, expr):
+ """Expect a given token type and return it. This accepts the same
+ argument as :meth:`jinja2.lexer.Token.test`.
+ """
+ if not self.current.test(expr):
+ expr = describe_token_expr(expr)
+ if self.current.type is TOKEN_EOF:
+ raise TemplateSyntaxError('unexpected end of template, '
+ 'expected %r.' % expr,
+ self.current.lineno,
+ self.name, self.filename)
+ raise TemplateSyntaxError("expected token %r, got %r" %
+ (expr, describe_token(self.current)),
+ self.current.lineno,
+ self.name, self.filename)
+ try:
+ return self.current
+ finally:
+ next(self)
+
+
+def get_lexer(environment):
+ """Return a lexer which is probably cached."""
+ key = (environment.block_start_string,
+ environment.block_end_string,
+ environment.variable_start_string,
+ environment.variable_end_string,
+ environment.comment_start_string,
+ environment.comment_end_string,
+ environment.line_statement_prefix,
+ environment.line_comment_prefix,
+ environment.trim_blocks,
+ environment.lstrip_blocks,
+ environment.newline_sequence,
+ environment.keep_trailing_newline)
+ lexer = _lexer_cache.get(key)
+ if lexer is None:
+ lexer = Lexer(environment)
+ _lexer_cache[key] = lexer
+ return lexer
+
+
+class Lexer(object):
+ """Class that implements a lexer for a given environment. Automatically
+ created by the environment class, usually you don't have to do that.
+
+ Note that the lexer is not automatically bound to an environment.
+ Multiple environments can share the same lexer.
+ """
+
+ def __init__(self, environment):
+ # shortcuts
+ c = lambda x: re.compile(x, re.M | re.S)
+ e = re.escape
+
+ # lexing rules for tags
+ tag_rules = [
+ (whitespace_re, TOKEN_WHITESPACE, None),
+ (float_re, TOKEN_FLOAT, None),
+ (integer_re, TOKEN_INTEGER, None),
+ (name_re, TOKEN_NAME, None),
+ (string_re, TOKEN_STRING, None),
+ (operator_re, TOKEN_OPERATOR, None)
+ ]
+
+ # assemble the root lexing rule. because "|" is ungreedy
+ # we have to sort by length so that the lexer continues working
+ # as expected when we have parsing rules like <% for block and
+ # <%= for variables. (if someone wants asp like syntax)
+ # variables are just part of the rules if variable processing
+ # is required.
+ root_tag_rules = compile_rules(environment)
+
+ # block suffix if trimming is enabled
+ block_suffix_re = environment.trim_blocks and '\\n?' or ''
+
+ # strip leading spaces if lstrip_blocks is enabled
+ prefix_re = {}
+ if environment.lstrip_blocks:
+ # use '{%+' to manually disable lstrip_blocks behavior
+ no_lstrip_re = e('+')
+ # detect overlap between block and variable or comment strings
+ block_diff = c(r'^%s(.*)' % e(environment.block_start_string))
+ # make sure we don't mistake a block for a variable or a comment
+ m = block_diff.match(environment.comment_start_string)
+ no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
+ m = block_diff.match(environment.variable_start_string)
+ no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
+
+ # detect overlap between comment and variable strings
+ comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string))
+ m = comment_diff.match(environment.variable_start_string)
+ no_variable_re = m and r'(?!%s)' % e(m.group(1)) or ''
+
+ lstrip_re = r'^[ \t]*'
+ block_prefix_re = r'%s%s(?!%s)|%s\+?' % (
+ lstrip_re,
+ e(environment.block_start_string),
+ no_lstrip_re,
+ e(environment.block_start_string),
+ )
+ comment_prefix_re = r'%s%s%s|%s\+?' % (
+ lstrip_re,
+ e(environment.comment_start_string),
+ no_variable_re,
+ e(environment.comment_start_string),
+ )
+ prefix_re['block'] = block_prefix_re
+ prefix_re['comment'] = comment_prefix_re
+ else:
+ block_prefix_re = '%s' % e(environment.block_start_string)
+
+ self.newline_sequence = environment.newline_sequence
+ self.keep_trailing_newline = environment.keep_trailing_newline
+
+ # global lexing rules
+ self.rules = {
+ 'root': [
+ # directives
+ (c('(.*?)(?:%s)' % '|'.join(
+ [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
+ e(environment.block_start_string),
+ block_prefix_re,
+ e(environment.block_end_string),
+ e(environment.block_end_string)
+ )] + [
+ r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r))
+ for n, r in root_tag_rules
+ ])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
+ # data
+ (c('.+'), TOKEN_DATA, None)
+ ],
+ # comments
+ TOKEN_COMMENT_BEGIN: [
+ (c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
+ e(environment.comment_end_string),
+ e(environment.comment_end_string),
+ block_suffix_re
+ )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
+ (c('(.)'), (Failure('Missing end of comment tag'),), None)
+ ],
+ # blocks
+ TOKEN_BLOCK_BEGIN: [
+ (c('(?:\-%s\s*|%s)%s' % (
+ e(environment.block_end_string),
+ e(environment.block_end_string),
+ block_suffix_re
+ )), TOKEN_BLOCK_END, '#pop'),
+ ] + tag_rules,
+ # variables
+ TOKEN_VARIABLE_BEGIN: [
+ (c('\-%s\s*|%s' % (
+ e(environment.variable_end_string),
+ e(environment.variable_end_string)
+ )), TOKEN_VARIABLE_END, '#pop')
+ ] + tag_rules,
+ # raw block
+ TOKEN_RAW_BEGIN: [
+ (c('(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
+ e(environment.block_start_string),
+ block_prefix_re,
+ e(environment.block_end_string),
+ e(environment.block_end_string),
+ block_suffix_re
+ )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
+ (c('(.)'), (Failure('Missing end of raw directive'),), None)
+ ],
+ # line statements
+ TOKEN_LINESTATEMENT_BEGIN: [
+ (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
+ ] + tag_rules,
+ # line comments
+ TOKEN_LINECOMMENT_BEGIN: [
+ (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
+ TOKEN_LINECOMMENT_END), '#pop')
+ ]
+ }
+
+ def _normalize_newlines(self, value):
+ """Called for strings and template data to normalize it to unicode."""
+ return newline_re.sub(self.newline_sequence, value)
+
+ def tokenize(self, source, name=None, filename=None, state=None):
+ """Calls tokeniter + tokenize and wraps it in a token stream.
+ """
+ stream = self.tokeniter(source, name, filename, state)
+ return TokenStream(self.wrap(stream, name, filename), name, filename)
+
+ def wrap(self, stream, name=None, filename=None):
+ """This is called with the stream as returned by `tokenize` and wraps
+ every token in a :class:`Token` and converts the value.
+ """
+ for lineno, token, value in stream:
+ if token in ignored_tokens:
+ continue
+ elif token == 'linestatement_begin':
+ token = 'block_begin'
+ elif token == 'linestatement_end':
+ token = 'block_end'
+ # we are not interested in those tokens in the parser
+ elif token in ('raw_begin', 'raw_end'):
+ continue
+ elif token == 'data':
+ value = self._normalize_newlines(value)
+ elif token == 'keyword':
+ token = value
+ elif token == 'name':
+ value = str(value)
+ elif token == 'string':
+ # try to unescape string
+ try:
+ value = self._normalize_newlines(value[1:-1]) \
+ .encode('ascii', 'backslashreplace') \
+ .decode('unicode-escape')
+ except Exception as e:
+ msg = str(e).split(':')[-1].strip()
+ raise TemplateSyntaxError(msg, lineno, name, filename)
+ # if we can express it as bytestring (ascii only)
+ # we do that for support of semi broken APIs
+ # as datetime.datetime.strftime. On python 3 this
+ # call becomes a noop thanks to 2to3
+ try:
+ value = str(value)
+ except UnicodeError:
+ pass
+ elif token == 'integer':
+ value = int(value)
+ elif token == 'float':
+ value = float(value)
+ elif token == 'operator':
+ token = operators[value]
+ yield Token(lineno, token, value)
+
+ def tokeniter(self, source, name, filename=None, state=None):
+ """This method tokenizes the text and returns the tokens in a
+ generator. Use this method if you just want to tokenize a template.
+ """
+ source = text_type(source)
+ lines = source.splitlines()
+ if self.keep_trailing_newline and source:
+ for newline in ('\r\n', '\r', '\n'):
+ if source.endswith(newline):
+ lines.append('')
+ break
+ source = '\n'.join(lines)
+ pos = 0
+ lineno = 1
+ stack = ['root']
+ if state is not None and state != 'root':
+ assert state in ('variable', 'block'), 'invalid state'
+ stack.append(state + '_begin')
+ else:
+ state = 'root'
+ statetokens = self.rules[stack[-1]]
+ source_length = len(source)
+
+ balancing_stack = []
+
+ while 1:
+ # tokenizer loop
+ for regex, tokens, new_state in statetokens:
+ m = regex.match(source, pos)
+ # if no match we try again with the next rule
+ if m is None:
+ continue
+
+ # we only match blocks and variables if braces / parentheses
+ # are balanced. continue parsing with the lower rule which
+ # is the operator rule. do this only if the end tags look
+ # like operators
+ if balancing_stack and \
+ tokens in ('variable_end', 'block_end',
+ 'linestatement_end'):
+ continue
+
+ # tuples support more options
+ if isinstance(tokens, tuple):
+ for idx, token in enumerate(tokens):
+ # failure group
+ if token.__class__ is Failure:
+ raise token(lineno, filename)
+ # bygroup is a bit more complex, in that case we
+ # yield for the current token the first named
+ # group that matched
+ elif token == '#bygroup':
+ for key, value in iteritems(m.groupdict()):
+ if value is not None:
+ yield lineno, key, value
+ lineno += value.count('\n')
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve '
+ 'the token dynamically'
+ ' but no group matched'
+ % regex)
+ # normal group
+ else:
+ data = m.group(idx + 1)
+ if data or token not in ignore_if_empty:
+ yield lineno, token, data
+ lineno += data.count('\n')
+
+ # strings as token just are yielded as it.
+ else:
+ data = m.group()
+ # update brace/parentheses balance
+ if tokens == 'operator':
+ if data == '{':
+ balancing_stack.append('}')
+ elif data == '(':
+ balancing_stack.append(')')
+ elif data == '[':
+ balancing_stack.append(']')
+ elif data in ('}', ')', ']'):
+ if not balancing_stack:
+ raise TemplateSyntaxError('unexpected \'%s\'' %
+ data, lineno, name,
+ filename)
+ expected_op = balancing_stack.pop()
+ if expected_op != data:
+ raise TemplateSyntaxError('unexpected \'%s\', '
+ 'expected \'%s\'' %
+ (data, expected_op),
+ lineno, name,
+ filename)
+ # yield items
+ if data or tokens not in ignore_if_empty:
+ yield lineno, tokens, data
+ lineno += data.count('\n')
+
+ # fetch new position into new variable so that we can check
+ # if there is a internal parsing error which would result
+ # in an infinite loop
+ pos2 = m.end()
+
+ # handle state changes
+ if new_state is not None:
+ # remove the uppermost state
+ if new_state == '#pop':
+ stack.pop()
+ # resolve the new state by group checking
+ elif new_state == '#bygroup':
+ for key, value in iteritems(m.groupdict()):
+ if value is not None:
+ stack.append(key)
+ break
+ else:
+ raise RuntimeError('%r wanted to resolve the '
+ 'new state dynamically but'
+ ' no group matched' %
+ regex)
+ # direct state name given
+ else:
+ stack.append(new_state)
+ statetokens = self.rules[stack[-1]]
+ # we are still at the same position and no stack change.
+ # this means a loop without break condition, avoid that and
+ # raise error
+ elif pos2 == pos:
+ raise RuntimeError('%r yielded empty string without '
+ 'stack change' % regex)
+ # publish new function and start again
+ pos = pos2
+ break
+ # if loop terminated without break we haven't found a single match
+ # either we are at the end of the file or we have a problem
+ else:
+ # end of text
+ if pos >= source_length:
+ return
+ # something went wrong
+ raise TemplateSyntaxError('unexpected char %r at %d' %
+ (source[pos], pos), lineno,
+ name, filename)
diff --git a/pyload/lib/jinja2/loaders.py b/pyload/lib/jinja2/loaders.py
new file mode 100644
index 000000000..cc9c6836e
--- /dev/null
+++ b/pyload/lib/jinja2/loaders.py
@@ -0,0 +1,471 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.loaders
+ ~~~~~~~~~~~~~~
+
+ Jinja loader classes.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+import weakref
+from types import ModuleType
+from os import path
+from hashlib import sha1
+from jinja2.exceptions import TemplateNotFound
+from jinja2.utils import open_if_exists, internalcode
+from jinja2._compat import string_types, iteritems
+
+
+def split_template_path(template):
+ """Split a path into segments and perform a sanity check. If it detects
+ '..' in the path it will raise a `TemplateNotFound` error.
+ """
+ pieces = []
+ for piece in template.split('/'):
+ if path.sep in piece \
+ or (path.altsep and path.altsep in piece) or \
+ piece == path.pardir:
+ raise TemplateNotFound(template)
+ elif piece and piece != '.':
+ pieces.append(piece)
+ return pieces
+
+
+class BaseLoader(object):
+ """Baseclass for all loaders. Subclass this and override `get_source` to
+ implement a custom loading mechanism. The environment provides a
+ `get_template` method that calls the loader's `load` method to get the
+ :class:`Template` object.
+
+ A very basic example for a loader that looks up templates on the file
+ system could look like this::
+
+ from jinja2 import BaseLoader, TemplateNotFound
+ from os.path import join, exists, getmtime
+
+ class MyLoader(BaseLoader):
+
+ def __init__(self, path):
+ self.path = path
+
+ def get_source(self, environment, template):
+ path = join(self.path, template)
+ if not exists(path):
+ raise TemplateNotFound(template)
+ mtime = getmtime(path)
+ with file(path) as f:
+ source = f.read().decode('utf-8')
+ return source, path, lambda: mtime == getmtime(path)
+ """
+
+ #: if set to `False` it indicates that the loader cannot provide access
+ #: to the source of templates.
+ #:
+ #: .. versionadded:: 2.4
+ has_source_access = True
+
+ def get_source(self, environment, template):
+ """Get the template source, filename and reload helper for a template.
+ It's passed the environment and template name and has to return a
+ tuple in the form ``(source, filename, uptodate)`` or raise a
+ `TemplateNotFound` error if it can't locate the template.
+
+ The source part of the returned tuple must be the source of the
+ template as unicode string or a ASCII bytestring. The filename should
+ be the name of the file on the filesystem if it was loaded from there,
+ otherwise `None`. The filename is used by python for the tracebacks
+ if no loader extension is used.
+
+ The last item in the tuple is the `uptodate` function. If auto
+ reloading is enabled it's always called to check if the template
+ changed. No arguments are passed so the function must store the
+ old state somewhere (for example in a closure). If it returns `False`
+ the template will be reloaded.
+ """
+ if not self.has_source_access:
+ raise RuntimeError('%s cannot provide access to the source' %
+ self.__class__.__name__)
+ raise TemplateNotFound(template)
+
+ def list_templates(self):
+ """Iterates over all templates. If the loader does not support that
+ it should raise a :exc:`TypeError` which is the default behavior.
+ """
+ raise TypeError('this loader cannot iterate over all templates')
+
+ @internalcode
+ def load(self, environment, name, globals=None):
+ """Loads a template. This method looks up the template in the cache
+ or loads one by calling :meth:`get_source`. Subclasses should not
+ override this method as loaders working on collections of other
+ loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
+ will not call this method but `get_source` directly.
+ """
+ code = None
+ if globals is None:
+ globals = {}
+
+ # first we try to get the source for this template together
+ # with the filename and the uptodate function.
+ source, filename, uptodate = self.get_source(environment, name)
+
+ # try to load the code from the bytecode cache if there is a
+ # bytecode cache configured.
+ bcc = environment.bytecode_cache
+ if bcc is not None:
+ bucket = bcc.get_bucket(environment, name, filename, source)
+ code = bucket.code
+
+ # if we don't have code so far (not cached, no longer up to
+ # date) etc. we compile the template
+ if code is None:
+ code = environment.compile(source, name, filename)
+
+ # if the bytecode cache is available and the bucket doesn't
+ # have a code so far, we give the bucket the new code and put
+ # it back to the bytecode cache.
+ if bcc is not None and bucket.code is None:
+ bucket.code = code
+ bcc.set_bucket(bucket)
+
+ return environment.template_class.from_code(environment, code,
+ globals, uptodate)
+
+
+class FileSystemLoader(BaseLoader):
+ """Loads templates from the file system. This loader can find templates
+ in folders on the file system and is the preferred way to load them.
+
+ The loader takes the path to the templates as string, or if multiple
+ locations are wanted a list of them which is then looked up in the
+ given order:
+
+ >>> loader = FileSystemLoader('/path/to/templates')
+ >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
+
+ Per default the template encoding is ``'utf-8'`` which can be changed
+ by setting the `encoding` parameter to something else.
+ """
+
+ def __init__(self, searchpath, encoding='utf-8'):
+ if isinstance(searchpath, string_types):
+ searchpath = [searchpath]
+ self.searchpath = list(searchpath)
+ self.encoding = encoding
+
+ def get_source(self, environment, template):
+ pieces = split_template_path(template)
+ for searchpath in self.searchpath:
+ filename = path.join(searchpath, *pieces)
+ f = open_if_exists(filename)
+ if f is None:
+ continue
+ try:
+ contents = f.read().decode(self.encoding)
+ finally:
+ f.close()
+
+ mtime = path.getmtime(filename)
+ def uptodate():
+ try:
+ return path.getmtime(filename) == mtime
+ except OSError:
+ return False
+ return contents, filename, uptodate
+ raise TemplateNotFound(template)
+
+ def list_templates(self):
+ found = set()
+ for searchpath in self.searchpath:
+ for dirpath, dirnames, filenames in os.walk(searchpath):
+ for filename in filenames:
+ template = os.path.join(dirpath, filename) \
+ [len(searchpath):].strip(os.path.sep) \
+ .replace(os.path.sep, '/')
+ if template[:2] == './':
+ template = template[2:]
+ if template not in found:
+ found.add(template)
+ return sorted(found)
+
+
+class PackageLoader(BaseLoader):
+ """Load templates from python eggs or packages. It is constructed with
+ the name of the python package and the path to the templates in that
+ package::
+
+ loader = PackageLoader('mypackage', 'views')
+
+ If the package path is not given, ``'templates'`` is assumed.
+
+ Per default the template encoding is ``'utf-8'`` which can be changed
+ by setting the `encoding` parameter to something else. Due to the nature
+ of eggs it's only possible to reload templates if the package was loaded
+ from the file system and not a zip file.
+ """
+
+ def __init__(self, package_name, package_path='templates',
+ encoding='utf-8'):
+ from pkg_resources import DefaultProvider, ResourceManager, \
+ get_provider
+ provider = get_provider(package_name)
+ self.encoding = encoding
+ self.manager = ResourceManager()
+ self.filesystem_bound = isinstance(provider, DefaultProvider)
+ self.provider = provider
+ self.package_path = package_path
+
+ def get_source(self, environment, template):
+ pieces = split_template_path(template)
+ p = '/'.join((self.package_path,) + tuple(pieces))
+ if not self.provider.has_resource(p):
+ raise TemplateNotFound(template)
+
+ filename = uptodate = None
+ if self.filesystem_bound:
+ filename = self.provider.get_resource_filename(self.manager, p)
+ mtime = path.getmtime(filename)
+ def uptodate():
+ try:
+ return path.getmtime(filename) == mtime
+ except OSError:
+ return False
+
+ source = self.provider.get_resource_string(self.manager, p)
+ return source.decode(self.encoding), filename, uptodate
+
+ def list_templates(self):
+ path = self.package_path
+ if path[:2] == './':
+ path = path[2:]
+ elif path == '.':
+ path = ''
+ offset = len(path)
+ results = []
+ def _walk(path):
+ for filename in self.provider.resource_listdir(path):
+ fullname = path + '/' + filename
+ if self.provider.resource_isdir(fullname):
+ _walk(fullname)
+ else:
+ results.append(fullname[offset:].lstrip('/'))
+ _walk(path)
+ results.sort()
+ return results
+
+
+class DictLoader(BaseLoader):
+ """Loads a template from a python dict. It's passed a dict of unicode
+ strings bound to template names. This loader is useful for unittesting:
+
+ >>> loader = DictLoader({'index.html': 'source here'})
+
+ Because auto reloading is rarely useful this is disabled per default.
+ """
+
+ def __init__(self, mapping):
+ self.mapping = mapping
+
+ def get_source(self, environment, template):
+ if template in self.mapping:
+ source = self.mapping[template]
+ return source, None, lambda: source == self.mapping.get(template)
+ raise TemplateNotFound(template)
+
+ def list_templates(self):
+ return sorted(self.mapping)
+
+
+class FunctionLoader(BaseLoader):
+ """A loader that is passed a function which does the loading. The
+ function becomes the name of the template passed and has to return either
+ an unicode string with the template source, a tuple in the form ``(source,
+ filename, uptodatefunc)`` or `None` if the template does not exist.
+
+ >>> def load_template(name):
+ ... if name == 'index.html':
+ ... return '...'
+ ...
+ >>> loader = FunctionLoader(load_template)
+
+ The `uptodatefunc` is a function that is called if autoreload is enabled
+ and has to return `True` if the template is still up to date. For more
+ details have a look at :meth:`BaseLoader.get_source` which has the same
+ return value.
+ """
+
+ def __init__(self, load_func):
+ self.load_func = load_func
+
+ def get_source(self, environment, template):
+ rv = self.load_func(template)
+ if rv is None:
+ raise TemplateNotFound(template)
+ elif isinstance(rv, string_types):
+ return rv, None, None
+ return rv
+
+
+class PrefixLoader(BaseLoader):
+ """A loader that is passed a dict of loaders where each loader is bound
+ to a prefix. The prefix is delimited from the template by a slash per
+ default, which can be changed by setting the `delimiter` argument to
+ something else::
+
+ loader = PrefixLoader({
+ 'app1': PackageLoader('mypackage.app1'),
+ 'app2': PackageLoader('mypackage.app2')
+ })
+
+ By loading ``'app1/index.html'`` the file from the app1 package is loaded,
+ by loading ``'app2/index.html'`` the file from the second.
+ """
+
+ def __init__(self, mapping, delimiter='/'):
+ self.mapping = mapping
+ self.delimiter = delimiter
+
+ def get_loader(self, template):
+ try:
+ prefix, name = template.split(self.delimiter, 1)
+ loader = self.mapping[prefix]
+ except (ValueError, KeyError):
+ raise TemplateNotFound(template)
+ return loader, name
+
+ def get_source(self, environment, template):
+ loader, name = self.get_loader(template)
+ try:
+ return loader.get_source(environment, name)
+ except TemplateNotFound:
+ # re-raise the exception with the correct fileame here.
+ # (the one that includes the prefix)
+ raise TemplateNotFound(template)
+
+ @internalcode
+ def load(self, environment, name, globals=None):
+ loader, local_name = self.get_loader(name)
+ try:
+ return loader.load(environment, local_name, globals)
+ except TemplateNotFound:
+ # re-raise the exception with the correct fileame here.
+ # (the one that includes the prefix)
+ raise TemplateNotFound(name)
+
+ def list_templates(self):
+ result = []
+ for prefix, loader in iteritems(self.mapping):
+ for template in loader.list_templates():
+ result.append(prefix + self.delimiter + template)
+ return result
+
+
+class ChoiceLoader(BaseLoader):
+ """This loader works like the `PrefixLoader` just that no prefix is
+ specified. If a template could not be found by one loader the next one
+ is tried.
+
+ >>> loader = ChoiceLoader([
+ ... FileSystemLoader('/path/to/user/templates'),
+ ... FileSystemLoader('/path/to/system/templates')
+ ... ])
+
+ This is useful if you want to allow users to override builtin templates
+ from a different location.
+ """
+
+ def __init__(self, loaders):
+ self.loaders = loaders
+
+ def get_source(self, environment, template):
+ for loader in self.loaders:
+ try:
+ return loader.get_source(environment, template)
+ except TemplateNotFound:
+ pass
+ raise TemplateNotFound(template)
+
+ @internalcode
+ def load(self, environment, name, globals=None):
+ for loader in self.loaders:
+ try:
+ return loader.load(environment, name, globals)
+ except TemplateNotFound:
+ pass
+ raise TemplateNotFound(name)
+
+ def list_templates(self):
+ found = set()
+ for loader in self.loaders:
+ found.update(loader.list_templates())
+ return sorted(found)
+
+
+class _TemplateModule(ModuleType):
+ """Like a normal module but with support for weak references"""
+
+
+class ModuleLoader(BaseLoader):
+ """This loader loads templates from precompiled templates.
+
+ Example usage:
+
+ >>> loader = ChoiceLoader([
+ ... ModuleLoader('/path/to/compiled/templates'),
+ ... FileSystemLoader('/path/to/templates')
+ ... ])
+
+ Templates can be precompiled with :meth:`Environment.compile_templates`.
+ """
+
+ has_source_access = False
+
+ def __init__(self, path):
+ package_name = '_jinja2_module_templates_%x' % id(self)
+
+ # create a fake module that looks for the templates in the
+ # path given.
+ mod = _TemplateModule(package_name)
+ if isinstance(path, string_types):
+ path = [path]
+ else:
+ path = list(path)
+ mod.__path__ = path
+
+ sys.modules[package_name] = weakref.proxy(mod,
+ lambda x: sys.modules.pop(package_name, None))
+
+ # the only strong reference, the sys.modules entry is weak
+ # so that the garbage collector can remove it once the
+ # loader that created it goes out of business.
+ self.module = mod
+ self.package_name = package_name
+
+ @staticmethod
+ def get_template_key(name):
+ return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
+
+ @staticmethod
+ def get_module_filename(name):
+ return ModuleLoader.get_template_key(name) + '.py'
+
+ @internalcode
+ def load(self, environment, name, globals=None):
+ key = self.get_template_key(name)
+ module = '%s.%s' % (self.package_name, key)
+ mod = getattr(self.module, module, None)
+ if mod is None:
+ try:
+ mod = __import__(module, None, None, ['root'])
+ except ImportError:
+ raise TemplateNotFound(name)
+
+ # remove the entry from sys.modules, we only want the attribute
+ # on the module object we have stored on the loader.
+ sys.modules.pop(module, None)
+
+ return environment.template_class.from_module_dict(
+ environment, mod.__dict__, globals)
diff --git a/pyload/lib/jinja2/meta.py b/pyload/lib/jinja2/meta.py
new file mode 100644
index 000000000..3110cff60
--- /dev/null
+++ b/pyload/lib/jinja2/meta.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.meta
+ ~~~~~~~~~~~
+
+ This module implements various functions that exposes information about
+ templates that might be interesting for various kinds of applications.
+
+ :copyright: (c) 2010 by the Jinja Team, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja2 import nodes
+from jinja2.compiler import CodeGenerator
+from jinja2._compat import string_types
+
+
+class TrackingCodeGenerator(CodeGenerator):
+ """We abuse the code generator for introspection."""
+
+ def __init__(self, environment):
+ CodeGenerator.__init__(self, environment, '<introspection>',
+ '<introspection>')
+ self.undeclared_identifiers = set()
+
+ def write(self, x):
+ """Don't write."""
+
+ def pull_locals(self, frame):
+ """Remember all undeclared identifiers."""
+ self.undeclared_identifiers.update(frame.identifiers.undeclared)
+
+
+def find_undeclared_variables(ast):
+ """Returns a set of all variables in the AST that will be looked up from
+ the context at runtime. Because at compile time it's not known which
+ variables will be used depending on the path the execution takes at
+ runtime, all variables are returned.
+
+ >>> from jinja2 import Environment, meta
+ >>> env = Environment()
+ >>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
+ >>> meta.find_undeclared_variables(ast)
+ set(['bar'])
+
+ .. admonition:: Implementation
+
+ Internally the code generator is used for finding undeclared variables.
+ This is good to know because the code generator might raise a
+ :exc:`TemplateAssertionError` during compilation and as a matter of
+ fact this function can currently raise that exception as well.
+ """
+ codegen = TrackingCodeGenerator(ast.environment)
+ codegen.visit(ast)
+ return codegen.undeclared_identifiers
+
+
+def find_referenced_templates(ast):
+ """Finds all the referenced templates from the AST. This will return an
+ iterator over all the hardcoded template extensions, inclusions and
+ imports. If dynamic inheritance or inclusion is used, `None` will be
+ yielded.
+
+ >>> from jinja2 import Environment, meta
+ >>> env = Environment()
+ >>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
+ >>> list(meta.find_referenced_templates(ast))
+ ['layout.html', None]
+
+ This function is useful for dependency tracking. For example if you want
+ to rebuild parts of the website after a layout template has changed.
+ """
+ for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
+ nodes.Include)):
+ if not isinstance(node.template, nodes.Const):
+ # a tuple with some non consts in there
+ if isinstance(node.template, (nodes.Tuple, nodes.List)):
+ for template_name in node.template.items:
+ # something const, only yield the strings and ignore
+ # non-string consts that really just make no sense
+ if isinstance(template_name, nodes.Const):
+ if isinstance(template_name.value, string_types):
+ yield template_name.value
+ # something dynamic in there
+ else:
+ yield None
+ # something dynamic we don't know about here
+ else:
+ yield None
+ continue
+ # constant is a basestring, direct template name
+ if isinstance(node.template.value, string_types):
+ yield node.template.value
+ # a tuple or list (latter *should* not happen) made of consts,
+ # yield the consts that are strings. We could warn here for
+ # non string values
+ elif isinstance(node, nodes.Include) and \
+ isinstance(node.template.value, (tuple, list)):
+ for template_name in node.template.value:
+ if isinstance(template_name, string_types):
+ yield template_name
+ # something else we don't care about, we could warn here
+ else:
+ yield None
diff --git a/pyload/lib/jinja2/nodes.py b/pyload/lib/jinja2/nodes.py
new file mode 100644
index 000000000..c5697e6b5
--- /dev/null
+++ b/pyload/lib/jinja2/nodes.py
@@ -0,0 +1,914 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.nodes
+ ~~~~~~~~~~~~
+
+ This module implements additional nodes derived from the ast base node.
+
+ It also provides some node tree helper functions like `in_lineno` and
+ `get_nodes` used by the parser and translator in order to normalize
+ python and jinja nodes.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import operator
+
+from collections import deque
+from jinja2.utils import Markup
+from jinja2._compat import next, izip, with_metaclass, text_type, \
+ method_type, function_type
+
+
+#: the types we support for context functions
+_context_function_types = (function_type, method_type)
+
+
+_binop_to_func = {
+ '*': operator.mul,
+ '/': operator.truediv,
+ '//': operator.floordiv,
+ '**': operator.pow,
+ '%': operator.mod,
+ '+': operator.add,
+ '-': operator.sub
+}
+
+_uaop_to_func = {
+ 'not': operator.not_,
+ '+': operator.pos,
+ '-': operator.neg
+}
+
+_cmpop_to_func = {
+ 'eq': operator.eq,
+ 'ne': operator.ne,
+ 'gt': operator.gt,
+ 'gteq': operator.ge,
+ 'lt': operator.lt,
+ 'lteq': operator.le,
+ 'in': lambda a, b: a in b,
+ 'notin': lambda a, b: a not in b
+}
+
+
+class Impossible(Exception):
+ """Raised if the node could not perform a requested action."""
+
+
+class NodeType(type):
+ """A metaclass for nodes that handles the field and attribute
+ inheritance. fields and attributes from the parent class are
+ automatically forwarded to the child."""
+
+ def __new__(cls, name, bases, d):
+ for attr in 'fields', 'attributes':
+ storage = []
+ storage.extend(getattr(bases[0], attr, ()))
+ storage.extend(d.get(attr, ()))
+ assert len(bases) == 1, 'multiple inheritance not allowed'
+ assert len(storage) == len(set(storage)), 'layout conflict'
+ d[attr] = tuple(storage)
+ d.setdefault('abstract', False)
+ return type.__new__(cls, name, bases, d)
+
+
+class EvalContext(object):
+ """Holds evaluation time information. Custom attributes can be attached
+ to it in extensions.
+ """
+
+ def __init__(self, environment, template_name=None):
+ self.environment = environment
+ if callable(environment.autoescape):
+ self.autoescape = environment.autoescape(template_name)
+ else:
+ self.autoescape = environment.autoescape
+ self.volatile = False
+
+ def save(self):
+ return self.__dict__.copy()
+
+ def revert(self, old):
+ self.__dict__.clear()
+ self.__dict__.update(old)
+
+
+def get_eval_context(node, ctx):
+ if ctx is None:
+ if node.environment is None:
+ raise RuntimeError('if no eval context is passed, the '
+ 'node must have an attached '
+ 'environment.')
+ return EvalContext(node.environment)
+ return ctx
+
+
+class Node(with_metaclass(NodeType, object)):
+ """Baseclass for all Jinja2 nodes. There are a number of nodes available
+ of different types. There are four major types:
+
+ - :class:`Stmt`: statements
+ - :class:`Expr`: expressions
+ - :class:`Helper`: helper nodes
+ - :class:`Template`: the outermost wrapper node
+
+ All nodes have fields and attributes. Fields may be other nodes, lists,
+ or arbitrary values. Fields are passed to the constructor as regular
+ positional arguments, attributes as keyword arguments. Each node has
+ two attributes: `lineno` (the line number of the node) and `environment`.
+ The `environment` attribute is set at the end of the parsing process for
+ all nodes automatically.
+ """
+ fields = ()
+ attributes = ('lineno', 'environment')
+ abstract = True
+
+ def __init__(self, *fields, **attributes):
+ if self.abstract:
+ raise TypeError('abstract nodes are not instanciable')
+ if fields:
+ if len(fields) != len(self.fields):
+ if not self.fields:
+ raise TypeError('%r takes 0 arguments' %
+ self.__class__.__name__)
+ raise TypeError('%r takes 0 or %d argument%s' % (
+ self.__class__.__name__,
+ len(self.fields),
+ len(self.fields) != 1 and 's' or ''
+ ))
+ for name, arg in izip(self.fields, fields):
+ setattr(self, name, arg)
+ for attr in self.attributes:
+ setattr(self, attr, attributes.pop(attr, None))
+ if attributes:
+ raise TypeError('unknown attribute %r' %
+ next(iter(attributes)))
+
+ def iter_fields(self, exclude=None, only=None):
+ """This method iterates over all fields that are defined and yields
+ ``(key, value)`` tuples. Per default all fields are returned, but
+ it's possible to limit that to some fields by providing the `only`
+ parameter or to exclude some using the `exclude` parameter. Both
+ should be sets or tuples of field names.
+ """
+ for name in self.fields:
+ if (exclude is only is None) or \
+ (exclude is not None and name not in exclude) or \
+ (only is not None and name in only):
+ try:
+ yield name, getattr(self, name)
+ except AttributeError:
+ pass
+
+ def iter_child_nodes(self, exclude=None, only=None):
+ """Iterates over all direct child nodes of the node. This iterates
+ over all fields and yields the values of they are nodes. If the value
+ of a field is a list all the nodes in that list are returned.
+ """
+ for field, item in self.iter_fields(exclude, only):
+ if isinstance(item, list):
+ for n in item:
+ if isinstance(n, Node):
+ yield n
+ elif isinstance(item, Node):
+ yield item
+
+ def find(self, node_type):
+ """Find the first node of a given type. If no such node exists the
+ return value is `None`.
+ """
+ for result in self.find_all(node_type):
+ return result
+
+ def find_all(self, node_type):
+ """Find all the nodes of a given type. If the type is a tuple,
+ the check is performed for any of the tuple items.
+ """
+ for child in self.iter_child_nodes():
+ if isinstance(child, node_type):
+ yield child
+ for result in child.find_all(node_type):
+ yield result
+
+ def set_ctx(self, ctx):
+ """Reset the context of a node and all child nodes. Per default the
+ parser will all generate nodes that have a 'load' context as it's the
+ most common one. This method is used in the parser to set assignment
+ targets and other nodes to a store context.
+ """
+ todo = deque([self])
+ while todo:
+ node = todo.popleft()
+ if 'ctx' in node.fields:
+ node.ctx = ctx
+ todo.extend(node.iter_child_nodes())
+ return self
+
+ def set_lineno(self, lineno, override=False):
+ """Set the line numbers of the node and children."""
+ todo = deque([self])
+ while todo:
+ node = todo.popleft()
+ if 'lineno' in node.attributes:
+ if node.lineno is None or override:
+ node.lineno = lineno
+ todo.extend(node.iter_child_nodes())
+ return self
+
+ def set_environment(self, environment):
+ """Set the environment for all nodes."""
+ todo = deque([self])
+ while todo:
+ node = todo.popleft()
+ node.environment = environment
+ todo.extend(node.iter_child_nodes())
+ return self
+
+ def __eq__(self, other):
+ return type(self) is type(other) and \
+ tuple(self.iter_fields()) == tuple(other.iter_fields())
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ # Restore Python 2 hashing behavior on Python 3
+ __hash__ = object.__hash__
+
+ def __repr__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__,
+ ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
+ arg in self.fields)
+ )
+
+
+class Stmt(Node):
+ """Base node for all statements."""
+ abstract = True
+
+
+class Helper(Node):
+ """Nodes that exist in a specific context only."""
+ abstract = True
+
+
+class Template(Node):
+ """Node that represents a template. This must be the outermost node that
+ is passed to the compiler.
+ """
+ fields = ('body',)
+
+
+class Output(Stmt):
+ """A node that holds multiple expressions which are then printed out.
+ This is used both for the `print` statement and the regular template data.
+ """
+ fields = ('nodes',)
+
+
+class Extends(Stmt):
+ """Represents an extends statement."""
+ fields = ('template',)
+
+
+class For(Stmt):
+ """The for loop. `target` is the target for the iteration (usually a
+ :class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
+ of nodes that are used as loop-body, and `else_` a list of nodes for the
+ `else` block. If no else node exists it has to be an empty list.
+
+ For filtered nodes an expression can be stored as `test`, otherwise `None`.
+ """
+ fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
+
+
+class If(Stmt):
+ """If `test` is true, `body` is rendered, else `else_`."""
+ fields = ('test', 'body', 'else_')
+
+
+class Macro(Stmt):
+ """A macro definition. `name` is the name of the macro, `args` a list of
+ arguments and `defaults` a list of defaults if there are any. `body` is
+ a list of nodes for the macro body.
+ """
+ fields = ('name', 'args', 'defaults', 'body')
+
+
+class CallBlock(Stmt):
+ """Like a macro without a name but a call instead. `call` is called with
+ the unnamed macro as `caller` argument this node holds.
+ """
+ fields = ('call', 'args', 'defaults', 'body')
+
+
+class FilterBlock(Stmt):
+ """Node for filter sections."""
+ fields = ('body', 'filter')
+
+
+class Block(Stmt):
+ """A node that represents a block."""
+ fields = ('name', 'body', 'scoped')
+
+
+class Include(Stmt):
+ """A node that represents the include tag."""
+ fields = ('template', 'with_context', 'ignore_missing')
+
+
+class Import(Stmt):
+ """A node that represents the import tag."""
+ fields = ('template', 'target', 'with_context')
+
+
+class FromImport(Stmt):
+ """A node that represents the from import tag. It's important to not
+ pass unsafe names to the name attribute. The compiler translates the
+ attribute lookups directly into getattr calls and does *not* use the
+ subscript callback of the interface. As exported variables may not
+ start with double underscores (which the parser asserts) this is not a
+ problem for regular Jinja code, but if this node is used in an extension
+ extra care must be taken.
+
+ The list of names may contain tuples if aliases are wanted.
+ """
+ fields = ('template', 'names', 'with_context')
+
+
+class ExprStmt(Stmt):
+ """A statement that evaluates an expression and discards the result."""
+ fields = ('node',)
+
+
+class Assign(Stmt):
+ """Assigns an expression to a target."""
+ fields = ('target', 'node')
+
+
+class Expr(Node):
+ """Baseclass for all expressions."""
+ abstract = True
+
+ def as_const(self, eval_ctx=None):
+ """Return the value of the expression as constant or raise
+ :exc:`Impossible` if this was not possible.
+
+ An :class:`EvalContext` can be provided, if none is given
+ a default context is created which requires the nodes to have
+ an attached environment.
+
+ .. versionchanged:: 2.4
+ the `eval_ctx` parameter was added.
+ """
+ raise Impossible()
+
+ def can_assign(self):
+ """Check if it's possible to assign something to this node."""
+ return False
+
+
+class BinExpr(Expr):
+ """Baseclass for all binary expressions."""
+ fields = ('left', 'right')
+ operator = None
+ abstract = True
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ # intercepted operators cannot be folded at compile time
+ if self.environment.sandboxed and \
+ self.operator in self.environment.intercepted_binops:
+ raise Impossible()
+ f = _binop_to_func[self.operator]
+ try:
+ return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+
+
+class UnaryExpr(Expr):
+ """Baseclass for all unary expressions."""
+ fields = ('node',)
+ operator = None
+ abstract = True
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ # intercepted operators cannot be folded at compile time
+ if self.environment.sandboxed and \
+ self.operator in self.environment.intercepted_unops:
+ raise Impossible()
+ f = _uaop_to_func[self.operator]
+ try:
+ return f(self.node.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+
+
+class Name(Expr):
+ """Looks up a name or stores a value in a name.
+ The `ctx` of the node can be one of the following values:
+
+ - `store`: store a value in the name
+ - `load`: load that name
+ - `param`: like `store` but if the name was defined as function parameter.
+ """
+ fields = ('name', 'ctx')
+
+ def can_assign(self):
+ return self.name not in ('true', 'false', 'none',
+ 'True', 'False', 'None')
+
+
+class Literal(Expr):
+ """Baseclass for literals."""
+ abstract = True
+
+
+class Const(Literal):
+ """All constant values. The parser will return this node for simple
+ constants such as ``42`` or ``"foo"`` but it can be used to store more
+ complex values such as lists too. Only constants with a safe
+ representation (objects where ``eval(repr(x)) == x`` is true).
+ """
+ fields = ('value',)
+
+ def as_const(self, eval_ctx=None):
+ return self.value
+
+ @classmethod
+ def from_untrusted(cls, value, lineno=None, environment=None):
+ """Return a const object if the value is representable as
+ constant value in the generated code, otherwise it will raise
+ an `Impossible` exception.
+ """
+ from .compiler import has_safe_repr
+ if not has_safe_repr(value):
+ raise Impossible()
+ return cls(value, lineno=lineno, environment=environment)
+
+
+class TemplateData(Literal):
+ """A constant template string."""
+ fields = ('data',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ if eval_ctx.autoescape:
+ return Markup(self.data)
+ return self.data
+
+
+class Tuple(Literal):
+ """For loop unpacking and some other things like multiple arguments
+ for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
+ is used for loading the names or storing.
+ """
+ fields = ('items', 'ctx')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return tuple(x.as_const(eval_ctx) for x in self.items)
+
+ def can_assign(self):
+ for item in self.items:
+ if not item.can_assign():
+ return False
+ return True
+
+
+class List(Literal):
+ """Any list literal such as ``[1, 2, 3]``"""
+ fields = ('items',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return [x.as_const(eval_ctx) for x in self.items]
+
+
+class Dict(Literal):
+ """Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
+ :class:`Pair` nodes.
+ """
+ fields = ('items',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return dict(x.as_const(eval_ctx) for x in self.items)
+
+
+class Pair(Helper):
+ """A key, value pair for dicts."""
+ fields = ('key', 'value')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
+
+
+class Keyword(Helper):
+ """A key, value pair for keyword arguments where key is a string."""
+ fields = ('key', 'value')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key, self.value.as_const(eval_ctx)
+
+
+class CondExpr(Expr):
+ """A conditional expression (inline if expression). (``{{
+ foo if bar else baz }}``)
+ """
+ fields = ('test', 'expr1', 'expr2')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if self.test.as_const(eval_ctx):
+ return self.expr1.as_const(eval_ctx)
+
+ # if we evaluate to an undefined object, we better do that at runtime
+ if self.expr2 is None:
+ raise Impossible()
+
+ return self.expr2.as_const(eval_ctx)
+
+
+class Filter(Expr):
+ """This node applies a filter on an expression. `name` is the name of
+ the filter, the rest of the fields are the same as for :class:`Call`.
+
+ If the `node` of a filter is `None` the contents of the last buffer are
+ filtered. Buffers are created by macros and filter blocks.
+ """
+ fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile or self.node is None:
+ raise Impossible()
+ # we have to be careful here because we call filter_ below.
+ # if this variable would be called filter, 2to3 would wrap the
+ # call in a list beause it is assuming we are talking about the
+ # builtin filter function here which no longer returns a list in
+ # python 3. because of that, do not rename filter_ to filter!
+ filter_ = self.environment.filters.get(self.name)
+ if filter_ is None or getattr(filter_, 'contextfilter', False):
+ raise Impossible()
+ obj = self.node.as_const(eval_ctx)
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if getattr(filter_, 'evalcontextfilter', False):
+ args.insert(0, eval_ctx)
+ elif getattr(filter_, 'environmentfilter', False):
+ args.insert(0, self.environment)
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
+ if self.dyn_args is not None:
+ try:
+ args.extend(self.dyn_args.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+ if self.dyn_kwargs is not None:
+ try:
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+ try:
+ return filter_(obj, *args, **kwargs)
+ except Exception:
+ raise Impossible()
+
+
+class Test(Expr):
+ """Applies a test on an expression. `name` is the name of the test, the
+ rest of the fields are the same as for :class:`Call`.
+ """
+ fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+
+
+class Call(Expr):
+ """Calls an expression. `args` is a list of arguments, `kwargs` a list
+ of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
+ and `dyn_kwargs` has to be either `None` or a node that is used as
+ node for dynamic positional (``*args``) or keyword (``**kwargs``)
+ arguments.
+ """
+ fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ obj = self.node.as_const(eval_ctx)
+
+ # don't evaluate context functions
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if isinstance(obj, _context_function_types):
+ if getattr(obj, 'contextfunction', False):
+ raise Impossible()
+ elif getattr(obj, 'evalcontextfunction', False):
+ args.insert(0, eval_ctx)
+ elif getattr(obj, 'environmentfunction', False):
+ args.insert(0, self.environment)
+
+ kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
+ if self.dyn_args is not None:
+ try:
+ args.extend(self.dyn_args.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+ if self.dyn_kwargs is not None:
+ try:
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+ try:
+ return obj(*args, **kwargs)
+ except Exception:
+ raise Impossible()
+
+
+class Getitem(Expr):
+ """Get an attribute or item from an expression and prefer the item."""
+ fields = ('node', 'arg', 'ctx')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if self.ctx != 'load':
+ raise Impossible()
+ try:
+ return self.environment.getitem(self.node.as_const(eval_ctx),
+ self.arg.as_const(eval_ctx))
+ except Exception:
+ raise Impossible()
+
+ def can_assign(self):
+ return False
+
+
+class Getattr(Expr):
+ """Get an attribute or item from an expression that is a ascii-only
+ bytestring and prefer the attribute.
+ """
+ fields = ('node', 'attr', 'ctx')
+
+ def as_const(self, eval_ctx=None):
+ if self.ctx != 'load':
+ raise Impossible()
+ try:
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.environment.getattr(self.node.as_const(eval_ctx),
+ self.attr)
+ except Exception:
+ raise Impossible()
+
+ def can_assign(self):
+ return False
+
+
+class Slice(Expr):
+ """Represents a slice object. This must only be used as argument for
+ :class:`Subscript`.
+ """
+ fields = ('start', 'stop', 'step')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ def const(obj):
+ if obj is None:
+ return None
+ return obj.as_const(eval_ctx)
+ return slice(const(self.start), const(self.stop), const(self.step))
+
+
+class Concat(Expr):
+ """Concatenates the list of expressions provided after converting them to
+ unicode.
+ """
+ fields = ('nodes',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
+
+
+class Compare(Expr):
+ """Compares an expression with some other expressions. `ops` must be a
+ list of :class:`Operand`\s.
+ """
+ fields = ('expr', 'ops')
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ result = value = self.expr.as_const(eval_ctx)
+ try:
+ for op in self.ops:
+ new_value = op.expr.as_const(eval_ctx)
+ result = _cmpop_to_func[op.op](value, new_value)
+ value = new_value
+ except Exception:
+ raise Impossible()
+ return result
+
+
+class Operand(Helper):
+ """Holds an operator and an expression."""
+ fields = ('op', 'expr')
+
+if __debug__:
+ Operand.__doc__ += '\nThe following operators are available: ' + \
+ ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
+ set(_uaop_to_func) | set(_cmpop_to_func)))
+
+
+class Mul(BinExpr):
+ """Multiplies the left with the right node."""
+ operator = '*'
+
+
+class Div(BinExpr):
+ """Divides the left by the right node."""
+ operator = '/'
+
+
+class FloorDiv(BinExpr):
+ """Divides the left by the right node and truncates conver the
+ result into an integer by truncating.
+ """
+ operator = '//'
+
+
+class Add(BinExpr):
+ """Add the left to the right node."""
+ operator = '+'
+
+
+class Sub(BinExpr):
+ """Substract the right from the left node."""
+ operator = '-'
+
+
+class Mod(BinExpr):
+ """Left modulo right."""
+ operator = '%'
+
+
+class Pow(BinExpr):
+ """Left to the power of right."""
+ operator = '**'
+
+
+class And(BinExpr):
+ """Short circuited AND."""
+ operator = 'and'
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
+
+
+class Or(BinExpr):
+ """Short circuited OR."""
+ operator = 'or'
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
+
+
+class Not(UnaryExpr):
+ """Negate the expression."""
+ operator = 'not'
+
+
+class Neg(UnaryExpr):
+ """Make the expression negative."""
+ operator = '-'
+
+
+class Pos(UnaryExpr):
+ """Make the expression positive (noop for most expressions)"""
+ operator = '+'
+
+
+# Helpers for extensions
+
+
+class EnvironmentAttribute(Expr):
+ """Loads an attribute from the environment object. This is useful for
+ extensions that want to call a callback stored on the environment.
+ """
+ fields = ('name',)
+
+
+class ExtensionAttribute(Expr):
+ """Returns the attribute of an extension bound to the environment.
+ The identifier is the identifier of the :class:`Extension`.
+
+ This node is usually constructed by calling the
+ :meth:`~jinja2.ext.Extension.attr` method on an extension.
+ """
+ fields = ('identifier', 'name')
+
+
+class ImportedName(Expr):
+ """If created with an import name the import name is returned on node
+ access. For example ``ImportedName('cgi.escape')`` returns the `escape`
+ function from the cgi module on evaluation. Imports are optimized by the
+ compiler so there is no need to assign them to local variables.
+ """
+ fields = ('importname',)
+
+
+class InternalName(Expr):
+ """An internal name in the compiler. You cannot create these nodes
+ yourself but the parser provides a
+ :meth:`~jinja2.parser.Parser.free_identifier` method that creates
+ a new identifier for you. This identifier is not available from the
+ template and is not threated specially by the compiler.
+ """
+ fields = ('name',)
+
+ def __init__(self):
+ raise TypeError('Can\'t create internal names. Use the '
+ '`free_identifier` method on a parser.')
+
+
+class MarkSafe(Expr):
+ """Mark the wrapped expression as safe (wrap it as `Markup`)."""
+ fields = ('expr',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return Markup(self.expr.as_const(eval_ctx))
+
+
+class MarkSafeIfAutoescape(Expr):
+ """Mark the wrapped expression as safe (wrap it as `Markup`) but
+ only if autoescaping is active.
+
+ .. versionadded:: 2.5
+ """
+ fields = ('expr',)
+
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile:
+ raise Impossible()
+ expr = self.expr.as_const(eval_ctx)
+ if eval_ctx.autoescape:
+ return Markup(expr)
+ return expr
+
+
+class ContextReference(Expr):
+ """Returns the current template context. It can be used like a
+ :class:`Name` node, with a ``'load'`` ctx and will return the
+ current :class:`~jinja2.runtime.Context` object.
+
+ Here an example that assigns the current template name to a
+ variable named `foo`::
+
+ Assign(Name('foo', ctx='store'),
+ Getattr(ContextReference(), 'name'))
+ """
+
+
+class Continue(Stmt):
+ """Continue a loop."""
+
+
+class Break(Stmt):
+ """Break a loop."""
+
+
+class Scope(Stmt):
+ """An artificial scope."""
+ fields = ('body',)
+
+
+class EvalContextModifier(Stmt):
+ """Modifies the eval context. For each option that should be modified,
+ a :class:`Keyword` has to be added to the :attr:`options` list.
+
+ Example to change the `autoescape` setting::
+
+ EvalContextModifier(options=[Keyword('autoescape', Const(True))])
+ """
+ fields = ('options',)
+
+
+class ScopedEvalContextModifier(EvalContextModifier):
+ """Modifies the eval context and reverts it later. Works exactly like
+ :class:`EvalContextModifier` but will only modify the
+ :class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
+ """
+ fields = ('body',)
+
+
+# make sure nobody creates custom nodes
+def _failing_new(*args, **kwargs):
+ raise TypeError('can\'t create custom node types')
+NodeType.__new__ = staticmethod(_failing_new); del _failing_new
diff --git a/module/lib/jinja2/optimizer.py b/pyload/lib/jinja2/optimizer.py
index 00eab115e..00eab115e 100644
--- a/module/lib/jinja2/optimizer.py
+++ b/pyload/lib/jinja2/optimizer.py
diff --git a/pyload/lib/jinja2/parser.py b/pyload/lib/jinja2/parser.py
new file mode 100644
index 000000000..f60cd018c
--- /dev/null
+++ b/pyload/lib/jinja2/parser.py
@@ -0,0 +1,895 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.parser
+ ~~~~~~~~~~~~~
+
+ Implements the template parser.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+from jinja2 import nodes
+from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
+from jinja2.lexer import describe_token, describe_token_expr
+from jinja2._compat import next, imap
+
+
+#: statements that callinto
+_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
+ 'macro', 'include', 'from', 'import',
+ 'set'])
+_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
+
+
+class Parser(object):
+ """This is the central parsing class Jinja2 uses. It's passed to
+ extensions and can be used to parse expressions or statements.
+ """
+
+ def __init__(self, environment, source, name=None, filename=None,
+ state=None):
+ self.environment = environment
+ self.stream = environment._tokenize(source, name, filename, state)
+ self.name = name
+ self.filename = filename
+ self.closed = False
+ self.extensions = {}
+ for extension in environment.iter_extensions():
+ for tag in extension.tags:
+ self.extensions[tag] = extension.parse
+ self._last_identifier = 0
+ self._tag_stack = []
+ self._end_token_stack = []
+
+ def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
+ """Convenience method that raises `exc` with the message, passed
+ line number or last line number as well as the current name and
+ filename.
+ """
+ if lineno is None:
+ lineno = self.stream.current.lineno
+ raise exc(msg, lineno, self.name, self.filename)
+
+ def _fail_ut_eof(self, name, end_token_stack, lineno):
+ expected = []
+ for exprs in end_token_stack:
+ expected.extend(imap(describe_token_expr, exprs))
+ if end_token_stack:
+ currently_looking = ' or '.join(
+ "'%s'" % describe_token_expr(expr)
+ for expr in end_token_stack[-1])
+ else:
+ currently_looking = None
+
+ if name is None:
+ message = ['Unexpected end of template.']
+ else:
+ message = ['Encountered unknown tag \'%s\'.' % name]
+
+ if currently_looking:
+ if name is not None and name in expected:
+ message.append('You probably made a nesting mistake. Jinja '
+ 'is expecting this tag, but currently looking '
+ 'for %s.' % currently_looking)
+ else:
+ message.append('Jinja was looking for the following tags: '
+ '%s.' % currently_looking)
+
+ if self._tag_stack:
+ message.append('The innermost block that needs to be '
+ 'closed is \'%s\'.' % self._tag_stack[-1])
+
+ self.fail(' '.join(message), lineno)
+
+ def fail_unknown_tag(self, name, lineno=None):
+ """Called if the parser encounters an unknown tag. Tries to fail
+ with a human readable error message that could help to identify
+ the problem.
+ """
+ return self._fail_ut_eof(name, self._end_token_stack, lineno)
+
+ def fail_eof(self, end_tokens=None, lineno=None):
+ """Like fail_unknown_tag but for end of template situations."""
+ stack = list(self._end_token_stack)
+ if end_tokens is not None:
+ stack.append(end_tokens)
+ return self._fail_ut_eof(None, stack, lineno)
+
+ def is_tuple_end(self, extra_end_rules=None):
+ """Are we at the end of a tuple?"""
+ if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
+ return True
+ elif extra_end_rules is not None:
+ return self.stream.current.test_any(extra_end_rules)
+ return False
+
+ def free_identifier(self, lineno=None):
+ """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
+ self._last_identifier += 1
+ rv = object.__new__(nodes.InternalName)
+ nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
+ return rv
+
+ def parse_statement(self):
+ """Parse a single statement."""
+ token = self.stream.current
+ if token.type != 'name':
+ self.fail('tag name expected', token.lineno)
+ self._tag_stack.append(token.value)
+ pop_tag = True
+ try:
+ if token.value in _statement_keywords:
+ return getattr(self, 'parse_' + self.stream.current.value)()
+ if token.value == 'call':
+ return self.parse_call_block()
+ if token.value == 'filter':
+ return self.parse_filter_block()
+ ext = self.extensions.get(token.value)
+ if ext is not None:
+ return ext(self)
+
+ # did not work out, remove the token we pushed by accident
+ # from the stack so that the unknown tag fail function can
+ # produce a proper error message.
+ self._tag_stack.pop()
+ pop_tag = False
+ self.fail_unknown_tag(token.value, token.lineno)
+ finally:
+ if pop_tag:
+ self._tag_stack.pop()
+
+ def parse_statements(self, end_tokens, drop_needle=False):
+ """Parse multiple statements into a list until one of the end tokens
+ is reached. This is used to parse the body of statements as it also
+ parses template data if appropriate. The parser checks first if the
+ current token is a colon and skips it if there is one. Then it checks
+ for the block end and parses until if one of the `end_tokens` is
+ reached. Per default the active token in the stream at the end of
+ the call is the matched end token. If this is not wanted `drop_needle`
+ can be set to `True` and the end token is removed.
+ """
+ # the first token may be a colon for python compatibility
+ self.stream.skip_if('colon')
+
+ # in the future it would be possible to add whole code sections
+ # by adding some sort of end of statement token and parsing those here.
+ self.stream.expect('block_end')
+ result = self.subparse(end_tokens)
+
+ # we reached the end of the template too early, the subparser
+ # does not check for this, so we do that now
+ if self.stream.current.type == 'eof':
+ self.fail_eof(end_tokens)
+
+ if drop_needle:
+ next(self.stream)
+ return result
+
+ def parse_set(self):
+ """Parse an assign statement."""
+ lineno = next(self.stream).lineno
+ target = self.parse_assign_target()
+ self.stream.expect('assign')
+ expr = self.parse_tuple()
+ return nodes.Assign(target, expr, lineno=lineno)
+
+ def parse_for(self):
+ """Parse a for loop."""
+ lineno = self.stream.expect('name:for').lineno
+ target = self.parse_assign_target(extra_end_rules=('name:in',))
+ self.stream.expect('name:in')
+ iter = self.parse_tuple(with_condexpr=False,
+ extra_end_rules=('name:recursive',))
+ test = None
+ if self.stream.skip_if('name:if'):
+ test = self.parse_expression()
+ recursive = self.stream.skip_if('name:recursive')
+ body = self.parse_statements(('name:endfor', 'name:else'))
+ if next(self.stream).value == 'endfor':
+ else_ = []
+ else:
+ else_ = self.parse_statements(('name:endfor',), drop_needle=True)
+ return nodes.For(target, iter, body, else_, test,
+ recursive, lineno=lineno)
+
+ def parse_if(self):
+ """Parse an if construct."""
+ node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
+ while 1:
+ node.test = self.parse_tuple(with_condexpr=False)
+ node.body = self.parse_statements(('name:elif', 'name:else',
+ 'name:endif'))
+ token = next(self.stream)
+ if token.test('name:elif'):
+ new_node = nodes.If(lineno=self.stream.current.lineno)
+ node.else_ = [new_node]
+ node = new_node
+ continue
+ elif token.test('name:else'):
+ node.else_ = self.parse_statements(('name:endif',),
+ drop_needle=True)
+ else:
+ node.else_ = []
+ break
+ return result
+
+ def parse_block(self):
+ node = nodes.Block(lineno=next(self.stream).lineno)
+ node.name = self.stream.expect('name').value
+ node.scoped = self.stream.skip_if('name:scoped')
+
+ # common problem people encounter when switching from django
+ # to jinja. we do not support hyphens in block names, so let's
+ # raise a nicer error message in that case.
+ if self.stream.current.type == 'sub':
+ self.fail('Block names in Jinja have to be valid Python '
+ 'identifiers and may not contain hyphens, use an '
+ 'underscore instead.')
+
+ node.body = self.parse_statements(('name:endblock',), drop_needle=True)
+ self.stream.skip_if('name:' + node.name)
+ return node
+
+ def parse_extends(self):
+ node = nodes.Extends(lineno=next(self.stream).lineno)
+ node.template = self.parse_expression()
+ return node
+
+ def parse_import_context(self, node, default):
+ if self.stream.current.test_any('name:with', 'name:without') and \
+ self.stream.look().test('name:context'):
+ node.with_context = next(self.stream).value == 'with'
+ self.stream.skip()
+ else:
+ node.with_context = default
+ return node
+
+ def parse_include(self):
+ node = nodes.Include(lineno=next(self.stream).lineno)
+ node.template = self.parse_expression()
+ if self.stream.current.test('name:ignore') and \
+ self.stream.look().test('name:missing'):
+ node.ignore_missing = True
+ self.stream.skip(2)
+ else:
+ node.ignore_missing = False
+ return self.parse_import_context(node, True)
+
+ def parse_import(self):
+ node = nodes.Import(lineno=next(self.stream).lineno)
+ node.template = self.parse_expression()
+ self.stream.expect('name:as')
+ node.target = self.parse_assign_target(name_only=True).name
+ return self.parse_import_context(node, False)
+
+ def parse_from(self):
+ node = nodes.FromImport(lineno=next(self.stream).lineno)
+ node.template = self.parse_expression()
+ self.stream.expect('name:import')
+ node.names = []
+
+ def parse_context():
+ if self.stream.current.value in ('with', 'without') and \
+ self.stream.look().test('name:context'):
+ node.with_context = next(self.stream).value == 'with'
+ self.stream.skip()
+ return True
+ return False
+
+ while 1:
+ if node.names:
+ self.stream.expect('comma')
+ if self.stream.current.type == 'name':
+ if parse_context():
+ break
+ target = self.parse_assign_target(name_only=True)
+ if target.name.startswith('_'):
+ self.fail('names starting with an underline can not '
+ 'be imported', target.lineno,
+ exc=TemplateAssertionError)
+ if self.stream.skip_if('name:as'):
+ alias = self.parse_assign_target(name_only=True)
+ node.names.append((target.name, alias.name))
+ else:
+ node.names.append(target.name)
+ if parse_context() or self.stream.current.type != 'comma':
+ break
+ else:
+ break
+ if not hasattr(node, 'with_context'):
+ node.with_context = False
+ self.stream.skip_if('comma')
+ return node
+
+ def parse_signature(self, node):
+ node.args = args = []
+ node.defaults = defaults = []
+ self.stream.expect('lparen')
+ while self.stream.current.type != 'rparen':
+ if args:
+ self.stream.expect('comma')
+ arg = self.parse_assign_target(name_only=True)
+ arg.set_ctx('param')
+ if self.stream.skip_if('assign'):
+ defaults.append(self.parse_expression())
+ args.append(arg)
+ self.stream.expect('rparen')
+
+ def parse_call_block(self):
+ node = nodes.CallBlock(lineno=next(self.stream).lineno)
+ if self.stream.current.type == 'lparen':
+ self.parse_signature(node)
+ else:
+ node.args = []
+ node.defaults = []
+
+ node.call = self.parse_expression()
+ if not isinstance(node.call, nodes.Call):
+ self.fail('expected call', node.lineno)
+ node.body = self.parse_statements(('name:endcall',), drop_needle=True)
+ return node
+
+ def parse_filter_block(self):
+ node = nodes.FilterBlock(lineno=next(self.stream).lineno)
+ node.filter = self.parse_filter(None, start_inline=True)
+ node.body = self.parse_statements(('name:endfilter',),
+ drop_needle=True)
+ return node
+
+ def parse_macro(self):
+ node = nodes.Macro(lineno=next(self.stream).lineno)
+ node.name = self.parse_assign_target(name_only=True).name
+ self.parse_signature(node)
+ node.body = self.parse_statements(('name:endmacro',),
+ drop_needle=True)
+ return node
+
+ def parse_print(self):
+ node = nodes.Output(lineno=next(self.stream).lineno)
+ node.nodes = []
+ while self.stream.current.type != 'block_end':
+ if node.nodes:
+ self.stream.expect('comma')
+ node.nodes.append(self.parse_expression())
+ return node
+
+ def parse_assign_target(self, with_tuple=True, name_only=False,
+ extra_end_rules=None):
+ """Parse an assignment target. As Jinja2 allows assignments to
+ tuples, this function can parse all allowed assignment targets. Per
+ default assignments to tuples are parsed, that can be disable however
+ by setting `with_tuple` to `False`. If only assignments to names are
+ wanted `name_only` can be set to `True`. The `extra_end_rules`
+ parameter is forwarded to the tuple parsing function.
+ """
+ if name_only:
+ token = self.stream.expect('name')
+ target = nodes.Name(token.value, 'store', lineno=token.lineno)
+ else:
+ if with_tuple:
+ target = self.parse_tuple(simplified=True,
+ extra_end_rules=extra_end_rules)
+ else:
+ target = self.parse_primary()
+ target.set_ctx('store')
+ if not target.can_assign():
+ self.fail('can\'t assign to %r' % target.__class__.
+ __name__.lower(), target.lineno)
+ return target
+
+ def parse_expression(self, with_condexpr=True):
+ """Parse an expression. Per default all expressions are parsed, if
+ the optional `with_condexpr` parameter is set to `False` conditional
+ expressions are not parsed.
+ """
+ if with_condexpr:
+ return self.parse_condexpr()
+ return self.parse_or()
+
+ def parse_condexpr(self):
+ lineno = self.stream.current.lineno
+ expr1 = self.parse_or()
+ while self.stream.skip_if('name:if'):
+ expr2 = self.parse_or()
+ if self.stream.skip_if('name:else'):
+ expr3 = self.parse_condexpr()
+ else:
+ expr3 = None
+ expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return expr1
+
+ def parse_or(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_and()
+ while self.stream.skip_if('name:or'):
+ right = self.parse_and()
+ left = nodes.Or(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_and(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_not()
+ while self.stream.skip_if('name:and'):
+ right = self.parse_not()
+ left = nodes.And(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_not(self):
+ if self.stream.current.test('name:not'):
+ lineno = next(self.stream).lineno
+ return nodes.Not(self.parse_not(), lineno=lineno)
+ return self.parse_compare()
+
+ def parse_compare(self):
+ lineno = self.stream.current.lineno
+ expr = self.parse_add()
+ ops = []
+ while 1:
+ token_type = self.stream.current.type
+ if token_type in _compare_operators:
+ next(self.stream)
+ ops.append(nodes.Operand(token_type, self.parse_add()))
+ elif self.stream.skip_if('name:in'):
+ ops.append(nodes.Operand('in', self.parse_add()))
+ elif self.stream.current.test('name:not') and \
+ self.stream.look().test('name:in'):
+ self.stream.skip(2)
+ ops.append(nodes.Operand('notin', self.parse_add()))
+ else:
+ break
+ lineno = self.stream.current.lineno
+ if not ops:
+ return expr
+ return nodes.Compare(expr, ops, lineno=lineno)
+
+ def parse_add(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_sub()
+ while self.stream.current.type == 'add':
+ next(self.stream)
+ right = self.parse_sub()
+ left = nodes.Add(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_sub(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_concat()
+ while self.stream.current.type == 'sub':
+ next(self.stream)
+ right = self.parse_concat()
+ left = nodes.Sub(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_concat(self):
+ lineno = self.stream.current.lineno
+ args = [self.parse_mul()]
+ while self.stream.current.type == 'tilde':
+ next(self.stream)
+ args.append(self.parse_mul())
+ if len(args) == 1:
+ return args[0]
+ return nodes.Concat(args, lineno=lineno)
+
+ def parse_mul(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_div()
+ while self.stream.current.type == 'mul':
+ next(self.stream)
+ right = self.parse_div()
+ left = nodes.Mul(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_div(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_floordiv()
+ while self.stream.current.type == 'div':
+ next(self.stream)
+ right = self.parse_floordiv()
+ left = nodes.Div(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_floordiv(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_mod()
+ while self.stream.current.type == 'floordiv':
+ next(self.stream)
+ right = self.parse_mod()
+ left = nodes.FloorDiv(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_mod(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_pow()
+ while self.stream.current.type == 'mod':
+ next(self.stream)
+ right = self.parse_pow()
+ left = nodes.Mod(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_pow(self):
+ lineno = self.stream.current.lineno
+ left = self.parse_unary()
+ while self.stream.current.type == 'pow':
+ next(self.stream)
+ right = self.parse_unary()
+ left = nodes.Pow(left, right, lineno=lineno)
+ lineno = self.stream.current.lineno
+ return left
+
+ def parse_unary(self, with_filter=True):
+ token_type = self.stream.current.type
+ lineno = self.stream.current.lineno
+ if token_type == 'sub':
+ next(self.stream)
+ node = nodes.Neg(self.parse_unary(False), lineno=lineno)
+ elif token_type == 'add':
+ next(self.stream)
+ node = nodes.Pos(self.parse_unary(False), lineno=lineno)
+ else:
+ node = self.parse_primary()
+ node = self.parse_postfix(node)
+ if with_filter:
+ node = self.parse_filter_expr(node)
+ return node
+
+ def parse_primary(self):
+ token = self.stream.current
+ if token.type == 'name':
+ if token.value in ('true', 'false', 'True', 'False'):
+ node = nodes.Const(token.value in ('true', 'True'),
+ lineno=token.lineno)
+ elif token.value in ('none', 'None'):
+ node = nodes.Const(None, lineno=token.lineno)
+ else:
+ node = nodes.Name(token.value, 'load', lineno=token.lineno)
+ next(self.stream)
+ elif token.type == 'string':
+ next(self.stream)
+ buf = [token.value]
+ lineno = token.lineno
+ while self.stream.current.type == 'string':
+ buf.append(self.stream.current.value)
+ next(self.stream)
+ node = nodes.Const(''.join(buf), lineno=lineno)
+ elif token.type in ('integer', 'float'):
+ next(self.stream)
+ node = nodes.Const(token.value, lineno=token.lineno)
+ elif token.type == 'lparen':
+ next(self.stream)
+ node = self.parse_tuple(explicit_parentheses=True)
+ self.stream.expect('rparen')
+ elif token.type == 'lbracket':
+ node = self.parse_list()
+ elif token.type == 'lbrace':
+ node = self.parse_dict()
+ else:
+ self.fail("unexpected '%s'" % describe_token(token), token.lineno)
+ return node
+
+ def parse_tuple(self, simplified=False, with_condexpr=True,
+ extra_end_rules=None, explicit_parentheses=False):
+ """Works like `parse_expression` but if multiple expressions are
+ delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
+ This method could also return a regular expression instead of a tuple
+ if no commas where found.
+
+ The default parsing mode is a full tuple. If `simplified` is `True`
+ only names and literals are parsed. The `no_condexpr` parameter is
+ forwarded to :meth:`parse_expression`.
+
+ Because tuples do not require delimiters and may end in a bogus comma
+ an extra hint is needed that marks the end of a tuple. For example
+ for loops support tuples between `for` and `in`. In that case the
+ `extra_end_rules` is set to ``['name:in']``.
+
+ `explicit_parentheses` is true if the parsing was triggered by an
+ expression in parentheses. This is used to figure out if an empty
+ tuple is a valid expression or not.
+ """
+ lineno = self.stream.current.lineno
+ if simplified:
+ parse = self.parse_primary
+ elif with_condexpr:
+ parse = self.parse_expression
+ else:
+ parse = lambda: self.parse_expression(with_condexpr=False)
+ args = []
+ is_tuple = False
+ while 1:
+ if args:
+ self.stream.expect('comma')
+ if self.is_tuple_end(extra_end_rules):
+ break
+ args.append(parse())
+ if self.stream.current.type == 'comma':
+ is_tuple = True
+ else:
+ break
+ lineno = self.stream.current.lineno
+
+ if not is_tuple:
+ if args:
+ return args[0]
+
+ # if we don't have explicit parentheses, an empty tuple is
+ # not a valid expression. This would mean nothing (literally
+ # nothing) in the spot of an expression would be an empty
+ # tuple.
+ if not explicit_parentheses:
+ self.fail('Expected an expression, got \'%s\'' %
+ describe_token(self.stream.current))
+
+ return nodes.Tuple(args, 'load', lineno=lineno)
+
+ def parse_list(self):
+ token = self.stream.expect('lbracket')
+ items = []
+ while self.stream.current.type != 'rbracket':
+ if items:
+ self.stream.expect('comma')
+ if self.stream.current.type == 'rbracket':
+ break
+ items.append(self.parse_expression())
+ self.stream.expect('rbracket')
+ return nodes.List(items, lineno=token.lineno)
+
+ def parse_dict(self):
+ token = self.stream.expect('lbrace')
+ items = []
+ while self.stream.current.type != 'rbrace':
+ if items:
+ self.stream.expect('comma')
+ if self.stream.current.type == 'rbrace':
+ break
+ key = self.parse_expression()
+ self.stream.expect('colon')
+ value = self.parse_expression()
+ items.append(nodes.Pair(key, value, lineno=key.lineno))
+ self.stream.expect('rbrace')
+ return nodes.Dict(items, lineno=token.lineno)
+
+ def parse_postfix(self, node):
+ while 1:
+ token_type = self.stream.current.type
+ if token_type == 'dot' or token_type == 'lbracket':
+ node = self.parse_subscript(node)
+ # calls are valid both after postfix expressions (getattr
+ # and getitem) as well as filters and tests
+ elif token_type == 'lparen':
+ node = self.parse_call(node)
+ else:
+ break
+ return node
+
+ def parse_filter_expr(self, node):
+ while 1:
+ token_type = self.stream.current.type
+ if token_type == 'pipe':
+ node = self.parse_filter(node)
+ elif token_type == 'name' and self.stream.current.value == 'is':
+ node = self.parse_test(node)
+ # calls are valid both after postfix expressions (getattr
+ # and getitem) as well as filters and tests
+ elif token_type == 'lparen':
+ node = self.parse_call(node)
+ else:
+ break
+ return node
+
+ def parse_subscript(self, node):
+ token = next(self.stream)
+ if token.type == 'dot':
+ attr_token = self.stream.current
+ next(self.stream)
+ if attr_token.type == 'name':
+ return nodes.Getattr(node, attr_token.value, 'load',
+ lineno=token.lineno)
+ elif attr_token.type != 'integer':
+ self.fail('expected name or number', attr_token.lineno)
+ arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
+ return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
+ if token.type == 'lbracket':
+ args = []
+ while self.stream.current.type != 'rbracket':
+ if args:
+ self.stream.expect('comma')
+ args.append(self.parse_subscribed())
+ self.stream.expect('rbracket')
+ if len(args) == 1:
+ arg = args[0]
+ else:
+ arg = nodes.Tuple(args, 'load', lineno=token.lineno)
+ return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
+ self.fail('expected subscript expression', self.lineno)
+
+ def parse_subscribed(self):
+ lineno = self.stream.current.lineno
+
+ if self.stream.current.type == 'colon':
+ next(self.stream)
+ args = [None]
+ else:
+ node = self.parse_expression()
+ if self.stream.current.type != 'colon':
+ return node
+ next(self.stream)
+ args = [node]
+
+ if self.stream.current.type == 'colon':
+ args.append(None)
+ elif self.stream.current.type not in ('rbracket', 'comma'):
+ args.append(self.parse_expression())
+ else:
+ args.append(None)
+
+ if self.stream.current.type == 'colon':
+ next(self.stream)
+ if self.stream.current.type not in ('rbracket', 'comma'):
+ args.append(self.parse_expression())
+ else:
+ args.append(None)
+ else:
+ args.append(None)
+
+ return nodes.Slice(lineno=lineno, *args)
+
+ def parse_call(self, node):
+ token = self.stream.expect('lparen')
+ args = []
+ kwargs = []
+ dyn_args = dyn_kwargs = None
+ require_comma = False
+
+ def ensure(expr):
+ if not expr:
+ self.fail('invalid syntax for function call expression',
+ token.lineno)
+
+ while self.stream.current.type != 'rparen':
+ if require_comma:
+ self.stream.expect('comma')
+ # support for trailing comma
+ if self.stream.current.type == 'rparen':
+ break
+ if self.stream.current.type == 'mul':
+ ensure(dyn_args is None and dyn_kwargs is None)
+ next(self.stream)
+ dyn_args = self.parse_expression()
+ elif self.stream.current.type == 'pow':
+ ensure(dyn_kwargs is None)
+ next(self.stream)
+ dyn_kwargs = self.parse_expression()
+ else:
+ ensure(dyn_args is None and dyn_kwargs is None)
+ if self.stream.current.type == 'name' and \
+ self.stream.look().type == 'assign':
+ key = self.stream.current.value
+ self.stream.skip(2)
+ value = self.parse_expression()
+ kwargs.append(nodes.Keyword(key, value,
+ lineno=value.lineno))
+ else:
+ ensure(not kwargs)
+ args.append(self.parse_expression())
+
+ require_comma = True
+ self.stream.expect('rparen')
+
+ if node is None:
+ return args, kwargs, dyn_args, dyn_kwargs
+ return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
+ lineno=token.lineno)
+
+ def parse_filter(self, node, start_inline=False):
+ while self.stream.current.type == 'pipe' or start_inline:
+ if not start_inline:
+ next(self.stream)
+ token = self.stream.expect('name')
+ name = token.value
+ while self.stream.current.type == 'dot':
+ next(self.stream)
+ name += '.' + self.stream.expect('name').value
+ if self.stream.current.type == 'lparen':
+ args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
+ else:
+ args = []
+ kwargs = []
+ dyn_args = dyn_kwargs = None
+ node = nodes.Filter(node, name, args, kwargs, dyn_args,
+ dyn_kwargs, lineno=token.lineno)
+ start_inline = False
+ return node
+
+ def parse_test(self, node):
+ token = next(self.stream)
+ if self.stream.current.test('name:not'):
+ next(self.stream)
+ negated = True
+ else:
+ negated = False
+ name = self.stream.expect('name').value
+ while self.stream.current.type == 'dot':
+ next(self.stream)
+ name += '.' + self.stream.expect('name').value
+ dyn_args = dyn_kwargs = None
+ kwargs = []
+ if self.stream.current.type == 'lparen':
+ args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
+ elif self.stream.current.type in ('name', 'string', 'integer',
+ 'float', 'lparen', 'lbracket',
+ 'lbrace') and not \
+ self.stream.current.test_any('name:else', 'name:or',
+ 'name:and'):
+ if self.stream.current.test('name:is'):
+ self.fail('You cannot chain multiple tests with is')
+ args = [self.parse_expression()]
+ else:
+ args = []
+ node = nodes.Test(node, name, args, kwargs, dyn_args,
+ dyn_kwargs, lineno=token.lineno)
+ if negated:
+ node = nodes.Not(node, lineno=token.lineno)
+ return node
+
+ def subparse(self, end_tokens=None):
+ body = []
+ data_buffer = []
+ add_data = data_buffer.append
+
+ if end_tokens is not None:
+ self._end_token_stack.append(end_tokens)
+
+ def flush_data():
+ if data_buffer:
+ lineno = data_buffer[0].lineno
+ body.append(nodes.Output(data_buffer[:], lineno=lineno))
+ del data_buffer[:]
+
+ try:
+ while self.stream:
+ token = self.stream.current
+ if token.type == 'data':
+ if token.value:
+ add_data(nodes.TemplateData(token.value,
+ lineno=token.lineno))
+ next(self.stream)
+ elif token.type == 'variable_begin':
+ next(self.stream)
+ add_data(self.parse_tuple(with_condexpr=True))
+ self.stream.expect('variable_end')
+ elif token.type == 'block_begin':
+ flush_data()
+ next(self.stream)
+ if end_tokens is not None and \
+ self.stream.current.test_any(*end_tokens):
+ return body
+ rv = self.parse_statement()
+ if isinstance(rv, list):
+ body.extend(rv)
+ else:
+ body.append(rv)
+ self.stream.expect('block_end')
+ else:
+ raise AssertionError('internal parsing error')
+
+ flush_data()
+ finally:
+ if end_tokens is not None:
+ self._end_token_stack.pop()
+
+ return body
+
+ def parse(self):
+ """Parse the whole template into a `Template` node."""
+ result = nodes.Template(self.subparse(), lineno=1)
+ result.set_environment(self.environment)
+ return result
diff --git a/pyload/lib/jinja2/runtime.py b/pyload/lib/jinja2/runtime.py
new file mode 100644
index 000000000..7791c645a
--- /dev/null
+++ b/pyload/lib/jinja2/runtime.py
@@ -0,0 +1,581 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.runtime
+ ~~~~~~~~~~~~~~
+
+ Runtime helpers.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
+"""
+from itertools import chain
+from jinja2.nodes import EvalContext, _context_function_types
+from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
+ internalcode, object_type_repr
+from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
+ TemplateNotFound
+from jinja2._compat import next, imap, text_type, iteritems, \
+ implements_iterator, implements_to_string, string_types, PY2
+
+
+# these variables are exported to the template runtime
+__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
+ 'TemplateRuntimeError', 'missing', 'concat', 'escape',
+ 'markup_join', 'unicode_join', 'to_string', 'identity',
+ 'TemplateNotFound']
+
+#: the name of the function that is used to convert something into
+#: a string. We can just use the text type here.
+to_string = text_type
+
+#: the identity function. Useful for certain things in the environment
+identity = lambda x: x
+
+_last_iteration = object()
+
+
+def markup_join(seq):
+ """Concatenation that escapes if necessary and converts to unicode."""
+ buf = []
+ iterator = imap(soft_unicode, seq)
+ for arg in iterator:
+ buf.append(arg)
+ if hasattr(arg, '__html__'):
+ return Markup(u'').join(chain(buf, iterator))
+ return concat(buf)
+
+
+def unicode_join(seq):
+ """Simple args to unicode conversion and concatenation."""
+ return concat(imap(text_type, seq))
+
+
+def new_context(environment, template_name, blocks, vars=None,
+ shared=None, globals=None, locals=None):
+ """Internal helper to for context creation."""
+ if vars is None:
+ vars = {}
+ if shared:
+ parent = vars
+ else:
+ parent = dict(globals or (), **vars)
+ if locals:
+ # if the parent is shared a copy should be created because
+ # we don't want to modify the dict passed
+ if shared:
+ parent = dict(parent)
+ for key, value in iteritems(locals):
+ if key[:2] == 'l_' and value is not missing:
+ parent[key[2:]] = value
+ return Context(environment, parent, template_name, blocks)
+
+
+class TemplateReference(object):
+ """The `self` in templates."""
+
+ def __init__(self, context):
+ self.__context = context
+
+ def __getitem__(self, name):
+ blocks = self.__context.blocks[name]
+ return BlockReference(name, self.__context, blocks, 0)
+
+ def __repr__(self):
+ return '<%s %r>' % (
+ self.__class__.__name__,
+ self.__context.name
+ )
+
+
+class Context(object):
+ """The template context holds the variables of a template. It stores the
+ values passed to the template and also the names the template exports.
+ Creating instances is neither supported nor useful as it's created
+ automatically at various stages of the template evaluation and should not
+ be created by hand.
+
+ The context is immutable. Modifications on :attr:`parent` **must not**
+ happen and modifications on :attr:`vars` are allowed from generated
+ template code only. Template filters and global functions marked as
+ :func:`contextfunction`\s get the active context passed as first argument
+ and are allowed to access the context read-only.
+
+ The template context supports read only dict operations (`get`,
+ `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
+ `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
+ method that doesn't fail with a `KeyError` but returns an
+ :class:`Undefined` object for missing variables.
+ """
+ __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
+ 'name', 'blocks', '__weakref__')
+
+ def __init__(self, environment, parent, name, blocks):
+ self.parent = parent
+ self.vars = {}
+ self.environment = environment
+ self.eval_ctx = EvalContext(self.environment, name)
+ self.exported_vars = set()
+ self.name = name
+
+ # create the initial mapping of blocks. Whenever template inheritance
+ # takes place the runtime will update this mapping with the new blocks
+ # from the template.
+ self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
+
+ def super(self, name, current):
+ """Render a parent block."""
+ try:
+ blocks = self.blocks[name]
+ index = blocks.index(current) + 1
+ blocks[index]
+ except LookupError:
+ return self.environment.undefined('there is no parent block '
+ 'called %r.' % name,
+ name='super')
+ return BlockReference(name, self, blocks, index)
+
+ def get(self, key, default=None):
+ """Returns an item from the template context, if it doesn't exist
+ `default` is returned.
+ """
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def resolve(self, key):
+ """Looks up a variable like `__getitem__` or `get` but returns an
+ :class:`Undefined` object with the name of the name looked up.
+ """
+ if key in self.vars:
+ return self.vars[key]
+ if key in self.parent:
+ return self.parent[key]
+ return self.environment.undefined(name=key)
+
+ def get_exported(self):
+ """Get a new dict with the exported variables."""
+ return dict((k, self.vars[k]) for k in self.exported_vars)
+
+ def get_all(self):
+ """Return a copy of the complete context as dict including the
+ exported variables.
+ """
+ return dict(self.parent, **self.vars)
+
+ @internalcode
+ def call(__self, __obj, *args, **kwargs):
+ """Call the callable with the arguments and keyword arguments
+ provided but inject the active context or environment as first
+ argument if the callable is a :func:`contextfunction` or
+ :func:`environmentfunction`.
+ """
+ if __debug__:
+ __traceback_hide__ = True
+
+ # Allow callable classes to take a context
+ fn = __obj.__call__
+ for fn_type in ('contextfunction',
+ 'evalcontextfunction',
+ 'environmentfunction'):
+ if hasattr(fn, fn_type):
+ __obj = fn
+ break
+
+ if isinstance(__obj, _context_function_types):
+ if getattr(__obj, 'contextfunction', 0):
+ args = (__self,) + args
+ elif getattr(__obj, 'evalcontextfunction', 0):
+ args = (__self.eval_ctx,) + args
+ elif getattr(__obj, 'environmentfunction', 0):
+ args = (__self.environment,) + args
+ try:
+ return __obj(*args, **kwargs)
+ except StopIteration:
+ return __self.environment.undefined('value was undefined because '
+ 'a callable raised a '
+ 'StopIteration exception')
+
+ def derived(self, locals=None):
+ """Internal helper function to create a derived context."""
+ context = new_context(self.environment, self.name, {},
+ self.parent, True, None, locals)
+ context.vars.update(self.vars)
+ context.eval_ctx = self.eval_ctx
+ context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
+ return context
+
+ def _all(meth):
+ proxy = lambda self: getattr(self.get_all(), meth)()
+ proxy.__doc__ = getattr(dict, meth).__doc__
+ proxy.__name__ = meth
+ return proxy
+
+ keys = _all('keys')
+ values = _all('values')
+ items = _all('items')
+
+ # not available on python 3
+ if PY2:
+ iterkeys = _all('iterkeys')
+ itervalues = _all('itervalues')
+ iteritems = _all('iteritems')
+ del _all
+
+ def __contains__(self, name):
+ return name in self.vars or name in self.parent
+
+ def __getitem__(self, key):
+ """Lookup a variable or raise `KeyError` if the variable is
+ undefined.
+ """
+ item = self.resolve(key)
+ if isinstance(item, Undefined):
+ raise KeyError(key)
+ return item
+
+ def __repr__(self):
+ return '<%s %s of %r>' % (
+ self.__class__.__name__,
+ repr(self.get_all()),
+ self.name
+ )
+
+
+# register the context as mapping if possible
+try:
+ from collections import Mapping
+ Mapping.register(Context)
+except ImportError:
+ pass
+
+
+class BlockReference(object):
+ """One block on a template reference."""
+
+ def __init__(self, name, context, stack, depth):
+ self.name = name
+ self._context = context
+ self._stack = stack
+ self._depth = depth
+
+ @property
+ def super(self):
+ """Super the block."""
+ if self._depth + 1 >= len(self._stack):
+ return self._context.environment. \
+ undefined('there is no parent block called %r.' %
+ self.name, name='super')
+ return BlockReference(self.name, self._context, self._stack,
+ self._depth + 1)
+
+ @internalcode
+ def __call__(self):
+ rv = concat(self._stack[self._depth](self._context))
+ if self._context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv
+
+
+class LoopContext(object):
+ """A loop context for dynamic iteration."""
+
+ def __init__(self, iterable, recurse=None, depth0=0):
+ self._iterator = iter(iterable)
+ self._recurse = recurse
+ self._after = self._safe_next()
+ self.index0 = -1
+ self.depth0 = depth0
+
+ # try to get the length of the iterable early. This must be done
+ # here because there are some broken iterators around where there
+ # __len__ is the number of iterations left (i'm looking at your
+ # listreverseiterator!).
+ try:
+ self._length = len(iterable)
+ except (TypeError, AttributeError):
+ self._length = None
+
+ def cycle(self, *args):
+ """Cycles among the arguments with the current loop index."""
+ if not args:
+ raise TypeError('no items for cycling given')
+ return args[self.index0 % len(args)]
+
+ first = property(lambda x: x.index0 == 0)
+ last = property(lambda x: x._after is _last_iteration)
+ index = property(lambda x: x.index0 + 1)
+ revindex = property(lambda x: x.length - x.index0)
+ revindex0 = property(lambda x: x.length - x.index)
+ depth = property(lambda x: x.depth0 + 1)
+
+ def __len__(self):
+ return self.length
+
+ def __iter__(self):
+ return LoopContextIterator(self)
+
+ def _safe_next(self):
+ try:
+ return next(self._iterator)
+ except StopIteration:
+ return _last_iteration
+
+ @internalcode
+ def loop(self, iterable):
+ if self._recurse is None:
+ raise TypeError('Tried to call non recursive loop. Maybe you '
+ "forgot the 'recursive' modifier.")
+ return self._recurse(iterable, self._recurse, self.depth0 + 1)
+
+ # a nifty trick to enhance the error message if someone tried to call
+ # the the loop without or with too many arguments.
+ __call__ = loop
+ del loop
+
+ @property
+ def length(self):
+ if self._length is None:
+ # if was not possible to get the length of the iterator when
+ # the loop context was created (ie: iterating over a generator)
+ # we have to convert the iterable into a sequence and use the
+ # length of that.
+ iterable = tuple(self._iterator)
+ self._iterator = iter(iterable)
+ self._length = len(iterable) + self.index0 + 1
+ return self._length
+
+ def __repr__(self):
+ return '<%s %r/%r>' % (
+ self.__class__.__name__,
+ self.index,
+ self.length
+ )
+
+
+@implements_iterator
+class LoopContextIterator(object):
+ """The iterator for a loop context."""
+ __slots__ = ('context',)
+
+ def __init__(self, context):
+ self.context = context
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ ctx = self.context
+ ctx.index0 += 1
+ if ctx._after is _last_iteration:
+ raise StopIteration()
+ next_elem = ctx._after
+ ctx._after = ctx._safe_next()
+ return next_elem, ctx
+
+
+class Macro(object):
+ """Wraps a macro function."""
+
+ def __init__(self, environment, func, name, arguments, defaults,
+ catch_kwargs, catch_varargs, caller):
+ self._environment = environment
+ self._func = func
+ self._argument_count = len(arguments)
+ self.name = name
+ self.arguments = arguments
+ self.defaults = defaults
+ self.catch_kwargs = catch_kwargs
+ self.catch_varargs = catch_varargs
+ self.caller = caller
+
+ @internalcode
+ def __call__(self, *args, **kwargs):
+ # try to consume the positional arguments
+ arguments = list(args[:self._argument_count])
+ off = len(arguments)
+
+ # if the number of arguments consumed is not the number of
+ # arguments expected we start filling in keyword arguments
+ # and defaults.
+ if off != self._argument_count:
+ for idx, name in enumerate(self.arguments[len(arguments):]):
+ try:
+ value = kwargs.pop(name)
+ except KeyError:
+ try:
+ value = self.defaults[idx - self._argument_count + off]
+ except IndexError:
+ value = self._environment.undefined(
+ 'parameter %r was not provided' % name, name=name)
+ arguments.append(value)
+
+ # it's important that the order of these arguments does not change
+ # if not also changed in the compiler's `function_scoping` method.
+ # the order is caller, keyword arguments, positional arguments!
+ if self.caller:
+ caller = kwargs.pop('caller', None)
+ if caller is None:
+ caller = self._environment.undefined('No caller defined',
+ name='caller')
+ arguments.append(caller)
+ if self.catch_kwargs:
+ arguments.append(kwargs)
+ elif kwargs:
+ raise TypeError('macro %r takes no keyword argument %r' %
+ (self.name, next(iter(kwargs))))
+ if self.catch_varargs:
+ arguments.append(args[self._argument_count:])
+ elif len(args) > self._argument_count:
+ raise TypeError('macro %r takes not more than %d argument(s)' %
+ (self.name, len(self.arguments)))
+ return self._func(*arguments)
+
+ def __repr__(self):
+ return '<%s %s>' % (
+ self.__class__.__name__,
+ self.name is None and 'anonymous' or repr(self.name)
+ )
+
+
+@implements_to_string
+class Undefined(object):
+ """The default undefined type. This undefined type can be printed and
+ iterated over, but every other access will raise an :exc:`UndefinedError`:
+
+ >>> foo = Undefined(name='foo')
+ >>> str(foo)
+ ''
+ >>> not foo
+ True
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ """
+ __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
+ '_undefined_exception')
+
+ def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
+ self._undefined_hint = hint
+ self._undefined_obj = obj
+ self._undefined_name = name
+ self._undefined_exception = exc
+
+ @internalcode
+ def _fail_with_undefined_error(self, *args, **kwargs):
+ """Regular callback function for undefined objects that raises an
+ `UndefinedError` on call.
+ """
+ if self._undefined_hint is None:
+ if self._undefined_obj is missing:
+ hint = '%r is undefined' % self._undefined_name
+ elif not isinstance(self._undefined_name, string_types):
+ hint = '%s has no element %r' % (
+ object_type_repr(self._undefined_obj),
+ self._undefined_name
+ )
+ else:
+ hint = '%r has no attribute %r' % (
+ object_type_repr(self._undefined_obj),
+ self._undefined_name
+ )
+ else:
+ hint = self._undefined_hint
+ raise self._undefined_exception(hint)
+
+ @internalcode
+ def __getattr__(self, name):
+ if name[:2] == '__':
+ raise AttributeError(name)
+ return self._fail_with_undefined_error()
+
+ __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
+ __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
+ __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
+ __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
+ __float__ = __complex__ = __pow__ = __rpow__ = \
+ _fail_with_undefined_error
+
+ def __eq__(self, other):
+ return type(self) is type(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return id(type(self))
+
+ def __str__(self):
+ return u''
+
+ def __len__(self):
+ return 0
+
+ def __iter__(self):
+ if 0:
+ yield None
+
+ def __nonzero__(self):
+ return False
+
+ def __repr__(self):
+ return 'Undefined'
+
+
+@implements_to_string
+class DebugUndefined(Undefined):
+ """An undefined that returns the debug info when printed.
+
+ >>> foo = DebugUndefined(name='foo')
+ >>> str(foo)
+ '{{ foo }}'
+ >>> not foo
+ True
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ """
+ __slots__ = ()
+
+ def __str__(self):
+ if self._undefined_hint is None:
+ if self._undefined_obj is missing:
+ return u'{{ %s }}' % self._undefined_name
+ return '{{ no such element: %s[%r] }}' % (
+ object_type_repr(self._undefined_obj),
+ self._undefined_name
+ )
+ return u'{{ undefined value printed: %s }}' % self._undefined_hint
+
+
+@implements_to_string
+class StrictUndefined(Undefined):
+ """An undefined that barks on print and iteration as well as boolean
+ tests and all kinds of comparisons. In other words: you can do nothing
+ with it except checking if it's defined using the `defined` test.
+
+ >>> foo = StrictUndefined(name='foo')
+ >>> str(foo)
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ >>> not foo
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ """
+ __slots__ = ()
+ __iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \
+ __ne__ = __bool__ = __hash__ = \
+ Undefined._fail_with_undefined_error
+
+
+# remove remaining slots attributes, after the metaclass did the magic they
+# are unneeded and irritating as they contain wrong data for the subclasses.
+del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
diff --git a/pyload/lib/jinja2/sandbox.py b/pyload/lib/jinja2/sandbox.py
new file mode 100644
index 000000000..da479c1ba
--- /dev/null
+++ b/pyload/lib/jinja2/sandbox.py
@@ -0,0 +1,368 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.sandbox
+ ~~~~~~~~~~~~~~
+
+ Adds a sandbox layer to Jinja as it was the default behavior in the old
+ Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
+ default behavior is easier to use.
+
+ The behavior can be changed by subclassing the environment.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
+"""
+import operator
+from jinja2.environment import Environment
+from jinja2.exceptions import SecurityError
+from jinja2._compat import string_types, function_type, method_type, \
+ traceback_type, code_type, frame_type, generator_type, PY2
+
+
+#: maximum number of items a range may produce
+MAX_RANGE = 100000
+
+#: attributes of function objects that are considered unsafe.
+UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
+ 'func_defaults', 'func_globals'])
+
+#: unsafe method attributes. function attributes are unsafe for methods too
+UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
+
+#: unsafe generator attirbutes.
+UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
+
+# On versions > python 2 the special attributes on functions are gone,
+# but they remain on methods and generators for whatever reason.
+if not PY2:
+ UNSAFE_FUNCTION_ATTRIBUTES = set()
+
+import warnings
+
+# make sure we don't warn in python 2.6 about stuff we don't care about
+warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
+ module='jinja2.sandbox')
+
+from collections import deque
+
+_mutable_set_types = (set,)
+_mutable_mapping_types = (dict,)
+_mutable_sequence_types = (list,)
+
+
+# on python 2.x we can register the user collection types
+try:
+ from UserDict import UserDict, DictMixin
+ from UserList import UserList
+ _mutable_mapping_types += (UserDict, DictMixin)
+ _mutable_set_types += (UserList,)
+except ImportError:
+ pass
+
+# if sets is still available, register the mutable set from there as well
+try:
+ from sets import Set
+ _mutable_set_types += (Set,)
+except ImportError:
+ pass
+
+#: register Python 2.6 abstract base classes
+try:
+ from collections import MutableSet, MutableMapping, MutableSequence
+ _mutable_set_types += (MutableSet,)
+ _mutable_mapping_types += (MutableMapping,)
+ _mutable_sequence_types += (MutableSequence,)
+except ImportError:
+ pass
+
+_mutable_spec = (
+ (_mutable_set_types, frozenset([
+ 'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
+ 'symmetric_difference_update', 'update'
+ ])),
+ (_mutable_mapping_types, frozenset([
+ 'clear', 'pop', 'popitem', 'setdefault', 'update'
+ ])),
+ (_mutable_sequence_types, frozenset([
+ 'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
+ ])),
+ (deque, frozenset([
+ 'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
+ 'popleft', 'remove', 'rotate'
+ ]))
+)
+
+
+def safe_range(*args):
+ """A range that can't generate ranges with a length of more than
+ MAX_RANGE items.
+ """
+ rng = range(*args)
+ if len(rng) > MAX_RANGE:
+ raise OverflowError('range too big, maximum size for range is %d' %
+ MAX_RANGE)
+ return rng
+
+
+def unsafe(f):
+ """Marks a function or method as unsafe.
+
+ ::
+
+ @unsafe
+ def delete(self):
+ pass
+ """
+ f.unsafe_callable = True
+ return f
+
+
+def is_internal_attribute(obj, attr):
+ """Test if the attribute given is an internal python attribute. For
+ example this function returns `True` for the `func_code` attribute of
+ python objects. This is useful if the environment method
+ :meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
+
+ >>> from jinja2.sandbox import is_internal_attribute
+ >>> is_internal_attribute(lambda: None, "func_code")
+ True
+ >>> is_internal_attribute((lambda x:x).func_code, 'co_code')
+ True
+ >>> is_internal_attribute(str, "upper")
+ False
+ """
+ if isinstance(obj, function_type):
+ if attr in UNSAFE_FUNCTION_ATTRIBUTES:
+ return True
+ elif isinstance(obj, method_type):
+ if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
+ attr in UNSAFE_METHOD_ATTRIBUTES:
+ return True
+ elif isinstance(obj, type):
+ if attr == 'mro':
+ return True
+ elif isinstance(obj, (code_type, traceback_type, frame_type)):
+ return True
+ elif isinstance(obj, generator_type):
+ if attr in UNSAFE_GENERATOR_ATTRIBUTES:
+ return True
+ return attr.startswith('__')
+
+
+def modifies_known_mutable(obj, attr):
+ """This function checks if an attribute on a builtin mutable object
+ (list, dict, set or deque) would modify it if called. It also supports
+ the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
+ with Python 2.6 onwards the abstract base classes `MutableSet`,
+ `MutableMapping`, and `MutableSequence`.
+
+ >>> modifies_known_mutable({}, "clear")
+ True
+ >>> modifies_known_mutable({}, "keys")
+ False
+ >>> modifies_known_mutable([], "append")
+ True
+ >>> modifies_known_mutable([], "index")
+ False
+
+ If called with an unsupported object (such as unicode) `False` is
+ returned.
+
+ >>> modifies_known_mutable("foo", "upper")
+ False
+ """
+ for typespec, unsafe in _mutable_spec:
+ if isinstance(obj, typespec):
+ return attr in unsafe
+ return False
+
+
+class SandboxedEnvironment(Environment):
+ """The sandboxed environment. It works like the regular environment but
+ tells the compiler to generate sandboxed code. Additionally subclasses of
+ this environment may override the methods that tell the runtime what
+ attributes or functions are safe to access.
+
+ If the template tries to access insecure code a :exc:`SecurityError` is
+ raised. However also other exceptions may occour during the rendering so
+ the caller has to ensure that all exceptions are catched.
+ """
+ sandboxed = True
+
+ #: default callback table for the binary operators. A copy of this is
+ #: available on each instance of a sandboxed environment as
+ #: :attr:`binop_table`
+ default_binop_table = {
+ '+': operator.add,
+ '-': operator.sub,
+ '*': operator.mul,
+ '/': operator.truediv,
+ '//': operator.floordiv,
+ '**': operator.pow,
+ '%': operator.mod
+ }
+
+ #: default callback table for the unary operators. A copy of this is
+ #: available on each instance of a sandboxed environment as
+ #: :attr:`unop_table`
+ default_unop_table = {
+ '+': operator.pos,
+ '-': operator.neg
+ }
+
+ #: a set of binary operators that should be intercepted. Each operator
+ #: that is added to this set (empty by default) is delegated to the
+ #: :meth:`call_binop` method that will perform the operator. The default
+ #: operator callback is specified by :attr:`binop_table`.
+ #:
+ #: The following binary operators are interceptable:
+ #: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
+ #:
+ #: The default operation form the operator table corresponds to the
+ #: builtin function. Intercepted calls are always slower than the native
+ #: operator call, so make sure only to intercept the ones you are
+ #: interested in.
+ #:
+ #: .. versionadded:: 2.6
+ intercepted_binops = frozenset()
+
+ #: a set of unary operators that should be intercepted. Each operator
+ #: that is added to this set (empty by default) is delegated to the
+ #: :meth:`call_unop` method that will perform the operator. The default
+ #: operator callback is specified by :attr:`unop_table`.
+ #:
+ #: The following unary operators are interceptable: ``+``, ``-``
+ #:
+ #: The default operation form the operator table corresponds to the
+ #: builtin function. Intercepted calls are always slower than the native
+ #: operator call, so make sure only to intercept the ones you are
+ #: interested in.
+ #:
+ #: .. versionadded:: 2.6
+ intercepted_unops = frozenset()
+
+ def intercept_unop(self, operator):
+ """Called during template compilation with the name of a unary
+ operator to check if it should be intercepted at runtime. If this
+ method returns `True`, :meth:`call_unop` is excuted for this unary
+ operator. The default implementation of :meth:`call_unop` will use
+ the :attr:`unop_table` dictionary to perform the operator with the
+ same logic as the builtin one.
+
+ The following unary operators are interceptable: ``+`` and ``-``
+
+ Intercepted calls are always slower than the native operator call,
+ so make sure only to intercept the ones you are interested in.
+
+ .. versionadded:: 2.6
+ """
+ return False
+
+
+ def __init__(self, *args, **kwargs):
+ Environment.__init__(self, *args, **kwargs)
+ self.globals['range'] = safe_range
+ self.binop_table = self.default_binop_table.copy()
+ self.unop_table = self.default_unop_table.copy()
+
+ def is_safe_attribute(self, obj, attr, value):
+ """The sandboxed environment will call this method to check if the
+ attribute of an object is safe to access. Per default all attributes
+ starting with an underscore are considered private as well as the
+ special attributes of internal python objects as returned by the
+ :func:`is_internal_attribute` function.
+ """
+ return not (attr.startswith('_') or is_internal_attribute(obj, attr))
+
+ def is_safe_callable(self, obj):
+ """Check if an object is safely callable. Per default a function is
+ considered safe unless the `unsafe_callable` attribute exists and is
+ True. Override this method to alter the behavior, but this won't
+ affect the `unsafe` decorator from this module.
+ """
+ return not (getattr(obj, 'unsafe_callable', False) or
+ getattr(obj, 'alters_data', False))
+
+ def call_binop(self, context, operator, left, right):
+ """For intercepted binary operator calls (:meth:`intercepted_binops`)
+ this function is executed instead of the builtin operator. This can
+ be used to fine tune the behavior of certain operators.
+
+ .. versionadded:: 2.6
+ """
+ return self.binop_table[operator](left, right)
+
+ def call_unop(self, context, operator, arg):
+ """For intercepted unary operator calls (:meth:`intercepted_unops`)
+ this function is executed instead of the builtin operator. This can
+ be used to fine tune the behavior of certain operators.
+
+ .. versionadded:: 2.6
+ """
+ return self.unop_table[operator](arg)
+
+ def getitem(self, obj, argument):
+ """Subscribe an object from sandboxed code."""
+ try:
+ return obj[argument]
+ except (TypeError, LookupError):
+ if isinstance(argument, string_types):
+ try:
+ attr = str(argument)
+ except Exception:
+ pass
+ else:
+ try:
+ value = getattr(obj, attr)
+ except AttributeError:
+ pass
+ else:
+ if self.is_safe_attribute(obj, argument, value):
+ return value
+ return self.unsafe_undefined(obj, argument)
+ return self.undefined(obj=obj, name=argument)
+
+ def getattr(self, obj, attribute):
+ """Subscribe an object from sandboxed code and prefer the
+ attribute. The attribute passed *must* be a bytestring.
+ """
+ try:
+ value = getattr(obj, attribute)
+ except AttributeError:
+ try:
+ return obj[attribute]
+ except (TypeError, LookupError):
+ pass
+ else:
+ if self.is_safe_attribute(obj, attribute, value):
+ return value
+ return self.unsafe_undefined(obj, attribute)
+ return self.undefined(obj=obj, name=attribute)
+
+ def unsafe_undefined(self, obj, attribute):
+ """Return an undefined object for unsafe attributes."""
+ return self.undefined('access to attribute %r of %r '
+ 'object is unsafe.' % (
+ attribute,
+ obj.__class__.__name__
+ ), name=attribute, obj=obj, exc=SecurityError)
+
+ def call(__self, __context, __obj, *args, **kwargs):
+ """Call an object from sandboxed code."""
+ # the double prefixes are to avoid double keyword argument
+ # errors when proxying the call.
+ if not __self.is_safe_callable(__obj):
+ raise SecurityError('%r is not safely callable' % (__obj,))
+ return __context.call(__obj, *args, **kwargs)
+
+
+class ImmutableSandboxedEnvironment(SandboxedEnvironment):
+ """Works exactly like the regular `SandboxedEnvironment` but does not
+ permit modifications on the builtin mutable objects `list`, `set`, and
+ `dict` by using the :func:`modifies_known_mutable` function.
+ """
+
+ def is_safe_attribute(self, obj, attr, value):
+ if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
+ return False
+ return not modifies_known_mutable(obj, attr)
diff --git a/pyload/lib/jinja2/tests.py b/pyload/lib/jinja2/tests.py
new file mode 100644
index 000000000..48a3e0618
--- /dev/null
+++ b/pyload/lib/jinja2/tests.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.tests
+ ~~~~~~~~~~~~
+
+ Jinja test functions. Used with the "is" operator.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+from jinja2.runtime import Undefined
+from jinja2._compat import text_type, string_types, mapping_types
+
+
+number_re = re.compile(r'^-?\d+(\.\d+)?$')
+regex_type = type(number_re)
+
+
+test_callable = callable
+
+
+def test_odd(value):
+ """Return true if the variable is odd."""
+ return value % 2 == 1
+
+
+def test_even(value):
+ """Return true if the variable is even."""
+ return value % 2 == 0
+
+
+def test_divisibleby(value, num):
+ """Check if a variable is divisible by a number."""
+ return value % num == 0
+
+
+def test_defined(value):
+ """Return true if the variable is defined:
+
+ .. sourcecode:: jinja
+
+ {% if variable is defined %}
+ value of variable: {{ variable }}
+ {% else %}
+ variable is not defined
+ {% endif %}
+
+ See the :func:`default` filter for a simple way to set undefined
+ variables.
+ """
+ return not isinstance(value, Undefined)
+
+
+def test_undefined(value):
+ """Like :func:`defined` but the other way round."""
+ return isinstance(value, Undefined)
+
+
+def test_none(value):
+ """Return true if the variable is none."""
+ return value is None
+
+
+def test_lower(value):
+ """Return true if the variable is lowercased."""
+ return text_type(value).islower()
+
+
+def test_upper(value):
+ """Return true if the variable is uppercased."""
+ return text_type(value).isupper()
+
+
+def test_string(value):
+ """Return true if the object is a string."""
+ return isinstance(value, string_types)
+
+
+def test_mapping(value):
+ """Return true if the object is a mapping (dict etc.).
+
+ .. versionadded:: 2.6
+ """
+ return isinstance(value, mapping_types)
+
+
+def test_number(value):
+ """Return true if the variable is a number."""
+ return isinstance(value, (int, float, complex))
+
+
+def test_sequence(value):
+ """Return true if the variable is a sequence. Sequences are variables
+ that are iterable.
+ """
+ try:
+ len(value)
+ value.__getitem__
+ except:
+ return False
+ return True
+
+
+def test_sameas(value, other):
+ """Check if an object points to the same memory address than another
+ object:
+
+ .. sourcecode:: jinja
+
+ {% if foo.attribute is sameas false %}
+ the foo attribute really is the `False` singleton
+ {% endif %}
+ """
+ return value is other
+
+
+def test_iterable(value):
+ """Check if it's possible to iterate over an object."""
+ try:
+ iter(value)
+ except TypeError:
+ return False
+ return True
+
+
+def test_escaped(value):
+ """Check if the value is escaped."""
+ return hasattr(value, '__html__')
+
+
+TESTS = {
+ 'odd': test_odd,
+ 'even': test_even,
+ 'divisibleby': test_divisibleby,
+ 'defined': test_defined,
+ 'undefined': test_undefined,
+ 'none': test_none,
+ 'lower': test_lower,
+ 'upper': test_upper,
+ 'string': test_string,
+ 'mapping': test_mapping,
+ 'number': test_number,
+ 'sequence': test_sequence,
+ 'iterable': test_iterable,
+ 'callable': test_callable,
+ 'sameas': test_sameas,
+ 'escaped': test_escaped
+}
diff --git a/pyload/lib/jinja2/testsuite/__init__.py b/pyload/lib/jinja2/testsuite/__init__.py
new file mode 100644
index 000000000..635c83e5d
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/__init__.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite
+ ~~~~~~~~~~~~~~~~
+
+ All the unittests of Jinja2. These tests can be executed by
+ either running run-tests.py using multiple Python versions at
+ the same time.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import re
+import sys
+import unittest
+from traceback import format_exception
+from jinja2 import loaders
+from jinja2._compat import PY2
+
+
+here = os.path.dirname(os.path.abspath(__file__))
+
+dict_loader = loaders.DictLoader({
+ 'justdict.html': 'FOO'
+})
+package_loader = loaders.PackageLoader('jinja2.testsuite.res', 'templates')
+filesystem_loader = loaders.FileSystemLoader(here + '/res/templates')
+function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get)
+choice_loader = loaders.ChoiceLoader([dict_loader, package_loader])
+prefix_loader = loaders.PrefixLoader({
+ 'a': filesystem_loader,
+ 'b': dict_loader
+})
+
+
+class JinjaTestCase(unittest.TestCase):
+
+ ### use only these methods for testing. If you need standard
+ ### unittest method, wrap them!
+
+ def setup(self):
+ pass
+
+ def teardown(self):
+ pass
+
+ def setUp(self):
+ self.setup()
+
+ def tearDown(self):
+ self.teardown()
+
+ def assert_equal(self, a, b):
+ return self.assertEqual(a, b)
+
+ def assert_raises(self, *args, **kwargs):
+ return self.assertRaises(*args, **kwargs)
+
+ def assert_traceback_matches(self, callback, expected_tb):
+ try:
+ callback()
+ except Exception as e:
+ tb = format_exception(*sys.exc_info())
+ if re.search(expected_tb.strip(), ''.join(tb)) is None:
+ raise self.fail('Traceback did not match:\n\n%s\nexpected:\n%s'
+ % (''.join(tb), expected_tb))
+ else:
+ self.fail('Expected exception')
+
+
+def find_all_tests(suite):
+ """Yields all the tests and their names from a given suite."""
+ suites = [suite]
+ while suites:
+ s = suites.pop()
+ try:
+ suites.extend(s)
+ except TypeError:
+ yield s, '%s.%s.%s' % (
+ s.__class__.__module__,
+ s.__class__.__name__,
+ s._testMethodName
+ )
+
+
+class BetterLoader(unittest.TestLoader):
+ """A nicer loader that solves two problems. First of all we are setting
+ up tests from different sources and we're doing this programmatically
+ which breaks the default loading logic so this is required anyways.
+ Secondly this loader has a nicer interpolation for test names than the
+ default one so you can just do ``run-tests.py ViewTestCase`` and it
+ will work.
+ """
+
+ def getRootSuite(self):
+ return suite()
+
+ def loadTestsFromName(self, name, module=None):
+ root = self.getRootSuite()
+ if name == 'suite':
+ return root
+
+ all_tests = []
+ for testcase, testname in find_all_tests(root):
+ if testname == name or \
+ testname.endswith('.' + name) or \
+ ('.' + name + '.') in testname or \
+ testname.startswith(name + '.'):
+ all_tests.append(testcase)
+
+ if not all_tests:
+ raise LookupError('could not find test case for "%s"' % name)
+
+ if len(all_tests) == 1:
+ return all_tests[0]
+ rv = unittest.TestSuite()
+ for test in all_tests:
+ rv.addTest(test)
+ return rv
+
+
+def suite():
+ from jinja2.testsuite import ext, filters, tests, core_tags, \
+ loader, inheritance, imports, lexnparse, security, api, \
+ regression, debug, utils, bytecode_cache, doctests
+ suite = unittest.TestSuite()
+ suite.addTest(ext.suite())
+ suite.addTest(filters.suite())
+ suite.addTest(tests.suite())
+ suite.addTest(core_tags.suite())
+ suite.addTest(loader.suite())
+ suite.addTest(inheritance.suite())
+ suite.addTest(imports.suite())
+ suite.addTest(lexnparse.suite())
+ suite.addTest(security.suite())
+ suite.addTest(api.suite())
+ suite.addTest(regression.suite())
+ suite.addTest(debug.suite())
+ suite.addTest(utils.suite())
+ suite.addTest(bytecode_cache.suite())
+
+ # doctests will not run on python 3 currently. Too many issues
+ # with that, do not test that on that platform.
+ if PY2:
+ suite.addTest(doctests.suite())
+
+ return suite
+
+
+def main():
+ """Runs the testsuite as command line application."""
+ try:
+ unittest.main(testLoader=BetterLoader(), defaultTest='suite')
+ except Exception as e:
+ print('Error: %s' % e)
diff --git a/pyload/lib/jinja2/testsuite/api.py b/pyload/lib/jinja2/testsuite/api.py
new file mode 100644
index 000000000..1b68bf8b3
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/api.py
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.api
+ ~~~~~~~~~~~~~~~~~~~~
+
+ Tests the public API and related stuff.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+import os
+import tempfile
+import shutil
+
+from jinja2.testsuite import JinjaTestCase
+from jinja2._compat import next
+
+from jinja2 import Environment, Undefined, DebugUndefined, \
+ StrictUndefined, UndefinedError, meta, \
+ is_undefined, Template, DictLoader
+from jinja2.utils import Cycler
+
+env = Environment()
+
+
+class ExtendedAPITestCase(JinjaTestCase):
+
+ def test_item_and_attribute(self):
+ from jinja2.sandbox import SandboxedEnvironment
+
+ for env in Environment(), SandboxedEnvironment():
+ # the |list is necessary for python3
+ tmpl = env.from_string('{{ foo.items()|list }}')
+ assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+ tmpl = env.from_string('{{ foo|attr("items")()|list }}')
+ assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+ tmpl = env.from_string('{{ foo["items"] }}')
+ assert tmpl.render(foo={'items': 42}) == '42'
+
+ def test_finalizer(self):
+ def finalize_none_empty(value):
+ if value is None:
+ value = u''
+ return value
+ env = Environment(finalize=finalize_none_empty)
+ tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
+ assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
+ tmpl = env.from_string('<{{ none }}>')
+ assert tmpl.render() == '<>'
+
+ def test_cycler(self):
+ items = 1, 2, 3
+ c = Cycler(*items)
+ for item in items + items:
+ assert c.current == item
+ assert next(c) == item
+ next(c)
+ assert c.current == 2
+ c.reset()
+ assert c.current == 1
+
+ def test_expressions(self):
+ expr = env.compile_expression("foo")
+ assert expr() is None
+ assert expr(foo=42) == 42
+ expr2 = env.compile_expression("foo", undefined_to_none=False)
+ assert is_undefined(expr2())
+
+ expr = env.compile_expression("42 + foo")
+ assert expr(foo=42) == 84
+
+ def test_template_passthrough(self):
+ t = Template('Content')
+ assert env.get_template(t) is t
+ assert env.select_template([t]) is t
+ assert env.get_or_select_template([t]) is t
+ assert env.get_or_select_template(t) is t
+
+ def test_autoescape_autoselect(self):
+ def select_autoescape(name):
+ if name is None or '.' not in name:
+ return False
+ return name.endswith('.html')
+ env = Environment(autoescape=select_autoescape,
+ loader=DictLoader({
+ 'test.txt': '{{ foo }}',
+ 'test.html': '{{ foo }}'
+ }))
+ t = env.get_template('test.txt')
+ assert t.render(foo='<foo>') == '<foo>'
+ t = env.get_template('test.html')
+ assert t.render(foo='<foo>') == '&lt;foo&gt;'
+ t = env.from_string('{{ foo }}')
+ assert t.render(foo='<foo>') == '<foo>'
+
+
+class MetaTestCase(JinjaTestCase):
+
+ def test_find_undeclared_variables(self):
+ ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
+ x = meta.find_undeclared_variables(ast)
+ assert x == set(['bar'])
+
+ ast = env.parse('{% set foo = 42 %}{{ bar + foo }}'
+ '{% macro meh(x) %}{{ x }}{% endmacro %}'
+ '{% for item in seq %}{{ muh(item) + meh(seq) }}{% endfor %}')
+ x = meta.find_undeclared_variables(ast)
+ assert x == set(['bar', 'seq', 'muh'])
+
+ def test_find_refererenced_templates(self):
+ ast = env.parse('{% extends "layout.html" %}{% include helper %}')
+ i = meta.find_referenced_templates(ast)
+ assert next(i) == 'layout.html'
+ assert next(i) is None
+ assert list(i) == []
+
+ ast = env.parse('{% extends "layout.html" %}'
+ '{% from "test.html" import a, b as c %}'
+ '{% import "meh.html" as meh %}'
+ '{% include "muh.html" %}')
+ i = meta.find_referenced_templates(ast)
+ assert list(i) == ['layout.html', 'test.html', 'meh.html', 'muh.html']
+
+ def test_find_included_templates(self):
+ ast = env.parse('{% include ["foo.html", "bar.html"] %}')
+ i = meta.find_referenced_templates(ast)
+ assert list(i) == ['foo.html', 'bar.html']
+
+ ast = env.parse('{% include ("foo.html", "bar.html") %}')
+ i = meta.find_referenced_templates(ast)
+ assert list(i) == ['foo.html', 'bar.html']
+
+ ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
+ i = meta.find_referenced_templates(ast)
+ assert list(i) == ['foo.html', 'bar.html', None]
+
+ ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
+ i = meta.find_referenced_templates(ast)
+ assert list(i) == ['foo.html', 'bar.html', None]
+
+
+class StreamingTestCase(JinjaTestCase):
+
+ def test_basic_streaming(self):
+ tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
+ "}} - {{ item }}</li>{%- endfor %}</ul>")
+ stream = tmpl.stream(seq=list(range(4)))
+ self.assert_equal(next(stream), '<ul>')
+ self.assert_equal(next(stream), '<li>1 - 0</li>')
+ self.assert_equal(next(stream), '<li>2 - 1</li>')
+ self.assert_equal(next(stream), '<li>3 - 2</li>')
+ self.assert_equal(next(stream), '<li>4 - 3</li>')
+ self.assert_equal(next(stream), '</ul>')
+
+ def test_buffered_streaming(self):
+ tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
+ "}} - {{ item }}</li>{%- endfor %}</ul>")
+ stream = tmpl.stream(seq=list(range(4)))
+ stream.enable_buffering(size=3)
+ self.assert_equal(next(stream), u'<ul><li>1 - 0</li><li>2 - 1</li>')
+ self.assert_equal(next(stream), u'<li>3 - 2</li><li>4 - 3</li></ul>')
+
+ def test_streaming_behavior(self):
+ tmpl = env.from_string("")
+ stream = tmpl.stream()
+ assert not stream.buffered
+ stream.enable_buffering(20)
+ assert stream.buffered
+ stream.disable_buffering()
+ assert not stream.buffered
+
+ def test_dump_stream(self):
+ tmp = tempfile.mkdtemp()
+ try:
+ tmpl = env.from_string(u"\u2713")
+ stream = tmpl.stream()
+ stream.dump(os.path.join(tmp, 'dump.txt'), 'utf-8')
+ with open(os.path.join(tmp, 'dump.txt'), 'rb') as f:
+ self.assertEqual(f.read(), b'\xe2\x9c\x93')
+ finally:
+ shutil.rmtree(tmp)
+
+
+class UndefinedTestCase(JinjaTestCase):
+
+ def test_stopiteration_is_undefined(self):
+ def test():
+ raise StopIteration()
+ t = Template('A{{ test() }}B')
+ assert t.render(test=test) == 'AB'
+ t = Template('A{{ test().missingattribute }}B')
+ self.assert_raises(UndefinedError, t.render, test=test)
+
+ def test_undefined_and_special_attributes(self):
+ try:
+ Undefined('Foo').__dict__
+ except AttributeError:
+ pass
+ else:
+ assert False, "Expected actual attribute error"
+
+ def test_default_undefined(self):
+ env = Environment(undefined=Undefined)
+ self.assert_equal(env.from_string('{{ missing }}').render(), u'')
+ self.assert_raises(UndefinedError,
+ env.from_string('{{ missing.attribute }}').render)
+ self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
+ self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
+ self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
+ self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
+
+ def test_debug_undefined(self):
+ env = Environment(undefined=DebugUndefined)
+ self.assert_equal(env.from_string('{{ missing }}').render(), '{{ missing }}')
+ self.assert_raises(UndefinedError,
+ env.from_string('{{ missing.attribute }}').render)
+ self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
+ self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
+ self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42),
+ u"{{ no such element: int object['missing'] }}")
+ self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
+
+ def test_strict_undefined(self):
+ env = Environment(undefined=StrictUndefined)
+ self.assert_raises(UndefinedError, env.from_string('{{ missing }}').render)
+ self.assert_raises(UndefinedError, env.from_string('{{ missing.attribute }}').render)
+ self.assert_raises(UndefinedError, env.from_string('{{ missing|list }}').render)
+ self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
+ self.assert_raises(UndefinedError, env.from_string('{{ foo.missing }}').render, foo=42)
+ self.assert_raises(UndefinedError, env.from_string('{{ not missing }}').render)
+ self.assert_equal(env.from_string('{{ missing|default("default", true) }}').render(), 'default')
+
+ def test_indexing_gives_undefined(self):
+ t = Template("{{ var[42].foo }}")
+ self.assert_raises(UndefinedError, t.render, var=0)
+
+ def test_none_gives_proper_error(self):
+ try:
+ Environment().getattr(None, 'split')()
+ except UndefinedError as e:
+ assert e.message == "'None' has no attribute 'split'"
+ else:
+ assert False, 'expected exception'
+
+ def test_object_repr(self):
+ try:
+ Undefined(obj=42, name='upper')()
+ except UndefinedError as e:
+ assert e.message == "'int object' has no attribute 'upper'"
+ else:
+ assert False, 'expected exception'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ExtendedAPITestCase))
+ suite.addTest(unittest.makeSuite(MetaTestCase))
+ suite.addTest(unittest.makeSuite(StreamingTestCase))
+ suite.addTest(unittest.makeSuite(UndefinedTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/bytecode_cache.py b/pyload/lib/jinja2/testsuite/bytecode_cache.py
new file mode 100644
index 000000000..9f5c635b8
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/bytecode_cache.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.bytecode_cache
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test bytecode caching
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase, package_loader
+
+from jinja2 import Environment
+from jinja2.bccache import FileSystemBytecodeCache
+from jinja2.exceptions import TemplateNotFound
+
+bytecode_cache = FileSystemBytecodeCache()
+env = Environment(
+ loader=package_loader,
+ bytecode_cache=bytecode_cache,
+)
+
+
+class ByteCodeCacheTestCase(JinjaTestCase):
+
+ def test_simple(self):
+ tmpl = env.get_template('test.html')
+ assert tmpl.render().strip() == 'BAR'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ByteCodeCacheTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/core_tags.py b/pyload/lib/jinja2/testsuite/core_tags.py
new file mode 100644
index 000000000..f1a20fd44
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/core_tags.py
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.core_tags
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test the core tags like for and if.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, TemplateSyntaxError, UndefinedError, \
+ DictLoader
+
+env = Environment()
+
+
+class ForLoopTestCase(JinjaTestCase):
+
+ def test_simple(self):
+ tmpl = env.from_string('{% for item in seq %}{{ item }}{% endfor %}')
+ assert tmpl.render(seq=list(range(10))) == '0123456789'
+
+ def test_else(self):
+ tmpl = env.from_string('{% for item in seq %}XXX{% else %}...{% endfor %}')
+ assert tmpl.render() == '...'
+
+ def test_empty_blocks(self):
+ tmpl = env.from_string('<{% for item in seq %}{% else %}{% endfor %}>')
+ assert tmpl.render() == '<>'
+
+ def test_context_vars(self):
+ tmpl = env.from_string('''{% for item in seq -%}
+ {{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
+ loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
+ loop.length }}###{% endfor %}''')
+ one, two, _ = tmpl.render(seq=[0, 1]).split('###')
+ (one_index, one_index0, one_revindex, one_revindex0, one_first,
+ one_last, one_length) = one.split('|')
+ (two_index, two_index0, two_revindex, two_revindex0, two_first,
+ two_last, two_length) = two.split('|')
+
+ assert int(one_index) == 1 and int(two_index) == 2
+ assert int(one_index0) == 0 and int(two_index0) == 1
+ assert int(one_revindex) == 2 and int(two_revindex) == 1
+ assert int(one_revindex0) == 1 and int(two_revindex0) == 0
+ assert one_first == 'True' and two_first == 'False'
+ assert one_last == 'False' and two_last == 'True'
+ assert one_length == two_length == '2'
+
+ def test_cycling(self):
+ tmpl = env.from_string('''{% for item in seq %}{{
+ loop.cycle('<1>', '<2>') }}{% endfor %}{%
+ for item in seq %}{{ loop.cycle(*through) }}{% endfor %}''')
+ output = tmpl.render(seq=list(range(4)), through=('<1>', '<2>'))
+ assert output == '<1><2>' * 4
+
+ def test_scope(self):
+ tmpl = env.from_string('{% for item in seq %}{% endfor %}{{ item }}')
+ output = tmpl.render(seq=list(range(10)))
+ assert not output
+
+ def test_varlen(self):
+ def inner():
+ for item in range(5):
+ yield item
+ tmpl = env.from_string('{% for item in iter %}{{ item }}{% endfor %}')
+ output = tmpl.render(iter=inner())
+ assert output == '01234'
+
+ def test_noniter(self):
+ tmpl = env.from_string('{% for item in none %}...{% endfor %}')
+ self.assert_raises(TypeError, tmpl.render)
+
+ def test_recursive(self):
+ tmpl = env.from_string('''{% for item in seq recursive -%}
+ [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+ {%- endfor %}''')
+ assert tmpl.render(seq=[
+ dict(a=1, b=[dict(a=1), dict(a=2)]),
+ dict(a=2, b=[dict(a=1), dict(a=2)]),
+ dict(a=3, b=[dict(a='a')])
+ ]) == '[1<[1][2]>][2<[1][2]>][3<[a]>]'
+
+ def test_recursive_depth0(self):
+ tmpl = env.from_string('''{% for item in seq recursive -%}
+ [{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+ {%- endfor %}''')
+ self.assertEqual(tmpl.render(seq=[
+ dict(a=1, b=[dict(a=1), dict(a=2)]),
+ dict(a=2, b=[dict(a=1), dict(a=2)]),
+ dict(a=3, b=[dict(a='a')])
+ ]), '[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]')
+
+ def test_recursive_depth(self):
+ tmpl = env.from_string('''{% for item in seq recursive -%}
+ [{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+ {%- endfor %}''')
+ self.assertEqual(tmpl.render(seq=[
+ dict(a=1, b=[dict(a=1), dict(a=2)]),
+ dict(a=2, b=[dict(a=1), dict(a=2)]),
+ dict(a=3, b=[dict(a='a')])
+ ]), '[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]')
+
+ def test_looploop(self):
+ tmpl = env.from_string('''{% for row in table %}
+ {%- set rowloop = loop -%}
+ {% for cell in row -%}
+ [{{ rowloop.index }}|{{ loop.index }}]
+ {%- endfor %}
+ {%- endfor %}''')
+ assert tmpl.render(table=['ab', 'cd']) == '[1|1][1|2][2|1][2|2]'
+
+ def test_reversed_bug(self):
+ tmpl = env.from_string('{% for i in items %}{{ i }}'
+ '{% if not loop.last %}'
+ ',{% endif %}{% endfor %}')
+ assert tmpl.render(items=reversed([3, 2, 1])) == '1,2,3'
+
+ def test_loop_errors(self):
+ tmpl = env.from_string('''{% for item in [1] if loop.index
+ == 0 %}...{% endfor %}''')
+ self.assert_raises(UndefinedError, tmpl.render)
+ tmpl = env.from_string('''{% for item in [] %}...{% else
+ %}{{ loop }}{% endfor %}''')
+ assert tmpl.render() == ''
+
+ def test_loop_filter(self):
+ tmpl = env.from_string('{% for item in range(10) if item '
+ 'is even %}[{{ item }}]{% endfor %}')
+ assert tmpl.render() == '[0][2][4][6][8]'
+ tmpl = env.from_string('''
+ {%- for item in range(10) if item is even %}[{{
+ loop.index }}:{{ item }}]{% endfor %}''')
+ assert tmpl.render() == '[1:0][2:2][3:4][4:6][5:8]'
+
+ def test_loop_unassignable(self):
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ '{% for loop in seq %}...{% endfor %}')
+
+ def test_scoped_special_var(self):
+ t = env.from_string('{% for s in seq %}[{{ loop.first }}{% for c in s %}'
+ '|{{ loop.first }}{% endfor %}]{% endfor %}')
+ assert t.render(seq=('ab', 'cd')) == '[True|True|False][False|True|False]'
+
+ def test_scoped_loop_var(self):
+ t = env.from_string('{% for x in seq %}{{ loop.first }}'
+ '{% for y in seq %}{% endfor %}{% endfor %}')
+ assert t.render(seq='ab') == 'TrueFalse'
+ t = env.from_string('{% for x in seq %}{% for y in seq %}'
+ '{{ loop.first }}{% endfor %}{% endfor %}')
+ assert t.render(seq='ab') == 'TrueFalseTrueFalse'
+
+ def test_recursive_empty_loop_iter(self):
+ t = env.from_string('''
+ {%- for item in foo recursive -%}{%- endfor -%}
+ ''')
+ assert t.render(dict(foo=[])) == ''
+
+ def test_call_in_loop(self):
+ t = env.from_string('''
+ {%- macro do_something() -%}
+ [{{ caller() }}]
+ {%- endmacro %}
+
+ {%- for i in [1, 2, 3] %}
+ {%- call do_something() -%}
+ {{ i }}
+ {%- endcall %}
+ {%- endfor -%}
+ ''')
+ assert t.render() == '[1][2][3]'
+
+ def test_scoping_bug(self):
+ t = env.from_string('''
+ {%- for item in foo %}...{{ item }}...{% endfor %}
+ {%- macro item(a) %}...{{ a }}...{% endmacro %}
+ {{- item(2) -}}
+ ''')
+ assert t.render(foo=(1,)) == '...1......2...'
+
+ def test_unpacking(self):
+ tmpl = env.from_string('{% for a, b, c in [[1, 2, 3]] %}'
+ '{{ a }}|{{ b }}|{{ c }}{% endfor %}')
+ assert tmpl.render() == '1|2|3'
+
+
+class IfConditionTestCase(JinjaTestCase):
+
+ def test_simple(self):
+ tmpl = env.from_string('''{% if true %}...{% endif %}''')
+ assert tmpl.render() == '...'
+
+ def test_elif(self):
+ tmpl = env.from_string('''{% if false %}XXX{% elif true
+ %}...{% else %}XXX{% endif %}''')
+ assert tmpl.render() == '...'
+
+ def test_else(self):
+ tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}')
+ assert tmpl.render() == '...'
+
+ def test_empty(self):
+ tmpl = env.from_string('[{% if true %}{% else %}{% endif %}]')
+ assert tmpl.render() == '[]'
+
+ def test_complete(self):
+ tmpl = env.from_string('{% if a %}A{% elif b %}B{% elif c == d %}'
+ 'C{% else %}D{% endif %}')
+ assert tmpl.render(a=0, b=False, c=42, d=42.0) == 'C'
+
+ def test_no_scope(self):
+ tmpl = env.from_string('{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}')
+ assert tmpl.render(a=True) == '1'
+ tmpl = env.from_string('{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}')
+ assert tmpl.render() == '1'
+
+
+class MacrosTestCase(JinjaTestCase):
+ env = Environment(trim_blocks=True)
+
+ def test_simple(self):
+ tmpl = self.env.from_string('''\
+{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
+{{ say_hello('Peter') }}''')
+ assert tmpl.render() == 'Hello Peter!'
+
+ def test_scoping(self):
+ tmpl = self.env.from_string('''\
+{% macro level1(data1) %}
+{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
+{{ level2('bar') }}{% endmacro %}
+{{ level1('foo') }}''')
+ assert tmpl.render() == 'foo|bar'
+
+ def test_arguments(self):
+ tmpl = self.env.from_string('''\
+{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
+{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''')
+ assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d'
+
+ def test_varargs(self):
+ tmpl = self.env.from_string('''\
+{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
+{{ test(1, 2, 3) }}''')
+ assert tmpl.render() == '1|2|3'
+
+ def test_simple_call(self):
+ tmpl = self.env.from_string('''\
+{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
+{% call test() %}data{% endcall %}''')
+ assert tmpl.render() == '[[data]]'
+
+ def test_complex_call(self):
+ tmpl = self.env.from_string('''\
+{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
+{% call(data) test() %}{{ data }}{% endcall %}''')
+ assert tmpl.render() == '[[data]]'
+
+ def test_caller_undefined(self):
+ tmpl = self.env.from_string('''\
+{% set caller = 42 %}\
+{% macro test() %}{{ caller is not defined }}{% endmacro %}\
+{{ test() }}''')
+ assert tmpl.render() == 'True'
+
+ def test_include(self):
+ self.env = Environment(loader=DictLoader({'include':
+ '{% macro test(foo) %}[{{ foo }}]{% endmacro %}'}))
+ tmpl = self.env.from_string('{% from "include" import test %}{{ test("foo") }}')
+ assert tmpl.render() == '[foo]'
+
+ def test_macro_api(self):
+ tmpl = self.env.from_string('{% macro foo(a, b) %}{% endmacro %}'
+ '{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}'
+ '{% macro baz() %}{{ caller() }}{% endmacro %}')
+ assert tmpl.module.foo.arguments == ('a', 'b')
+ assert tmpl.module.foo.defaults == ()
+ assert tmpl.module.foo.name == 'foo'
+ assert not tmpl.module.foo.caller
+ assert not tmpl.module.foo.catch_kwargs
+ assert not tmpl.module.foo.catch_varargs
+ assert tmpl.module.bar.arguments == ()
+ assert tmpl.module.bar.defaults == ()
+ assert not tmpl.module.bar.caller
+ assert tmpl.module.bar.catch_kwargs
+ assert tmpl.module.bar.catch_varargs
+ assert tmpl.module.baz.caller
+
+ def test_callself(self):
+ tmpl = self.env.from_string('{% macro foo(x) %}{{ x }}{% if x > 1 %}|'
+ '{{ foo(x - 1) }}{% endif %}{% endmacro %}'
+ '{{ foo(5) }}')
+ assert tmpl.render() == '5|4|3|2|1'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ForLoopTestCase))
+ suite.addTest(unittest.makeSuite(IfConditionTestCase))
+ suite.addTest(unittest.makeSuite(MacrosTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/debug.py b/pyload/lib/jinja2/testsuite/debug.py
new file mode 100644
index 000000000..2588a83ea
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/debug.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.debug
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests the debug system.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase, filesystem_loader
+
+from jinja2 import Environment, TemplateSyntaxError
+
+env = Environment(loader=filesystem_loader)
+
+
+class DebugTestCase(JinjaTestCase):
+
+ def test_runtime_error(self):
+ def test():
+ tmpl.render(fail=lambda: 1 / 0)
+ tmpl = env.get_template('broken.html')
+ self.assert_traceback_matches(test, r'''
+ File ".*?broken.html", line 2, in (top-level template code|<module>)
+ \{\{ fail\(\) \}\}
+ File ".*?debug.pyc?", line \d+, in <lambda>
+ tmpl\.render\(fail=lambda: 1 / 0\)
+ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero
+''')
+
+ def test_syntax_error(self):
+ # XXX: the .*? is necessary for python3 which does not hide
+ # some of the stack frames we don't want to show. Not sure
+ # what's up with that, but that is not that critical. Should
+ # be fixed though.
+ self.assert_traceback_matches(lambda: env.get_template('syntaxerror.html'), r'''(?sm)
+ File ".*?syntaxerror.html", line 4, in (template|<module>)
+ \{% endif %\}.*?
+(jinja2\.exceptions\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
+ ''')
+
+ def test_regular_syntax_error(self):
+ def test():
+ raise TemplateSyntaxError('wtf', 42)
+ self.assert_traceback_matches(test, r'''
+ File ".*debug.pyc?", line \d+, in test
+ raise TemplateSyntaxError\('wtf', 42\)
+(jinja2\.exceptions\.)?TemplateSyntaxError: wtf
+ line 42''')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(DebugTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/doctests.py b/pyload/lib/jinja2/testsuite/doctests.py
new file mode 100644
index 000000000..616d3b6ee
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/doctests.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.doctests
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The doctests. Collects all tests we want to test from
+ the Jinja modules.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+import doctest
+
+
+def suite():
+ from jinja2 import utils, sandbox, runtime, meta, loaders, \
+ ext, environment, bccache, nodes
+ suite = unittest.TestSuite()
+ suite.addTest(doctest.DocTestSuite(utils))
+ suite.addTest(doctest.DocTestSuite(sandbox))
+ suite.addTest(doctest.DocTestSuite(runtime))
+ suite.addTest(doctest.DocTestSuite(meta))
+ suite.addTest(doctest.DocTestSuite(loaders))
+ suite.addTest(doctest.DocTestSuite(ext))
+ suite.addTest(doctest.DocTestSuite(environment))
+ suite.addTest(doctest.DocTestSuite(bccache))
+ suite.addTest(doctest.DocTestSuite(nodes))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/ext.py b/pyload/lib/jinja2/testsuite/ext.py
new file mode 100644
index 000000000..0f93be945
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/ext.py
@@ -0,0 +1,459 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.ext
+ ~~~~~~~~~~~~~~~~~~~~
+
+ Tests for the extensions.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, DictLoader, contextfunction, nodes
+from jinja2.exceptions import TemplateAssertionError
+from jinja2.ext import Extension
+from jinja2.lexer import Token, count_newlines
+from jinja2._compat import next, BytesIO, itervalues, text_type
+
+importable_object = 23
+
+_gettext_re = re.compile(r'_\((.*?)\)(?s)')
+
+
+i18n_templates = {
+ 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
+ '{% block body %}{% endblock %}',
+ 'child.html': '{% extends "master.html" %}{% block body %}'
+ '{% trans %}watch out{% endtrans %}{% endblock %}',
+ 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
+ '{{ user_count }} users online{% endtrans %}',
+ 'plural2.html': '{% trans user_count=get_user_count() %}{{ user_count }}s'
+ '{% pluralize %}{{ user_count }}p{% endtrans %}',
+ 'stringformat.html': '{{ _("User: %(num)s")|format(num=user_count) }}'
+}
+
+newstyle_i18n_templates = {
+ 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
+ '{% block body %}{% endblock %}',
+ 'child.html': '{% extends "master.html" %}{% block body %}'
+ '{% trans %}watch out{% endtrans %}{% endblock %}',
+ 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
+ '{{ user_count }} users online{% endtrans %}',
+ 'stringformat.html': '{{ _("User: %(num)s", num=user_count) }}',
+ 'ngettext.html': '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
+ 'ngettext_long.html': '{% trans num=apples %}{{ num }} apple{% pluralize %}'
+ '{{ num }} apples{% endtrans %}',
+ 'transvars1.html': '{% trans %}User: {{ num }}{% endtrans %}',
+ 'transvars2.html': '{% trans num=count %}User: {{ num }}{% endtrans %}',
+ 'transvars3.html': '{% trans count=num %}User: {{ count }}{% endtrans %}',
+ 'novars.html': '{% trans %}%(hello)s{% endtrans %}',
+ 'vars.html': '{% trans %}{{ foo }}%(foo)s{% endtrans %}',
+ 'explicitvars.html': '{% trans foo="42" %}%(foo)s{% endtrans %}'
+}
+
+
+languages = {
+ 'de': {
+ 'missing': u'fehlend',
+ 'watch out': u'pass auf',
+ 'One user online': u'Ein Benutzer online',
+ '%(user_count)s users online': u'%(user_count)s Benutzer online',
+ 'User: %(num)s': u'Benutzer: %(num)s',
+ 'User: %(count)s': u'Benutzer: %(count)s',
+ '%(num)s apple': u'%(num)s Apfel',
+ '%(num)s apples': u'%(num)s Äpfel'
+ }
+}
+
+
+@contextfunction
+def gettext(context, string):
+ language = context.get('LANGUAGE', 'en')
+ return languages.get(language, {}).get(string, string)
+
+
+@contextfunction
+def ngettext(context, s, p, n):
+ language = context.get('LANGUAGE', 'en')
+ if n != 1:
+ return languages.get(language, {}).get(p, p)
+ return languages.get(language, {}).get(s, s)
+
+
+i18n_env = Environment(
+ loader=DictLoader(i18n_templates),
+ extensions=['jinja2.ext.i18n']
+)
+i18n_env.globals.update({
+ '_': gettext,
+ 'gettext': gettext,
+ 'ngettext': ngettext
+})
+
+newstyle_i18n_env = Environment(
+ loader=DictLoader(newstyle_i18n_templates),
+ extensions=['jinja2.ext.i18n']
+)
+newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
+
+class TestExtension(Extension):
+ tags = set(['test'])
+ ext_attr = 42
+
+ def parse(self, parser):
+ return nodes.Output([self.call_method('_dump', [
+ nodes.EnvironmentAttribute('sandboxed'),
+ self.attr('ext_attr'),
+ nodes.ImportedName(__name__ + '.importable_object'),
+ nodes.ContextReference()
+ ])]).set_lineno(next(parser.stream).lineno)
+
+ def _dump(self, sandboxed, ext_attr, imported_object, context):
+ return '%s|%s|%s|%s' % (
+ sandboxed,
+ ext_attr,
+ imported_object,
+ context.blocks
+ )
+
+
+class PreprocessorExtension(Extension):
+
+ def preprocess(self, source, name, filename=None):
+ return source.replace('[[TEST]]', '({{ foo }})')
+
+
+class StreamFilterExtension(Extension):
+
+ def filter_stream(self, stream):
+ for token in stream:
+ if token.type == 'data':
+ for t in self.interpolate(token):
+ yield t
+ else:
+ yield token
+
+ def interpolate(self, token):
+ pos = 0
+ end = len(token.value)
+ lineno = token.lineno
+ while 1:
+ match = _gettext_re.search(token.value, pos)
+ if match is None:
+ break
+ value = token.value[pos:match.start()]
+ if value:
+ yield Token(lineno, 'data', value)
+ lineno += count_newlines(token.value)
+ yield Token(lineno, 'variable_begin', None)
+ yield Token(lineno, 'name', 'gettext')
+ yield Token(lineno, 'lparen', None)
+ yield Token(lineno, 'string', match.group(1))
+ yield Token(lineno, 'rparen', None)
+ yield Token(lineno, 'variable_end', None)
+ pos = match.end()
+ if pos < end:
+ yield Token(lineno, 'data', token.value[pos:])
+
+
+class ExtensionsTestCase(JinjaTestCase):
+
+ def test_extend_late(self):
+ env = Environment()
+ env.add_extension('jinja2.ext.autoescape')
+ t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
+ assert t.render() == '&lt;test&gt;'
+
+ def test_loop_controls(self):
+ env = Environment(extensions=['jinja2.ext.loopcontrols'])
+
+ tmpl = env.from_string('''
+ {%- for item in [1, 2, 3, 4] %}
+ {%- if item % 2 == 0 %}{% continue %}{% endif -%}
+ {{ item }}
+ {%- endfor %}''')
+ assert tmpl.render() == '13'
+
+ tmpl = env.from_string('''
+ {%- for item in [1, 2, 3, 4] %}
+ {%- if item > 2 %}{% break %}{% endif -%}
+ {{ item }}
+ {%- endfor %}''')
+ assert tmpl.render() == '12'
+
+ def test_do(self):
+ env = Environment(extensions=['jinja2.ext.do'])
+ tmpl = env.from_string('''
+ {%- set items = [] %}
+ {%- for char in "foo" %}
+ {%- do items.append(loop.index0 ~ char) %}
+ {%- endfor %}{{ items|join(', ') }}''')
+ assert tmpl.render() == '0f, 1o, 2o'
+
+ def test_with(self):
+ env = Environment(extensions=['jinja2.ext.with_'])
+ tmpl = env.from_string('''\
+ {% with a=42, b=23 -%}
+ {{ a }} = {{ b }}
+ {% endwith -%}
+ {{ a }} = {{ b }}\
+ ''')
+ assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
+ == ['42 = 23', '1 = 2']
+
+ def test_extension_nodes(self):
+ env = Environment(extensions=[TestExtension])
+ tmpl = env.from_string('{% test %}')
+ assert tmpl.render() == 'False|42|23|{}'
+
+ def test_identifier(self):
+ assert TestExtension.identifier == __name__ + '.TestExtension'
+
+ def test_rebinding(self):
+ original = Environment(extensions=[TestExtension])
+ overlay = original.overlay()
+ for env in original, overlay:
+ for ext in itervalues(env.extensions):
+ assert ext.environment is env
+
+ def test_preprocessor_extension(self):
+ env = Environment(extensions=[PreprocessorExtension])
+ tmpl = env.from_string('{[[TEST]]}')
+ assert tmpl.render(foo=42) == '{(42)}'
+
+ def test_streamfilter_extension(self):
+ env = Environment(extensions=[StreamFilterExtension])
+ env.globals['gettext'] = lambda x: x.upper()
+ tmpl = env.from_string('Foo _(bar) Baz')
+ out = tmpl.render()
+ assert out == 'Foo BAR Baz'
+
+ def test_extension_ordering(self):
+ class T1(Extension):
+ priority = 1
+ class T2(Extension):
+ priority = 2
+ env = Environment(extensions=[T1, T2])
+ ext = list(env.iter_extensions())
+ assert ext[0].__class__ is T1
+ assert ext[1].__class__ is T2
+
+
+class InternationalizationTestCase(JinjaTestCase):
+
+ def test_trans(self):
+ tmpl = i18n_env.get_template('child.html')
+ assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
+
+ def test_trans_plural(self):
+ tmpl = i18n_env.get_template('plural.html')
+ assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
+ assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
+
+ def test_trans_plural_with_functions(self):
+ tmpl = i18n_env.get_template('plural2.html')
+ def get_user_count():
+ get_user_count.called += 1
+ return 1
+ get_user_count.called = 0
+ assert tmpl.render(LANGUAGE='de', get_user_count=get_user_count) == '1s'
+ assert get_user_count.called == 1
+
+ def test_complex_plural(self):
+ tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
+ 'pluralize count %}{{ count }} items{% endtrans %}')
+ assert tmpl.render() == '2 items'
+ self.assert_raises(TemplateAssertionError, i18n_env.from_string,
+ '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
+
+ def test_trans_stringformatting(self):
+ tmpl = i18n_env.get_template('stringformat.html')
+ assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
+
+ def test_extract(self):
+ from jinja2.ext import babel_extract
+ source = BytesIO('''
+ {{ gettext('Hello World') }}
+ {% trans %}Hello World{% endtrans %}
+ {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
+ '''.encode('ascii')) # make python 3 happy
+ assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
+ (2, 'gettext', u'Hello World', []),
+ (3, 'gettext', u'Hello World', []),
+ (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
+ ]
+
+ def test_comment_extract(self):
+ from jinja2.ext import babel_extract
+ source = BytesIO('''
+ {# trans first #}
+ {{ gettext('Hello World') }}
+ {% trans %}Hello World{% endtrans %}{# trans second #}
+ {#: third #}
+ {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
+ '''.encode('utf-8')) # make python 3 happy
+ assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
+ (3, 'gettext', u'Hello World', ['first']),
+ (4, 'gettext', u'Hello World', ['second']),
+ (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
+ ]
+
+
+class NewstyleInternationalizationTestCase(JinjaTestCase):
+
+ def test_trans(self):
+ tmpl = newstyle_i18n_env.get_template('child.html')
+ assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
+
+ def test_trans_plural(self):
+ tmpl = newstyle_i18n_env.get_template('plural.html')
+ assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
+ assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
+
+ def test_complex_plural(self):
+ tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
+ 'pluralize count %}{{ count }} items{% endtrans %}')
+ assert tmpl.render() == '2 items'
+ self.assert_raises(TemplateAssertionError, i18n_env.from_string,
+ '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
+
+ def test_trans_stringformatting(self):
+ tmpl = newstyle_i18n_env.get_template('stringformat.html')
+ assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
+
+ def test_newstyle_plural(self):
+ tmpl = newstyle_i18n_env.get_template('ngettext.html')
+ assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
+ assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
+
+ def test_autoescape_support(self):
+ env = Environment(extensions=['jinja2.ext.autoescape',
+ 'jinja2.ext.i18n'])
+ env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
+ lambda s, p, n: s, newstyle=True)
+ t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
+ '"<test>") }}{% endautoescape %}')
+ assert t.render(ae=True) == '<strong>Wert: &lt;test&gt;</strong>'
+ assert t.render(ae=False) == '<strong>Wert: <test></strong>'
+
+ def test_num_used_twice(self):
+ tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
+ assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
+
+ def test_num_called_num(self):
+ source = newstyle_i18n_env.compile('''
+ {% trans num=3 %}{{ num }} apple{% pluralize
+ %}{{ num }} apples{% endtrans %}
+ ''', raw=True)
+ # quite hacky, but the only way to properly test that. The idea is
+ # that the generated code does not pass num twice (although that
+ # would work) for better performance. This only works on the
+ # newstyle gettext of course
+ assert re.search(r"l_ngettext, u?'\%\(num\)s apple', u?'\%\(num\)s "
+ r"apples', 3", source) is not None
+
+ def test_trans_vars(self):
+ t1 = newstyle_i18n_env.get_template('transvars1.html')
+ t2 = newstyle_i18n_env.get_template('transvars2.html')
+ t3 = newstyle_i18n_env.get_template('transvars3.html')
+ assert t1.render(num=1, LANGUAGE='de') == 'Benutzer: 1'
+ assert t2.render(count=23, LANGUAGE='de') == 'Benutzer: 23'
+ assert t3.render(num=42, LANGUAGE='de') == 'Benutzer: 42'
+
+ def test_novars_vars_escaping(self):
+ t = newstyle_i18n_env.get_template('novars.html')
+ assert t.render() == '%(hello)s'
+ t = newstyle_i18n_env.get_template('vars.html')
+ assert t.render(foo='42') == '42%(foo)s'
+ t = newstyle_i18n_env.get_template('explicitvars.html')
+ assert t.render() == '%(foo)s'
+
+
+class AutoEscapeTestCase(JinjaTestCase):
+
+ def test_scoped_setting(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('''
+ {{ "<HelloWorld>" }}
+ {% autoescape false %}
+ {{ "<HelloWorld>" }}
+ {% endautoescape %}
+ {{ "<HelloWorld>" }}
+ ''')
+ assert tmpl.render().split() == \
+ [u'&lt;HelloWorld&gt;', u'<HelloWorld>', u'&lt;HelloWorld&gt;']
+
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=False)
+ tmpl = env.from_string('''
+ {{ "<HelloWorld>" }}
+ {% autoescape true %}
+ {{ "<HelloWorld>" }}
+ {% endautoescape %}
+ {{ "<HelloWorld>" }}
+ ''')
+ assert tmpl.render().split() == \
+ [u'<HelloWorld>', u'&lt;HelloWorld&gt;', u'<HelloWorld>']
+
+ def test_nonvolatile(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
+ assert tmpl.render() == ' foo="&lt;test&gt;"'
+ tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
+ '|xmlattr|escape }}{% endautoescape %}')
+ assert tmpl.render() == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
+
+ def test_volatile(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
+ '|xmlattr|escape }}{% endautoescape %}')
+ assert tmpl.render(foo=False) == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
+ assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
+
+ def test_scoping(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'])
+ tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
+ '{% endautoescape %}{{ x }}{{ "<y>" }}')
+ assert tmpl.render(x=1) == '&lt;x&gt;1<y>'
+
+ def test_volatile_scoping(self):
+ env = Environment(extensions=['jinja2.ext.autoescape'])
+ tmplsource = '''
+ {% autoescape val %}
+ {% macro foo(x) %}
+ [{{ x }}]
+ {% endmacro %}
+ {{ foo().__class__.__name__ }}
+ {% endautoescape %}
+ {{ '<testing>' }}
+ '''
+ tmpl = env.from_string(tmplsource)
+ assert tmpl.render(val=True).split()[0] == 'Markup'
+ assert tmpl.render(val=False).split()[0] == text_type.__name__
+
+ # looking at the source we should see <testing> there in raw
+ # (and then escaped as well)
+ env = Environment(extensions=['jinja2.ext.autoescape'])
+ pysource = env.compile(tmplsource, raw=True)
+ assert '<testing>\\n' in pysource
+
+ env = Environment(extensions=['jinja2.ext.autoescape'],
+ autoescape=True)
+ pysource = env.compile(tmplsource, raw=True)
+ assert '&lt;testing&gt;\\n' in pysource
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ExtensionsTestCase))
+ suite.addTest(unittest.makeSuite(InternationalizationTestCase))
+ suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
+ suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/filters.py b/pyload/lib/jinja2/testsuite/filters.py
new file mode 100644
index 000000000..282dd2d85
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/filters.py
@@ -0,0 +1,515 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.filters
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests for the jinja filters.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Markup, Environment
+from jinja2._compat import text_type, implements_to_string
+
+env = Environment()
+
+
+class FilterTestCase(JinjaTestCase):
+
+ def test_filter_calling(self):
+ rv = env.call_filter('sum', [1, 2, 3])
+ self.assert_equal(rv, 6)
+
+ def test_capitalize(self):
+ tmpl = env.from_string('{{ "foo bar"|capitalize }}')
+ assert tmpl.render() == 'Foo bar'
+
+ def test_center(self):
+ tmpl = env.from_string('{{ "foo"|center(9) }}')
+ assert tmpl.render() == ' foo '
+
+ def test_default(self):
+ tmpl = env.from_string(
+ "{{ missing|default('no') }}|{{ false|default('no') }}|"
+ "{{ false|default('no', true) }}|{{ given|default('no') }}"
+ )
+ assert tmpl.render(given='yes') == 'no|False|no|yes'
+
+ def test_dictsort(self):
+ tmpl = env.from_string(
+ '{{ foo|dictsort }}|'
+ '{{ foo|dictsort(true) }}|'
+ '{{ foo|dictsort(false, "value") }}'
+ )
+ out = tmpl.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
+ assert out == ("[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]|"
+ "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]|"
+ "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]")
+
+ def test_batch(self):
+ tmpl = env.from_string("{{ foo|batch(3)|list }}|"
+ "{{ foo|batch(3, 'X')|list }}")
+ out = tmpl.render(foo=list(range(10)))
+ assert out == ("[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
+ "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]")
+
+ def test_slice(self):
+ tmpl = env.from_string('{{ foo|slice(3)|list }}|'
+ '{{ foo|slice(3, "X")|list }}')
+ out = tmpl.render(foo=list(range(10)))
+ assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
+ "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]")
+
+ def test_escape(self):
+ tmpl = env.from_string('''{{ '<">&'|escape }}''')
+ out = tmpl.render()
+ assert out == '&lt;&#34;&gt;&amp;'
+
+ def test_striptags(self):
+ tmpl = env.from_string('''{{ foo|striptags }}''')
+ out = tmpl.render(foo=' <p>just a small \n <a href="#">'
+ 'example</a> link</p>\n<p>to a webpage</p> '
+ '<!-- <p>and some commented stuff</p> -->')
+ assert out == 'just a small example link to a webpage'
+
+ def test_filesizeformat(self):
+ tmpl = env.from_string(
+ '{{ 100|filesizeformat }}|'
+ '{{ 1000|filesizeformat }}|'
+ '{{ 1000000|filesizeformat }}|'
+ '{{ 1000000000|filesizeformat }}|'
+ '{{ 1000000000000|filesizeformat }}|'
+ '{{ 100|filesizeformat(true) }}|'
+ '{{ 1000|filesizeformat(true) }}|'
+ '{{ 1000000|filesizeformat(true) }}|'
+ '{{ 1000000000|filesizeformat(true) }}|'
+ '{{ 1000000000000|filesizeformat(true) }}'
+ )
+ out = tmpl.render()
+ self.assert_equal(out, (
+ '100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|'
+ '1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB'
+ ))
+
+ def test_filesizeformat_issue59(self):
+ tmpl = env.from_string(
+ '{{ 300|filesizeformat }}|'
+ '{{ 3000|filesizeformat }}|'
+ '{{ 3000000|filesizeformat }}|'
+ '{{ 3000000000|filesizeformat }}|'
+ '{{ 3000000000000|filesizeformat }}|'
+ '{{ 300|filesizeformat(true) }}|'
+ '{{ 3000|filesizeformat(true) }}|'
+ '{{ 3000000|filesizeformat(true) }}'
+ )
+ out = tmpl.render()
+ self.assert_equal(out, (
+ '300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|'
+ '2.9 KiB|2.9 MiB'
+ ))
+
+
+ def test_first(self):
+ tmpl = env.from_string('{{ foo|first }}')
+ out = tmpl.render(foo=list(range(10)))
+ assert out == '0'
+
+ def test_float(self):
+ tmpl = env.from_string('{{ "42"|float }}|'
+ '{{ "ajsghasjgd"|float }}|'
+ '{{ "32.32"|float }}')
+ out = tmpl.render()
+ assert out == '42.0|0.0|32.32'
+
+ def test_format(self):
+ tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''')
+ out = tmpl.render()
+ assert out == 'a|b'
+
+ def test_indent(self):
+ tmpl = env.from_string('{{ foo|indent(2) }}|{{ foo|indent(2, true) }}')
+ text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2)
+ out = tmpl.render(foo=text)
+ assert out == ('foo bar foo bar\n foo bar foo bar| '
+ 'foo bar foo bar\n foo bar foo bar')
+
+ def test_int(self):
+ tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|'
+ '{{ "32.32"|int }}')
+ out = tmpl.render()
+ assert out == '42|0|32'
+
+ def test_join(self):
+ tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
+ out = tmpl.render()
+ assert out == '1|2|3'
+
+ env2 = Environment(autoescape=True)
+ tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
+ assert tmpl.render() == '&lt;foo&gt;<span>foo</span>'
+
+ def test_join_attribute(self):
+ class User(object):
+ def __init__(self, username):
+ self.username = username
+ tmpl = env.from_string('''{{ users|join(', ', 'username') }}''')
+ assert tmpl.render(users=map(User, ['foo', 'bar'])) == 'foo, bar'
+
+ def test_last(self):
+ tmpl = env.from_string('''{{ foo|last }}''')
+ out = tmpl.render(foo=list(range(10)))
+ assert out == '9'
+
+ def test_length(self):
+ tmpl = env.from_string('''{{ "hello world"|length }}''')
+ out = tmpl.render()
+ assert out == '11'
+
+ def test_lower(self):
+ tmpl = env.from_string('''{{ "FOO"|lower }}''')
+ out = tmpl.render()
+ assert out == 'foo'
+
+ def test_pprint(self):
+ from pprint import pformat
+ tmpl = env.from_string('''{{ data|pprint }}''')
+ data = list(range(1000))
+ assert tmpl.render(data=data) == pformat(data)
+
+ def test_random(self):
+ tmpl = env.from_string('''{{ seq|random }}''')
+ seq = list(range(100))
+ for _ in range(10):
+ assert int(tmpl.render(seq=seq)) in seq
+
+ def test_reverse(self):
+ tmpl = env.from_string('{{ "foobar"|reverse|join }}|'
+ '{{ [1, 2, 3]|reverse|list }}')
+ assert tmpl.render() == 'raboof|[3, 2, 1]'
+
+ def test_string(self):
+ x = [1, 2, 3, 4, 5]
+ tmpl = env.from_string('''{{ obj|string }}''')
+ assert tmpl.render(obj=x) == text_type(x)
+
+ def test_title(self):
+ tmpl = env.from_string('''{{ "foo bar"|title }}''')
+ assert tmpl.render() == "Foo Bar"
+ tmpl = env.from_string('''{{ "foo's bar"|title }}''')
+ assert tmpl.render() == "Foo's Bar"
+ tmpl = env.from_string('''{{ "foo bar"|title }}''')
+ assert tmpl.render() == "Foo Bar"
+ tmpl = env.from_string('''{{ "f bar f"|title }}''')
+ assert tmpl.render() == "F Bar F"
+ tmpl = env.from_string('''{{ "foo-bar"|title }}''')
+ assert tmpl.render() == "Foo-Bar"
+ tmpl = env.from_string('''{{ "foo\tbar"|title }}''')
+ assert tmpl.render() == "Foo\tBar"
+ tmpl = env.from_string('''{{ "FOO\tBAR"|title }}''')
+ assert tmpl.render() == "Foo\tBar"
+
+ def test_truncate(self):
+ tmpl = env.from_string(
+ '{{ data|truncate(15, true, ">>>") }}|'
+ '{{ data|truncate(15, false, ">>>") }}|'
+ '{{ smalldata|truncate(15) }}'
+ )
+ out = tmpl.render(data='foobar baz bar' * 1000,
+ smalldata='foobar baz bar')
+ assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar'
+
+ def test_upper(self):
+ tmpl = env.from_string('{{ "foo"|upper }}')
+ assert tmpl.render() == 'FOO'
+
+ def test_urlize(self):
+ tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
+ assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
+ 'http://www.example.com/</a> bar'
+
+ def test_wordcount(self):
+ tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
+ assert tmpl.render() == '3'
+
+ def test_block(self):
+ tmpl = env.from_string('{% filter lower|escape %}<HEHE>{% endfilter %}')
+ assert tmpl.render() == '&lt;hehe&gt;'
+
+ def test_chaining(self):
+ tmpl = env.from_string('''{{ ['<foo>', '<bar>']|first|upper|escape }}''')
+ assert tmpl.render() == '&lt;FOO&gt;'
+
+ def test_sum(self):
+ tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''')
+ assert tmpl.render() == '21'
+
+ def test_sum_attributes(self):
+ tmpl = env.from_string('''{{ values|sum('value') }}''')
+ assert tmpl.render(values=[
+ {'value': 23},
+ {'value': 1},
+ {'value': 18},
+ ]) == '42'
+
+ def test_sum_attributes_nested(self):
+ tmpl = env.from_string('''{{ values|sum('real.value') }}''')
+ assert tmpl.render(values=[
+ {'real': {'value': 23}},
+ {'real': {'value': 1}},
+ {'real': {'value': 18}},
+ ]) == '42'
+
+ def test_sum_attributes_tuple(self):
+ tmpl = env.from_string('''{{ values.items()|sum('1') }}''')
+ assert tmpl.render(values={
+ 'foo': 23,
+ 'bar': 1,
+ 'baz': 18,
+ }) == '42'
+
+ def test_abs(self):
+ tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''')
+ assert tmpl.render() == '1|1', tmpl.render()
+
+ def test_round_positive(self):
+ tmpl = env.from_string('{{ 2.7|round }}|{{ 2.1|round }}|'
+ "{{ 2.1234|round(3, 'floor') }}|"
+ "{{ 2.1|round(0, 'ceil') }}")
+ assert tmpl.render() == '3.0|2.0|2.123|3.0', tmpl.render()
+
+ def test_round_negative(self):
+ tmpl = env.from_string('{{ 21.3|round(-1)}}|'
+ "{{ 21.3|round(-1, 'ceil')}}|"
+ "{{ 21.3|round(-1, 'floor')}}")
+ assert tmpl.render() == '20.0|30.0|20.0',tmpl.render()
+
+ def test_xmlattr(self):
+ tmpl = env.from_string("{{ {'foo': 42, 'bar': 23, 'fish': none, "
+ "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}")
+ out = tmpl.render().split()
+ assert len(out) == 3
+ assert 'foo="42"' in out
+ assert 'bar="23"' in out
+ assert 'blub:blub="&lt;?&gt;"' in out
+
+ def test_sort1(self):
+ tmpl = env.from_string('{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}')
+ assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
+
+ def test_sort2(self):
+ tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
+ assert tmpl.render() == 'AbcD'
+
+ def test_sort3(self):
+ tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
+ assert tmpl.render() == "['Bar', 'blah', 'foo']"
+
+ def test_sort4(self):
+ @implements_to_string
+ class Magic(object):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return text_type(self.value)
+ tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
+ assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
+
+ def test_groupby(self):
+ tmpl = env.from_string('''
+ {%- for grouper, list in [{'foo': 1, 'bar': 2},
+ {'foo': 2, 'bar': 3},
+ {'foo': 1, 'bar': 1},
+ {'foo': 3, 'bar': 4}]|groupby('foo') -%}
+ {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
+ {%- endfor %}''')
+ assert tmpl.render().split('|') == [
+ "1: 1, 2: 1, 1",
+ "2: 2, 3",
+ "3: 3, 4",
+ ""
+ ]
+
+ def test_groupby_tuple_index(self):
+ tmpl = env.from_string('''
+ {%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
+ {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
+ {%- endfor %}''')
+ assert tmpl.render() == 'a:1:2|b:1|'
+
+ def test_groupby_multidot(self):
+ class Date(object):
+ def __init__(self, day, month, year):
+ self.day = day
+ self.month = month
+ self.year = year
+ class Article(object):
+ def __init__(self, title, *date):
+ self.date = Date(*date)
+ self.title = title
+ articles = [
+ Article('aha', 1, 1, 1970),
+ Article('interesting', 2, 1, 1970),
+ Article('really?', 3, 1, 1970),
+ Article('totally not', 1, 1, 1971)
+ ]
+ tmpl = env.from_string('''
+ {%- for year, list in articles|groupby('date.year') -%}
+ {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
+ {%- endfor %}''')
+ assert tmpl.render(articles=articles).split('|') == [
+ '1970[aha][interesting][really?]',
+ '1971[totally not]',
+ ''
+ ]
+
+ def test_filtertag(self):
+ tmpl = env.from_string("{% filter upper|replace('FOO', 'foo') %}"
+ "foobar{% endfilter %}")
+ assert tmpl.render() == 'fooBAR'
+
+ def test_replace(self):
+ env = Environment()
+ tmpl = env.from_string('{{ string|replace("o", 42) }}')
+ assert tmpl.render(string='<foo>') == '<f4242>'
+ env = Environment(autoescape=True)
+ tmpl = env.from_string('{{ string|replace("o", 42) }}')
+ assert tmpl.render(string='<foo>') == '&lt;f4242&gt;'
+ tmpl = env.from_string('{{ string|replace("<", 42) }}')
+ assert tmpl.render(string='<foo>') == '42foo&gt;'
+ tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
+ assert tmpl.render(string=Markup('foo')) == 'f&gt;x&lt;&gt;x&lt;'
+
+ def test_forceescape(self):
+ tmpl = env.from_string('{{ x|forceescape }}')
+ assert tmpl.render(x=Markup('<div />')) == u'&lt;div /&gt;'
+
+ def test_safe(self):
+ env = Environment(autoescape=True)
+ tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
+ assert tmpl.render() == '<div>foo</div>'
+ tmpl = env.from_string('{{ "<div>foo</div>" }}')
+ assert tmpl.render() == '&lt;div&gt;foo&lt;/div&gt;'
+
+ def test_urlencode(self):
+ env = Environment(autoescape=True)
+ tmpl = env.from_string('{{ "Hello, world!"|urlencode }}')
+ assert tmpl.render() == 'Hello%2C%20world%21'
+ tmpl = env.from_string('{{ o|urlencode }}')
+ assert tmpl.render(o=u"Hello, world\u203d") == "Hello%2C%20world%E2%80%BD"
+ assert tmpl.render(o=(("f", 1),)) == "f=1"
+ assert tmpl.render(o=(('f', 1), ("z", 2))) == "f=1&amp;z=2"
+ assert tmpl.render(o=((u"\u203d", 1),)) == "%E2%80%BD=1"
+ assert tmpl.render(o={u"\u203d": 1}) == "%E2%80%BD=1"
+ assert tmpl.render(o={0: 1}) == "0=1"
+
+ def test_simple_map(self):
+ env = Environment()
+ tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
+ self.assertEqual(tmpl.render(), '6')
+
+ def test_attribute_map(self):
+ class User(object):
+ def __init__(self, name):
+ self.name = name
+ env = Environment()
+ users = [
+ User('john'),
+ User('jane'),
+ User('mike'),
+ ]
+ tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
+ self.assertEqual(tmpl.render(users=users), 'john|jane|mike')
+
+ def test_empty_map(self):
+ env = Environment()
+ tmpl = env.from_string('{{ none|map("upper")|list }}')
+ self.assertEqual(tmpl.render(), '[]')
+
+ def test_simple_select(self):
+ env = Environment()
+ tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
+ self.assertEqual(tmpl.render(), '1|3|5')
+
+ def test_bool_select(self):
+ env = Environment()
+ tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
+ self.assertEqual(tmpl.render(), '1|2|3|4|5')
+
+ def test_simple_reject(self):
+ env = Environment()
+ tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
+ self.assertEqual(tmpl.render(), '2|4')
+
+ def test_bool_reject(self):
+ env = Environment()
+ tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
+ self.assertEqual(tmpl.render(), 'None|False|0')
+
+ def test_simple_select_attr(self):
+ class User(object):
+ def __init__(self, name, is_active):
+ self.name = name
+ self.is_active = is_active
+ env = Environment()
+ users = [
+ User('john', True),
+ User('jane', True),
+ User('mike', False),
+ ]
+ tmpl = env.from_string('{{ users|selectattr("is_active")|'
+ 'map(attribute="name")|join("|") }}')
+ self.assertEqual(tmpl.render(users=users), 'john|jane')
+
+ def test_simple_reject_attr(self):
+ class User(object):
+ def __init__(self, name, is_active):
+ self.name = name
+ self.is_active = is_active
+ env = Environment()
+ users = [
+ User('john', True),
+ User('jane', True),
+ User('mike', False),
+ ]
+ tmpl = env.from_string('{{ users|rejectattr("is_active")|'
+ 'map(attribute="name")|join("|") }}')
+ self.assertEqual(tmpl.render(users=users), 'mike')
+
+ def test_func_select_attr(self):
+ class User(object):
+ def __init__(self, id, name):
+ self.id = id
+ self.name = name
+ env = Environment()
+ users = [
+ User(1, 'john'),
+ User(2, 'jane'),
+ User(3, 'mike'),
+ ]
+ tmpl = env.from_string('{{ users|selectattr("id", "odd")|'
+ 'map(attribute="name")|join("|") }}')
+ self.assertEqual(tmpl.render(users=users), 'john|mike')
+
+ def test_func_reject_attr(self):
+ class User(object):
+ def __init__(self, id, name):
+ self.id = id
+ self.name = name
+ env = Environment()
+ users = [
+ User(1, 'john'),
+ User(2, 'jane'),
+ User(3, 'mike'),
+ ]
+ tmpl = env.from_string('{{ users|rejectattr("id", "odd")|'
+ 'map(attribute="name")|join("|") }}')
+ self.assertEqual(tmpl.render(users=users), 'jane')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(FilterTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/imports.py b/pyload/lib/jinja2/testsuite/imports.py
new file mode 100644
index 000000000..3db9008de
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/imports.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.imports
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests the import features (with includes).
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, DictLoader
+from jinja2.exceptions import TemplateNotFound, TemplatesNotFound
+
+
+test_env = Environment(loader=DictLoader(dict(
+ module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
+ header='[{{ foo }}|{{ 23 }}]',
+ o_printer='({{ o }})'
+)))
+test_env.globals['bar'] = 23
+
+
+class ImportsTestCase(JinjaTestCase):
+
+ def test_context_imports(self):
+ t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
+ assert t.render(foo=42) == '[|23]'
+ t = test_env.from_string('{% import "module" as m without context %}{{ m.test() }}')
+ assert t.render(foo=42) == '[|23]'
+ t = test_env.from_string('{% import "module" as m with context %}{{ m.test() }}')
+ assert t.render(foo=42) == '[42|23]'
+ t = test_env.from_string('{% from "module" import test %}{{ test() }}')
+ assert t.render(foo=42) == '[|23]'
+ t = test_env.from_string('{% from "module" import test without context %}{{ test() }}')
+ assert t.render(foo=42) == '[|23]'
+ t = test_env.from_string('{% from "module" import test with context %}{{ test() }}')
+ assert t.render(foo=42) == '[42|23]'
+
+ def test_trailing_comma(self):
+ test_env.from_string('{% from "foo" import bar, baz with context %}')
+ test_env.from_string('{% from "foo" import bar, baz, with context %}')
+ test_env.from_string('{% from "foo" import bar, with context %}')
+ test_env.from_string('{% from "foo" import bar, with, context %}')
+ test_env.from_string('{% from "foo" import bar, with with context %}')
+
+ def test_exports(self):
+ m = test_env.from_string('''
+ {% macro toplevel() %}...{% endmacro %}
+ {% macro __private() %}...{% endmacro %}
+ {% set variable = 42 %}
+ {% for item in [1] %}
+ {% macro notthere() %}{% endmacro %}
+ {% endfor %}
+ ''').module
+ assert m.toplevel() == '...'
+ assert not hasattr(m, '__missing')
+ assert m.variable == 42
+ assert not hasattr(m, 'notthere')
+
+
+class IncludesTestCase(JinjaTestCase):
+
+ def test_context_include(self):
+ t = test_env.from_string('{% include "header" %}')
+ assert t.render(foo=42) == '[42|23]'
+ t = test_env.from_string('{% include "header" with context %}')
+ assert t.render(foo=42) == '[42|23]'
+ t = test_env.from_string('{% include "header" without context %}')
+ assert t.render(foo=42) == '[|23]'
+
+ def test_choice_includes(self):
+ t = test_env.from_string('{% include ["missing", "header"] %}')
+ assert t.render(foo=42) == '[42|23]'
+
+ t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
+ assert t.render(foo=42) == ''
+
+ t = test_env.from_string('{% include ["missing", "missing2"] %}')
+ self.assert_raises(TemplateNotFound, t.render)
+ try:
+ t.render()
+ except TemplatesNotFound as e:
+ assert e.templates == ['missing', 'missing2']
+ assert e.name == 'missing2'
+ else:
+ assert False, 'thou shalt raise'
+
+ def test_includes(t, **ctx):
+ ctx['foo'] = 42
+ assert t.render(ctx) == '[42|23]'
+
+ t = test_env.from_string('{% include ["missing", "header"] %}')
+ test_includes(t)
+ t = test_env.from_string('{% include x %}')
+ test_includes(t, x=['missing', 'header'])
+ t = test_env.from_string('{% include [x, "header"] %}')
+ test_includes(t, x='missing')
+ t = test_env.from_string('{% include x %}')
+ test_includes(t, x='header')
+ t = test_env.from_string('{% include x %}')
+ test_includes(t, x='header')
+ t = test_env.from_string('{% include [x] %}')
+ test_includes(t, x='header')
+
+ def test_include_ignoring_missing(self):
+ t = test_env.from_string('{% include "missing" %}')
+ self.assert_raises(TemplateNotFound, t.render)
+ for extra in '', 'with context', 'without context':
+ t = test_env.from_string('{% include "missing" ignore missing ' +
+ extra + ' %}')
+ assert t.render() == ''
+
+ def test_context_include_with_overrides(self):
+ env = Environment(loader=DictLoader(dict(
+ main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
+ item="{{ item }}"
+ )))
+ assert env.get_template("main").render() == "123"
+
+ def test_unoptimized_scopes(self):
+ t = test_env.from_string("""
+ {% macro outer(o) %}
+ {% macro inner() %}
+ {% include "o_printer" %}
+ {% endmacro %}
+ {{ inner() }}
+ {% endmacro %}
+ {{ outer("FOO") }}
+ """)
+ assert t.render().strip() == '(FOO)'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ImportsTestCase))
+ suite.addTest(unittest.makeSuite(IncludesTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/inheritance.py b/pyload/lib/jinja2/testsuite/inheritance.py
new file mode 100644
index 000000000..e0f51cda9
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/inheritance.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.inheritance
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests the template inheritance feature.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, DictLoader, TemplateError
+
+
+LAYOUTTEMPLATE = '''\
+|{% block block1 %}block 1 from layout{% endblock %}
+|{% block block2 %}block 2 from layout{% endblock %}
+|{% block block3 %}
+{% block block4 %}nested block 4 from layout{% endblock %}
+{% endblock %}|'''
+
+LEVEL1TEMPLATE = '''\
+{% extends "layout" %}
+{% block block1 %}block 1 from level1{% endblock %}'''
+
+LEVEL2TEMPLATE = '''\
+{% extends "level1" %}
+{% block block2 %}{% block block5 %}nested block 5 from level2{%
+endblock %}{% endblock %}'''
+
+LEVEL3TEMPLATE = '''\
+{% extends "level2" %}
+{% block block5 %}block 5 from level3{% endblock %}
+{% block block4 %}block 4 from level3{% endblock %}
+'''
+
+LEVEL4TEMPLATE = '''\
+{% extends "level3" %}
+{% block block3 %}block 3 from level4{% endblock %}
+'''
+
+WORKINGTEMPLATE = '''\
+{% extends "layout" %}
+{% block block1 %}
+ {% if false %}
+ {% block block2 %}
+ this should workd
+ {% endblock %}
+ {% endif %}
+{% endblock %}
+'''
+
+DOUBLEEXTENDS = '''\
+{% extends "layout" %}
+{% extends "layout" %}
+{% block block1 %}
+ {% if false %}
+ {% block block2 %}
+ this should workd
+ {% endblock %}
+ {% endif %}
+{% endblock %}
+'''
+
+
+env = Environment(loader=DictLoader({
+ 'layout': LAYOUTTEMPLATE,
+ 'level1': LEVEL1TEMPLATE,
+ 'level2': LEVEL2TEMPLATE,
+ 'level3': LEVEL3TEMPLATE,
+ 'level4': LEVEL4TEMPLATE,
+ 'working': WORKINGTEMPLATE,
+ 'doublee': DOUBLEEXTENDS,
+}), trim_blocks=True)
+
+
+class InheritanceTestCase(JinjaTestCase):
+
+ def test_layout(self):
+ tmpl = env.get_template('layout')
+ assert tmpl.render() == ('|block 1 from layout|block 2 from '
+ 'layout|nested block 4 from layout|')
+
+ def test_level1(self):
+ tmpl = env.get_template('level1')
+ assert tmpl.render() == ('|block 1 from level1|block 2 from '
+ 'layout|nested block 4 from layout|')
+
+ def test_level2(self):
+ tmpl = env.get_template('level2')
+ assert tmpl.render() == ('|block 1 from level1|nested block 5 from '
+ 'level2|nested block 4 from layout|')
+
+ def test_level3(self):
+ tmpl = env.get_template('level3')
+ assert tmpl.render() == ('|block 1 from level1|block 5 from level3|'
+ 'block 4 from level3|')
+
+ def test_level4(sel):
+ tmpl = env.get_template('level4')
+ assert tmpl.render() == ('|block 1 from level1|block 5 from '
+ 'level3|block 3 from level4|')
+
+ def test_super(self):
+ env = Environment(loader=DictLoader({
+ 'a': '{% block intro %}INTRO{% endblock %}|'
+ 'BEFORE|{% block data %}INNER{% endblock %}|AFTER',
+ 'b': '{% extends "a" %}{% block data %}({{ '
+ 'super() }}){% endblock %}',
+ 'c': '{% extends "b" %}{% block intro %}--{{ '
+ 'super() }}--{% endblock %}\n{% block data '
+ '%}[{{ super() }}]{% endblock %}'
+ }))
+ tmpl = env.get_template('c')
+ assert tmpl.render() == '--INTRO--|BEFORE|[(INNER)]|AFTER'
+
+ def test_working(self):
+ tmpl = env.get_template('working')
+
+ def test_reuse_blocks(self):
+ tmpl = env.from_string('{{ self.foo() }}|{% block foo %}42'
+ '{% endblock %}|{{ self.foo() }}')
+ assert tmpl.render() == '42|42|42'
+
+ def test_preserve_blocks(self):
+ env = Environment(loader=DictLoader({
+ 'a': '{% if false %}{% block x %}A{% endblock %}{% endif %}{{ self.x() }}',
+ 'b': '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}'
+ }))
+ tmpl = env.get_template('b')
+ assert tmpl.render() == 'BA'
+
+ def test_dynamic_inheritance(self):
+ env = Environment(loader=DictLoader({
+ 'master1': 'MASTER1{% block x %}{% endblock %}',
+ 'master2': 'MASTER2{% block x %}{% endblock %}',
+ 'child': '{% extends master %}{% block x %}CHILD{% endblock %}'
+ }))
+ tmpl = env.get_template('child')
+ for m in range(1, 3):
+ assert tmpl.render(master='master%d' % m) == 'MASTER%dCHILD' % m
+
+ def test_multi_inheritance(self):
+ env = Environment(loader=DictLoader({
+ 'master1': 'MASTER1{% block x %}{% endblock %}',
+ 'master2': 'MASTER2{% block x %}{% endblock %}',
+ 'child': '''{% if master %}{% extends master %}{% else %}{% extends
+ 'master1' %}{% endif %}{% block x %}CHILD{% endblock %}'''
+ }))
+ tmpl = env.get_template('child')
+ assert tmpl.render(master='master2') == 'MASTER2CHILD'
+ assert tmpl.render(master='master1') == 'MASTER1CHILD'
+ assert tmpl.render() == 'MASTER1CHILD'
+
+ def test_scoped_block(self):
+ env = Environment(loader=DictLoader({
+ 'master.html': '{% for item in seq %}[{% block item scoped %}'
+ '{% endblock %}]{% endfor %}'
+ }))
+ t = env.from_string('{% extends "master.html" %}{% block item %}'
+ '{{ item }}{% endblock %}')
+ assert t.render(seq=list(range(5))) == '[0][1][2][3][4]'
+
+ def test_super_in_scoped_block(self):
+ env = Environment(loader=DictLoader({
+ 'master.html': '{% for item in seq %}[{% block item scoped %}'
+ '{{ item }}{% endblock %}]{% endfor %}'
+ }))
+ t = env.from_string('{% extends "master.html" %}{% block item %}'
+ '{{ super() }}|{{ item * 2 }}{% endblock %}')
+ assert t.render(seq=list(range(5))) == '[0|0][1|2][2|4][3|6][4|8]'
+
+ def test_scoped_block_after_inheritance(self):
+ env = Environment(loader=DictLoader({
+ 'layout.html': '''
+ {% block useless %}{% endblock %}
+ ''',
+ 'index.html': '''
+ {%- extends 'layout.html' %}
+ {% from 'helpers.html' import foo with context %}
+ {% block useless %}
+ {% for x in [1, 2, 3] %}
+ {% block testing scoped %}
+ {{ foo(x) }}
+ {% endblock %}
+ {% endfor %}
+ {% endblock %}
+ ''',
+ 'helpers.html': '''
+ {% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
+ '''
+ }))
+ rv = env.get_template('index.html').render(the_foo=42).split()
+ assert rv == ['43', '44', '45']
+
+
+class BugFixTestCase(JinjaTestCase):
+
+ def test_fixed_macro_scoping_bug(self):
+ assert Environment(loader=DictLoader({
+ 'test.html': '''\
+ {% extends 'details.html' %}
+
+ {% macro my_macro() %}
+ my_macro
+ {% endmacro %}
+
+ {% block inner_box %}
+ {{ my_macro() }}
+ {% endblock %}
+ ''',
+ 'details.html': '''\
+ {% extends 'standard.html' %}
+
+ {% macro my_macro() %}
+ my_macro
+ {% endmacro %}
+
+ {% block content %}
+ {% block outer_box %}
+ outer_box
+ {% block inner_box %}
+ inner_box
+ {% endblock %}
+ {% endblock %}
+ {% endblock %}
+ ''',
+ 'standard.html': '''
+ {% block content %}&nbsp;{% endblock %}
+ '''
+ })).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
+
+ def test_double_extends(self):
+ """Ensures that a template with more than 1 {% extends ... %} usage
+ raises a ``TemplateError``.
+ """
+ try:
+ tmpl = env.get_template('doublee')
+ except Exception as e:
+ assert isinstance(e, TemplateError)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(InheritanceTestCase))
+ suite.addTest(unittest.makeSuite(BugFixTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/lexnparse.py b/pyload/lib/jinja2/testsuite/lexnparse.py
new file mode 100644
index 000000000..bd1c94cd3
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/lexnparse.py
@@ -0,0 +1,593 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.lexnparse
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ All the unittests regarding lexing, parsing and syntax.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, Template, TemplateSyntaxError, \
+ UndefinedError, nodes
+from jinja2._compat import next, iteritems, text_type, PY2
+from jinja2.lexer import Token, TokenStream, TOKEN_EOF, \
+ TOKEN_BLOCK_BEGIN, TOKEN_BLOCK_END
+
+env = Environment()
+
+
+# how does a string look like in jinja syntax?
+if PY2:
+ def jinja_string_repr(string):
+ return repr(string)[1:]
+else:
+ jinja_string_repr = repr
+
+
+class TokenStreamTestCase(JinjaTestCase):
+ test_tokens = [Token(1, TOKEN_BLOCK_BEGIN, ''),
+ Token(2, TOKEN_BLOCK_END, ''),
+ ]
+
+ def test_simple(self):
+ ts = TokenStream(self.test_tokens, "foo", "bar")
+ assert ts.current.type is TOKEN_BLOCK_BEGIN
+ assert bool(ts)
+ assert not bool(ts.eos)
+ next(ts)
+ assert ts.current.type is TOKEN_BLOCK_END
+ assert bool(ts)
+ assert not bool(ts.eos)
+ next(ts)
+ assert ts.current.type is TOKEN_EOF
+ assert not bool(ts)
+ assert bool(ts.eos)
+
+ def test_iter(self):
+ token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")]
+ assert token_types == ['block_begin', 'block_end', ]
+
+
+class LexerTestCase(JinjaTestCase):
+
+ def test_raw1(self):
+ tmpl = env.from_string('{% raw %}foo{% endraw %}|'
+ '{%raw%}{{ bar }}|{% baz %}{% endraw %}')
+ assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
+
+ def test_raw2(self):
+ tmpl = env.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
+ assert tmpl.render() == '123'
+
+ def test_balancing(self):
+ env = Environment('{%', '%}', '${', '}')
+ tmpl = env.from_string('''{% for item in seq
+ %}${{'foo': item}|upper}{% endfor %}''')
+ assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
+
+ def test_comments(self):
+ env = Environment('<!--', '-->', '{', '}')
+ tmpl = env.from_string('''\
+<ul>
+<!--- for item in seq -->
+ <li>{item}</li>
+<!--- endfor -->
+</ul>''')
+ assert tmpl.render(seq=list(range(3))) == ("<ul>\n <li>0</li>\n "
+ "<li>1</li>\n <li>2</li>\n</ul>")
+
+ def test_string_escapes(self):
+ for char in u'\0', u'\u2668', u'\xe4', u'\t', u'\r', u'\n':
+ tmpl = env.from_string('{{ %s }}' % jinja_string_repr(char))
+ assert tmpl.render() == char
+ assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u'\u2668'
+
+ def test_bytefallback(self):
+ from pprint import pformat
+ tmpl = env.from_string(u'''{{ 'foo'|pprint }}|{{ 'bÀr'|pprint }}''')
+ assert tmpl.render() == pformat('foo') + '|' + pformat(u'bÀr')
+
+ def test_operators(self):
+ from jinja2.lexer import operators
+ for test, expect in iteritems(operators):
+ if test in '([{}])':
+ continue
+ stream = env.lexer.tokenize('{{ %s }}' % test)
+ next(stream)
+ assert stream.current.type == expect
+
+ def test_normalizing(self):
+ for seq in '\r', '\r\n', '\n':
+ env = Environment(newline_sequence=seq)
+ tmpl = env.from_string('1\n2\r\n3\n4\n')
+ result = tmpl.render()
+ assert result.replace(seq, 'X') == '1X2X3X4'
+
+ def test_trailing_newline(self):
+ for keep in [True, False]:
+ env = Environment(keep_trailing_newline=keep)
+ for template,expected in [
+ ('', {}),
+ ('no\nnewline', {}),
+ ('with\nnewline\n', {False: 'with\nnewline'}),
+ ('with\nseveral\n\n\n', {False: 'with\nseveral\n\n'}),
+ ]:
+ tmpl = env.from_string(template)
+ expect = expected.get(keep, template)
+ result = tmpl.render()
+ assert result == expect, (keep, template, result, expect)
+
+class ParserTestCase(JinjaTestCase):
+
+ def test_php_syntax(self):
+ env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
+ tmpl = env.from_string('''\
+<!-- I'm a comment, I'm not interesting -->\
+<? for item in seq -?>
+ <?= item ?>
+<?- endfor ?>''')
+ assert tmpl.render(seq=list(range(5))) == '01234'
+
+ def test_erb_syntax(self):
+ env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
+ tmpl = env.from_string('''\
+<%# I'm a comment, I'm not interesting %>\
+<% for item in seq -%>
+ <%= item %>
+<%- endfor %>''')
+ assert tmpl.render(seq=list(range(5))) == '01234'
+
+ def test_comment_syntax(self):
+ env = Environment('<!--', '-->', '${', '}', '<!--#', '-->')
+ tmpl = env.from_string('''\
+<!--# I'm a comment, I'm not interesting -->\
+<!-- for item in seq --->
+ ${item}
+<!--- endfor -->''')
+ assert tmpl.render(seq=list(range(5))) == '01234'
+
+ def test_balancing(self):
+ tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
+ assert tmpl.render() == 'bar'
+
+ def test_start_comment(self):
+ tmpl = env.from_string('''{# foo comment
+and bar comment #}
+{% macro blub() %}foo{% endmacro %}
+{{ blub() }}''')
+ assert tmpl.render().strip() == 'foo'
+
+ def test_line_syntax(self):
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
+ tmpl = env.from_string('''\
+<%# regular comment %>
+% for item in seq:
+ ${item}
+% endfor''')
+ assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
+ list(range(5))
+
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
+ tmpl = env.from_string('''\
+<%# regular comment %>
+% for item in seq:
+ ${item} ## the rest of the stuff
+% endfor''')
+ assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
+ list(range(5))
+
+ def test_line_syntax_priority(self):
+ # XXX: why is the whitespace there in front of the newline?
+ env = Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
+ tmpl = env.from_string('''\
+/* ignore me.
+ I'm a multiline comment */
+## for item in seq:
+* ${item} # this is just extra stuff
+## endfor''')
+ assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
+ env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
+ tmpl = env.from_string('''\
+/* ignore me.
+ I'm a multiline comment */
+# for item in seq:
+* ${item} ## this is just extra stuff
+ ## extra stuff i just want to ignore
+# endfor''')
+ assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
+
+ def test_error_messages(self):
+ def assert_error(code, expected):
+ try:
+ Template(code)
+ except TemplateSyntaxError as e:
+ assert str(e) == expected, 'unexpected error message'
+ else:
+ assert False, 'that was supposed to be an error'
+
+ assert_error('{% for item in seq %}...{% endif %}',
+ "Encountered unknown tag 'endif'. Jinja was looking "
+ "for the following tags: 'endfor' or 'else'. The "
+ "innermost block that needs to be closed is 'for'.")
+ assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
+ "Encountered unknown tag 'endfor'. Jinja was looking for "
+ "the following tags: 'elif' or 'else' or 'endif'. The "
+ "innermost block that needs to be closed is 'if'.")
+ assert_error('{% if foo %}',
+ "Unexpected end of template. Jinja was looking for the "
+ "following tags: 'elif' or 'else' or 'endif'. The "
+ "innermost block that needs to be closed is 'if'.")
+ assert_error('{% for item in seq %}',
+ "Unexpected end of template. Jinja was looking for the "
+ "following tags: 'endfor' or 'else'. The innermost block "
+ "that needs to be closed is 'for'.")
+ assert_error('{% block foo-bar-baz %}',
+ "Block names in Jinja have to be valid Python identifiers "
+ "and may not contain hyphens, use an underscore instead.")
+ assert_error('{% unknown_tag %}',
+ "Encountered unknown tag 'unknown_tag'.")
+
+
+class SyntaxTestCase(JinjaTestCase):
+
+ def test_call(self):
+ env = Environment()
+ env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
+ tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
+ assert tmpl.render() == 'abdfh'
+
+ def test_slicing(self):
+ tmpl = env.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
+ assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
+
+ def test_attr(self):
+ tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
+ assert tmpl.render(foo={'bar': 42}) == '42|42'
+
+ def test_subscript(self):
+ tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
+ assert tmpl.render(foo=[0, 1, 2]) == '0|2'
+
+ def test_tuple(self):
+ tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
+ assert tmpl.render() == '()|(1,)|(1, 2)'
+
+ def test_math(self):
+ tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
+ assert tmpl.render() == '1.5|8'
+
+ def test_div(self):
+ tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
+ assert tmpl.render() == '1|1.5|1'
+
+ def test_unary(self):
+ tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
+ assert tmpl.render() == '3|-3'
+
+ def test_concat(self):
+ tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
+ assert tmpl.render() == '[1, 2]foo'
+
+ def test_compare(self):
+ tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
+ '{{ 2 == 2 }}|{{ 1 <= 1 }}')
+ assert tmpl.render() == 'True|True|True|True|True'
+
+ def test_inop(self):
+ tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
+ assert tmpl.render() == 'True|False'
+
+ def test_literals(self):
+ tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
+ assert tmpl.render().lower() == '[]|{}|()'
+
+ def test_bool(self):
+ tmpl = env.from_string('{{ true and false }}|{{ false '
+ 'or true }}|{{ not false }}')
+ assert tmpl.render() == 'False|True|True'
+
+ def test_grouping(self):
+ tmpl = env.from_string('{{ (true and false) or (false and true) and not false }}')
+ assert tmpl.render() == 'False'
+
+ def test_django_attr(self):
+ tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
+ assert tmpl.render() == '1|1'
+
+ def test_conditional_expression(self):
+ tmpl = env.from_string('''{{ 0 if true else 1 }}''')
+ assert tmpl.render() == '0'
+
+ def test_short_conditional_expression(self):
+ tmpl = env.from_string('<{{ 1 if false }}>')
+ assert tmpl.render() == '<>'
+
+ tmpl = env.from_string('<{{ (1 if false).bar }}>')
+ self.assert_raises(UndefinedError, tmpl.render)
+
+ def test_filter_priority(self):
+ tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
+ assert tmpl.render() == 'FOOBAR'
+
+ def test_function_calls(self):
+ tests = [
+ (True, '*foo, bar'),
+ (True, '*foo, *bar'),
+ (True, '*foo, bar=42'),
+ (True, '**foo, *bar'),
+ (True, '**foo, bar'),
+ (False, 'foo, bar'),
+ (False, 'foo, bar=42'),
+ (False, 'foo, bar=23, *args'),
+ (False, 'a, b=c, *d, **e'),
+ (False, '*foo, **bar')
+ ]
+ for should_fail, sig in tests:
+ if should_fail:
+ self.assert_raises(TemplateSyntaxError,
+ env.from_string, '{{ foo(%s) }}' % sig)
+ else:
+ env.from_string('foo(%s)' % sig)
+
+ def test_tuple_expr(self):
+ for tmpl in [
+ '{{ () }}',
+ '{{ (1, 2) }}',
+ '{{ (1, 2,) }}',
+ '{{ 1, }}',
+ '{{ 1, 2 }}',
+ '{% for foo, bar in seq %}...{% endfor %}',
+ '{% for x in foo, bar %}...{% endfor %}',
+ '{% for x in foo, %}...{% endfor %}'
+ ]:
+ assert env.from_string(tmpl)
+
+ def test_trailing_comma(self):
+ tmpl = env.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
+ assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
+
+ def test_block_end_name(self):
+ env.from_string('{% block foo %}...{% endblock foo %}')
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ '{% block x %}{% endblock y %}')
+
+ def test_constant_casing(self):
+ for const in True, False, None:
+ tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
+ str(const), str(const).lower(), str(const).upper()
+ ))
+ assert tmpl.render() == '%s|%s|' % (const, const)
+
+ def test_test_chaining(self):
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ '{{ foo is string is sequence }}')
+ assert env.from_string('{{ 42 is string or 42 is number }}'
+ ).render() == 'True'
+
+ def test_string_concatenation(self):
+ tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
+ assert tmpl.render() == 'foobarbaz'
+
+ def test_notin(self):
+ bar = range(100)
+ tmpl = env.from_string('''{{ not 42 in bar }}''')
+ assert tmpl.render(bar=bar) == text_type(not 42 in bar)
+
+ def test_implicit_subscribed_tuple(self):
+ class Foo(object):
+ def __getitem__(self, x):
+ return x
+ t = env.from_string('{{ foo[1, 2] }}')
+ assert t.render(foo=Foo()) == u'(1, 2)'
+
+ def test_raw2(self):
+ tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
+ assert tmpl.render() == '{{ FOO }} and {% BAR %}'
+
+ def test_const(self):
+ tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
+ '{{ none is defined }}|{{ missing is defined }}')
+ assert tmpl.render() == 'True|False|None|True|False'
+
+ def test_neg_filter_priority(self):
+ node = env.parse('{{ -1|foo }}')
+ assert isinstance(node.body[0].nodes[0], nodes.Filter)
+ assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
+
+ def test_const_assign(self):
+ constass1 = '''{% set true = 42 %}'''
+ constass2 = '''{% for none in seq %}{% endfor %}'''
+ for tmpl in constass1, constass2:
+ self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
+
+ def test_localset(self):
+ tmpl = env.from_string('''{% set foo = 0 %}\
+{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
+{{ foo }}''')
+ assert tmpl.render() == '0'
+
+ def test_parse_unary(self):
+ tmpl = env.from_string('{{ -foo["bar"] }}')
+ assert tmpl.render(foo={'bar': 42}) == '-42'
+ tmpl = env.from_string('{{ -foo["bar"]|abs }}')
+ assert tmpl.render(foo={'bar': 42}) == '42'
+
+
+class LstripBlocksTestCase(JinjaTestCase):
+
+ def test_lstrip(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
+ assert tmpl.render() == "\n"
+
+ def test_lstrip_trim(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
+ assert tmpl.render() == ""
+
+ def test_no_lstrip(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''')
+ assert tmpl.render() == " \n "
+
+ def test_lstrip_endline(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' hello{% if True %}\n goodbye{% endif %}''')
+ assert tmpl.render() == " hello\n goodbye"
+
+ def test_lstrip_inline(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {% if True %}hello {% endif %}''')
+ assert tmpl.render() == 'hello '
+
+ def test_lstrip_nested(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {% if True %}a {% if True %}b {% endif %}c {% endif %}''')
+ assert tmpl.render() == 'a b c '
+
+ def test_lstrip_left_chars(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' abc {% if True %}
+ hello{% endif %}''')
+ assert tmpl.render() == ' abc \n hello'
+
+ def test_lstrip_embeded_strings(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {% set x = " {% str %} " %}{{ x }}''')
+ assert tmpl.render() == ' {% str %} '
+
+ def test_lstrip_preserve_leading_newlines(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string('''\n\n\n{% set hello = 1 %}''')
+ assert tmpl.render() == '\n\n\n'
+
+ def test_lstrip_comment(self):
+ env = Environment(lstrip_blocks=True, trim_blocks=False)
+ tmpl = env.from_string(''' {# if True #}
+hello
+ {#endif#}''')
+ assert tmpl.render() == '\nhello\n'
+
+ def test_lstrip_angle_bracket_simple(self):
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string(''' <% if True %>hello <% endif %>''')
+ assert tmpl.render() == 'hello '
+
+ def test_lstrip_angle_bracket_comment(self):
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string(''' <%# if True %>hello <%# endif %>''')
+ assert tmpl.render() == 'hello '
+
+ def test_lstrip_angle_bracket(self):
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+ <%# regular comment %>
+ <% for item in seq %>
+${item} ## the rest of the stuff
+ <% endfor %>''')
+ assert tmpl.render(seq=range(5)) == \
+ ''.join('%s\n' % x for x in range(5))
+
+ def test_lstrip_angle_bracket_compact(self):
+ env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+ <%#regular comment%>
+ <%for item in seq%>
+${item} ## the rest of the stuff
+ <%endfor%>''')
+ assert tmpl.render(seq=range(5)) == \
+ ''.join('%s\n' % x for x in range(5))
+
+ def test_php_syntax_with_manual(self):
+ env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+ <!-- I'm a comment, I'm not interesting -->
+ <? for item in seq -?>
+ <?= item ?>
+ <?- endfor ?>''')
+ assert tmpl.render(seq=range(5)) == '01234'
+
+ def test_php_syntax(self):
+ env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+ <!-- I'm a comment, I'm not interesting -->
+ <? for item in seq ?>
+ <?= item ?>
+ <? endfor ?>''')
+ assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
+
+ def test_php_syntax_compact(self):
+ env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+ <!-- I'm a comment, I'm not interesting -->
+ <?for item in seq?>
+ <?=item?>
+ <?endfor?>''')
+ assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
+
+ def test_erb_syntax(self):
+ env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
+ lstrip_blocks=True, trim_blocks=True)
+ #env.from_string('')
+ #for n,r in env.lexer.rules.iteritems():
+ # print n
+ #print env.lexer.rules['root'][0][0].pattern
+ #print "'%s'" % tmpl.render(seq=range(5))
+ tmpl = env.from_string('''\
+<%# I'm a comment, I'm not interesting %>
+ <% for item in seq %>
+ <%= item %>
+ <% endfor %>
+''')
+ assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
+
+ def test_erb_syntax_with_manual(self):
+ env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+<%# I'm a comment, I'm not interesting %>
+ <% for item in seq -%>
+ <%= item %>
+ <%- endfor %>''')
+ assert tmpl.render(seq=range(5)) == '01234'
+
+ def test_erb_syntax_no_lstrip(self):
+ env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+<%# I'm a comment, I'm not interesting %>
+ <%+ for item in seq -%>
+ <%= item %>
+ <%- endfor %>''')
+ assert tmpl.render(seq=range(5)) == ' 01234'
+
+ def test_comment_syntax(self):
+ env = Environment('<!--', '-->', '${', '}', '<!--#', '-->',
+ lstrip_blocks=True, trim_blocks=True)
+ tmpl = env.from_string('''\
+<!--# I'm a comment, I'm not interesting -->\
+<!-- for item in seq --->
+ ${item}
+<!--- endfor -->''')
+ assert tmpl.render(seq=range(5)) == '01234'
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TokenStreamTestCase))
+ suite.addTest(unittest.makeSuite(LexerTestCase))
+ suite.addTest(unittest.makeSuite(ParserTestCase))
+ suite.addTest(unittest.makeSuite(SyntaxTestCase))
+ suite.addTest(unittest.makeSuite(LstripBlocksTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/loader.py b/pyload/lib/jinja2/testsuite/loader.py
new file mode 100644
index 000000000..a7350aab9
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/loader.py
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.loader
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test the loaders.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+import tempfile
+import shutil
+import unittest
+
+from jinja2.testsuite import JinjaTestCase, dict_loader, \
+ package_loader, filesystem_loader, function_loader, \
+ choice_loader, prefix_loader
+
+from jinja2 import Environment, loaders
+from jinja2._compat import PYPY, PY2
+from jinja2.loaders import split_template_path
+from jinja2.exceptions import TemplateNotFound
+
+
+class LoaderTestCase(JinjaTestCase):
+
+ def test_dict_loader(self):
+ env = Environment(loader=dict_loader)
+ tmpl = env.get_template('justdict.html')
+ assert tmpl.render().strip() == 'FOO'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+ def test_package_loader(self):
+ env = Environment(loader=package_loader)
+ tmpl = env.get_template('test.html')
+ assert tmpl.render().strip() == 'BAR'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+ def test_filesystem_loader(self):
+ env = Environment(loader=filesystem_loader)
+ tmpl = env.get_template('test.html')
+ assert tmpl.render().strip() == 'BAR'
+ tmpl = env.get_template('foo/test.html')
+ assert tmpl.render().strip() == 'FOO'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+ def test_choice_loader(self):
+ env = Environment(loader=choice_loader)
+ tmpl = env.get_template('justdict.html')
+ assert tmpl.render().strip() == 'FOO'
+ tmpl = env.get_template('test.html')
+ assert tmpl.render().strip() == 'BAR'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+ def test_function_loader(self):
+ env = Environment(loader=function_loader)
+ tmpl = env.get_template('justfunction.html')
+ assert tmpl.render().strip() == 'FOO'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
+
+ def test_prefix_loader(self):
+ env = Environment(loader=prefix_loader)
+ tmpl = env.get_template('a/test.html')
+ assert tmpl.render().strip() == 'BAR'
+ tmpl = env.get_template('b/justdict.html')
+ assert tmpl.render().strip() == 'FOO'
+ self.assert_raises(TemplateNotFound, env.get_template, 'missing')
+
+ def test_caching(self):
+ changed = False
+ class TestLoader(loaders.BaseLoader):
+ def get_source(self, environment, template):
+ return u'foo', None, lambda: not changed
+ env = Environment(loader=TestLoader(), cache_size=-1)
+ tmpl = env.get_template('template')
+ assert tmpl is env.get_template('template')
+ changed = True
+ assert tmpl is not env.get_template('template')
+ changed = False
+
+ env = Environment(loader=TestLoader(), cache_size=0)
+ assert env.get_template('template') \
+ is not env.get_template('template')
+
+ env = Environment(loader=TestLoader(), cache_size=2)
+ t1 = env.get_template('one')
+ t2 = env.get_template('two')
+ assert t2 is env.get_template('two')
+ assert t1 is env.get_template('one')
+ t3 = env.get_template('three')
+ assert 'one' in env.cache
+ assert 'two' not in env.cache
+ assert 'three' in env.cache
+
+ def test_dict_loader_cache_invalidates(self):
+ mapping = {'foo': "one"}
+ env = Environment(loader=loaders.DictLoader(mapping))
+ assert env.get_template('foo').render() == "one"
+ mapping['foo'] = "two"
+ assert env.get_template('foo').render() == "two"
+
+ def test_split_template_path(self):
+ assert split_template_path('foo/bar') == ['foo', 'bar']
+ assert split_template_path('./foo/bar') == ['foo', 'bar']
+ self.assert_raises(TemplateNotFound, split_template_path, '../foo')
+
+
+class ModuleLoaderTestCase(JinjaTestCase):
+ archive = None
+
+ def compile_down(self, zip='deflated', py_compile=False):
+ super(ModuleLoaderTestCase, self).setup()
+ log = []
+ self.reg_env = Environment(loader=prefix_loader)
+ if zip is not None:
+ self.archive = tempfile.mkstemp(suffix='.zip')[1]
+ else:
+ self.archive = tempfile.mkdtemp()
+ self.reg_env.compile_templates(self.archive, zip=zip,
+ log_function=log.append,
+ py_compile=py_compile)
+ self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
+ return ''.join(log)
+
+ def teardown(self):
+ super(ModuleLoaderTestCase, self).teardown()
+ if hasattr(self, 'mod_env'):
+ if os.path.isfile(self.archive):
+ os.remove(self.archive)
+ else:
+ shutil.rmtree(self.archive)
+ self.archive = None
+
+ def test_log(self):
+ log = self.compile_down()
+ assert 'Compiled "a/foo/test.html" as ' \
+ 'tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a' in log
+ assert 'Finished compiling templates' in log
+ assert 'Could not compile "a/syntaxerror.html": ' \
+ 'Encountered unknown tag \'endif\'' in log
+
+ def _test_common(self):
+ tmpl1 = self.reg_env.get_template('a/test.html')
+ tmpl2 = self.mod_env.get_template('a/test.html')
+ assert tmpl1.render() == tmpl2.render()
+
+ tmpl1 = self.reg_env.get_template('b/justdict.html')
+ tmpl2 = self.mod_env.get_template('b/justdict.html')
+ assert tmpl1.render() == tmpl2.render()
+
+ def test_deflated_zip_compile(self):
+ self.compile_down(zip='deflated')
+ self._test_common()
+
+ def test_stored_zip_compile(self):
+ self.compile_down(zip='stored')
+ self._test_common()
+
+ def test_filesystem_compile(self):
+ self.compile_down(zip=None)
+ self._test_common()
+
+ def test_weak_references(self):
+ self.compile_down()
+ tmpl = self.mod_env.get_template('a/test.html')
+ key = loaders.ModuleLoader.get_template_key('a/test.html')
+ name = self.mod_env.loader.module.__name__
+
+ assert hasattr(self.mod_env.loader.module, key)
+ assert name in sys.modules
+
+ # unset all, ensure the module is gone from sys.modules
+ self.mod_env = tmpl = None
+
+ try:
+ import gc
+ gc.collect()
+ except:
+ pass
+
+ assert name not in sys.modules
+
+ # This test only makes sense on non-pypy python 2
+ if PY2 and not PYPY:
+ def test_byte_compilation(self):
+ log = self.compile_down(py_compile=True)
+ assert 'Byte-compiled "a/test.html"' in log
+ tmpl1 = self.mod_env.get_template('a/test.html')
+ mod = self.mod_env.loader.module. \
+ tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
+ assert mod.__file__.endswith('.pyc')
+
+ def test_choice_loader(self):
+ log = self.compile_down()
+
+ self.mod_env.loader = loaders.ChoiceLoader([
+ self.mod_env.loader,
+ loaders.DictLoader({'DICT_SOURCE': 'DICT_TEMPLATE'})
+ ])
+
+ tmpl1 = self.mod_env.get_template('a/test.html')
+ self.assert_equal(tmpl1.render(), 'BAR')
+ tmpl2 = self.mod_env.get_template('DICT_SOURCE')
+ self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
+
+ def test_prefix_loader(self):
+ log = self.compile_down()
+
+ self.mod_env.loader = loaders.PrefixLoader({
+ 'MOD': self.mod_env.loader,
+ 'DICT': loaders.DictLoader({'test.html': 'DICT_TEMPLATE'})
+ })
+
+ tmpl1 = self.mod_env.get_template('MOD/a/test.html')
+ self.assert_equal(tmpl1.render(), 'BAR')
+ tmpl2 = self.mod_env.get_template('DICT/test.html')
+ self.assert_equal(tmpl2.render(), 'DICT_TEMPLATE')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(LoaderTestCase))
+ suite.addTest(unittest.makeSuite(ModuleLoaderTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/regression.py b/pyload/lib/jinja2/testsuite/regression.py
new file mode 100644
index 000000000..c5f7d5c65
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/regression.py
@@ -0,0 +1,279 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.regression
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests corner cases and bugs.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
+ TemplateNotFound, PrefixLoader
+from jinja2._compat import text_type
+
+env = Environment()
+
+
+class CornerTestCase(JinjaTestCase):
+
+ def test_assigned_scoping(self):
+ t = env.from_string('''
+ {%- for item in (1, 2, 3, 4) -%}
+ [{{ item }}]
+ {%- endfor %}
+ {{- item -}}
+ ''')
+ assert t.render(item=42) == '[1][2][3][4]42'
+
+ t = env.from_string('''
+ {%- for item in (1, 2, 3, 4) -%}
+ [{{ item }}]
+ {%- endfor %}
+ {%- set item = 42 %}
+ {{- item -}}
+ ''')
+ assert t.render() == '[1][2][3][4]42'
+
+ t = env.from_string('''
+ {%- set item = 42 %}
+ {%- for item in (1, 2, 3, 4) -%}
+ [{{ item }}]
+ {%- endfor %}
+ {{- item -}}
+ ''')
+ assert t.render() == '[1][2][3][4]42'
+
+ def test_closure_scoping(self):
+ t = env.from_string('''
+ {%- set wrapper = "<FOO>" %}
+ {%- for item in (1, 2, 3, 4) %}
+ {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+ {{- wrapper() }}
+ {%- endfor %}
+ {{- wrapper -}}
+ ''')
+ assert t.render() == '[1][2][3][4]<FOO>'
+
+ t = env.from_string('''
+ {%- for item in (1, 2, 3, 4) %}
+ {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+ {{- wrapper() }}
+ {%- endfor %}
+ {%- set wrapper = "<FOO>" %}
+ {{- wrapper -}}
+ ''')
+ assert t.render() == '[1][2][3][4]<FOO>'
+
+ t = env.from_string('''
+ {%- for item in (1, 2, 3, 4) %}
+ {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+ {{- wrapper() }}
+ {%- endfor %}
+ {{- wrapper -}}
+ ''')
+ assert t.render(wrapper=23) == '[1][2][3][4]23'
+
+
+class BugTestCase(JinjaTestCase):
+
+ def test_keyword_folding(self):
+ env = Environment()
+ env.filters['testing'] = lambda value, some: value + some
+ assert env.from_string("{{ 'test'|testing(some='stuff') }}") \
+ .render() == 'teststuff'
+
+ def test_extends_output_bugs(self):
+ env = Environment(loader=DictLoader({
+ 'parent.html': '(({% block title %}{% endblock %}))'
+ }))
+
+ t = env.from_string('{% if expr %}{% extends "parent.html" %}{% endif %}'
+ '[[{% block title %}title{% endblock %}]]'
+ '{% for item in [1, 2, 3] %}({{ item }}){% endfor %}')
+ assert t.render(expr=False) == '[[title]](1)(2)(3)'
+ assert t.render(expr=True) == '((title))'
+
+ def test_urlize_filter_escaping(self):
+ tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
+ assert tmpl.render() == '<a href="http://www.example.org/&lt;foo">http://www.example.org/&lt;foo</a>'
+
+ def test_loop_call_loop(self):
+ tmpl = env.from_string('''
+
+ {% macro test() %}
+ {{ caller() }}
+ {% endmacro %}
+
+ {% for num1 in range(5) %}
+ {% call test() %}
+ {% for num2 in range(10) %}
+ {{ loop.index }}
+ {% endfor %}
+ {% endcall %}
+ {% endfor %}
+
+ ''')
+
+ assert tmpl.render().split() == [text_type(x) for x in range(1, 11)] * 5
+
+ def test_weird_inline_comment(self):
+ env = Environment(line_statement_prefix='%')
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ '% for item in seq {# missing #}\n...% endfor')
+
+ def test_old_macro_loop_scoping_bug(self):
+ tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
+ '{% macro i() %}3{% endmacro %}{{ i() }}')
+ assert tmpl.render() == '123'
+
+ def test_partial_conditional_assignments(self):
+ tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
+ assert tmpl.render(a=23) == '23'
+ assert tmpl.render(b=True) == '42'
+
+ def test_stacked_locals_scoping_bug(self):
+ env = Environment(line_statement_prefix='#')
+ t = env.from_string('''\
+# for j in [1, 2]:
+# set x = 1
+# for i in [1, 2]:
+# print x
+# if i % 2 == 0:
+# set x = x + 1
+# endif
+# endfor
+# endfor
+# if a
+# print 'A'
+# elif b
+# print 'B'
+# elif c == d
+# print 'C'
+# else
+# print 'D'
+# endif
+ ''')
+ assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'
+
+ def test_stacked_locals_scoping_bug_twoframe(self):
+ t = Template('''
+ {% set x = 1 %}
+ {% for item in foo %}
+ {% if item == 1 %}
+ {% set x = 2 %}
+ {% endif %}
+ {% endfor %}
+ {{ x }}
+ ''')
+ rv = t.render(foo=[1]).strip()
+ assert rv == u'1'
+
+ def test_call_with_args(self):
+ t = Template("""{% macro dump_users(users) -%}
+ <ul>
+ {%- for user in users -%}
+ <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
+ {%- endfor -%}
+ </ul>
+ {%- endmacro -%}
+
+ {% call(user) dump_users(list_of_user) -%}
+ <dl>
+ <dl>Realname</dl>
+ <dd>{{ user.realname|e }}</dd>
+ <dl>Description</dl>
+ <dd>{{ user.description }}</dd>
+ </dl>
+ {% endcall %}""")
+
+ assert [x.strip() for x in t.render(list_of_user=[{
+ 'username':'apo',
+ 'realname':'something else',
+ 'description':'test'
+ }]).splitlines()] == [
+ u'<ul><li><p>apo</p><dl>',
+ u'<dl>Realname</dl>',
+ u'<dd>something else</dd>',
+ u'<dl>Description</dl>',
+ u'<dd>test</dd>',
+ u'</dl>',
+ u'</li></ul>'
+ ]
+
+ def test_empty_if_condition_fails(self):
+ self.assert_raises(TemplateSyntaxError, Template, '{% if %}....{% endif %}')
+ self.assert_raises(TemplateSyntaxError, Template, '{% if foo %}...{% elif %}...{% endif %}')
+ self.assert_raises(TemplateSyntaxError, Template, '{% for x in %}..{% endfor %}')
+
+ def test_recursive_loop_bug(self):
+ tpl1 = Template("""
+ {% for p in foo recursive%}
+ {{p.bar}}
+ {% for f in p.fields recursive%}
+ {{f.baz}}
+ {{p.bar}}
+ {% if f.rec %}
+ {{ loop(f.sub) }}
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ """)
+
+ tpl2 = Template("""
+ {% for p in foo%}
+ {{p.bar}}
+ {% for f in p.fields recursive%}
+ {{f.baz}}
+ {{p.bar}}
+ {% if f.rec %}
+ {{ loop(f.sub) }}
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+ """)
+
+ def test_else_loop_bug(self):
+ t = Template('''
+ {% for x in y %}
+ {{ loop.index0 }}
+ {% else %}
+ {% for i in range(3) %}{{ i }}{% endfor %}
+ {% endfor %}
+ ''')
+ self.assertEqual(t.render(y=[]).strip(), '012')
+
+ def test_correct_prefix_loader_name(self):
+ env = Environment(loader=PrefixLoader({
+ 'foo': DictLoader({})
+ }))
+ try:
+ env.get_template('foo/bar.html')
+ except TemplateNotFound as e:
+ assert e.name == 'foo/bar.html'
+ else:
+ assert False, 'expected error here'
+
+ def test_contextfunction_callable_classes(self):
+ from jinja2.utils import contextfunction
+ class CallableClass(object):
+ @contextfunction
+ def __call__(self, ctx):
+ return ctx.resolve('hello')
+
+ tpl = Template("""{{ callableclass() }}""")
+ output = tpl.render(callableclass = CallableClass(), hello = 'TEST')
+ expected = 'TEST'
+
+ self.assert_equal(output, expected)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(CornerTestCase))
+ suite.addTest(unittest.makeSuite(BugTestCase))
+ return suite
diff --git a/module/plugins/__init__.py b/pyload/lib/jinja2/testsuite/res/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/__init__.py
+++ b/pyload/lib/jinja2/testsuite/res/__init__.py
diff --git a/pyload/lib/jinja2/testsuite/res/templates/broken.html b/pyload/lib/jinja2/testsuite/res/templates/broken.html
new file mode 100644
index 000000000..77669fae5
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/res/templates/broken.html
@@ -0,0 +1,3 @@
+Before
+{{ fail() }}
+After
diff --git a/pyload/lib/jinja2/testsuite/res/templates/foo/test.html b/pyload/lib/jinja2/testsuite/res/templates/foo/test.html
new file mode 100644
index 000000000..b7d6715e2
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/res/templates/foo/test.html
@@ -0,0 +1 @@
+FOO
diff --git a/pyload/lib/jinja2/testsuite/res/templates/syntaxerror.html b/pyload/lib/jinja2/testsuite/res/templates/syntaxerror.html
new file mode 100644
index 000000000..f21b81793
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/res/templates/syntaxerror.html
@@ -0,0 +1,4 @@
+Foo
+{% for item in broken %}
+ ...
+{% endif %}
diff --git a/pyload/lib/jinja2/testsuite/res/templates/test.html b/pyload/lib/jinja2/testsuite/res/templates/test.html
new file mode 100644
index 000000000..ba578e48b
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/res/templates/test.html
@@ -0,0 +1 @@
+BAR
diff --git a/pyload/lib/jinja2/testsuite/security.py b/pyload/lib/jinja2/testsuite/security.py
new file mode 100644
index 000000000..246d0f073
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/security.py
@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.security
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Checks the sandbox and other security features.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment
+from jinja2.sandbox import SandboxedEnvironment, \
+ ImmutableSandboxedEnvironment, unsafe
+from jinja2 import Markup, escape
+from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
+ TemplateRuntimeError
+from jinja2._compat import text_type
+
+
+class PrivateStuff(object):
+
+ def bar(self):
+ return 23
+
+ @unsafe
+ def foo(self):
+ return 42
+
+ def __repr__(self):
+ return 'PrivateStuff'
+
+
+class PublicStuff(object):
+ bar = lambda self: 23
+ _foo = lambda self: 42
+
+ def __repr__(self):
+ return 'PublicStuff'
+
+
+class SandboxTestCase(JinjaTestCase):
+
+ def test_unsafe(self):
+ env = SandboxedEnvironment()
+ self.assert_raises(SecurityError, env.from_string("{{ foo.foo() }}").render,
+ foo=PrivateStuff())
+ self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()), '23')
+
+ self.assert_raises(SecurityError, env.from_string("{{ foo._foo() }}").render,
+ foo=PublicStuff())
+ self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()), '23')
+ self.assert_equal(env.from_string("{{ foo.__class__ }}").render(foo=42), '')
+ self.assert_equal(env.from_string("{{ foo.func_code }}").render(foo=lambda:None), '')
+ # security error comes from __class__ already.
+ self.assert_raises(SecurityError, env.from_string(
+ "{{ foo.__class__.__subclasses__() }}").render, foo=42)
+
+ def test_immutable_environment(self):
+ env = ImmutableSandboxedEnvironment()
+ self.assert_raises(SecurityError, env.from_string(
+ '{{ [].append(23) }}').render)
+ self.assert_raises(SecurityError, env.from_string(
+ '{{ {1:2}.clear() }}').render)
+
+ def test_restricted(self):
+ env = SandboxedEnvironment()
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ "{% for item.attribute in seq %}...{% endfor %}")
+ self.assert_raises(TemplateSyntaxError, env.from_string,
+ "{% for foo, bar.baz in seq %}...{% endfor %}")
+
+ def test_markup_operations(self):
+ # adding two strings should escape the unsafe one
+ unsafe = '<script type="application/x-some-script">alert("foo");</script>'
+ safe = Markup('<em>username</em>')
+ assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
+
+ # string interpolations are safe to use too
+ assert Markup('<em>%s</em>') % '<bad user>' == \
+ '<em>&lt;bad user&gt;</em>'
+ assert Markup('<em>%(username)s</em>') % {
+ 'username': '<bad user>'
+ } == '<em>&lt;bad user&gt;</em>'
+
+ # an escaped object is markup too
+ assert type(Markup('foo') + 'bar') is Markup
+
+ # and it implements __html__ by returning itself
+ x = Markup("foo")
+ assert x.__html__() is x
+
+ # it also knows how to treat __html__ objects
+ class Foo(object):
+ def __html__(self):
+ return '<em>awesome</em>'
+ def __unicode__(self):
+ return 'awesome'
+ assert Markup(Foo()) == '<em>awesome</em>'
+ assert Markup('<strong>%s</strong>') % Foo() == \
+ '<strong><em>awesome</em></strong>'
+
+ # escaping and unescaping
+ assert escape('"<>&\'') == '&#34;&lt;&gt;&amp;&#39;'
+ assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
+ assert Markup("&lt;test&gt;").unescape() == "<test>"
+
+ def test_template_data(self):
+ env = Environment(autoescape=True)
+ t = env.from_string('{% macro say_hello(name) %}'
+ '<p>Hello {{ name }}!</p>{% endmacro %}'
+ '{{ say_hello("<blink>foo</blink>") }}')
+ escaped_out = '<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>'
+ assert t.render() == escaped_out
+ assert text_type(t.module) == escaped_out
+ assert escape(t.module) == escaped_out
+ assert t.module.say_hello('<blink>foo</blink>') == escaped_out
+ assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
+
+ def test_attr_filter(self):
+ env = SandboxedEnvironment()
+ tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
+ self.assert_raises(SecurityError, tmpl.render, cls=int)
+
+ def test_binary_operator_intercepting(self):
+ def disable_op(left, right):
+ raise TemplateRuntimeError('that operator so does not work')
+ for expr, ctx, rv in ('1 + 2', {}, '3'), ('a + 2', {'a': 2}, '4'):
+ env = SandboxedEnvironment()
+ env.binop_table['+'] = disable_op
+ t = env.from_string('{{ %s }}' % expr)
+ assert t.render(ctx) == rv
+ env.intercepted_binops = frozenset(['+'])
+ t = env.from_string('{{ %s }}' % expr)
+ try:
+ t.render(ctx)
+ except TemplateRuntimeError as e:
+ pass
+ else:
+ self.fail('expected runtime error')
+
+ def test_unary_operator_intercepting(self):
+ def disable_op(arg):
+ raise TemplateRuntimeError('that operator so does not work')
+ for expr, ctx, rv in ('-1', {}, '-1'), ('-a', {'a': 2}, '-2'):
+ env = SandboxedEnvironment()
+ env.unop_table['-'] = disable_op
+ t = env.from_string('{{ %s }}' % expr)
+ assert t.render(ctx) == rv
+ env.intercepted_unops = frozenset(['-'])
+ t = env.from_string('{{ %s }}' % expr)
+ try:
+ t.render(ctx)
+ except TemplateRuntimeError as e:
+ pass
+ else:
+ self.fail('expected runtime error')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(SandboxTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/tests.py b/pyload/lib/jinja2/testsuite/tests.py
new file mode 100644
index 000000000..3ece7a8ff
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/tests.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.tests
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Who tests the tests?
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import unittest
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Markup, Environment
+
+env = Environment()
+
+
+class TestsTestCase(JinjaTestCase):
+
+ def test_defined(self):
+ tmpl = env.from_string('{{ missing is defined }}|{{ true is defined }}')
+ assert tmpl.render() == 'False|True'
+
+ def test_even(self):
+ tmpl = env.from_string('''{{ 1 is even }}|{{ 2 is even }}''')
+ assert tmpl.render() == 'False|True'
+
+ def test_odd(self):
+ tmpl = env.from_string('''{{ 1 is odd }}|{{ 2 is odd }}''')
+ assert tmpl.render() == 'True|False'
+
+ def test_lower(self):
+ tmpl = env.from_string('''{{ "foo" is lower }}|{{ "FOO" is lower }}''')
+ assert tmpl.render() == 'True|False'
+
+ def test_typechecks(self):
+ tmpl = env.from_string('''
+ {{ 42 is undefined }}
+ {{ 42 is defined }}
+ {{ 42 is none }}
+ {{ none is none }}
+ {{ 42 is number }}
+ {{ 42 is string }}
+ {{ "foo" is string }}
+ {{ "foo" is sequence }}
+ {{ [1] is sequence }}
+ {{ range is callable }}
+ {{ 42 is callable }}
+ {{ range(5) is iterable }}
+ {{ {} is mapping }}
+ {{ mydict is mapping }}
+ {{ [] is mapping }}
+ ''')
+ class MyDict(dict):
+ pass
+ assert tmpl.render(mydict=MyDict()).split() == [
+ 'False', 'True', 'False', 'True', 'True', 'False',
+ 'True', 'True', 'True', 'True', 'False', 'True',
+ 'True', 'True', 'False'
+ ]
+
+ def test_sequence(self):
+ tmpl = env.from_string(
+ '{{ [1, 2, 3] is sequence }}|'
+ '{{ "foo" is sequence }}|'
+ '{{ 42 is sequence }}'
+ )
+ assert tmpl.render() == 'True|True|False'
+
+ def test_upper(self):
+ tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}')
+ assert tmpl.render() == 'True|False'
+
+ def test_sameas(self):
+ tmpl = env.from_string('{{ foo is sameas false }}|'
+ '{{ 0 is sameas false }}')
+ assert tmpl.render(foo=False) == 'True|False'
+
+ def test_no_paren_for_arg1(self):
+ tmpl = env.from_string('{{ foo is sameas none }}')
+ assert tmpl.render(foo=None) == 'True'
+
+ def test_escaped(self):
+ env = Environment(autoescape=True)
+ tmpl = env.from_string('{{ x is escaped }}|{{ y is escaped }}')
+ assert tmpl.render(x='foo', y=Markup('foo')) == 'False|True'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestsTestCase))
+ return suite
diff --git a/pyload/lib/jinja2/testsuite/utils.py b/pyload/lib/jinja2/testsuite/utils.py
new file mode 100644
index 000000000..cab9b09a9
--- /dev/null
+++ b/pyload/lib/jinja2/testsuite/utils.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.testsuite.utils
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests utilities jinja uses.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import gc
+import unittest
+
+import pickle
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2.utils import LRUCache, escape, object_type_repr
+
+
+class LRUCacheTestCase(JinjaTestCase):
+
+ def test_simple(self):
+ d = LRUCache(3)
+ d["a"] = 1
+ d["b"] = 2
+ d["c"] = 3
+ d["a"]
+ d["d"] = 4
+ assert len(d) == 3
+ assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
+
+ def test_pickleable(self):
+ cache = LRUCache(2)
+ cache["foo"] = 42
+ cache["bar"] = 23
+ cache["foo"]
+
+ for protocol in range(3):
+ copy = pickle.loads(pickle.dumps(cache, protocol))
+ assert copy.capacity == cache.capacity
+ assert copy._mapping == cache._mapping
+ assert copy._queue == cache._queue
+
+
+class HelpersTestCase(JinjaTestCase):
+
+ def test_object_type_repr(self):
+ class X(object):
+ pass
+ self.assert_equal(object_type_repr(42), 'int object')
+ self.assert_equal(object_type_repr([]), 'list object')
+ self.assert_equal(object_type_repr(X()),
+ 'jinja2.testsuite.utils.X object')
+ self.assert_equal(object_type_repr(None), 'None')
+ self.assert_equal(object_type_repr(Ellipsis), 'Ellipsis')
+
+
+class MarkupLeakTestCase(JinjaTestCase):
+
+ def test_markup_leaks(self):
+ counts = set()
+ for count in range(20):
+ for item in range(1000):
+ escape("foo")
+ escape("<foo>")
+ escape(u"foo")
+ escape(u"<foo>")
+ counts.add(len(gc.get_objects()))
+ assert len(counts) == 1, 'ouch, c extension seems to leak objects'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(LRUCacheTestCase))
+ suite.addTest(unittest.makeSuite(HelpersTestCase))
+
+ # this test only tests the c extension
+ if not hasattr(escape, 'func_code'):
+ suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
+
+ return suite
diff --git a/pyload/lib/jinja2/utils.py b/pyload/lib/jinja2/utils.py
new file mode 100644
index 000000000..ddc47da0a
--- /dev/null
+++ b/pyload/lib/jinja2/utils.py
@@ -0,0 +1,520 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.utils
+ ~~~~~~~~~~~~
+
+ Utility functions.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import errno
+from collections import deque
+from jinja2._compat import text_type, string_types, implements_iterator, \
+ allocate_lock, url_quote
+
+
+_word_split_re = re.compile(r'(\s+)')
+_punctuation_re = re.compile(
+ '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
+ '|'.join(map(re.escape, ('(', '<', '&lt;'))),
+ '|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
+ )
+)
+_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
+_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
+_entity_re = re.compile(r'&([^;]+);')
+_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+_digits = '0123456789'
+
+# special singleton representing missing values for the runtime
+missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
+
+# internal code
+internal_code = set()
+
+concat = u''.join
+
+
+def contextfunction(f):
+ """This decorator can be used to mark a function or method context callable.
+ A context callable is passed the active :class:`Context` as first argument when
+ called from the template. This is useful if a function wants to get access
+ to the context or functions provided on the context object. For example
+ a function that returns a sorted list of template variables the current
+ template exports could look like this::
+
+ @contextfunction
+ def get_exported_names(context):
+ return sorted(context.exported_vars)
+ """
+ f.contextfunction = True
+ return f
+
+
+def evalcontextfunction(f):
+ """This decorator can be used to mark a function or method as an eval
+ context callable. This is similar to the :func:`contextfunction`
+ but instead of passing the context, an evaluation context object is
+ passed. For more information about the eval context, see
+ :ref:`eval-context`.
+
+ .. versionadded:: 2.4
+ """
+ f.evalcontextfunction = True
+ return f
+
+
+def environmentfunction(f):
+ """This decorator can be used to mark a function or method as environment
+ callable. This decorator works exactly like the :func:`contextfunction`
+ decorator just that the first argument is the active :class:`Environment`
+ and not context.
+ """
+ f.environmentfunction = True
+ return f
+
+
+def internalcode(f):
+ """Marks the function as internally used"""
+ internal_code.add(f.__code__)
+ return f
+
+
+def is_undefined(obj):
+ """Check if the object passed is undefined. This does nothing more than
+ performing an instance check against :class:`Undefined` but looks nicer.
+ This can be used for custom filters or tests that want to react to
+ undefined variables. For example a custom default filter can look like
+ this::
+
+ def default(var, default=''):
+ if is_undefined(var):
+ return default
+ return var
+ """
+ from jinja2.runtime import Undefined
+ return isinstance(obj, Undefined)
+
+
+def consume(iterable):
+ """Consumes an iterable without doing anything with it."""
+ for event in iterable:
+ pass
+
+
+def clear_caches():
+ """Jinja2 keeps internal caches for environments and lexers. These are
+ used so that Jinja2 doesn't have to recreate environments and lexers all
+ the time. Normally you don't have to care about that but if you are
+ messuring memory consumption you may want to clean the caches.
+ """
+ from jinja2.environment import _spontaneous_environments
+ from jinja2.lexer import _lexer_cache
+ _spontaneous_environments.clear()
+ _lexer_cache.clear()
+
+
+def import_string(import_name, silent=False):
+ """Imports an object based on a string. This is useful if you want to
+ use import paths as endpoints or something similar. An import path can
+ be specified either in dotted notation (``xml.sax.saxutils.escape``)
+ or with a colon as object delimiter (``xml.sax.saxutils:escape``).
+
+ If the `silent` is True the return value will be `None` if the import
+ fails.
+
+ :return: imported object
+ """
+ try:
+ if ':' in import_name:
+ module, obj = import_name.split(':', 1)
+ elif '.' in import_name:
+ items = import_name.split('.')
+ module = '.'.join(items[:-1])
+ obj = items[-1]
+ else:
+ return __import__(import_name)
+ return getattr(__import__(module, None, None, [obj]), obj)
+ except (ImportError, AttributeError):
+ if not silent:
+ raise
+
+
+def open_if_exists(filename, mode='rb'):
+ """Returns a file descriptor for the filename if that file exists,
+ otherwise `None`.
+ """
+ try:
+ return open(filename, mode)
+ except IOError as e:
+ if e.errno not in (errno.ENOENT, errno.EISDIR):
+ raise
+
+
+def object_type_repr(obj):
+ """Returns the name of the object's type. For some recognized
+ singletons the name of the object is returned instead. (For
+ example for `None` and `Ellipsis`).
+ """
+ if obj is None:
+ return 'None'
+ elif obj is Ellipsis:
+ return 'Ellipsis'
+ # __builtin__ in 2.x, builtins in 3.x
+ if obj.__class__.__module__ in ('__builtin__', 'builtins'):
+ name = obj.__class__.__name__
+ else:
+ name = obj.__class__.__module__ + '.' + obj.__class__.__name__
+ return '%s object' % name
+
+
+def pformat(obj, verbose=False):
+ """Prettyprint an object. Either use the `pretty` library or the
+ builtin `pprint`.
+ """
+ try:
+ from pretty import pretty
+ return pretty(obj, verbose=verbose)
+ except ImportError:
+ from pprint import pformat
+ return pformat(obj)
+
+
+def urlize(text, trim_url_limit=None, nofollow=False):
+ """Converts any URLs in text into clickable links. Works on http://,
+ https:// and www. links. Links can have trailing punctuation (periods,
+ commas, close-parens) and leading punctuation (opening parens) and
+ it'll still do the right thing.
+
+ If trim_url_limit is not None, the URLs in link text will be limited
+ to trim_url_limit characters.
+
+ If nofollow is True, the URLs in link text will get a rel="nofollow"
+ attribute.
+ """
+ trim_url = lambda x, limit=trim_url_limit: limit is not None \
+ and (x[:limit] + (len(x) >=limit and '...'
+ or '')) or x
+ words = _word_split_re.split(text_type(escape(text)))
+ nofollow_attr = nofollow and ' rel="nofollow"' or ''
+ for i, word in enumerate(words):
+ match = _punctuation_re.match(word)
+ if match:
+ lead, middle, trail = match.groups()
+ if middle.startswith('www.') or (
+ '@' not in middle and
+ not middle.startswith('http://') and
+ not middle.startswith('https://') and
+ len(middle) > 0 and
+ middle[0] in _letters + _digits and (
+ middle.endswith('.org') or
+ middle.endswith('.net') or
+ middle.endswith('.com')
+ )):
+ middle = '<a href="http://%s"%s>%s</a>' % (middle,
+ nofollow_attr, trim_url(middle))
+ if middle.startswith('http://') or \
+ middle.startswith('https://'):
+ middle = '<a href="%s"%s>%s</a>' % (middle,
+ nofollow_attr, trim_url(middle))
+ if '@' in middle and not middle.startswith('www.') and \
+ not ':' in middle and _simple_email_re.match(middle):
+ middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
+ if lead + middle + trail != word:
+ words[i] = lead + middle + trail
+ return u''.join(words)
+
+
+def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
+ """Generate some lorem impsum for the template."""
+ from jinja2.constants import LOREM_IPSUM_WORDS
+ from random import choice, randrange
+ words = LOREM_IPSUM_WORDS.split()
+ result = []
+
+ for _ in range(n):
+ next_capitalized = True
+ last_comma = last_fullstop = 0
+ word = None
+ last = None
+ p = []
+
+ # each paragraph contains out of 20 to 100 words.
+ for idx, _ in enumerate(range(randrange(min, max))):
+ while True:
+ word = choice(words)
+ if word != last:
+ last = word
+ break
+ if next_capitalized:
+ word = word.capitalize()
+ next_capitalized = False
+ # add commas
+ if idx - randrange(3, 8) > last_comma:
+ last_comma = idx
+ last_fullstop += 2
+ word += ','
+ # add end of sentences
+ if idx - randrange(10, 20) > last_fullstop:
+ last_comma = last_fullstop = idx
+ word += '.'
+ next_capitalized = True
+ p.append(word)
+
+ # ensure that the paragraph ends with a dot.
+ p = u' '.join(p)
+ if p.endswith(','):
+ p = p[:-1] + '.'
+ elif not p.endswith('.'):
+ p += '.'
+ result.append(p)
+
+ if not html:
+ return u'\n\n'.join(result)
+ return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
+
+
+def unicode_urlencode(obj, charset='utf-8'):
+ """URL escapes a single bytestring or unicode string with the
+ given charset if applicable to URL safe quoting under all rules
+ that need to be considered under all supported Python versions.
+
+ If non strings are provided they are converted to their unicode
+ representation first.
+ """
+ if not isinstance(obj, string_types):
+ obj = text_type(obj)
+ if isinstance(obj, text_type):
+ obj = obj.encode(charset)
+ return text_type(url_quote(obj))
+
+
+class LRUCache(object):
+ """A simple LRU Cache implementation."""
+
+ # this is fast for small capacities (something below 1000) but doesn't
+ # scale. But as long as it's only used as storage for templates this
+ # won't do any harm.
+
+ def __init__(self, capacity):
+ self.capacity = capacity
+ self._mapping = {}
+ self._queue = deque()
+ self._postinit()
+
+ def _postinit(self):
+ # alias all queue methods for faster lookup
+ self._popleft = self._queue.popleft
+ self._pop = self._queue.pop
+ self._remove = self._queue.remove
+ self._wlock = allocate_lock()
+ self._append = self._queue.append
+
+ def __getstate__(self):
+ return {
+ 'capacity': self.capacity,
+ '_mapping': self._mapping,
+ '_queue': self._queue
+ }
+
+ def __setstate__(self, d):
+ self.__dict__.update(d)
+ self._postinit()
+
+ def __getnewargs__(self):
+ return (self.capacity,)
+
+ def copy(self):
+ """Return a shallow copy of the instance."""
+ rv = self.__class__(self.capacity)
+ rv._mapping.update(self._mapping)
+ rv._queue = deque(self._queue)
+ return rv
+
+ def get(self, key, default=None):
+ """Return an item from the cache dict or `default`"""
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def setdefault(self, key, default=None):
+ """Set `default` if the key is not in the cache otherwise
+ leave unchanged. Return the value of this key.
+ """
+ self._wlock.acquire()
+ try:
+ try:
+ return self[key]
+ except KeyError:
+ self[key] = default
+ return default
+ finally:
+ self._wlock.release()
+
+ def clear(self):
+ """Clear the cache."""
+ self._wlock.acquire()
+ try:
+ self._mapping.clear()
+ self._queue.clear()
+ finally:
+ self._wlock.release()
+
+ def __contains__(self, key):
+ """Check if a key exists in this cache."""
+ return key in self._mapping
+
+ def __len__(self):
+ """Return the current size of the cache."""
+ return len(self._mapping)
+
+ def __repr__(self):
+ return '<%s %r>' % (
+ self.__class__.__name__,
+ self._mapping
+ )
+
+ def __getitem__(self, key):
+ """Get an item from the cache. Moves the item up so that it has the
+ highest priority then.
+
+ Raise a `KeyError` if it does not exist.
+ """
+ self._wlock.acquire()
+ try:
+ rv = self._mapping[key]
+ if self._queue[-1] != key:
+ try:
+ self._remove(key)
+ except ValueError:
+ # if something removed the key from the container
+ # when we read, ignore the ValueError that we would
+ # get otherwise.
+ pass
+ self._append(key)
+ return rv
+ finally:
+ self._wlock.release()
+
+ def __setitem__(self, key, value):
+ """Sets the value for an item. Moves the item up so that it
+ has the highest priority then.
+ """
+ self._wlock.acquire()
+ try:
+ if key in self._mapping:
+ self._remove(key)
+ elif len(self._mapping) == self.capacity:
+ del self._mapping[self._popleft()]
+ self._append(key)
+ self._mapping[key] = value
+ finally:
+ self._wlock.release()
+
+ def __delitem__(self, key):
+ """Remove an item from the cache dict.
+ Raise a `KeyError` if it does not exist.
+ """
+ self._wlock.acquire()
+ try:
+ del self._mapping[key]
+ try:
+ self._remove(key)
+ except ValueError:
+ # __getitem__ is not locked, it might happen
+ pass
+ finally:
+ self._wlock.release()
+
+ def items(self):
+ """Return a list of items."""
+ result = [(key, self._mapping[key]) for key in list(self._queue)]
+ result.reverse()
+ return result
+
+ def iteritems(self):
+ """Iterate over all items."""
+ return iter(self.items())
+
+ def values(self):
+ """Return a list of all values."""
+ return [x[1] for x in self.items()]
+
+ def itervalue(self):
+ """Iterate over all values."""
+ return iter(self.values())
+
+ def keys(self):
+ """Return a list of all keys ordered by most recent usage."""
+ return list(self)
+
+ def iterkeys(self):
+ """Iterate over all keys in the cache dict, ordered by
+ the most recent usage.
+ """
+ return reversed(tuple(self._queue))
+
+ __iter__ = iterkeys
+
+ def __reversed__(self):
+ """Iterate over the values in the cache dict, oldest items
+ coming first.
+ """
+ return iter(tuple(self._queue))
+
+ __copy__ = copy
+
+
+# register the LRU cache as mutable mapping if possible
+try:
+ from collections import MutableMapping
+ MutableMapping.register(LRUCache)
+except ImportError:
+ pass
+
+
+@implements_iterator
+class Cycler(object):
+ """A cycle helper for templates."""
+
+ def __init__(self, *items):
+ if not items:
+ raise RuntimeError('at least one item has to be provided')
+ self.items = items
+ self.reset()
+
+ def reset(self):
+ """Resets the cycle."""
+ self.pos = 0
+
+ @property
+ def current(self):
+ """Returns the current item."""
+ return self.items[self.pos]
+
+ def __next__(self):
+ """Goes one item ahead and returns it."""
+ rv = self.current
+ self.pos = (self.pos + 1) % len(self.items)
+ return rv
+
+
+class Joiner(object):
+ """A joining helper for templates."""
+
+ def __init__(self, sep=u', '):
+ self.sep = sep
+ self.used = False
+
+ def __call__(self):
+ if not self.used:
+ self.used = True
+ return u''
+ return self.sep
+
+
+# Imported here because that's where it was in the past
+from markupsafe import Markup, escape, soft_unicode
diff --git a/module/lib/jinja2/visitor.py b/pyload/lib/jinja2/visitor.py
index 413e7c309..413e7c309 100644
--- a/module/lib/jinja2/visitor.py
+++ b/pyload/lib/jinja2/visitor.py
diff --git a/pyload/lib/markupsafe/__init__.py b/pyload/lib/markupsafe/__init__.py
new file mode 100644
index 000000000..275540154
--- /dev/null
+++ b/pyload/lib/markupsafe/__init__.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+"""
+ markupsafe
+ ~~~~~~~~~~
+
+ Implements a Markup string.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import re
+import string
+from collections import Mapping
+from markupsafe._compat import text_type, string_types, int_types, \
+ unichr, iteritems, PY2
+
+
+__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
+
+
+_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
+_entity_re = re.compile(r'&([^;]+);')
+
+
+class Markup(text_type):
+ r"""Marks a string as being safe for inclusion in HTML/XML output without
+ needing to be escaped. This implements the `__html__` interface a couple
+ of frameworks and web applications use. :class:`Markup` is a direct
+ subclass of `unicode` and provides all the methods of `unicode` just that
+ it escapes arguments passed and always returns `Markup`.
+
+ The `escape` function returns markup objects so that double escaping can't
+ happen.
+
+ The constructor of the :class:`Markup` class can be used for three
+ different things: When passed an unicode object it's assumed to be safe,
+ when passed an object with an HTML representation (has an `__html__`
+ method) that representation is used, otherwise the object passed is
+ converted into a unicode string and then assumed to be safe:
+
+ >>> Markup("Hello <em>World</em>!")
+ Markup(u'Hello <em>World</em>!')
+ >>> class Foo(object):
+ ... def __html__(self):
+ ... return '<a href="#">foo</a>'
+ ...
+ >>> Markup(Foo())
+ Markup(u'<a href="#">foo</a>')
+
+ If you want object passed being always treated as unsafe you can use the
+ :meth:`escape` classmethod to create a :class:`Markup` object:
+
+ >>> Markup.escape("Hello <em>World</em>!")
+ Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')
+
+ Operations on a markup string are markup aware which means that all
+ arguments are passed through the :func:`escape` function:
+
+ >>> em = Markup("<em>%s</em>")
+ >>> em % "foo & bar"
+ Markup(u'<em>foo &amp; bar</em>')
+ >>> strong = Markup("<strong>%(text)s</strong>")
+ >>> strong % {'text': '<blink>hacker here</blink>'}
+ Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
+ >>> Markup("<em>Hello</em> ") + "<foo>"
+ Markup(u'<em>Hello</em> &lt;foo&gt;')
+ """
+ __slots__ = ()
+
+ def __new__(cls, base=u'', encoding=None, errors='strict'):
+ if hasattr(base, '__html__'):
+ base = base.__html__()
+ if encoding is None:
+ return text_type.__new__(cls, base)
+ return text_type.__new__(cls, base, encoding, errors)
+
+ def __html__(self):
+ return self
+
+ def __add__(self, other):
+ if isinstance(other, string_types) or hasattr(other, '__html__'):
+ return self.__class__(super(Markup, self).__add__(self.escape(other)))
+ return NotImplemented
+
+ def __radd__(self, other):
+ if hasattr(other, '__html__') or isinstance(other, string_types):
+ return self.escape(other).__add__(self)
+ return NotImplemented
+
+ def __mul__(self, num):
+ if isinstance(num, int_types):
+ return self.__class__(text_type.__mul__(self, num))
+ return NotImplemented
+ __rmul__ = __mul__
+
+ def __mod__(self, arg):
+ if isinstance(arg, tuple):
+ arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
+ else:
+ arg = _MarkupEscapeHelper(arg, self.escape)
+ return self.__class__(text_type.__mod__(self, arg))
+
+ def __repr__(self):
+ return '%s(%s)' % (
+ self.__class__.__name__,
+ text_type.__repr__(self)
+ )
+
+ def join(self, seq):
+ return self.__class__(text_type.join(self, map(self.escape, seq)))
+ join.__doc__ = text_type.join.__doc__
+
+ def split(self, *args, **kwargs):
+ return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
+ split.__doc__ = text_type.split.__doc__
+
+ def rsplit(self, *args, **kwargs):
+ return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
+ rsplit.__doc__ = text_type.rsplit.__doc__
+
+ def splitlines(self, *args, **kwargs):
+ return list(map(self.__class__, text_type.splitlines(
+ self, *args, **kwargs)))
+ splitlines.__doc__ = text_type.splitlines.__doc__
+
+ def unescape(self):
+ r"""Unescape markup again into an text_type string. This also resolves
+ known HTML4 and XHTML entities:
+
+ >>> Markup("Main &raquo; <em>About</em>").unescape()
+ u'Main \xbb <em>About</em>'
+ """
+ from markupsafe._constants import HTML_ENTITIES
+ def handle_match(m):
+ name = m.group(1)
+ if name in HTML_ENTITIES:
+ return unichr(HTML_ENTITIES[name])
+ try:
+ if name[:2] in ('#x', '#X'):
+ return unichr(int(name[2:], 16))
+ elif name.startswith('#'):
+ return unichr(int(name[1:]))
+ except ValueError:
+ pass
+ return u''
+ return _entity_re.sub(handle_match, text_type(self))
+
+ def striptags(self):
+ r"""Unescape markup into an text_type string and strip all tags. This
+ also resolves known HTML4 and XHTML entities. Whitespace is
+ normalized to one:
+
+ >>> Markup("Main &raquo; <em>About</em>").striptags()
+ u'Main \xbb About'
+ """
+ stripped = u' '.join(_striptags_re.sub('', self).split())
+ return Markup(stripped).unescape()
+
+ @classmethod
+ def escape(cls, s):
+ """Escape the string. Works like :func:`escape` with the difference
+ that for subclasses of :class:`Markup` this function would return the
+ correct subclass.
+ """
+ rv = escape(s)
+ if rv.__class__ is not cls:
+ return cls(rv)
+ return rv
+
+ def make_simple_escaping_wrapper(name):
+ orig = getattr(text_type, name)
+ def func(self, *args, **kwargs):
+ args = _escape_argspec(list(args), enumerate(args), self.escape)
+ _escape_argspec(kwargs, iteritems(kwargs), self.escape)
+ return self.__class__(orig(self, *args, **kwargs))
+ func.__name__ = orig.__name__
+ func.__doc__ = orig.__doc__
+ return func
+
+ for method in '__getitem__', 'capitalize', \
+ 'title', 'lower', 'upper', 'replace', 'ljust', \
+ 'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
+ 'translate', 'expandtabs', 'swapcase', 'zfill':
+ locals()[method] = make_simple_escaping_wrapper(method)
+
+ # new in python 2.5
+ if hasattr(text_type, 'partition'):
+ def partition(self, sep):
+ return tuple(map(self.__class__,
+ text_type.partition(self, self.escape(sep))))
+ def rpartition(self, sep):
+ return tuple(map(self.__class__,
+ text_type.rpartition(self, self.escape(sep))))
+
+ # new in python 2.6
+ if hasattr(text_type, 'format'):
+ def format(*args, **kwargs):
+ self, args = args[0], args[1:]
+ formatter = EscapeFormatter(self.escape)
+ kwargs = _MagicFormatMapping(args, kwargs)
+ return self.__class__(formatter.vformat(self, args, kwargs))
+
+ def __html_format__(self, format_spec):
+ if format_spec:
+ raise ValueError('Unsupported format specification '
+ 'for Markup.')
+ return self
+
+ # not in python 3
+ if hasattr(text_type, '__getslice__'):
+ __getslice__ = make_simple_escaping_wrapper('__getslice__')
+
+ del method, make_simple_escaping_wrapper
+
+
+class _MagicFormatMapping(Mapping):
+ """This class implements a dummy wrapper to fix a bug in the Python
+ standard library for string formatting.
+
+ See http://bugs.python.org/issue13598 for information about why
+ this is necessary.
+ """
+
+ def __init__(self, args, kwargs):
+ self._args = args
+ self._kwargs = kwargs
+ self._last_index = 0
+
+ def __getitem__(self, key):
+ if key == '':
+ idx = self._last_index
+ self._last_index += 1
+ try:
+ return self._args[idx]
+ except LookupError:
+ pass
+ key = str(idx)
+ return self._kwargs[key]
+
+ def __iter__(self):
+ return iter(self._kwargs)
+
+ def __len__(self):
+ return len(self._kwargs)
+
+
+if hasattr(text_type, 'format'):
+ class EscapeFormatter(string.Formatter):
+
+ def __init__(self, escape):
+ self.escape = escape
+
+ def format_field(self, value, format_spec):
+ if hasattr(value, '__html_format__'):
+ rv = value.__html_format__(format_spec)
+ elif hasattr(value, '__html__'):
+ if format_spec:
+ raise ValueError('No format specification allowed '
+ 'when formatting an object with '
+ 'its __html__ method.')
+ rv = value.__html__()
+ else:
+ rv = string.Formatter.format_field(self, value, format_spec)
+ return text_type(self.escape(rv))
+
+
+def _escape_argspec(obj, iterable, escape):
+ """Helper for various string-wrapped functions."""
+ for key, value in iterable:
+ if hasattr(value, '__html__') or isinstance(value, string_types):
+ obj[key] = escape(value)
+ return obj
+
+
+class _MarkupEscapeHelper(object):
+ """Helper for Markup.__mod__"""
+
+ def __init__(self, obj, escape):
+ self.obj = obj
+ self.escape = escape
+
+ __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x], s.escape)
+ __unicode__ = __str__ = lambda s: text_type(s.escape(s.obj))
+ __repr__ = lambda s: str(s.escape(repr(s.obj)))
+ __int__ = lambda s: int(s.obj)
+ __float__ = lambda s: float(s.obj)
+
+
+# we have to import it down here as the speedups and native
+# modules imports the markup type which is define above.
+try:
+ from markupsafe._speedups import escape, escape_silent, soft_unicode
+except ImportError:
+ from markupsafe._native import escape, escape_silent, soft_unicode
+
+if not PY2:
+ soft_str = soft_unicode
+ __all__.append('soft_str')
diff --git a/pyload/lib/markupsafe/_compat.py b/pyload/lib/markupsafe/_compat.py
new file mode 100644
index 000000000..62e5632ad
--- /dev/null
+++ b/pyload/lib/markupsafe/_compat.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+"""
+ markupsafe._compat
+ ~~~~~~~~~~~~~~~~~~
+
+ Compatibility module for different Python versions.
+
+ :copyright: (c) 2013 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import sys
+
+PY2 = sys.version_info[0] == 2
+
+if not PY2:
+ text_type = str
+ string_types = (str,)
+ unichr = chr
+ int_types = (int,)
+ iteritems = lambda x: iter(x.items())
+else:
+ text_type = unicode
+ string_types = (str, unicode)
+ unichr = unichr
+ int_types = (int, long)
+ iteritems = lambda x: x.iteritems()
diff --git a/module/lib/jinja2/_markupsafe/_constants.py b/pyload/lib/markupsafe/_constants.py
index 919bf03c5..919bf03c5 100644
--- a/module/lib/jinja2/_markupsafe/_constants.py
+++ b/pyload/lib/markupsafe/_constants.py
diff --git a/pyload/lib/markupsafe/_native.py b/pyload/lib/markupsafe/_native.py
new file mode 100644
index 000000000..5e83f10a1
--- /dev/null
+++ b/pyload/lib/markupsafe/_native.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+"""
+ markupsafe._native
+ ~~~~~~~~~~~~~~~~~~
+
+ Native Python implementation the C module is not compiled.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from markupsafe import Markup
+from markupsafe._compat import text_type
+
+
+def escape(s):
+ """Convert the characters &, <, >, ' and " in string s to HTML-safe
+ sequences. Use this if you need to display text that might contain
+ such characters in HTML. Marks return value as markup string.
+ """
+ if hasattr(s, '__html__'):
+ return s.__html__()
+ return Markup(text_type(s)
+ .replace('&', '&amp;')
+ .replace('>', '&gt;')
+ .replace('<', '&lt;')
+ .replace("'", '&#39;')
+ .replace('"', '&#34;')
+ )
+
+
+def escape_silent(s):
+ """Like :func:`escape` but converts `None` into an empty
+ markup string.
+ """
+ if s is None:
+ return Markup()
+ return escape(s)
+
+
+def soft_unicode(s):
+ """Make a string unicode if it isn't already. That way a markup
+ string is not converted back to unicode.
+ """
+ if not isinstance(s, text_type):
+ s = text_type(s)
+ return s
diff --git a/pyload/lib/markupsafe/_speedups.c b/pyload/lib/markupsafe/_speedups.c
new file mode 100644
index 000000000..f349febf2
--- /dev/null
+++ b/pyload/lib/markupsafe/_speedups.c
@@ -0,0 +1,239 @@
+/**
+ * markupsafe._speedups
+ * ~~~~~~~~~~~~~~~~~~~~
+ *
+ * This module implements functions for automatic escaping in C for better
+ * performance.
+ *
+ * :copyright: (c) 2010 by Armin Ronacher.
+ * :license: BSD.
+ */
+
+#include <Python.h>
+
+#define ESCAPED_CHARS_TABLE_SIZE 63
+#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
+
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#endif
+
+
+static PyObject* markup;
+static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
+static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
+
+static int
+init_constants(void)
+{
+ PyObject *module;
+ /* happing of characters to replace */
+ escaped_chars_repl['"'] = UNICHR("&#34;");
+ escaped_chars_repl['\''] = UNICHR("&#39;");
+ escaped_chars_repl['&'] = UNICHR("&amp;");
+ escaped_chars_repl['<'] = UNICHR("&lt;");
+ escaped_chars_repl['>'] = UNICHR("&gt;");
+
+ /* lengths of those characters when replaced - 1 */
+ memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
+ escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
+ escaped_chars_delta_len['&'] = 4;
+ escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
+
+ /* import markup type so that we can mark the return value */
+ module = PyImport_ImportModule("markupsafe");
+ if (!module)
+ return 0;
+ markup = PyObject_GetAttrString(module, "Markup");
+ Py_DECREF(module);
+
+ return 1;
+}
+
+static PyObject*
+escape_unicode(PyUnicodeObject *in)
+{
+ PyUnicodeObject *out;
+ Py_UNICODE *inp = PyUnicode_AS_UNICODE(in);
+ const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in);
+ Py_UNICODE *next_escp;
+ Py_UNICODE *outp;
+ Py_ssize_t delta=0, erepl=0, delta_len=0;
+
+ /* First we need to figure out how long the escaped string will be */
+ while (*(inp) || inp < inp_end) {
+ if (*inp < ESCAPED_CHARS_TABLE_SIZE) {
+ delta += escaped_chars_delta_len[*inp];
+ erepl += !!escaped_chars_delta_len[*inp];
+ }
+ ++inp;
+ }
+
+ /* Do we need to escape anything at all? */
+ if (!erepl) {
+ Py_INCREF(in);
+ return (PyObject*)in;
+ }
+
+ out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta);
+ if (!out)
+ return NULL;
+
+ outp = PyUnicode_AS_UNICODE(out);
+ inp = PyUnicode_AS_UNICODE(in);
+ while (erepl-- > 0) {
+ /* look for the next substitution */
+ next_escp = inp;
+ while (next_escp < inp_end) {
+ if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
+ (delta_len = escaped_chars_delta_len[*next_escp])) {
+ ++delta_len;
+ break;
+ }
+ ++next_escp;
+ }
+
+ if (next_escp > inp) {
+ /* copy unescaped chars between inp and next_escp */
+ Py_UNICODE_COPY(outp, inp, next_escp-inp);
+ outp += next_escp - inp;
+ }
+
+ /* escape 'next_escp' */
+ Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
+ outp += delta_len;
+
+ inp = next_escp + 1;
+ }
+ if (inp < inp_end)
+ Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in)));
+
+ return (PyObject*)out;
+}
+
+
+static PyObject*
+escape(PyObject *self, PyObject *text)
+{
+ PyObject *s = NULL, *rv = NULL, *html;
+
+ /* we don't have to escape integers, bools or floats */
+ if (PyLong_CheckExact(text) ||
+#if PY_MAJOR_VERSION < 3
+ PyInt_CheckExact(text) ||
+#endif
+ PyFloat_CheckExact(text) || PyBool_Check(text) ||
+ text == Py_None)
+ return PyObject_CallFunctionObjArgs(markup, text, NULL);
+
+ /* if the object has an __html__ method that performs the escaping */
+ html = PyObject_GetAttrString(text, "__html__");
+ if (html) {
+ rv = PyObject_CallObject(html, NULL);
+ Py_DECREF(html);
+ return rv;
+ }
+
+ /* otherwise make the object unicode if it isn't, then escape */
+ PyErr_Clear();
+ if (!PyUnicode_Check(text)) {
+#if PY_MAJOR_VERSION < 3
+ PyObject *unicode = PyObject_Unicode(text);
+#else
+ PyObject *unicode = PyObject_Str(text);
+#endif
+ if (!unicode)
+ return NULL;
+ s = escape_unicode((PyUnicodeObject*)unicode);
+ Py_DECREF(unicode);
+ }
+ else
+ s = escape_unicode((PyUnicodeObject*)text);
+
+ /* convert the unicode string into a markup object. */
+ rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
+ Py_DECREF(s);
+ return rv;
+}
+
+
+static PyObject*
+escape_silent(PyObject *self, PyObject *text)
+{
+ if (text != Py_None)
+ return escape(self, text);
+ return PyObject_CallFunctionObjArgs(markup, NULL);
+}
+
+
+static PyObject*
+soft_unicode(PyObject *self, PyObject *s)
+{
+ if (!PyUnicode_Check(s))
+#if PY_MAJOR_VERSION < 3
+ return PyObject_Unicode(s);
+#else
+ return PyObject_Str(s);
+#endif
+ Py_INCREF(s);
+ return s;
+}
+
+
+static PyMethodDef module_methods[] = {
+ {"escape", (PyCFunction)escape, METH_O,
+ "escape(s) -> markup\n\n"
+ "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
+ "sequences. Use this if you need to display text that might contain\n"
+ "such characters in HTML. Marks return value as markup string."},
+ {"escape_silent", (PyCFunction)escape_silent, METH_O,
+ "escape_silent(s) -> markup\n\n"
+ "Like escape but converts None to an empty string."},
+ {"soft_unicode", (PyCFunction)soft_unicode, METH_O,
+ "soft_unicode(object) -> string\n\n"
+ "Make a string unicode if it isn't already. That way a markup\n"
+ "string is not converted back to unicode."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+#if PY_MAJOR_VERSION < 3
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_speedups(void)
+{
+ if (!init_constants())
+ return;
+
+ Py_InitModule3("markupsafe._speedups", module_methods, "");
+}
+
+#else /* Python 3.x module initialization */
+
+static struct PyModuleDef module_definition = {
+ PyModuleDef_HEAD_INIT,
+ "markupsafe._speedups",
+ NULL,
+ -1,
+ module_methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PyMODINIT_FUNC
+PyInit__speedups(void)
+{
+ if (!init_constants())
+ return NULL;
+
+ return PyModule_Create(&module_definition);
+}
+
+#endif
diff --git a/pyload/lib/markupsafe/tests.py b/pyload/lib/markupsafe/tests.py
new file mode 100644
index 000000000..636993629
--- /dev/null
+++ b/pyload/lib/markupsafe/tests.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+import gc
+import sys
+import unittest
+from markupsafe import Markup, escape, escape_silent
+from markupsafe._compat import text_type
+
+
+class MarkupTestCase(unittest.TestCase):
+
+ def test_adding(self):
+ # adding two strings should escape the unsafe one
+ unsafe = '<script type="application/x-some-script">alert("foo");</script>'
+ safe = Markup('<em>username</em>')
+ assert unsafe + safe == text_type(escape(unsafe)) + text_type(safe)
+
+ def test_string_interpolation(self):
+ # string interpolations are safe to use too
+ assert Markup('<em>%s</em>') % '<bad user>' == \
+ '<em>&lt;bad user&gt;</em>'
+ assert Markup('<em>%(username)s</em>') % {
+ 'username': '<bad user>'
+ } == '<em>&lt;bad user&gt;</em>'
+
+ assert Markup('%i') % 3.14 == '3'
+ assert Markup('%.2f') % 3.14 == '3.14'
+
+ def test_type_behavior(self):
+ # an escaped object is markup too
+ assert type(Markup('foo') + 'bar') is Markup
+
+ # and it implements __html__ by returning itself
+ x = Markup("foo")
+ assert x.__html__() is x
+
+ def test_html_interop(self):
+ # it also knows how to treat __html__ objects
+ class Foo(object):
+ def __html__(self):
+ return '<em>awesome</em>'
+ def __unicode__(self):
+ return 'awesome'
+ __str__ = __unicode__
+ assert Markup(Foo()) == '<em>awesome</em>'
+ assert Markup('<strong>%s</strong>') % Foo() == \
+ '<strong><em>awesome</em></strong>'
+
+ def test_tuple_interpol(self):
+ self.assertEqual(Markup('<em>%s:%s</em>') % (
+ '<foo>',
+ '<bar>',
+ ), Markup(u'<em>&lt;foo&gt;:&lt;bar&gt;</em>'))
+
+ def test_dict_interpol(self):
+ self.assertEqual(Markup('<em>%(foo)s</em>') % {
+ 'foo': '<foo>',
+ }, Markup(u'<em>&lt;foo&gt;</em>'))
+ self.assertEqual(Markup('<em>%(foo)s:%(bar)s</em>') % {
+ 'foo': '<foo>',
+ 'bar': '<bar>',
+ }, Markup(u'<em>&lt;foo&gt;:&lt;bar&gt;</em>'))
+
+ def test_escaping(self):
+ # escaping and unescaping
+ assert escape('"<>&\'') == '&#34;&lt;&gt;&amp;&#39;'
+ assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
+ assert Markup("&lt;test&gt;").unescape() == "<test>"
+
+ def test_formatting(self):
+ for actual, expected in (
+ (Markup('%i') % 3.14, '3'),
+ (Markup('%.2f') % 3.14159, '3.14'),
+ (Markup('%s %s %s') % ('<', 123, '>'), '&lt; 123 &gt;'),
+ (Markup('<em>{awesome}</em>').format(awesome='<awesome>'),
+ '<em>&lt;awesome&gt;</em>'),
+ (Markup('{0[1][bar]}').format([0, {'bar': '<bar/>'}]),
+ '&lt;bar/&gt;'),
+ (Markup('{0[1][bar]}').format([0, {'bar': Markup('<bar/>')}]),
+ '<bar/>')):
+ assert actual == expected, "%r should be %r!" % (actual, expected)
+
+ # This is new in 2.7
+ if sys.version_info >= (2, 7):
+ def test_formatting_empty(self):
+ formatted = Markup('{}').format(0)
+ assert formatted == Markup('0')
+
+ def test_custom_formatting(self):
+ class HasHTMLOnly(object):
+ def __html__(self):
+ return Markup('<foo>')
+
+ class HasHTMLAndFormat(object):
+ def __html__(self):
+ return Markup('<foo>')
+ def __html_format__(self, spec):
+ return Markup('<FORMAT>')
+
+ assert Markup('{0}').format(HasHTMLOnly()) == Markup('<foo>')
+ assert Markup('{0}').format(HasHTMLAndFormat()) == Markup('<FORMAT>')
+
+ def test_complex_custom_formatting(self):
+ class User(object):
+ def __init__(self, id, username):
+ self.id = id
+ self.username = username
+ def __html_format__(self, format_spec):
+ if format_spec == 'link':
+ return Markup('<a href="/user/{0}">{1}</a>').format(
+ self.id,
+ self.__html__(),
+ )
+ elif format_spec:
+ raise ValueError('Invalid format spec')
+ return self.__html__()
+ def __html__(self):
+ return Markup('<span class=user>{0}</span>').format(self.username)
+
+ user = User(1, 'foo')
+ assert Markup('<p>User: {0:link}').format(user) == \
+ Markup('<p>User: <a href="/user/1"><span class=user>foo</span></a>')
+
+ def test_all_set(self):
+ import markupsafe as markup
+ for item in markup.__all__:
+ getattr(markup, item)
+
+ def test_escape_silent(self):
+ assert escape_silent(None) == Markup()
+ assert escape(None) == Markup(None)
+ assert escape_silent('<foo>') == Markup(u'&lt;foo&gt;')
+
+ def test_splitting(self):
+ self.assertEqual(Markup('a b').split(), [
+ Markup('a'),
+ Markup('b')
+ ])
+ self.assertEqual(Markup('a b').rsplit(), [
+ Markup('a'),
+ Markup('b')
+ ])
+ self.assertEqual(Markup('a\nb').splitlines(), [
+ Markup('a'),
+ Markup('b')
+ ])
+
+ def test_mul(self):
+ self.assertEqual(Markup('a') * 3, Markup('aaa'))
+
+
+class MarkupLeakTestCase(unittest.TestCase):
+
+ def test_markup_leaks(self):
+ counts = set()
+ for count in range(20):
+ for item in range(1000):
+ escape("foo")
+ escape("<foo>")
+ escape(u"foo")
+ escape(u"<foo>")
+ counts.add(len(gc.get_objects()))
+ assert len(counts) == 1, 'ouch, c extension seems to leak objects'
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(MarkupTestCase))
+
+ # this test only tests the c extension
+ if not hasattr(escape, 'func_code'):
+ suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
+
+ return suite
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
+
+# vim:sts=4:sw=4:et:
diff --git a/module/lib/rename_process.py b/pyload/lib/rename_process.py
index 2527cef39..2527cef39 100644
--- a/module/lib/rename_process.py
+++ b/pyload/lib/rename_process.py
diff --git a/pyload/lib/simplejson/__init__.py b/pyload/lib/simplejson/__init__.py
new file mode 100644
index 000000000..a02c4deab
--- /dev/null
+++ b/pyload/lib/simplejson/__init__.py
@@ -0,0 +1,560 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+ >>> import simplejson as json
+ >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+ '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+ >>> print(json.dumps("\"foo\bar"))
+ "\"foo\bar"
+ >>> print(json.dumps(u'\u1234'))
+ "\u1234"
+ >>> print(json.dumps('\\'))
+ "\\"
+ >>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
+ {"a": 0, "b": 0, "c": 0}
+ >>> from simplejson.compat import StringIO
+ >>> io = StringIO()
+ >>> json.dump(['streaming API'], io)
+ >>> io.getvalue()
+ '["streaming API"]'
+
+Compact encoding::
+
+ >>> import simplejson as json
+ >>> obj = [1,2,3,{'4': 5, '6': 7}]
+ >>> json.dumps(obj, separators=(',',':'), sort_keys=True)
+ '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+ >>> import simplejson as json
+ >>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=' '))
+ {
+ "4": 5,
+ "6": 7
+ }
+
+Decoding JSON::
+
+ >>> import simplejson as json
+ >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+ >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+ True
+ >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+ True
+ >>> from simplejson.compat import StringIO
+ >>> io = StringIO('["streaming API"]')
+ >>> json.load(io)[0] == 'streaming API'
+ True
+
+Specializing JSON object decoding::
+
+ >>> import simplejson as json
+ >>> def as_complex(dct):
+ ... if '__complex__' in dct:
+ ... return complex(dct['real'], dct['imag'])
+ ... return dct
+ ...
+ >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+ ... object_hook=as_complex)
+ (1+2j)
+ >>> from decimal import Decimal
+ >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
+ True
+
+Specializing JSON object encoding::
+
+ >>> import simplejson as json
+ >>> def encode_complex(obj):
+ ... if isinstance(obj, complex):
+ ... return [obj.real, obj.imag]
+ ... raise TypeError(repr(o) + " is not JSON serializable")
+ ...
+ >>> json.dumps(2 + 1j, default=encode_complex)
+ '[2.0, 1.0]'
+ >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+ '[2.0, 1.0]'
+ >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+ '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 3 (char 2)
+"""
+from __future__ import absolute_import
+__version__ = '3.6.3'
+__all__ = [
+ 'dump', 'dumps', 'load', 'loads',
+ 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
+ 'OrderedDict', 'simple_first',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decimal import Decimal
+
+from .scanner import JSONDecodeError
+from .decoder import JSONDecoder
+from .encoder import JSONEncoder, JSONEncoderForHTML
+def _import_OrderedDict():
+ import collections
+ try:
+ return collections.OrderedDict
+ except AttributeError:
+ from . import ordered_dict
+ return ordered_dict.OrderedDict
+OrderedDict = _import_OrderedDict()
+
+def _import_c_make_encoder():
+ try:
+ from ._speedups import make_encoder
+ return make_encoder
+ except ImportError:
+ return None
+
+_default_encoder = JSONEncoder(
+ skipkeys=False,
+ ensure_ascii=True,
+ check_circular=True,
+ allow_nan=True,
+ indent=None,
+ separators=None,
+ encoding='utf-8',
+ default=None,
+ use_decimal=True,
+ namedtuple_as_object=True,
+ tuple_as_array=True,
+ bigint_as_string=False,
+ item_sort_key=None,
+ for_json=False,
+ ignore_nan=False,
+ int_as_string_bitcount=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, use_decimal=True,
+ namedtuple_as_object=True, tuple_as_array=True,
+ bigint_as_string=False, sort_keys=False, item_sort_key=None,
+ for_json=False, ignore_nan=False, int_as_string_bitcount=None, **kw):
+ """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+ ``.write()``-supporting file-like object).
+
+ If *skipkeys* is true then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If *ensure_ascii* is false, then the some chunks written to ``fp``
+ may be ``unicode`` instances, subject to normal Python ``str`` to
+ ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+ understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+ to cause an error.
+
+ If *check_circular* is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If *allow_nan* is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+ in strict compliance of the original JSON specification, instead of using
+ the JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). See
+ *ignore_nan* for ECMA-262 compliant behavior.
+
+ If *indent* is a string, then JSON array elements and object members
+ will be pretty-printed with a newline followed by that string repeated
+ for each level of nesting. ``None`` (the default) selects the most compact
+ representation without any newlines. For backwards compatibility with
+ versions of simplejson earlier than 2.1.0, an integer is also accepted
+ and is converted to a string with that many spaces.
+
+ If specified, *separators* should be an
+ ``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')``
+ if *indent* is ``None`` and ``(',', ': ')`` otherwise. To get the most
+ compact JSON representation, you should specify ``(',', ':')`` to eliminate
+ whitespace.
+
+ *encoding* is the character encoding for str instances, default is UTF-8.
+
+ *default(obj)* is a function that should return a serializable version
+ of obj or raise ``TypeError``. The default simply raises ``TypeError``.
+
+ If *use_decimal* is true (default: ``True``) then decimal.Decimal
+ will be natively serialized to JSON with full precision.
+
+ If *namedtuple_as_object* is true (default: ``True``),
+ :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
+ as JSON objects.
+
+ If *tuple_as_array* is true (default: ``True``),
+ :class:`tuple` (and subclasses) will be encoded as JSON arrays.
+
+ If *bigint_as_string* is true (default: ``False``), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
+ rounding that happens in Javascript otherwise. Note that this is still a
+ lossy operation that will not round-trip correctly and should be used
+ sparingly.
+
+ If *int_as_string_bitcount* is a positive number (n), then int of size
+ greater than or equal to 2**n or lower than or equal to -2**n will be
+ encoded as strings.
+
+ If specified, *item_sort_key* is a callable used to sort the items in
+ each dictionary. This is useful if you want to sort items other than
+ in alphabetical order by key. This option takes precedence over
+ *sort_keys*.
+
+ If *sort_keys* is true (default: ``False``), the output of dictionaries
+ will be sorted by item.
+
+ If *for_json* is true (default: ``False``), objects with a ``for_json()``
+ method will use the return value of that method for encoding as JSON
+ instead of the object.
+
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+ ``null`` in compliance with the ECMA-262 specification. If true, this will
+ override *allow_nan*.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg. NOTE: You should use *default* or *for_json* instead
+ of subclassing whenever possible.
+
+ """
+ # cached encoder
+ if (not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and use_decimal
+ and namedtuple_as_object and tuple_as_array
+ and not bigint_as_string and int_as_string_bitcount is None
+ and not item_sort_key and not for_json and not ignore_nan and not kw):
+ iterable = _default_encoder.iterencode(obj)
+ else:
+ if cls is None:
+ cls = JSONEncoder
+ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding,
+ default=default, use_decimal=use_decimal,
+ namedtuple_as_object=namedtuple_as_object,
+ tuple_as_array=tuple_as_array,
+ bigint_as_string=bigint_as_string,
+ sort_keys=sort_keys,
+ item_sort_key=item_sort_key,
+ for_json=for_json,
+ ignore_nan=ignore_nan,
+ int_as_string_bitcount=int_as_string_bitcount,
+ **kw).iterencode(obj)
+ # could accelerate with writelines in some versions of Python, at
+ # a debuggability cost
+ for chunk in iterable:
+ fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+ allow_nan=True, cls=None, indent=None, separators=None,
+ encoding='utf-8', default=None, use_decimal=True,
+ namedtuple_as_object=True, tuple_as_array=True,
+ bigint_as_string=False, sort_keys=False, item_sort_key=None,
+ for_json=False, ignore_nan=False, int_as_string_bitcount=None, **kw):
+ """Serialize ``obj`` to a JSON formatted ``str``.
+
+ If ``skipkeys`` is false then ``dict`` keys that are not basic types
+ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+ will be skipped instead of raising a ``TypeError``.
+
+ If ``ensure_ascii`` is false, then the return value will be a
+ ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+ coercion rules instead of being escaped to an ASCII ``str``.
+
+ If ``check_circular`` is false, then the circular reference check
+ for container types will be skipped and a circular reference will
+ result in an ``OverflowError`` (or worse).
+
+ If ``allow_nan`` is false, then it will be a ``ValueError`` to
+ serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+ strict compliance of the JSON specification, instead of using the
+ JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+ If ``indent`` is a string, then JSON array elements and object members
+ will be pretty-printed with a newline followed by that string repeated
+ for each level of nesting. ``None`` (the default) selects the most compact
+ representation without any newlines. For backwards compatibility with
+ versions of simplejson earlier than 2.1.0, an integer is also accepted
+ and is converted to a string with that many spaces.
+
+ If specified, ``separators`` should be an
+ ``(item_separator, key_separator)`` tuple. The default is ``(', ', ': ')``
+ if *indent* is ``None`` and ``(',', ': ')`` otherwise. To get the most
+ compact JSON representation, you should specify ``(',', ':')`` to eliminate
+ whitespace.
+
+ ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+ ``default(obj)`` is a function that should return a serializable version
+ of obj or raise TypeError. The default simply raises TypeError.
+
+ If *use_decimal* is true (default: ``True``) then decimal.Decimal
+ will be natively serialized to JSON with full precision.
+
+ If *namedtuple_as_object* is true (default: ``True``),
+ :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
+ as JSON objects.
+
+ If *tuple_as_array* is true (default: ``True``),
+ :class:`tuple` (and subclasses) will be encoded as JSON arrays.
+
+ If *bigint_as_string* is true (not the default), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
+ rounding that happens in Javascript otherwise.
+
+ If *int_as_string_bitcount* is a positive number (n), then int of size
+ greater than or equal to 2**n or lower than or equal to -2**n will be
+ encoded as strings.
+
+ If specified, *item_sort_key* is a callable used to sort the items in
+ each dictionary. This is useful if you want to sort items other than
+ in alphabetical order by key. This option takes precendence over
+ *sort_keys*.
+
+ If *sort_keys* is true (default: ``False``), the output of dictionaries
+ will be sorted by item.
+
+ If *for_json* is true (default: ``False``), objects with a ``for_json()``
+ method will use the return value of that method for encoding as JSON
+ instead of the object.
+
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+ ``null`` in compliance with the ECMA-262 specification. If true, this will
+ override *allow_nan*.
+
+ To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+ ``.default()`` method to serialize additional types), specify it with
+ the ``cls`` kwarg. NOTE: You should use *default* instead of subclassing
+ whenever possible.
+
+ """
+ # cached encoder
+ if (
+ not skipkeys and ensure_ascii and
+ check_circular and allow_nan and
+ cls is None and indent is None and separators is None and
+ encoding == 'utf-8' and default is None and use_decimal
+ and namedtuple_as_object and tuple_as_array
+ and not bigint_as_string and int_as_string_bitcount is None
+ and not sort_keys and not item_sort_key and not for_json
+ and not ignore_nan and not kw
+ ):
+ return _default_encoder.encode(obj)
+ if cls is None:
+ cls = JSONEncoder
+ return cls(
+ skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+ check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+ separators=separators, encoding=encoding, default=default,
+ use_decimal=use_decimal,
+ namedtuple_as_object=namedtuple_as_object,
+ tuple_as_array=tuple_as_array,
+ bigint_as_string=bigint_as_string,
+ sort_keys=sort_keys,
+ item_sort_key=item_sort_key,
+ for_json=for_json,
+ ignore_nan=ignore_nan,
+ int_as_string_bitcount=int_as_string_bitcount,
+ **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None,
+ object_pairs_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, object_pairs_hook=None,
+ use_decimal=False, namedtuple_as_object=True, tuple_as_array=True,
+ **kw):
+ """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+ a JSON document) to a Python object.
+
+ *encoding* determines the encoding used to interpret any
+ :class:`str` objects decoded by this instance (``'utf-8'`` by
+ default). It has no effect when decoding :class:`unicode` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as :class:`unicode`.
+
+ *object_hook*, if specified, will be called with the result of every
+ JSON object decoded and its return value will be used in place of the
+ given :class:`dict`. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+
+ *object_pairs_hook* is an optional function that will be called with
+ the result of any object literal decode with an ordered list of pairs.
+ The return value of *object_pairs_hook* will be used instead of the
+ :class:`dict`. This feature can be used to implement custom decoders
+ that rely on the order that the key and value pairs are decoded (for
+ example, :func:`collections.OrderedDict` will remember the order of
+ insertion). If *object_hook* is also defined, the *object_pairs_hook*
+ takes priority.
+
+ *parse_float*, if specified, will be called with the string of every
+ JSON float to be decoded. By default, this is equivalent to
+ ``float(num_str)``. This can be used to use another datatype or parser
+ for JSON floats (e.g. :class:`decimal.Decimal`).
+
+ *parse_int*, if specified, will be called with the string of every
+ JSON int to be decoded. By default, this is equivalent to
+ ``int(num_str)``. This can be used to use another datatype or parser
+ for JSON integers (e.g. :class:`float`).
+
+ *parse_constant*, if specified, will be called with one of the
+ following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
+ can be used to raise an exception if invalid JSON numbers are
+ encountered.
+
+ If *use_decimal* is true (default: ``False``) then it implies
+ parse_float=decimal.Decimal for parity with ``dump``.
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
+ of subclassing whenever possible.
+
+ """
+ return loads(fp.read(),
+ encoding=encoding, cls=cls, object_hook=object_hook,
+ parse_float=parse_float, parse_int=parse_int,
+ parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
+ use_decimal=use_decimal, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, object_pairs_hook=None,
+ use_decimal=False, **kw):
+ """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+ document) to a Python object.
+
+ *encoding* determines the encoding used to interpret any
+ :class:`str` objects decoded by this instance (``'utf-8'`` by
+ default). It has no effect when decoding :class:`unicode` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as :class:`unicode`.
+
+ *object_hook*, if specified, will be called with the result of every
+ JSON object decoded and its return value will be used in place of the
+ given :class:`dict`. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+
+ *object_pairs_hook* is an optional function that will be called with
+ the result of any object literal decode with an ordered list of pairs.
+ The return value of *object_pairs_hook* will be used instead of the
+ :class:`dict`. This feature can be used to implement custom decoders
+ that rely on the order that the key and value pairs are decoded (for
+ example, :func:`collections.OrderedDict` will remember the order of
+ insertion). If *object_hook* is also defined, the *object_pairs_hook*
+ takes priority.
+
+ *parse_float*, if specified, will be called with the string of every
+ JSON float to be decoded. By default, this is equivalent to
+ ``float(num_str)``. This can be used to use another datatype or parser
+ for JSON floats (e.g. :class:`decimal.Decimal`).
+
+ *parse_int*, if specified, will be called with the string of every
+ JSON int to be decoded. By default, this is equivalent to
+ ``int(num_str)``. This can be used to use another datatype or parser
+ for JSON integers (e.g. :class:`float`).
+
+ *parse_constant*, if specified, will be called with one of the
+ following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
+ can be used to raise an exception if invalid JSON numbers are
+ encountered.
+
+ If *use_decimal* is true (default: ``False``) then it implies
+ parse_float=decimal.Decimal for parity with ``dump``.
+
+ To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+ kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
+ of subclassing whenever possible.
+
+ """
+ if (cls is None and encoding is None and object_hook is None and
+ parse_int is None and parse_float is None and
+ parse_constant is None and object_pairs_hook is None
+ and not use_decimal and not kw):
+ return _default_decoder.decode(s)
+ if cls is None:
+ cls = JSONDecoder
+ if object_hook is not None:
+ kw['object_hook'] = object_hook
+ if object_pairs_hook is not None:
+ kw['object_pairs_hook'] = object_pairs_hook
+ if parse_float is not None:
+ kw['parse_float'] = parse_float
+ if parse_int is not None:
+ kw['parse_int'] = parse_int
+ if parse_constant is not None:
+ kw['parse_constant'] = parse_constant
+ if use_decimal:
+ if parse_float is not None:
+ raise TypeError("use_decimal=True implies parse_float=Decimal")
+ kw['parse_float'] = Decimal
+ return cls(encoding=encoding, **kw).decode(s)
+
+
+def _toggle_speedups(enabled):
+ from . import decoder as dec
+ from . import encoder as enc
+ from . import scanner as scan
+ c_make_encoder = _import_c_make_encoder()
+ if enabled:
+ dec.scanstring = dec.c_scanstring or dec.py_scanstring
+ enc.c_make_encoder = c_make_encoder
+ enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
+ enc.py_encode_basestring_ascii)
+ scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
+ else:
+ dec.scanstring = dec.py_scanstring
+ enc.c_make_encoder = None
+ enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
+ scan.make_scanner = scan.py_make_scanner
+ dec.make_scanner = scan.make_scanner
+ global _default_decoder
+ _default_decoder = JSONDecoder(
+ encoding=None,
+ object_hook=None,
+ object_pairs_hook=None,
+ )
+ global _default_encoder
+ _default_encoder = JSONEncoder(
+ skipkeys=False,
+ ensure_ascii=True,
+ check_circular=True,
+ allow_nan=True,
+ indent=None,
+ separators=None,
+ encoding='utf-8',
+ default=None,
+ )
+
+def simple_first(kv):
+ """Helper function to pass to item_sort_key to sort simple
+ elements to the top, then container elements.
+ """
+ return (isinstance(kv[1], (list, dict, tuple)), kv[0])
diff --git a/pyload/lib/simplejson/_speedups.c b/pyload/lib/simplejson/_speedups.c
new file mode 100644
index 000000000..01614c49a
--- /dev/null
+++ b/pyload/lib/simplejson/_speedups.c
@@ -0,0 +1,3339 @@
+/* -*- mode: C; c-file-style: "python"; c-basic-offset: 4 -*- */
+#include "Python.h"
+#include "structmember.h"
+
+#if PY_MAJOR_VERSION >= 3
+#define PyInt_FromSsize_t PyLong_FromSsize_t
+#define PyInt_AsSsize_t PyLong_AsSsize_t
+#define PyString_Check PyBytes_Check
+#define PyString_GET_SIZE PyBytes_GET_SIZE
+#define PyString_AS_STRING PyBytes_AS_STRING
+#define PyString_FromStringAndSize PyBytes_FromStringAndSize
+#define PyInt_Check(obj) 0
+#define JSON_UNICHR Py_UCS4
+#define JSON_InternFromString PyUnicode_InternFromString
+#define JSON_Intern_GET_SIZE PyUnicode_GET_SIZE
+#define JSON_ASCII_Check PyUnicode_Check
+#define JSON_ASCII_AS_STRING PyUnicode_AsUTF8
+#define PyInt_Type PyLong_Type
+#define PyInt_FromString PyLong_FromString
+#define PY2_UNUSED
+#define PY3_UNUSED UNUSED
+#define JSON_NewEmptyUnicode() PyUnicode_New(0, 127)
+#else /* PY_MAJOR_VERSION >= 3 */
+#define PY2_UNUSED UNUSED
+#define PY3_UNUSED
+#define PyUnicode_READY(obj) 0
+#define PyUnicode_KIND(obj) (sizeof(Py_UNICODE))
+#define PyUnicode_DATA(obj) ((void *)(PyUnicode_AS_UNICODE(obj)))
+#define PyUnicode_READ(kind, data, index) ((JSON_UNICHR)((const Py_UNICODE *)(data))[(index)])
+#define PyUnicode_GetLength PyUnicode_GET_SIZE
+#define JSON_UNICHR Py_UNICODE
+#define JSON_ASCII_Check PyString_Check
+#define JSON_ASCII_AS_STRING PyString_AS_STRING
+#define JSON_InternFromString PyString_InternFromString
+#define JSON_Intern_GET_SIZE PyString_GET_SIZE
+#define JSON_NewEmptyUnicode() PyUnicode_FromUnicode(NULL, 0)
+#endif /* PY_MAJOR_VERSION < 3 */
+
+#if PY_VERSION_HEX < 0x02070000
+#if !defined(PyOS_string_to_double)
+#define PyOS_string_to_double json_PyOS_string_to_double
+static double
+json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception);
+static double
+json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception)
+{
+ double x;
+ assert(endptr == NULL);
+ assert(overflow_exception == NULL);
+ PyFPE_START_PROTECT("json_PyOS_string_to_double", return -1.0;)
+ x = PyOS_ascii_atof(s);
+ PyFPE_END_PROTECT(x)
+ return x;
+}
+#endif
+#endif /* PY_VERSION_HEX < 0x02070000 */
+
+#if PY_VERSION_HEX < 0x02060000
+#if !defined(Py_TYPE)
+#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
+#endif
+#if !defined(Py_SIZE)
+#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size)
+#endif
+#if !defined(PyVarObject_HEAD_INIT)
+#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
+#endif
+#endif /* PY_VERSION_HEX < 0x02060000 */
+
+#if PY_VERSION_HEX < 0x02050000
+#if !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#define PyInt_FromSsize_t PyInt_FromLong
+#define PyInt_AsSsize_t PyInt_AsLong
+#endif
+#if !defined(Py_IS_FINITE)
+#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X))
+#endif
+#endif /* PY_VERSION_HEX < 0x02050000 */
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((__unused__))
+#else
+#define UNUSED
+#endif
+
+#define DEFAULT_ENCODING "utf-8"
+
+#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType)
+#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType)
+#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
+#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+
+#define JSON_ALLOW_NAN 1
+#define JSON_IGNORE_NAN 2
+
+static PyTypeObject PyScannerType;
+static PyTypeObject PyEncoderType;
+
+typedef struct {
+ PyObject *large_strings; /* A list of previously accumulated large strings */
+ PyObject *small_strings; /* Pending small strings */
+} JSON_Accu;
+
+static int
+JSON_Accu_Init(JSON_Accu *acc);
+static int
+JSON_Accu_Accumulate(JSON_Accu *acc, PyObject *unicode);
+static PyObject *
+JSON_Accu_FinishAsList(JSON_Accu *acc);
+static void
+JSON_Accu_Destroy(JSON_Accu *acc);
+
+#define ERR_EXPECTING_VALUE "Expecting value"
+#define ERR_ARRAY_DELIMITER "Expecting ',' delimiter or ']'"
+#define ERR_ARRAY_VALUE_FIRST "Expecting value or ']'"
+#define ERR_OBJECT_DELIMITER "Expecting ',' delimiter or '}'"
+#define ERR_OBJECT_PROPERTY "Expecting property name enclosed in double quotes"
+#define ERR_OBJECT_PROPERTY_FIRST "Expecting property name enclosed in double quotes or '}'"
+#define ERR_OBJECT_PROPERTY_DELIMITER "Expecting ':' delimiter"
+#define ERR_STRING_UNTERMINATED "Unterminated string starting at"
+#define ERR_STRING_CONTROL "Invalid control character %r at"
+#define ERR_STRING_ESC1 "Invalid \\X escape sequence %r"
+#define ERR_STRING_ESC4 "Invalid \\uXXXX escape sequence"
+
+typedef struct _PyScannerObject {
+ PyObject_HEAD
+ PyObject *encoding;
+ PyObject *strict;
+ PyObject *object_hook;
+ PyObject *pairs_hook;
+ PyObject *parse_float;
+ PyObject *parse_int;
+ PyObject *parse_constant;
+ PyObject *memo;
+} PyScannerObject;
+
+static PyMemberDef scanner_members[] = {
+ {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"},
+ {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"},
+ {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"},
+ {"object_pairs_hook", T_OBJECT, offsetof(PyScannerObject, pairs_hook), READONLY, "object_pairs_hook"},
+ {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"},
+ {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"},
+ {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"},
+ {NULL}
+};
+
+typedef struct _PyEncoderObject {
+ PyObject_HEAD
+ PyObject *markers;
+ PyObject *defaultfn;
+ PyObject *encoder;
+ PyObject *indent;
+ PyObject *key_separator;
+ PyObject *item_separator;
+ PyObject *sort_keys;
+ PyObject *key_memo;
+ PyObject *encoding;
+ PyObject *Decimal;
+ PyObject *skipkeys_bool;
+ int skipkeys;
+ int fast_encode;
+ /* 0, JSON_ALLOW_NAN, JSON_IGNORE_NAN */
+ int allow_or_ignore_nan;
+ int use_decimal;
+ int namedtuple_as_object;
+ int tuple_as_array;
+ PyObject *max_long_size;
+ PyObject *min_long_size;
+ PyObject *item_sort_key;
+ PyObject *item_sort_kw;
+ int for_json;
+} PyEncoderObject;
+
+static PyMemberDef encoder_members[] = {
+ {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"},
+ {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"},
+ {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"},
+ {"encoding", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoding"},
+ {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"},
+ {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"},
+ {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"},
+ {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
+ /* Python 2.5 does not support T_BOOl */
+ {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys_bool), READONLY, "skipkeys"},
+ {"key_memo", T_OBJECT, offsetof(PyEncoderObject, key_memo), READONLY, "key_memo"},
+ {"item_sort_key", T_OBJECT, offsetof(PyEncoderObject, item_sort_key), READONLY, "item_sort_key"},
+ {"max_long_size", T_OBJECT, offsetof(PyEncoderObject, max_long_size), READONLY, "max_long_size"},
+ {"min_long_size", T_OBJECT, offsetof(PyEncoderObject, min_long_size), READONLY, "min_long_size"},
+ {NULL}
+};
+
+static PyObject *
+join_list_unicode(PyObject *lst);
+static PyObject *
+JSON_ParseEncoding(PyObject *encoding);
+static PyObject *
+JSON_UnicodeFromChar(JSON_UNICHR c);
+static PyObject *
+maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj);
+static Py_ssize_t
+ascii_char_size(JSON_UNICHR c);
+static Py_ssize_t
+ascii_escape_char(JSON_UNICHR c, char *output, Py_ssize_t chars);
+static PyObject *
+ascii_escape_unicode(PyObject *pystr);
+static PyObject *
+ascii_escape_str(PyObject *pystr);
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr);
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+join_list_string(PyObject *lst);
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr);
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+#endif
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr);
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+scanner_dealloc(PyObject *self);
+static int
+scanner_clear(PyObject *self);
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+encoder_dealloc(PyObject *self);
+static int
+encoder_clear(PyObject *self);
+static PyObject *
+encoder_stringify_key(PyEncoderObject *s, PyObject *key);
+static int
+encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level);
+static int
+encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ssize_t indent_level);
+static int
+encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_ssize_t indent_level);
+static PyObject *
+_encoded_const(PyObject *obj);
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end);
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj);
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr);
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr);
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj);
+static int
+_is_namedtuple(PyObject *obj);
+static int
+_has_for_json_hook(PyObject *obj);
+static PyObject *
+moduleinit(void);
+
+#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"')
+#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+#define MIN_EXPANSION 6
+
+static int
+JSON_Accu_Init(JSON_Accu *acc)
+{
+ /* Lazily allocated */
+ acc->large_strings = NULL;
+ acc->small_strings = PyList_New(0);
+ if (acc->small_strings == NULL)
+ return -1;
+ return 0;
+}
+
+static int
+flush_accumulator(JSON_Accu *acc)
+{
+ Py_ssize_t nsmall = PyList_GET_SIZE(acc->small_strings);
+ if (nsmall) {
+ int ret;
+ PyObject *joined;
+ if (acc->large_strings == NULL) {
+ acc->large_strings = PyList_New(0);
+ if (acc->large_strings == NULL)
+ return -1;
+ }
+#if PY_MAJOR_VERSION >= 3
+ joined = join_list_unicode(acc->small_strings);
+#else /* PY_MAJOR_VERSION >= 3 */
+ joined = join_list_string(acc->small_strings);
+#endif /* PY_MAJOR_VERSION < 3 */
+ if (joined == NULL)
+ return -1;
+ if (PyList_SetSlice(acc->small_strings, 0, nsmall, NULL)) {
+ Py_DECREF(joined);
+ return -1;
+ }
+ ret = PyList_Append(acc->large_strings, joined);
+ Py_DECREF(joined);
+ return ret;
+ }
+ return 0;
+}
+
+static int
+JSON_Accu_Accumulate(JSON_Accu *acc, PyObject *unicode)
+{
+ Py_ssize_t nsmall;
+#if PY_MAJOR_VERSION >= 3
+ assert(PyUnicode_Check(unicode));
+#else /* PY_MAJOR_VERSION >= 3 */
+ assert(JSON_ASCII_Check(unicode) || PyUnicode_Check(unicode));
+#endif /* PY_MAJOR_VERSION < 3 */
+
+ if (PyList_Append(acc->small_strings, unicode))
+ return -1;
+ nsmall = PyList_GET_SIZE(acc->small_strings);
+ /* Each item in a list of unicode objects has an overhead (in 64-bit
+ * builds) of:
+ * - 8 bytes for the list slot
+ * - 56 bytes for the header of the unicode object
+ * that is, 64 bytes. 100000 such objects waste more than 6MB
+ * compared to a single concatenated string.
+ */
+ if (nsmall < 100000)
+ return 0;
+ return flush_accumulator(acc);
+}
+
+static PyObject *
+JSON_Accu_FinishAsList(JSON_Accu *acc)
+{
+ int ret;
+ PyObject *res;
+
+ ret = flush_accumulator(acc);
+ Py_CLEAR(acc->small_strings);
+ if (ret) {
+ Py_CLEAR(acc->large_strings);
+ return NULL;
+ }
+ res = acc->large_strings;
+ acc->large_strings = NULL;
+ if (res == NULL)
+ return PyList_New(0);
+ return res;
+}
+
+static void
+JSON_Accu_Destroy(JSON_Accu *acc)
+{
+ Py_CLEAR(acc->small_strings);
+ Py_CLEAR(acc->large_strings);
+}
+
+static int
+IS_DIGIT(JSON_UNICHR c)
+{
+ return c >= '0' && c <= '9';
+}
+
+static PyObject *
+JSON_UnicodeFromChar(JSON_UNICHR c)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *rval = PyUnicode_New(1, c);
+ if (rval)
+ PyUnicode_WRITE(PyUnicode_KIND(rval), PyUnicode_DATA(rval), 0, c);
+ return rval;
+#else /* PY_MAJOR_VERSION >= 3 */
+ return PyUnicode_FromUnicode(&c, 1);
+#endif /* PY_MAJOR_VERSION < 3 */
+}
+
+static PyObject *
+maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj)
+{
+ if (s->max_long_size != Py_None && s->min_long_size != Py_None) {
+ if (PyObject_RichCompareBool(obj, s->max_long_size, Py_GE) ||
+ PyObject_RichCompareBool(obj, s->min_long_size, Py_LE)) {
+#if PY_MAJOR_VERSION >= 3
+ PyObject* quoted = PyUnicode_FromFormat("\"%U\"", encoded);
+#else
+ PyObject* quoted = PyString_FromFormat("\"%s\"",
+ PyString_AsString(encoded));
+#endif
+ Py_DECREF(encoded);
+ encoded = quoted;
+ }
+ }
+
+ return encoded;
+}
+
+static int
+_is_namedtuple(PyObject *obj)
+{
+ int rval = 0;
+ PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict");
+ if (_asdict == NULL) {
+ PyErr_Clear();
+ return 0;
+ }
+ rval = PyCallable_Check(_asdict);
+ Py_DECREF(_asdict);
+ return rval;
+}
+
+static int
+_has_for_json_hook(PyObject *obj)
+{
+ int rval = 0;
+ PyObject *for_json = PyObject_GetAttrString(obj, "for_json");
+ if (for_json == NULL) {
+ PyErr_Clear();
+ return 0;
+ }
+ rval = PyCallable_Check(for_json);
+ Py_DECREF(for_json);
+ return rval;
+}
+
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr)
+{
+ /* PyObject to Py_ssize_t converter */
+ *size_ptr = PyInt_AsSsize_t(o);
+ if (*size_ptr == -1 && PyErr_Occurred())
+ return 0;
+ return 1;
+}
+
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr)
+{
+ /* Py_ssize_t to PyObject converter */
+ return PyInt_FromSsize_t(*size_ptr);
+}
+
+static Py_ssize_t
+ascii_escape_char(JSON_UNICHR c, char *output, Py_ssize_t chars)
+{
+ /* Escape unicode code point c to ASCII escape sequences
+ in char *output. output must have at least 12 bytes unused to
+ accommodate an escaped surrogate pair "\uXXXX\uXXXX" */
+ if (S_CHAR(c)) {
+ output[chars++] = (char)c;
+ }
+ else {
+ output[chars++] = '\\';
+ switch (c) {
+ case '\\': output[chars++] = (char)c; break;
+ case '"': output[chars++] = (char)c; break;
+ case '\b': output[chars++] = 'b'; break;
+ case '\f': output[chars++] = 'f'; break;
+ case '\n': output[chars++] = 'n'; break;
+ case '\r': output[chars++] = 'r'; break;
+ case '\t': output[chars++] = 't'; break;
+ default:
+#if defined(Py_UNICODE_WIDE) || PY_MAJOR_VERSION >= 3
+ if (c >= 0x10000) {
+ /* UTF-16 surrogate pair */
+ JSON_UNICHR v = c - 0x10000;
+ c = 0xd800 | ((v >> 10) & 0x3ff);
+ output[chars++] = 'u';
+ output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c ) & 0xf];
+ c = 0xdc00 | (v & 0x3ff);
+ output[chars++] = '\\';
+ }
+#endif
+ output[chars++] = 'u';
+ output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c >> 8) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c >> 4) & 0xf];
+ output[chars++] = "0123456789abcdef"[(c ) & 0xf];
+ }
+ }
+ return chars;
+}
+
+static Py_ssize_t
+ascii_char_size(JSON_UNICHR c)
+{
+ if (S_CHAR(c)) {
+ return 1;
+ }
+ else if (c == '\\' ||
+ c == '"' ||
+ c == '\b' ||
+ c == '\f' ||
+ c == '\n' ||
+ c == '\r' ||
+ c == '\t') {
+ return 2;
+ }
+#if defined(Py_UNICODE_WIDE) || PY_MAJOR_VERSION >= 3
+ else if (c >= 0x10000U) {
+ return 2 * MIN_EXPANSION;
+ }
+#endif
+ else {
+ return MIN_EXPANSION;
+ }
+}
+
+static PyObject *
+ascii_escape_unicode(PyObject *pystr)
+{
+ /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */
+ Py_ssize_t i;
+ Py_ssize_t input_chars;
+ Py_ssize_t output_size;
+ Py_ssize_t chars;
+ PY2_UNUSED int kind;
+ void *data;
+ PyObject *rval;
+ char *output;
+
+ if (PyUnicode_READY(pystr))
+ return NULL;
+
+ kind = PyUnicode_KIND(pystr);
+ data = PyUnicode_DATA(pystr);
+ input_chars = PyUnicode_GetLength(pystr);
+ output_size = 2;
+ for (i = 0; i < input_chars; i++) {
+ output_size += ascii_char_size(PyUnicode_READ(kind, data, i));
+ }
+#if PY_MAJOR_VERSION >= 3
+ rval = PyUnicode_New(output_size, 127);
+ if (rval == NULL) {
+ return NULL;
+ }
+ assert(PyUnicode_KIND(rval) == PyUnicode_1BYTE_KIND);
+ output = (char *)PyUnicode_DATA(rval);
+#else
+ rval = PyString_FromStringAndSize(NULL, output_size);
+ if (rval == NULL) {
+ return NULL;
+ }
+ output = PyString_AS_STRING(rval);
+#endif
+ chars = 0;
+ output[chars++] = '"';
+ for (i = 0; i < input_chars; i++) {
+ chars = ascii_escape_char(PyUnicode_READ(kind, data, i), output, chars);
+ }
+ output[chars++] = '"';
+ assert(chars == output_size);
+ return rval;
+}
+
+#if PY_MAJOR_VERSION >= 3
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+ PyObject *rval;
+ PyObject *input = PyUnicode_DecodeUTF8(PyString_AS_STRING(pystr), PyString_GET_SIZE(pystr), NULL);
+ if (input == NULL)
+ return NULL;
+ rval = ascii_escape_unicode(input);
+ Py_DECREF(input);
+ return rval;
+}
+
+#else /* PY_MAJOR_VERSION >= 3 */
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+ /* Take a PyString pystr and return a new ASCII-only escaped PyString */
+ Py_ssize_t i;
+ Py_ssize_t input_chars;
+ Py_ssize_t output_size;
+ Py_ssize_t chars;
+ PyObject *rval;
+ char *output;
+ char *input_str;
+
+ input_chars = PyString_GET_SIZE(pystr);
+ input_str = PyString_AS_STRING(pystr);
+ output_size = 2;
+
+ /* Fast path for a string that's already ASCII */
+ for (i = 0; i < input_chars; i++) {
+ JSON_UNICHR c = (JSON_UNICHR)input_str[i];
+ if (c > 0x7f) {
+ /* We hit a non-ASCII character, bail to unicode mode */
+ PyObject *uni;
+ uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict");
+ if (uni == NULL) {
+ return NULL;
+ }
+ rval = ascii_escape_unicode(uni);
+ Py_DECREF(uni);
+ return rval;
+ }
+ output_size += ascii_char_size(c);
+ }
+
+ rval = PyString_FromStringAndSize(NULL, output_size);
+ if (rval == NULL) {
+ return NULL;
+ }
+ chars = 0;
+ output = PyString_AS_STRING(rval);
+ output[chars++] = '"';
+ for (i = 0; i < input_chars; i++) {
+ chars = ascii_escape_char((JSON_UNICHR)input_str[i], output, chars);
+ }
+ output[chars++] = '"';
+ assert(chars == output_size);
+ return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+encoder_stringify_key(PyEncoderObject *s, PyObject *key)
+{
+ if (PyUnicode_Check(key)) {
+ Py_INCREF(key);
+ return key;
+ }
+ else if (PyString_Check(key)) {
+#if PY_MAJOR_VERSION >= 3
+ return PyUnicode_Decode(
+ PyString_AS_STRING(key),
+ PyString_GET_SIZE(key),
+ JSON_ASCII_AS_STRING(s->encoding),
+ NULL);
+#else /* PY_MAJOR_VERSION >= 3 */
+ Py_INCREF(key);
+ return key;
+#endif /* PY_MAJOR_VERSION < 3 */
+ }
+ else if (PyFloat_Check(key)) {
+ return encoder_encode_float(s, key);
+ }
+ else if (key == Py_True || key == Py_False || key == Py_None) {
+ /* This must come before the PyInt_Check because
+ True and False are also 1 and 0.*/
+ return _encoded_const(key);
+ }
+ else if (PyInt_Check(key) || PyLong_Check(key)) {
+ return PyObject_Str(key);
+ }
+ else if (s->use_decimal && PyObject_TypeCheck(key, (PyTypeObject *)s->Decimal)) {
+ return PyObject_Str(key);
+ }
+ else if (s->skipkeys) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ PyErr_SetString(PyExc_TypeError, "keys must be a string");
+ return NULL;
+}
+
+static PyObject *
+encoder_dict_iteritems(PyEncoderObject *s, PyObject *dct)
+{
+ PyObject *items;
+ PyObject *iter = NULL;
+ PyObject *lst = NULL;
+ PyObject *item = NULL;
+ PyObject *kstr = NULL;
+ static PyObject *sortfun = NULL;
+ static PyObject *sortargs = NULL;
+
+ if (sortargs == NULL) {
+ sortargs = PyTuple_New(0);
+ if (sortargs == NULL)
+ return NULL;
+ }
+
+ if (PyDict_CheckExact(dct))
+ items = PyDict_Items(dct);
+ else
+ items = PyMapping_Items(dct);
+ if (items == NULL)
+ return NULL;
+ iter = PyObject_GetIter(items);
+ Py_DECREF(items);
+ if (iter == NULL)
+ return NULL;
+ if (s->item_sort_kw == Py_None)
+ return iter;
+ lst = PyList_New(0);
+ if (lst == NULL)
+ goto bail;
+ while ((item = PyIter_Next(iter))) {
+ PyObject *key, *value;
+ if (!PyTuple_Check(item) || Py_SIZE(item) != 2) {
+ PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
+ goto bail;
+ }
+ key = PyTuple_GET_ITEM(item, 0);
+ if (key == NULL)
+ goto bail;
+#if PY_MAJOR_VERSION < 3
+ else if (PyString_Check(key)) {
+ /* item can be added as-is */
+ }
+#endif /* PY_MAJOR_VERSION < 3 */
+ else if (PyUnicode_Check(key)) {
+ /* item can be added as-is */
+ }
+ else {
+ PyObject *tpl;
+ kstr = encoder_stringify_key(s, key);
+ if (kstr == NULL)
+ goto bail;
+ else if (kstr == Py_None) {
+ /* skipkeys */
+ Py_DECREF(kstr);
+ continue;
+ }
+ value = PyTuple_GET_ITEM(item, 1);
+ if (value == NULL)
+ goto bail;
+ tpl = PyTuple_Pack(2, kstr, value);
+ if (tpl == NULL)
+ goto bail;
+ Py_CLEAR(kstr);
+ Py_DECREF(item);
+ item = tpl;
+ }
+ if (PyList_Append(lst, item))
+ goto bail;
+ Py_DECREF(item);
+ }
+ Py_CLEAR(iter);
+ if (PyErr_Occurred())
+ goto bail;
+ sortfun = PyObject_GetAttrString(lst, "sort");
+ if (sortfun == NULL)
+ goto bail;
+ if (!PyObject_Call(sortfun, sortargs, s->item_sort_kw))
+ goto bail;
+ Py_CLEAR(sortfun);
+ iter = PyObject_GetIter(lst);
+ Py_CLEAR(lst);
+ return iter;
+bail:
+ Py_XDECREF(sortfun);
+ Py_XDECREF(kstr);
+ Py_XDECREF(item);
+ Py_XDECREF(lst);
+ Py_XDECREF(iter);
+ return NULL;
+}
+
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end)
+{
+ /* Use JSONDecodeError exception to raise a nice looking ValueError subclass */
+ static PyObject *JSONDecodeError = NULL;
+ PyObject *exc;
+ if (JSONDecodeError == NULL) {
+ PyObject *scanner = PyImport_ImportModule("simplejson.scanner");
+ if (scanner == NULL)
+ return;
+ JSONDecodeError = PyObject_GetAttrString(scanner, "JSONDecodeError");
+ Py_DECREF(scanner);
+ if (JSONDecodeError == NULL)
+ return;
+ }
+ exc = PyObject_CallFunction(JSONDecodeError, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end);
+ if (exc) {
+ PyErr_SetObject(JSONDecodeError, exc);
+ Py_DECREF(exc);
+ }
+}
+
+static PyObject *
+join_list_unicode(PyObject *lst)
+{
+ /* return u''.join(lst) */
+ static PyObject *joinfn = NULL;
+ if (joinfn == NULL) {
+ PyObject *ustr = JSON_NewEmptyUnicode();
+ if (ustr == NULL)
+ return NULL;
+
+ joinfn = PyObject_GetAttrString(ustr, "join");
+ Py_DECREF(ustr);
+ if (joinfn == NULL)
+ return NULL;
+ }
+ return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+#if PY_MAJOR_VERSION >= 3
+#define join_list_string join_list_unicode
+#else /* PY_MAJOR_VERSION >= 3 */
+static PyObject *
+join_list_string(PyObject *lst)
+{
+ /* return ''.join(lst) */
+ static PyObject *joinfn = NULL;
+ if (joinfn == NULL) {
+ PyObject *ustr = PyString_FromStringAndSize(NULL, 0);
+ if (ustr == NULL)
+ return NULL;
+
+ joinfn = PyObject_GetAttrString(ustr, "join");
+ Py_DECREF(ustr);
+ if (joinfn == NULL)
+ return NULL;
+ }
+ return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx)
+{
+ /* return (rval, idx) tuple, stealing reference to rval */
+ PyObject *tpl;
+ PyObject *pyidx;
+ /*
+ steal a reference to rval, returns (rval, idx)
+ */
+ if (rval == NULL) {
+ assert(PyErr_Occurred());
+ return NULL;
+ }
+ pyidx = PyInt_FromSsize_t(idx);
+ if (pyidx == NULL) {
+ Py_DECREF(rval);
+ return NULL;
+ }
+ tpl = PyTuple_New(2);
+ if (tpl == NULL) {
+ Py_DECREF(pyidx);
+ Py_DECREF(rval);
+ return NULL;
+ }
+ PyTuple_SET_ITEM(tpl, 0, rval);
+ PyTuple_SET_ITEM(tpl, 1, pyidx);
+ return tpl;
+}
+
+#define APPEND_OLD_CHUNK \
+ if (chunk != NULL) { \
+ if (chunks == NULL) { \
+ chunks = PyList_New(0); \
+ if (chunks == NULL) { \
+ goto bail; \
+ } \
+ } \
+ if (PyList_Append(chunks, chunk)) { \
+ goto bail; \
+ } \
+ Py_CLEAR(chunk); \
+ }
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr)
+{
+ /* Read the JSON string from PyString pystr.
+ end is the index of the first character after the quote.
+ encoding is the encoding of pystr (must be an ASCII superset)
+ if strict is zero then literal control characters are allowed
+ *next_end_ptr is a return-by-reference index of the character
+ after the end quote
+
+ Return value is a new PyString (if ASCII-only) or PyUnicode
+ */
+ PyObject *rval;
+ Py_ssize_t len = PyString_GET_SIZE(pystr);
+ Py_ssize_t begin = end - 1;
+ Py_ssize_t next = begin;
+ int has_unicode = 0;
+ char *buf = PyString_AS_STRING(pystr);
+ PyObject *chunks = NULL;
+ PyObject *chunk = NULL;
+ PyObject *strchunk = NULL;
+
+ if (len == end) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ else if (end < 0 || len < end) {
+ PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+ goto bail;
+ }
+ while (1) {
+ /* Find the end of the string or the next escape */
+ Py_UNICODE c = 0;
+ for (next = end; next < len; next++) {
+ c = (unsigned char)buf[next];
+ if (c == '"' || c == '\\') {
+ break;
+ }
+ else if (strict && c <= 0x1f) {
+ raise_errmsg(ERR_STRING_CONTROL, pystr, next);
+ goto bail;
+ }
+ else if (c > 0x7f) {
+ has_unicode = 1;
+ }
+ }
+ if (!(c == '"' || c == '\\')) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ /* Pick up this chunk if it's not zero length */
+ if (next != end) {
+ APPEND_OLD_CHUNK
+#if PY_MAJOR_VERSION >= 3
+ if (!has_unicode) {
+ chunk = PyUnicode_DecodeASCII(&buf[end], next - end, NULL);
+ }
+ else {
+ chunk = PyUnicode_Decode(&buf[end], next - end, encoding, NULL);
+ }
+ if (chunk == NULL) {
+ goto bail;
+ }
+#else /* PY_MAJOR_VERSION >= 3 */
+ strchunk = PyString_FromStringAndSize(&buf[end], next - end);
+ if (strchunk == NULL) {
+ goto bail;
+ }
+ if (has_unicode) {
+ chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL);
+ Py_DECREF(strchunk);
+ if (chunk == NULL) {
+ goto bail;
+ }
+ }
+ else {
+ chunk = strchunk;
+ }
+#endif /* PY_MAJOR_VERSION < 3 */
+ }
+ next++;
+ if (c == '"') {
+ end = next;
+ break;
+ }
+ if (next == len) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ c = buf[next];
+ if (c != 'u') {
+ /* Non-unicode backslash escapes */
+ end = next + 1;
+ switch (c) {
+ case '"': break;
+ case '\\': break;
+ case '/': break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ default: c = 0;
+ }
+ if (c == 0) {
+ raise_errmsg(ERR_STRING_ESC1, pystr, end - 2);
+ goto bail;
+ }
+ }
+ else {
+ c = 0;
+ next++;
+ end = next + 4;
+ if (end >= len) {
+ raise_errmsg(ERR_STRING_ESC4, pystr, next - 1);
+ goto bail;
+ }
+ /* Decode 4 hex digits */
+ for (; next < end; next++) {
+ JSON_UNICHR digit = (JSON_UNICHR)buf[next];
+ c <<= 4;
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ c |= (digit - '0'); break;
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ c |= (digit - 'a' + 10); break;
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ c |= (digit - 'A' + 10); break;
+ default:
+ raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+ goto bail;
+ }
+ }
+#if (PY_MAJOR_VERSION >= 3 || defined(Py_UNICODE_WIDE))
+ /* Surrogate pair */
+ if ((c & 0xfc00) == 0xd800) {
+ if (end + 6 < len && buf[next] == '\\' && buf[next+1] == 'u') {
+ JSON_UNICHR c2 = 0;
+ end += 6;
+ /* Decode 4 hex digits */
+ for (next += 2; next < end; next++) {
+ c2 <<= 4;
+ JSON_UNICHR digit = buf[next];
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ c2 |= (digit - '0'); break;
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ c2 |= (digit - 'a' + 10); break;
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ c2 |= (digit - 'A' + 10); break;
+ default:
+ raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+ goto bail;
+ }
+ }
+ if ((c2 & 0xfc00) != 0xdc00) {
+ /* not a low surrogate, rewind */
+ end -= 6;
+ next = end;
+ }
+ else {
+ c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+ }
+ }
+ }
+#endif /* PY_MAJOR_VERSION >= 3 || Py_UNICODE_WIDE */
+ }
+ if (c > 0x7f) {
+ has_unicode = 1;
+ }
+ APPEND_OLD_CHUNK
+#if PY_MAJOR_VERSION >= 3
+ chunk = JSON_UnicodeFromChar(c);
+ if (chunk == NULL) {
+ goto bail;
+ }
+#else /* PY_MAJOR_VERSION >= 3 */
+ if (has_unicode) {
+ chunk = JSON_UnicodeFromChar(c);
+ if (chunk == NULL) {
+ goto bail;
+ }
+ }
+ else {
+ char c_char = Py_CHARMASK(c);
+ chunk = PyString_FromStringAndSize(&c_char, 1);
+ if (chunk == NULL) {
+ goto bail;
+ }
+ }
+#endif
+ }
+
+ if (chunks == NULL) {
+ if (chunk != NULL)
+ rval = chunk;
+ else
+ rval = JSON_NewEmptyUnicode();
+ }
+ else {
+ APPEND_OLD_CHUNK
+ rval = join_list_string(chunks);
+ if (rval == NULL) {
+ goto bail;
+ }
+ Py_CLEAR(chunks);
+ }
+
+ *next_end_ptr = end;
+ return rval;
+bail:
+ *next_end_ptr = -1;
+ Py_XDECREF(chunk);
+ Py_XDECREF(chunks);
+ return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
+{
+ /* Read the JSON string from PyUnicode pystr.
+ end is the index of the first character after the quote.
+ if strict is zero then literal control characters are allowed
+ *next_end_ptr is a return-by-reference index of the character
+ after the end quote
+
+ Return value is a new PyUnicode
+ */
+ PyObject *rval;
+ Py_ssize_t begin = end - 1;
+ Py_ssize_t next = begin;
+ PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+ Py_ssize_t len = PyUnicode_GetLength(pystr);
+ void *buf = PyUnicode_DATA(pystr);
+ PyObject *chunks = NULL;
+ PyObject *chunk = NULL;
+
+ if (len == end) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ else if (end < 0 || len < end) {
+ PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+ goto bail;
+ }
+ while (1) {
+ /* Find the end of the string or the next escape */
+ JSON_UNICHR c = 0;
+ for (next = end; next < len; next++) {
+ c = PyUnicode_READ(kind, buf, next);
+ if (c == '"' || c == '\\') {
+ break;
+ }
+ else if (strict && c <= 0x1f) {
+ raise_errmsg(ERR_STRING_CONTROL, pystr, next);
+ goto bail;
+ }
+ }
+ if (!(c == '"' || c == '\\')) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ /* Pick up this chunk if it's not zero length */
+ if (next != end) {
+ APPEND_OLD_CHUNK
+#if PY_MAJOR_VERSION < 3
+ chunk = PyUnicode_FromUnicode(&((const Py_UNICODE *)buf)[end], next - end);
+#else
+ chunk = PyUnicode_Substring(pystr, end, next);
+#endif
+ if (chunk == NULL) {
+ goto bail;
+ }
+ }
+ next++;
+ if (c == '"') {
+ end = next;
+ break;
+ }
+ if (next == len) {
+ raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+ goto bail;
+ }
+ c = PyUnicode_READ(kind, buf, next);
+ if (c != 'u') {
+ /* Non-unicode backslash escapes */
+ end = next + 1;
+ switch (c) {
+ case '"': break;
+ case '\\': break;
+ case '/': break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ default: c = 0;
+ }
+ if (c == 0) {
+ raise_errmsg(ERR_STRING_ESC1, pystr, end - 2);
+ goto bail;
+ }
+ }
+ else {
+ c = 0;
+ next++;
+ end = next + 4;
+ if (end >= len) {
+ raise_errmsg(ERR_STRING_ESC4, pystr, next - 1);
+ goto bail;
+ }
+ /* Decode 4 hex digits */
+ for (; next < end; next++) {
+ JSON_UNICHR digit = PyUnicode_READ(kind, buf, next);
+ c <<= 4;
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ c |= (digit - '0'); break;
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ c |= (digit - 'a' + 10); break;
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ c |= (digit - 'A' + 10); break;
+ default:
+ raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+ goto bail;
+ }
+ }
+#if PY_MAJOR_VERSION >= 3 || defined(Py_UNICODE_WIDE)
+ /* Surrogate pair */
+ if ((c & 0xfc00) == 0xd800) {
+ JSON_UNICHR c2 = 0;
+ if (end + 6 < len &&
+ PyUnicode_READ(kind, buf, next) == '\\' &&
+ PyUnicode_READ(kind, buf, next + 1) == 'u') {
+ end += 6;
+ /* Decode 4 hex digits */
+ for (next += 2; next < end; next++) {
+ JSON_UNICHR digit = PyUnicode_READ(kind, buf, next);
+ c2 <<= 4;
+ switch (digit) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ c2 |= (digit - '0'); break;
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ c2 |= (digit - 'a' + 10); break;
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ c2 |= (digit - 'A' + 10); break;
+ default:
+ raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+ goto bail;
+ }
+ }
+ if ((c2 & 0xfc00) != 0xdc00) {
+ /* not a low surrogate, rewind */
+ end -= 6;
+ next = end;
+ }
+ else {
+ c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+ }
+ }
+ }
+#endif
+ }
+ APPEND_OLD_CHUNK
+ chunk = JSON_UnicodeFromChar(c);
+ if (chunk == NULL) {
+ goto bail;
+ }
+ }
+
+ if (chunks == NULL) {
+ if (chunk != NULL)
+ rval = chunk;
+ else
+ rval = JSON_NewEmptyUnicode();
+ }
+ else {
+ APPEND_OLD_CHUNK
+ rval = join_list_unicode(chunks);
+ if (rval == NULL) {
+ goto bail;
+ }
+ Py_CLEAR(chunks);
+ }
+ *next_end_ptr = end;
+ return rval;
+bail:
+ *next_end_ptr = -1;
+ Py_XDECREF(chunk);
+ Py_XDECREF(chunks);
+ return NULL;
+}
+
+PyDoc_STRVAR(pydoc_scanstring,
+ "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n"
+ "\n"
+ "Scan the string s for a JSON string. End is the index of the\n"
+ "character in s after the quote that started the JSON string.\n"
+ "Unescapes all valid JSON string escape sequences and raises ValueError\n"
+ "on attempt to decode an invalid string. If strict is False then literal\n"
+ "control characters are allowed in the string.\n"
+ "\n"
+ "Returns a tuple of the decoded string and the index of the character in s\n"
+ "after the end quote."
+);
+
+static PyObject *
+py_scanstring(PyObject* self UNUSED, PyObject *args)
+{
+ PyObject *pystr;
+ PyObject *rval;
+ Py_ssize_t end;
+ Py_ssize_t next_end = -1;
+ char *encoding = NULL;
+ int strict = 1;
+ if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) {
+ return NULL;
+ }
+ if (encoding == NULL) {
+ encoding = DEFAULT_ENCODING;
+ }
+ if (PyUnicode_Check(pystr)) {
+ rval = scanstring_unicode(pystr, end, strict, &next_end);
+ }
+#if PY_MAJOR_VERSION < 3
+ /* Using a bytes input is unsupported for scanning in Python 3.
+ It is coerced to str in the decoder before it gets here. */
+ else if (PyString_Check(pystr)) {
+ rval = scanstring_str(pystr, end, encoding, strict, &next_end);
+ }
+#endif
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "first argument must be a string, not %.80s",
+ Py_TYPE(pystr)->tp_name);
+ return NULL;
+ }
+ return _build_rval_index_tuple(rval, next_end);
+}
+
+PyDoc_STRVAR(pydoc_encode_basestring_ascii,
+ "encode_basestring_ascii(basestring) -> str\n"
+ "\n"
+ "Return an ASCII-only JSON representation of a Python string"
+);
+
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr)
+{
+ /* Return an ASCII-only JSON representation of a Python string */
+ /* METH_O */
+ if (PyString_Check(pystr)) {
+ return ascii_escape_str(pystr);
+ }
+ else if (PyUnicode_Check(pystr)) {
+ return ascii_escape_unicode(pystr);
+ }
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "first argument must be a string, not %.80s",
+ Py_TYPE(pystr)->tp_name);
+ return NULL;
+ }
+}
+
+static void
+scanner_dealloc(PyObject *self)
+{
+ /* Deallocate scanner object */
+ scanner_clear(self);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static int
+scanner_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ PyScannerObject *s;
+ assert(PyScanner_Check(self));
+ s = (PyScannerObject *)self;
+ Py_VISIT(s->encoding);
+ Py_VISIT(s->strict);
+ Py_VISIT(s->object_hook);
+ Py_VISIT(s->pairs_hook);
+ Py_VISIT(s->parse_float);
+ Py_VISIT(s->parse_int);
+ Py_VISIT(s->parse_constant);
+ Py_VISIT(s->memo);
+ return 0;
+}
+
+static int
+scanner_clear(PyObject *self)
+{
+ PyScannerObject *s;
+ assert(PyScanner_Check(self));
+ s = (PyScannerObject *)self;
+ Py_CLEAR(s->encoding);
+ Py_CLEAR(s->strict);
+ Py_CLEAR(s->object_hook);
+ Py_CLEAR(s->pairs_hook);
+ Py_CLEAR(s->parse_float);
+ Py_CLEAR(s->parse_int);
+ Py_CLEAR(s->parse_constant);
+ Py_CLEAR(s->memo);
+ return 0;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON object from PyString pystr.
+ idx is the index of the first character after the opening curly brace.
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the closing curly brace.
+
+ Returns a new PyObject (usually a dict, but object_hook or
+ object_pairs_hook can change that)
+ */
+ char *str = PyString_AS_STRING(pystr);
+ Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+ PyObject *rval = NULL;
+ PyObject *pairs = NULL;
+ PyObject *item;
+ PyObject *key = NULL;
+ PyObject *val = NULL;
+ char *encoding = JSON_ASCII_AS_STRING(s->encoding);
+ int strict = PyObject_IsTrue(s->strict);
+ int has_pairs_hook = (s->pairs_hook != Py_None);
+ int did_parse = 0;
+ Py_ssize_t next_idx;
+ if (has_pairs_hook) {
+ pairs = PyList_New(0);
+ if (pairs == NULL)
+ return NULL;
+ }
+ else {
+ rval = PyDict_New();
+ if (rval == NULL)
+ return NULL;
+ }
+
+ /* skip whitespace after { */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+ /* only loop if the object is non-empty */
+ if (idx <= end_idx && str[idx] != '}') {
+ int trailing_delimiter = 0;
+ while (idx <= end_idx) {
+ PyObject *memokey;
+ trailing_delimiter = 0;
+
+ /* read key */
+ if (str[idx] != '"') {
+ raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+ goto bail;
+ }
+ key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx);
+ if (key == NULL)
+ goto bail;
+ memokey = PyDict_GetItem(s->memo, key);
+ if (memokey != NULL) {
+ Py_INCREF(memokey);
+ Py_DECREF(key);
+ key = memokey;
+ }
+ else {
+ if (PyDict_SetItem(s->memo, key, key) < 0)
+ goto bail;
+ }
+ idx = next_idx;
+
+ /* skip whitespace between key and : delimiter, read :, skip whitespace */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+ if (idx > end_idx || str[idx] != ':') {
+ raise_errmsg(ERR_OBJECT_PROPERTY_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+ /* read any JSON data type */
+ val = scan_once_str(s, pystr, idx, &next_idx);
+ if (val == NULL)
+ goto bail;
+
+ if (has_pairs_hook) {
+ item = PyTuple_Pack(2, key, val);
+ if (item == NULL)
+ goto bail;
+ Py_CLEAR(key);
+ Py_CLEAR(val);
+ if (PyList_Append(pairs, item) == -1) {
+ Py_DECREF(item);
+ goto bail;
+ }
+ Py_DECREF(item);
+ }
+ else {
+ if (PyDict_SetItem(rval, key, val) < 0)
+ goto bail;
+ Py_CLEAR(key);
+ Py_CLEAR(val);
+ }
+ idx = next_idx;
+
+ /* skip whitespace before } or , */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+ /* bail if the object is closed or we didn't get the , delimiter */
+ did_parse = 1;
+ if (idx > end_idx) break;
+ if (str[idx] == '}') {
+ break;
+ }
+ else if (str[idx] != ',') {
+ raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+
+ /* skip whitespace after , delimiter */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+ trailing_delimiter = 1;
+ }
+ if (trailing_delimiter) {
+ raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+ goto bail;
+ }
+ }
+ /* verify that idx < end_idx, str[idx] should be '}' */
+ if (idx > end_idx || str[idx] != '}') {
+ if (did_parse) {
+ raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+ } else {
+ raise_errmsg(ERR_OBJECT_PROPERTY_FIRST, pystr, idx);
+ }
+ goto bail;
+ }
+
+ /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */
+ if (s->pairs_hook != Py_None) {
+ val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL);
+ if (val == NULL)
+ goto bail;
+ Py_DECREF(pairs);
+ *next_idx_ptr = idx + 1;
+ return val;
+ }
+
+ /* if object_hook is not None: rval = object_hook(rval) */
+ if (s->object_hook != Py_None) {
+ val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+ if (val == NULL)
+ goto bail;
+ Py_DECREF(rval);
+ rval = val;
+ val = NULL;
+ }
+ *next_idx_ptr = idx + 1;
+ return rval;
+bail:
+ Py_XDECREF(rval);
+ Py_XDECREF(key);
+ Py_XDECREF(val);
+ Py_XDECREF(pairs);
+ return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON object from PyUnicode pystr.
+ idx is the index of the first character after the opening curly brace.
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the closing curly brace.
+
+ Returns a new PyObject (usually a dict, but object_hook can change that)
+ */
+ void *str = PyUnicode_DATA(pystr);
+ Py_ssize_t end_idx = PyUnicode_GetLength(pystr) - 1;
+ PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+ PyObject *rval = NULL;
+ PyObject *pairs = NULL;
+ PyObject *item;
+ PyObject *key = NULL;
+ PyObject *val = NULL;
+ int strict = PyObject_IsTrue(s->strict);
+ int has_pairs_hook = (s->pairs_hook != Py_None);
+ int did_parse = 0;
+ Py_ssize_t next_idx;
+
+ if (has_pairs_hook) {
+ pairs = PyList_New(0);
+ if (pairs == NULL)
+ return NULL;
+ }
+ else {
+ rval = PyDict_New();
+ if (rval == NULL)
+ return NULL;
+ }
+
+ /* skip whitespace after { */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* only loop if the object is non-empty */
+ if (idx <= end_idx && PyUnicode_READ(kind, str, idx) != '}') {
+ int trailing_delimiter = 0;
+ while (idx <= end_idx) {
+ PyObject *memokey;
+ trailing_delimiter = 0;
+
+ /* read key */
+ if (PyUnicode_READ(kind, str, idx) != '"') {
+ raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+ goto bail;
+ }
+ key = scanstring_unicode(pystr, idx + 1, strict, &next_idx);
+ if (key == NULL)
+ goto bail;
+ memokey = PyDict_GetItem(s->memo, key);
+ if (memokey != NULL) {
+ Py_INCREF(memokey);
+ Py_DECREF(key);
+ key = memokey;
+ }
+ else {
+ if (PyDict_SetItem(s->memo, key, key) < 0)
+ goto bail;
+ }
+ idx = next_idx;
+
+ /* skip whitespace between key and : delimiter, read :, skip
+ whitespace */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+ if (idx > end_idx || PyUnicode_READ(kind, str, idx) != ':') {
+ raise_errmsg(ERR_OBJECT_PROPERTY_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* read any JSON term */
+ val = scan_once_unicode(s, pystr, idx, &next_idx);
+ if (val == NULL)
+ goto bail;
+
+ if (has_pairs_hook) {
+ item = PyTuple_Pack(2, key, val);
+ if (item == NULL)
+ goto bail;
+ Py_CLEAR(key);
+ Py_CLEAR(val);
+ if (PyList_Append(pairs, item) == -1) {
+ Py_DECREF(item);
+ goto bail;
+ }
+ Py_DECREF(item);
+ }
+ else {
+ if (PyDict_SetItem(rval, key, val) < 0)
+ goto bail;
+ Py_CLEAR(key);
+ Py_CLEAR(val);
+ }
+ idx = next_idx;
+
+ /* skip whitespace before } or , */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* bail if the object is closed or we didn't get the ,
+ delimiter */
+ did_parse = 1;
+ if (idx > end_idx) break;
+ if (PyUnicode_READ(kind, str, idx) == '}') {
+ break;
+ }
+ else if (PyUnicode_READ(kind, str, idx) != ',') {
+ raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+
+ /* skip whitespace after , delimiter */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+ trailing_delimiter = 1;
+ }
+ if (trailing_delimiter) {
+ raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+ goto bail;
+ }
+ }
+
+ /* verify that idx < end_idx, str[idx] should be '}' */
+ if (idx > end_idx || PyUnicode_READ(kind, str, idx) != '}') {
+ if (did_parse) {
+ raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+ } else {
+ raise_errmsg(ERR_OBJECT_PROPERTY_FIRST, pystr, idx);
+ }
+ goto bail;
+ }
+
+ /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */
+ if (s->pairs_hook != Py_None) {
+ val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL);
+ if (val == NULL)
+ goto bail;
+ Py_DECREF(pairs);
+ *next_idx_ptr = idx + 1;
+ return val;
+ }
+
+ /* if object_hook is not None: rval = object_hook(rval) */
+ if (s->object_hook != Py_None) {
+ val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+ if (val == NULL)
+ goto bail;
+ Py_DECREF(rval);
+ rval = val;
+ val = NULL;
+ }
+ *next_idx_ptr = idx + 1;
+ return rval;
+bail:
+ Py_XDECREF(rval);
+ Py_XDECREF(key);
+ Py_XDECREF(val);
+ Py_XDECREF(pairs);
+ return NULL;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON array from PyString pystr.
+ idx is the index of the first character after the opening brace.
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the closing brace.
+
+ Returns a new PyList
+ */
+ char *str = PyString_AS_STRING(pystr);
+ Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+ PyObject *val = NULL;
+ PyObject *rval = PyList_New(0);
+ Py_ssize_t next_idx;
+ if (rval == NULL)
+ return NULL;
+
+ /* skip whitespace after [ */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+ /* only loop if the array is non-empty */
+ if (idx <= end_idx && str[idx] != ']') {
+ int trailing_delimiter = 0;
+ while (idx <= end_idx) {
+ trailing_delimiter = 0;
+ /* read any JSON term and de-tuplefy the (rval, idx) */
+ val = scan_once_str(s, pystr, idx, &next_idx);
+ if (val == NULL) {
+ goto bail;
+ }
+
+ if (PyList_Append(rval, val) == -1)
+ goto bail;
+
+ Py_CLEAR(val);
+ idx = next_idx;
+
+ /* skip whitespace between term and , */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+ /* bail if the array is closed or we didn't get the , delimiter */
+ if (idx > end_idx) break;
+ if (str[idx] == ']') {
+ break;
+ }
+ else if (str[idx] != ',') {
+ raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+
+ /* skip whitespace after , */
+ while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+ trailing_delimiter = 1;
+ }
+ if (trailing_delimiter) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ goto bail;
+ }
+ }
+
+ /* verify that idx < end_idx, str[idx] should be ']' */
+ if (idx > end_idx || str[idx] != ']') {
+ if (PyList_GET_SIZE(rval)) {
+ raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+ } else {
+ raise_errmsg(ERR_ARRAY_VALUE_FIRST, pystr, idx);
+ }
+ goto bail;
+ }
+ *next_idx_ptr = idx + 1;
+ return rval;
+bail:
+ Py_XDECREF(val);
+ Py_DECREF(rval);
+ return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON array from PyString pystr.
+ idx is the index of the first character after the opening brace.
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the closing brace.
+
+ Returns a new PyList
+ */
+ PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+ void *str = PyUnicode_DATA(pystr);
+ Py_ssize_t end_idx = PyUnicode_GetLength(pystr) - 1;
+ PyObject *val = NULL;
+ PyObject *rval = PyList_New(0);
+ Py_ssize_t next_idx;
+ if (rval == NULL)
+ return NULL;
+
+ /* skip whitespace after [ */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* only loop if the array is non-empty */
+ if (idx <= end_idx && PyUnicode_READ(kind, str, idx) != ']') {
+ int trailing_delimiter = 0;
+ while (idx <= end_idx) {
+ trailing_delimiter = 0;
+ /* read any JSON term */
+ val = scan_once_unicode(s, pystr, idx, &next_idx);
+ if (val == NULL) {
+ goto bail;
+ }
+
+ if (PyList_Append(rval, val) == -1)
+ goto bail;
+
+ Py_CLEAR(val);
+ idx = next_idx;
+
+ /* skip whitespace between term and , */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* bail if the array is closed or we didn't get the , delimiter */
+ if (idx > end_idx) break;
+ if (PyUnicode_READ(kind, str, idx) == ']') {
+ break;
+ }
+ else if (PyUnicode_READ(kind, str, idx) != ',') {
+ raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+ goto bail;
+ }
+ idx++;
+
+ /* skip whitespace after , */
+ while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+ trailing_delimiter = 1;
+ }
+ if (trailing_delimiter) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ goto bail;
+ }
+ }
+
+ /* verify that idx < end_idx, str[idx] should be ']' */
+ if (idx > end_idx || PyUnicode_READ(kind, str, idx) != ']') {
+ if (PyList_GET_SIZE(rval)) {
+ raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+ } else {
+ raise_errmsg(ERR_ARRAY_VALUE_FIRST, pystr, idx);
+ }
+ goto bail;
+ }
+ *next_idx_ptr = idx + 1;
+ return rval;
+bail:
+ Py_XDECREF(val);
+ Py_DECREF(rval);
+ return NULL;
+}
+
+static PyObject *
+_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON constant from PyString pystr.
+ constant is the constant string that was found
+ ("NaN", "Infinity", "-Infinity").
+ idx is the index of the first character of the constant
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the constant.
+
+ Returns the result of parse_constant
+ */
+ PyObject *cstr;
+ PyObject *rval;
+ /* constant is "NaN", "Infinity", or "-Infinity" */
+ cstr = JSON_InternFromString(constant);
+ if (cstr == NULL)
+ return NULL;
+
+ /* rval = parse_constant(constant) */
+ rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL);
+ idx += JSON_Intern_GET_SIZE(cstr);
+ Py_DECREF(cstr);
+ *next_idx_ptr = idx;
+ return rval;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON number from PyString pystr.
+ idx is the index of the first character of the number
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the number.
+
+ Returns a new PyObject representation of that number:
+ PyInt, PyLong, or PyFloat.
+ May return other types if parse_int or parse_float are set
+ */
+ char *str = PyString_AS_STRING(pystr);
+ Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+ Py_ssize_t idx = start;
+ int is_float = 0;
+ PyObject *rval;
+ PyObject *numstr;
+
+ /* read a sign if it's there, make sure it's not the end of the string */
+ if (str[idx] == '-') {
+ if (idx >= end_idx) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+ idx++;
+ }
+
+ /* read as many integer digits as we find as long as it doesn't start with 0 */
+ if (str[idx] >= '1' && str[idx] <= '9') {
+ idx++;
+ while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+ }
+ /* if it starts with 0 we only expect one integer digit */
+ else if (str[idx] == '0') {
+ idx++;
+ }
+ /* no integer digits, error */
+ else {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+
+ /* if the next char is '.' followed by a digit then read all float digits */
+ if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+ is_float = 1;
+ idx += 2;
+ while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+ }
+
+ /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+ if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+
+ /* save the index of the 'e' or 'E' just in case we need to backtrack */
+ Py_ssize_t e_start = idx;
+ idx++;
+
+ /* read an exponent sign if present */
+ if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+ /* read all digits */
+ while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+ /* if we got a digit, then parse as float. if not, backtrack */
+ if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+ is_float = 1;
+ }
+ else {
+ idx = e_start;
+ }
+ }
+
+ /* copy the section we determined to be a number */
+ numstr = PyString_FromStringAndSize(&str[start], idx - start);
+ if (numstr == NULL)
+ return NULL;
+ if (is_float) {
+ /* parse as a float using a fast path if available, otherwise call user defined method */
+ if (s->parse_float != (PyObject *)&PyFloat_Type) {
+ rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+ }
+ else {
+ /* rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); */
+ double d = PyOS_string_to_double(PyString_AS_STRING(numstr),
+ NULL, NULL);
+ if (d == -1.0 && PyErr_Occurred())
+ return NULL;
+ rval = PyFloat_FromDouble(d);
+ }
+ }
+ else {
+ /* parse as an int using a fast path if available, otherwise call user defined method */
+ if (s->parse_int != (PyObject *)&PyInt_Type) {
+ rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+ }
+ else {
+ rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10);
+ }
+ }
+ Py_DECREF(numstr);
+ *next_idx_ptr = idx;
+ return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr)
+{
+ /* Read a JSON number from PyUnicode pystr.
+ idx is the index of the first character of the number
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the number.
+
+ Returns a new PyObject representation of that number:
+ PyInt, PyLong, or PyFloat.
+ May return other types if parse_int or parse_float are set
+ */
+ PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+ void *str = PyUnicode_DATA(pystr);
+ Py_ssize_t end_idx = PyUnicode_GetLength(pystr) - 1;
+ Py_ssize_t idx = start;
+ int is_float = 0;
+ JSON_UNICHR c;
+ PyObject *rval;
+ PyObject *numstr;
+
+ /* read a sign if it's there, make sure it's not the end of the string */
+ if (PyUnicode_READ(kind, str, idx) == '-') {
+ if (idx >= end_idx) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+ idx++;
+ }
+
+ /* read as many integer digits as we find as long as it doesn't start with 0 */
+ c = PyUnicode_READ(kind, str, idx);
+ if (c == '0') {
+ /* if it starts with 0 we only expect one integer digit */
+ idx++;
+ }
+ else if (IS_DIGIT(c)) {
+ idx++;
+ while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) {
+ idx++;
+ }
+ }
+ else {
+ /* no integer digits, error */
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+
+ /* if the next char is '.' followed by a digit then read all float digits */
+ if (idx < end_idx &&
+ PyUnicode_READ(kind, str, idx) == '.' &&
+ IS_DIGIT(PyUnicode_READ(kind, str, idx + 1))) {
+ is_float = 1;
+ idx += 2;
+ while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) idx++;
+ }
+
+ /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+ if (idx < end_idx &&
+ (PyUnicode_READ(kind, str, idx) == 'e' ||
+ PyUnicode_READ(kind, str, idx) == 'E')) {
+ Py_ssize_t e_start = idx;
+ idx++;
+
+ /* read an exponent sign if present */
+ if (idx < end_idx &&
+ (PyUnicode_READ(kind, str, idx) == '-' ||
+ PyUnicode_READ(kind, str, idx) == '+')) idx++;
+
+ /* read all digits */
+ while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) idx++;
+
+ /* if we got a digit, then parse as float. if not, backtrack */
+ if (IS_DIGIT(PyUnicode_READ(kind, str, idx - 1))) {
+ is_float = 1;
+ }
+ else {
+ idx = e_start;
+ }
+ }
+
+ /* copy the section we determined to be a number */
+#if PY_MAJOR_VERSION >= 3
+ numstr = PyUnicode_Substring(pystr, start, idx);
+#else
+ numstr = PyUnicode_FromUnicode(&((Py_UNICODE *)str)[start], idx - start);
+#endif
+ if (numstr == NULL)
+ return NULL;
+ if (is_float) {
+ /* parse as a float using a fast path if available, otherwise call user defined method */
+ if (s->parse_float != (PyObject *)&PyFloat_Type) {
+ rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+ }
+ else {
+#if PY_MAJOR_VERSION >= 3
+ rval = PyFloat_FromString(numstr);
+#else
+ rval = PyFloat_FromString(numstr, NULL);
+#endif
+ }
+ }
+ else {
+ /* no fast path for unicode -> int, just call */
+ rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+ }
+ Py_DECREF(numstr);
+ *next_idx_ptr = idx;
+ return rval;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read one JSON term (of any kind) from PyString pystr.
+ idx is the index of the first character of the term
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the number.
+
+ Returns a new PyObject representation of the term.
+ */
+ char *str = PyString_AS_STRING(pystr);
+ Py_ssize_t length = PyString_GET_SIZE(pystr);
+ PyObject *rval = NULL;
+ int fallthrough = 0;
+ if (idx < 0 || idx >= length) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+ switch (str[idx]) {
+ case '"':
+ /* string */
+ rval = scanstring_str(pystr, idx + 1,
+ JSON_ASCII_AS_STRING(s->encoding),
+ PyObject_IsTrue(s->strict),
+ next_idx_ptr);
+ break;
+ case '{':
+ /* object */
+ if (Py_EnterRecursiveCall(" while decoding a JSON object "
+ "from a string"))
+ return NULL;
+ rval = _parse_object_str(s, pystr, idx + 1, next_idx_ptr);
+ Py_LeaveRecursiveCall();
+ break;
+ case '[':
+ /* array */
+ if (Py_EnterRecursiveCall(" while decoding a JSON array "
+ "from a string"))
+ return NULL;
+ rval = _parse_array_str(s, pystr, idx + 1, next_idx_ptr);
+ Py_LeaveRecursiveCall();
+ break;
+ case 'n':
+ /* null */
+ if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+ Py_INCREF(Py_None);
+ *next_idx_ptr = idx + 4;
+ rval = Py_None;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 't':
+ /* true */
+ if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+ Py_INCREF(Py_True);
+ *next_idx_ptr = idx + 4;
+ rval = Py_True;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'f':
+ /* false */
+ if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+ Py_INCREF(Py_False);
+ *next_idx_ptr = idx + 5;
+ rval = Py_False;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'N':
+ /* NaN */
+ if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+ rval = _parse_constant(s, "NaN", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'I':
+ /* Infinity */
+ if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+ rval = _parse_constant(s, "Infinity", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ case '-':
+ /* -Infinity */
+ if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+ rval = _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ default:
+ fallthrough = 1;
+ }
+ /* Didn't find a string, object, array, or named constant. Look for a number. */
+ if (fallthrough)
+ rval = _match_number_str(s, pystr, idx, next_idx_ptr);
+ return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+ /* Read one JSON term (of any kind) from PyUnicode pystr.
+ idx is the index of the first character of the term
+ *next_idx_ptr is a return-by-reference index to the first character after
+ the number.
+
+ Returns a new PyObject representation of the term.
+ */
+ PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+ void *str = PyUnicode_DATA(pystr);
+ Py_ssize_t length = PyUnicode_GetLength(pystr);
+ PyObject *rval = NULL;
+ int fallthrough = 0;
+ if (idx < 0 || idx >= length) {
+ raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+ return NULL;
+ }
+ switch (PyUnicode_READ(kind, str, idx)) {
+ case '"':
+ /* string */
+ rval = scanstring_unicode(pystr, idx + 1,
+ PyObject_IsTrue(s->strict),
+ next_idx_ptr);
+ break;
+ case '{':
+ /* object */
+ if (Py_EnterRecursiveCall(" while decoding a JSON object "
+ "from a unicode string"))
+ return NULL;
+ rval = _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
+ Py_LeaveRecursiveCall();
+ break;
+ case '[':
+ /* array */
+ if (Py_EnterRecursiveCall(" while decoding a JSON array "
+ "from a unicode string"))
+ return NULL;
+ rval = _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
+ Py_LeaveRecursiveCall();
+ break;
+ case 'n':
+ /* null */
+ if ((idx + 3 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'u' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'l' &&
+ PyUnicode_READ(kind, str, idx + 3) == 'l') {
+ Py_INCREF(Py_None);
+ *next_idx_ptr = idx + 4;
+ rval = Py_None;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 't':
+ /* true */
+ if ((idx + 3 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'r' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'u' &&
+ PyUnicode_READ(kind, str, idx + 3) == 'e') {
+ Py_INCREF(Py_True);
+ *next_idx_ptr = idx + 4;
+ rval = Py_True;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'f':
+ /* false */
+ if ((idx + 4 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'a' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'l' &&
+ PyUnicode_READ(kind, str, idx + 3) == 's' &&
+ PyUnicode_READ(kind, str, idx + 4) == 'e') {
+ Py_INCREF(Py_False);
+ *next_idx_ptr = idx + 5;
+ rval = Py_False;
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'N':
+ /* NaN */
+ if ((idx + 2 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'a' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'N') {
+ rval = _parse_constant(s, "NaN", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ case 'I':
+ /* Infinity */
+ if ((idx + 7 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'n' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'f' &&
+ PyUnicode_READ(kind, str, idx + 3) == 'i' &&
+ PyUnicode_READ(kind, str, idx + 4) == 'n' &&
+ PyUnicode_READ(kind, str, idx + 5) == 'i' &&
+ PyUnicode_READ(kind, str, idx + 6) == 't' &&
+ PyUnicode_READ(kind, str, idx + 7) == 'y') {
+ rval = _parse_constant(s, "Infinity", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ case '-':
+ /* -Infinity */
+ if ((idx + 8 < length) &&
+ PyUnicode_READ(kind, str, idx + 1) == 'I' &&
+ PyUnicode_READ(kind, str, idx + 2) == 'n' &&
+ PyUnicode_READ(kind, str, idx + 3) == 'f' &&
+ PyUnicode_READ(kind, str, idx + 4) == 'i' &&
+ PyUnicode_READ(kind, str, idx + 5) == 'n' &&
+ PyUnicode_READ(kind, str, idx + 6) == 'i' &&
+ PyUnicode_READ(kind, str, idx + 7) == 't' &&
+ PyUnicode_READ(kind, str, idx + 8) == 'y') {
+ rval = _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+ }
+ else
+ fallthrough = 1;
+ break;
+ default:
+ fallthrough = 1;
+ }
+ /* Didn't find a string, object, array, or named constant. Look for a number. */
+ if (fallthrough)
+ rval = _match_number_unicode(s, pystr, idx, next_idx_ptr);
+ return rval;
+}
+
+static PyObject *
+scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ /* Python callable interface to scan_once_{str,unicode} */
+ PyObject *pystr;
+ PyObject *rval;
+ Py_ssize_t idx;
+ Py_ssize_t next_idx = -1;
+ static char *kwlist[] = {"string", "idx", NULL};
+ PyScannerObject *s;
+ assert(PyScanner_Check(self));
+ s = (PyScannerObject *)self;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx))
+ return NULL;
+
+ if (PyUnicode_Check(pystr)) {
+ rval = scan_once_unicode(s, pystr, idx, &next_idx);
+ }
+#if PY_MAJOR_VERSION < 3
+ else if (PyString_Check(pystr)) {
+ rval = scan_once_str(s, pystr, idx, &next_idx);
+ }
+#endif /* PY_MAJOR_VERSION < 3 */
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "first argument must be a string, not %.80s",
+ Py_TYPE(pystr)->tp_name);
+ return NULL;
+ }
+ PyDict_Clear(s->memo);
+ return _build_rval_index_tuple(rval, next_idx);
+}
+
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyScannerObject *s;
+ s = (PyScannerObject *)type->tp_alloc(type, 0);
+ if (s != NULL) {
+ s->encoding = NULL;
+ s->strict = NULL;
+ s->object_hook = NULL;
+ s->pairs_hook = NULL;
+ s->parse_float = NULL;
+ s->parse_int = NULL;
+ s->parse_constant = NULL;
+ }
+ return (PyObject *)s;
+}
+
+static PyObject *
+JSON_ParseEncoding(PyObject *encoding)
+{
+ if (encoding == NULL)
+ return NULL;
+ if (encoding == Py_None)
+ return JSON_InternFromString(DEFAULT_ENCODING);
+#if PY_MAJOR_VERSION < 3
+ if (PyUnicode_Check(encoding))
+ return PyUnicode_AsEncodedString(encoding, NULL, NULL);
+#endif
+ if (JSON_ASCII_Check(encoding)) {
+ Py_INCREF(encoding);
+ return encoding;
+ }
+ PyErr_SetString(PyExc_TypeError, "encoding must be a string");
+ return NULL;
+}
+
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ /* Initialize Scanner object */
+ PyObject *ctx;
+ static char *kwlist[] = {"context", NULL};
+ PyScannerObject *s;
+ PyObject *encoding;
+
+ assert(PyScanner_Check(self));
+ s = (PyScannerObject *)self;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx))
+ return -1;
+
+ if (s->memo == NULL) {
+ s->memo = PyDict_New();
+ if (s->memo == NULL)
+ goto bail;
+ }
+
+ /* JSON_ASCII_AS_STRING is used on encoding */
+ encoding = PyObject_GetAttrString(ctx, "encoding");
+ s->encoding = JSON_ParseEncoding(encoding);
+ Py_XDECREF(encoding);
+ if (s->encoding == NULL)
+ goto bail;
+
+ /* All of these will fail "gracefully" so we don't need to verify them */
+ s->strict = PyObject_GetAttrString(ctx, "strict");
+ if (s->strict == NULL)
+ goto bail;
+ s->object_hook = PyObject_GetAttrString(ctx, "object_hook");
+ if (s->object_hook == NULL)
+ goto bail;
+ s->pairs_hook = PyObject_GetAttrString(ctx, "object_pairs_hook");
+ if (s->pairs_hook == NULL)
+ goto bail;
+ s->parse_float = PyObject_GetAttrString(ctx, "parse_float");
+ if (s->parse_float == NULL)
+ goto bail;
+ s->parse_int = PyObject_GetAttrString(ctx, "parse_int");
+ if (s->parse_int == NULL)
+ goto bail;
+ s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant");
+ if (s->parse_constant == NULL)
+ goto bail;
+
+ return 0;
+
+bail:
+ Py_CLEAR(s->encoding);
+ Py_CLEAR(s->strict);
+ Py_CLEAR(s->object_hook);
+ Py_CLEAR(s->pairs_hook);
+ Py_CLEAR(s->parse_float);
+ Py_CLEAR(s->parse_int);
+ Py_CLEAR(s->parse_constant);
+ return -1;
+}
+
+PyDoc_STRVAR(scanner_doc, "JSON scanner object");
+
+static
+PyTypeObject PyScannerType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "simplejson._speedups.Scanner", /* tp_name */
+ sizeof(PyScannerObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ scanner_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ scanner_call, /* tp_call */
+ 0, /* tp_str */
+ 0,/* PyObject_GenericGetAttr, */ /* tp_getattro */
+ 0,/* PyObject_GenericSetAttr, */ /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ scanner_doc, /* tp_doc */
+ scanner_traverse, /* tp_traverse */
+ scanner_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ scanner_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ scanner_init, /* tp_init */
+ 0,/* PyType_GenericAlloc, */ /* tp_alloc */
+ scanner_new, /* tp_new */
+ 0,/* PyObject_GC_Del, */ /* tp_free */
+};
+
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ PyEncoderObject *s;
+ s = (PyEncoderObject *)type->tp_alloc(type, 0);
+ if (s != NULL) {
+ s->markers = NULL;
+ s->defaultfn = NULL;
+ s->encoder = NULL;
+ s->encoding = NULL;
+ s->indent = NULL;
+ s->key_separator = NULL;
+ s->item_separator = NULL;
+ s->key_memo = NULL;
+ s->sort_keys = NULL;
+ s->item_sort_key = NULL;
+ s->item_sort_kw = NULL;
+ s->Decimal = NULL;
+ s->max_long_size = NULL;
+ s->min_long_size = NULL;
+ }
+ return (PyObject *)s;
+}
+
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ /* initialize Encoder object */
+ static char *kwlist[] = {
+ "markers",
+ "default",
+ "encoder",
+ "indent",
+ "key_separator",
+ "item_separator",
+ "sort_keys",
+ "skipkeys",
+ "allow_nan",
+ "key_memo",
+ "use_decimal",
+ "namedtuple_as_object",
+ "tuple_as_array",
+ "int_as_string_bitcount",
+ "item_sort_key",
+ "encoding",
+ "for_json",
+ "ignore_nan",
+ "Decimal",
+ NULL};
+
+ PyEncoderObject *s;
+ PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
+ PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo;
+ PyObject *use_decimal, *namedtuple_as_object, *tuple_as_array;
+ PyObject *int_as_string_bitcount, *item_sort_key, *encoding, *for_json;
+ PyObject *ignore_nan, *Decimal;
+
+ assert(PyEncoder_Check(self));
+ s = (PyEncoderObject *)self;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOOOOOO:make_encoder", kwlist,
+ &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator,
+ &sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal,
+ &namedtuple_as_object, &tuple_as_array,
+ &int_as_string_bitcount, &item_sort_key, &encoding, &for_json,
+ &ignore_nan, &Decimal))
+ return -1;
+
+ Py_INCREF(markers);
+ s->markers = markers;
+ Py_INCREF(defaultfn);
+ s->defaultfn = defaultfn;
+ Py_INCREF(encoder);
+ s->encoder = encoder;
+ s->encoding = JSON_ParseEncoding(encoding);
+ if (s->encoding == NULL)
+ return -1;
+ Py_INCREF(indent);
+ s->indent = indent;
+ Py_INCREF(key_separator);
+ s->key_separator = key_separator;
+ Py_INCREF(item_separator);
+ s->item_separator = item_separator;
+ Py_INCREF(skipkeys);
+ s->skipkeys_bool = skipkeys;
+ s->skipkeys = PyObject_IsTrue(skipkeys);
+ Py_INCREF(key_memo);
+ s->key_memo = key_memo;
+ s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
+ s->allow_or_ignore_nan = (
+ (PyObject_IsTrue(ignore_nan) ? JSON_IGNORE_NAN : 0) |
+ (PyObject_IsTrue(allow_nan) ? JSON_ALLOW_NAN : 0));
+ s->use_decimal = PyObject_IsTrue(use_decimal);
+ s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object);
+ s->tuple_as_array = PyObject_IsTrue(tuple_as_array);
+ if (PyInt_Check(int_as_string_bitcount) || PyLong_Check(int_as_string_bitcount)) {
+ static const unsigned int long_long_bitsize = SIZEOF_LONG_LONG * 8;
+ int int_as_string_bitcount_val = PyLong_AsLong(int_as_string_bitcount);
+ if (int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < long_long_bitsize) {
+ s->max_long_size = PyLong_FromUnsignedLongLong(1ULL << int_as_string_bitcount_val);
+ s->min_long_size = PyLong_FromLongLong(-1LL << int_as_string_bitcount_val);
+ if (s->min_long_size == NULL || s->max_long_size == NULL) {
+ return -1;
+ }
+ }
+ else {
+ PyErr_Format(PyExc_TypeError,
+ "int_as_string_bitcount (%d) must be greater than 0 and less than the number of bits of a `long long` type (%u bits)",
+ int_as_string_bitcount_val, long_long_bitsize);
+ return -1;
+ }
+ }
+ else if (int_as_string_bitcount == Py_None) {
+ Py_INCREF(Py_None);
+ s->max_long_size = Py_None;
+ Py_INCREF(Py_None);
+ s->min_long_size = Py_None;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "int_as_string_bitcount must be None or an integer");
+ return -1;
+ }
+ if (item_sort_key != Py_None) {
+ if (!PyCallable_Check(item_sort_key)) {
+ PyErr_SetString(PyExc_TypeError, "item_sort_key must be None or callable");
+ return -1;
+ }
+ }
+ else if (PyObject_IsTrue(sort_keys)) {
+ static PyObject *itemgetter0 = NULL;
+ if (!itemgetter0) {
+ PyObject *operator = PyImport_ImportModule("operator");
+ if (!operator)
+ return -1;
+ itemgetter0 = PyObject_CallMethod(operator, "itemgetter", "i", 0);
+ Py_DECREF(operator);
+ }
+ item_sort_key = itemgetter0;
+ if (!item_sort_key)
+ return -1;
+ }
+ if (item_sort_key == Py_None) {
+ Py_INCREF(Py_None);
+ s->item_sort_kw = Py_None;
+ }
+ else {
+ s->item_sort_kw = PyDict_New();
+ if (s->item_sort_kw == NULL)
+ return -1;
+ if (PyDict_SetItemString(s->item_sort_kw, "key", item_sort_key))
+ return -1;
+ }
+ Py_INCREF(sort_keys);
+ s->sort_keys = sort_keys;
+ Py_INCREF(item_sort_key);
+ s->item_sort_key = item_sort_key;
+ Py_INCREF(Decimal);
+ s->Decimal = Decimal;
+ s->for_json = PyObject_IsTrue(for_json);
+
+ return 0;
+}
+
+static PyObject *
+encoder_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+ /* Python callable interface to encode_listencode_obj */
+ static char *kwlist[] = {"obj", "_current_indent_level", NULL};
+ PyObject *obj;
+ Py_ssize_t indent_level;
+ PyEncoderObject *s;
+ JSON_Accu rval;
+ assert(PyEncoder_Check(self));
+ s = (PyEncoderObject *)self;
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist,
+ &obj, _convertPyInt_AsSsize_t, &indent_level))
+ return NULL;
+ if (JSON_Accu_Init(&rval))
+ return NULL;
+ if (encoder_listencode_obj(s, &rval, obj, indent_level)) {
+ JSON_Accu_Destroy(&rval);
+ return NULL;
+ }
+ return JSON_Accu_FinishAsList(&rval);
+}
+
+static PyObject *
+_encoded_const(PyObject *obj)
+{
+ /* Return the JSON string representation of None, True, False */
+ if (obj == Py_None) {
+ static PyObject *s_null = NULL;
+ if (s_null == NULL) {
+ s_null = JSON_InternFromString("null");
+ }
+ Py_INCREF(s_null);
+ return s_null;
+ }
+ else if (obj == Py_True) {
+ static PyObject *s_true = NULL;
+ if (s_true == NULL) {
+ s_true = JSON_InternFromString("true");
+ }
+ Py_INCREF(s_true);
+ return s_true;
+ }
+ else if (obj == Py_False) {
+ static PyObject *s_false = NULL;
+ if (s_false == NULL) {
+ s_false = JSON_InternFromString("false");
+ }
+ Py_INCREF(s_false);
+ return s_false;
+ }
+ else {
+ PyErr_SetString(PyExc_ValueError, "not a const");
+ return NULL;
+ }
+}
+
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj)
+{
+ /* Return the JSON representation of a PyFloat */
+ double i = PyFloat_AS_DOUBLE(obj);
+ if (!Py_IS_FINITE(i)) {
+ if (!s->allow_or_ignore_nan) {
+ PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
+ return NULL;
+ }
+ if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
+ return _encoded_const(Py_None);
+ }
+ /* JSON_ALLOW_NAN is set */
+ else if (i > 0) {
+ static PyObject *sInfinity = NULL;
+ if (sInfinity == NULL)
+ sInfinity = JSON_InternFromString("Infinity");
+ if (sInfinity)
+ Py_INCREF(sInfinity);
+ return sInfinity;
+ }
+ else if (i < 0) {
+ static PyObject *sNegInfinity = NULL;
+ if (sNegInfinity == NULL)
+ sNegInfinity = JSON_InternFromString("-Infinity");
+ if (sNegInfinity)
+ Py_INCREF(sNegInfinity);
+ return sNegInfinity;
+ }
+ else {
+ static PyObject *sNaN = NULL;
+ if (sNaN == NULL)
+ sNaN = JSON_InternFromString("NaN");
+ if (sNaN)
+ Py_INCREF(sNaN);
+ return sNaN;
+ }
+ }
+ /* Use a better float format here? */
+ return PyObject_Repr(obj);
+}
+
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj)
+{
+ /* Return the JSON representation of a string */
+ if (s->fast_encode)
+ return py_encode_basestring_ascii(NULL, obj);
+ else
+ return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL);
+}
+
+static int
+_steal_accumulate(JSON_Accu *accu, PyObject *stolen)
+{
+ /* Append stolen and then decrement its reference count */
+ int rval = JSON_Accu_Accumulate(accu, stolen);
+ Py_DECREF(stolen);
+ return rval;
+}
+
+static int
+encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ssize_t indent_level)
+{
+ /* Encode Python object obj to a JSON term, rval is a PyList */
+ int rv = -1;
+ do {
+ if (obj == Py_None || obj == Py_True || obj == Py_False) {
+ PyObject *cstr = _encoded_const(obj);
+ if (cstr != NULL)
+ rv = _steal_accumulate(rval, cstr);
+ }
+ else if (PyString_Check(obj) || PyUnicode_Check(obj))
+ {
+ PyObject *encoded = encoder_encode_string(s, obj);
+ if (encoded != NULL)
+ rv = _steal_accumulate(rval, encoded);
+ }
+ else if (PyInt_Check(obj) || PyLong_Check(obj)) {
+ PyObject *encoded = PyObject_Str(obj);
+ if (encoded != NULL) {
+ encoded = maybe_quote_bigint(s, encoded, obj);
+ if (encoded == NULL)
+ break;
+ rv = _steal_accumulate(rval, encoded);
+ }
+ }
+ else if (PyFloat_Check(obj)) {
+ PyObject *encoded = encoder_encode_float(s, obj);
+ if (encoded != NULL)
+ rv = _steal_accumulate(rval, encoded);
+ }
+ else if (s->for_json && _has_for_json_hook(obj)) {
+ PyObject *newobj;
+ if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+ return rv;
+ newobj = PyObject_CallMethod(obj, "for_json", NULL);
+ if (newobj != NULL) {
+ rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+ Py_DECREF(newobj);
+ }
+ Py_LeaveRecursiveCall();
+ }
+ else if (s->namedtuple_as_object && _is_namedtuple(obj)) {
+ PyObject *newobj;
+ if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+ return rv;
+ newobj = PyObject_CallMethod(obj, "_asdict", NULL);
+ if (newobj != NULL) {
+ rv = encoder_listencode_dict(s, rval, newobj, indent_level);
+ Py_DECREF(newobj);
+ }
+ Py_LeaveRecursiveCall();
+ }
+ else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) {
+ if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+ return rv;
+ rv = encoder_listencode_list(s, rval, obj, indent_level);
+ Py_LeaveRecursiveCall();
+ }
+ else if (PyDict_Check(obj)) {
+ if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+ return rv;
+ rv = encoder_listencode_dict(s, rval, obj, indent_level);
+ Py_LeaveRecursiveCall();
+ }
+ else if (s->use_decimal && PyObject_TypeCheck(obj, (PyTypeObject *)s->Decimal)) {
+ PyObject *encoded = PyObject_Str(obj);
+ if (encoded != NULL)
+ rv = _steal_accumulate(rval, encoded);
+ }
+ else {
+ PyObject *ident = NULL;
+ PyObject *newobj;
+ if (s->markers != Py_None) {
+ int has_key;
+ ident = PyLong_FromVoidPtr(obj);
+ if (ident == NULL)
+ break;
+ has_key = PyDict_Contains(s->markers, ident);
+ if (has_key) {
+ if (has_key != -1)
+ PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+ Py_DECREF(ident);
+ break;
+ }
+ if (PyDict_SetItem(s->markers, ident, obj)) {
+ Py_DECREF(ident);
+ break;
+ }
+ }
+ if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+ return rv;
+ newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL);
+ if (newobj == NULL) {
+ Py_XDECREF(ident);
+ Py_LeaveRecursiveCall();
+ break;
+ }
+ rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+ Py_LeaveRecursiveCall();
+ Py_DECREF(newobj);
+ if (rv) {
+ Py_XDECREF(ident);
+ rv = -1;
+ }
+ else if (ident != NULL) {
+ if (PyDict_DelItem(s->markers, ident)) {
+ Py_XDECREF(ident);
+ rv = -1;
+ }
+ Py_XDECREF(ident);
+ }
+ }
+ } while (0);
+ return rv;
+}
+
+static int
+encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_ssize_t indent_level)
+{
+ /* Encode Python dict dct a JSON term */
+ static PyObject *open_dict = NULL;
+ static PyObject *close_dict = NULL;
+ static PyObject *empty_dict = NULL;
+ PyObject *kstr = NULL;
+ PyObject *ident = NULL;
+ PyObject *iter = NULL;
+ PyObject *item = NULL;
+ PyObject *items = NULL;
+ PyObject *encoded = NULL;
+ Py_ssize_t idx;
+
+ if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) {
+ open_dict = JSON_InternFromString("{");
+ close_dict = JSON_InternFromString("}");
+ empty_dict = JSON_InternFromString("{}");
+ if (open_dict == NULL || close_dict == NULL || empty_dict == NULL)
+ return -1;
+ }
+ if (PyDict_Size(dct) == 0)
+ return JSON_Accu_Accumulate(rval, empty_dict);
+
+ if (s->markers != Py_None) {
+ int has_key;
+ ident = PyLong_FromVoidPtr(dct);
+ if (ident == NULL)
+ goto bail;
+ has_key = PyDict_Contains(s->markers, ident);
+ if (has_key) {
+ if (has_key != -1)
+ PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+ goto bail;
+ }
+ if (PyDict_SetItem(s->markers, ident, dct)) {
+ goto bail;
+ }
+ }
+
+ if (JSON_Accu_Accumulate(rval, open_dict))
+ goto bail;
+
+ if (s->indent != Py_None) {
+ /* TODO: DOES NOT RUN */
+ indent_level += 1;
+ /*
+ newline_indent = '\n' + (_indent * _current_indent_level)
+ separator = _item_separator + newline_indent
+ buf += newline_indent
+ */
+ }
+
+ iter = encoder_dict_iteritems(s, dct);
+ if (iter == NULL)
+ goto bail;
+
+ idx = 0;
+ while ((item = PyIter_Next(iter))) {
+ PyObject *encoded, *key, *value;
+ if (!PyTuple_Check(item) || Py_SIZE(item) != 2) {
+ PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
+ goto bail;
+ }
+ key = PyTuple_GET_ITEM(item, 0);
+ if (key == NULL)
+ goto bail;
+ value = PyTuple_GET_ITEM(item, 1);
+ if (value == NULL)
+ goto bail;
+
+ encoded = PyDict_GetItem(s->key_memo, key);
+ if (encoded != NULL) {
+ Py_INCREF(encoded);
+ } else {
+ kstr = encoder_stringify_key(s, key);
+ if (kstr == NULL)
+ goto bail;
+ else if (kstr == Py_None) {
+ /* skipkeys */
+ Py_DECREF(item);
+ Py_DECREF(kstr);
+ continue;
+ }
+ }
+ if (idx) {
+ if (JSON_Accu_Accumulate(rval, s->item_separator))
+ goto bail;
+ }
+ if (encoded == NULL) {
+ encoded = encoder_encode_string(s, kstr);
+ Py_CLEAR(kstr);
+ if (encoded == NULL)
+ goto bail;
+ if (PyDict_SetItem(s->key_memo, key, encoded))
+ goto bail;
+ }
+ if (JSON_Accu_Accumulate(rval, encoded)) {
+ goto bail;
+ }
+ Py_CLEAR(encoded);
+ if (JSON_Accu_Accumulate(rval, s->key_separator))
+ goto bail;
+ if (encoder_listencode_obj(s, rval, value, indent_level))
+ goto bail;
+ Py_CLEAR(item);
+ idx += 1;
+ }
+ Py_CLEAR(iter);
+ if (PyErr_Occurred())
+ goto bail;
+ if (ident != NULL) {
+ if (PyDict_DelItem(s->markers, ident))
+ goto bail;
+ Py_CLEAR(ident);
+ }
+ if (s->indent != Py_None) {
+ /* TODO: DOES NOT RUN */
+ indent_level -= 1;
+ /*
+ yield '\n' + (_indent * _current_indent_level)
+ */
+ }
+ if (JSON_Accu_Accumulate(rval, close_dict))
+ goto bail;
+ return 0;
+
+bail:
+ Py_XDECREF(encoded);
+ Py_XDECREF(items);
+ Py_XDECREF(iter);
+ Py_XDECREF(kstr);
+ Py_XDECREF(ident);
+ return -1;
+}
+
+
+static int
+encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level)
+{
+ /* Encode Python list seq to a JSON term */
+ static PyObject *open_array = NULL;
+ static PyObject *close_array = NULL;
+ static PyObject *empty_array = NULL;
+ PyObject *ident = NULL;
+ PyObject *iter = NULL;
+ PyObject *obj = NULL;
+ int is_true;
+ int i = 0;
+
+ if (open_array == NULL || close_array == NULL || empty_array == NULL) {
+ open_array = JSON_InternFromString("[");
+ close_array = JSON_InternFromString("]");
+ empty_array = JSON_InternFromString("[]");
+ if (open_array == NULL || close_array == NULL || empty_array == NULL)
+ return -1;
+ }
+ ident = NULL;
+ is_true = PyObject_IsTrue(seq);
+ if (is_true == -1)
+ return -1;
+ else if (is_true == 0)
+ return JSON_Accu_Accumulate(rval, empty_array);
+
+ if (s->markers != Py_None) {
+ int has_key;
+ ident = PyLong_FromVoidPtr(seq);
+ if (ident == NULL)
+ goto bail;
+ has_key = PyDict_Contains(s->markers, ident);
+ if (has_key) {
+ if (has_key != -1)
+ PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+ goto bail;
+ }
+ if (PyDict_SetItem(s->markers, ident, seq)) {
+ goto bail;
+ }
+ }
+
+ iter = PyObject_GetIter(seq);
+ if (iter == NULL)
+ goto bail;
+
+ if (JSON_Accu_Accumulate(rval, open_array))
+ goto bail;
+ if (s->indent != Py_None) {
+ /* TODO: DOES NOT RUN */
+ indent_level += 1;
+ /*
+ newline_indent = '\n' + (_indent * _current_indent_level)
+ separator = _item_separator + newline_indent
+ buf += newline_indent
+ */
+ }
+ while ((obj = PyIter_Next(iter))) {
+ if (i) {
+ if (JSON_Accu_Accumulate(rval, s->item_separator))
+ goto bail;
+ }
+ if (encoder_listencode_obj(s, rval, obj, indent_level))
+ goto bail;
+ i++;
+ Py_CLEAR(obj);
+ }
+ Py_CLEAR(iter);
+ if (PyErr_Occurred())
+ goto bail;
+ if (ident != NULL) {
+ if (PyDict_DelItem(s->markers, ident))
+ goto bail;
+ Py_CLEAR(ident);
+ }
+ if (s->indent != Py_None) {
+ /* TODO: DOES NOT RUN */
+ indent_level -= 1;
+ /*
+ yield '\n' + (_indent * _current_indent_level)
+ */
+ }
+ if (JSON_Accu_Accumulate(rval, close_array))
+ goto bail;
+ return 0;
+
+bail:
+ Py_XDECREF(obj);
+ Py_XDECREF(iter);
+ Py_XDECREF(ident);
+ return -1;
+}
+
+static void
+encoder_dealloc(PyObject *self)
+{
+ /* Deallocate Encoder */
+ encoder_clear(self);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static int
+encoder_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ PyEncoderObject *s;
+ assert(PyEncoder_Check(self));
+ s = (PyEncoderObject *)self;
+ Py_VISIT(s->markers);
+ Py_VISIT(s->defaultfn);
+ Py_VISIT(s->encoder);
+ Py_VISIT(s->encoding);
+ Py_VISIT(s->indent);
+ Py_VISIT(s->key_separator);
+ Py_VISIT(s->item_separator);
+ Py_VISIT(s->key_memo);
+ Py_VISIT(s->sort_keys);
+ Py_VISIT(s->item_sort_kw);
+ Py_VISIT(s->item_sort_key);
+ Py_VISIT(s->max_long_size);
+ Py_VISIT(s->min_long_size);
+ Py_VISIT(s->Decimal);
+ return 0;
+}
+
+static int
+encoder_clear(PyObject *self)
+{
+ /* Deallocate Encoder */
+ PyEncoderObject *s;
+ assert(PyEncoder_Check(self));
+ s = (PyEncoderObject *)self;
+ Py_CLEAR(s->markers);
+ Py_CLEAR(s->defaultfn);
+ Py_CLEAR(s->encoder);
+ Py_CLEAR(s->encoding);
+ Py_CLEAR(s->indent);
+ Py_CLEAR(s->key_separator);
+ Py_CLEAR(s->item_separator);
+ Py_CLEAR(s->key_memo);
+ Py_CLEAR(s->skipkeys_bool);
+ Py_CLEAR(s->sort_keys);
+ Py_CLEAR(s->item_sort_kw);
+ Py_CLEAR(s->item_sort_key);
+ Py_CLEAR(s->max_long_size);
+ Py_CLEAR(s->min_long_size);
+ Py_CLEAR(s->Decimal);
+ return 0;
+}
+
+PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable");
+
+static
+PyTypeObject PyEncoderType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "simplejson._speedups.Encoder", /* tp_name */
+ sizeof(PyEncoderObject), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ encoder_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ encoder_call, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ encoder_doc, /* tp_doc */
+ encoder_traverse, /* tp_traverse */
+ encoder_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ encoder_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ encoder_init, /* tp_init */
+ 0, /* tp_alloc */
+ encoder_new, /* tp_new */
+ 0, /* tp_free */
+};
+
+static PyMethodDef speedups_methods[] = {
+ {"encode_basestring_ascii",
+ (PyCFunction)py_encode_basestring_ascii,
+ METH_O,
+ pydoc_encode_basestring_ascii},
+ {"scanstring",
+ (PyCFunction)py_scanstring,
+ METH_VARARGS,
+ pydoc_scanstring},
+ {NULL, NULL, 0, NULL}
+};
+
+PyDoc_STRVAR(module_doc,
+"simplejson speedups\n");
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_speedups", /* m_name */
+ module_doc, /* m_doc */
+ -1, /* m_size */
+ speedups_methods, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear*/
+ NULL, /* m_free */
+};
+#endif
+
+static PyObject *
+moduleinit(void)
+{
+ PyObject *m;
+ PyScannerType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&PyScannerType) < 0)
+ return NULL;
+ PyEncoderType.tp_new = PyType_GenericNew;
+ if (PyType_Ready(&PyEncoderType) < 0)
+ return NULL;
+
+#if PY_MAJOR_VERSION >= 3
+ m = PyModule_Create(&moduledef);
+#else
+ m = Py_InitModule3("_speedups", speedups_methods, module_doc);
+#endif
+ Py_INCREF((PyObject*)&PyScannerType);
+ PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType);
+ Py_INCREF((PyObject*)&PyEncoderType);
+ PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType);
+ return m;
+}
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC
+PyInit__speedups(void)
+{
+ return moduleinit();
+}
+#else
+void
+init_speedups(void)
+{
+ moduleinit();
+}
+#endif
diff --git a/pyload/lib/simplejson/compat.py b/pyload/lib/simplejson/compat.py
new file mode 100644
index 000000000..a0af4a1cb
--- /dev/null
+++ b/pyload/lib/simplejson/compat.py
@@ -0,0 +1,46 @@
+"""Python 3 compatibility shims
+"""
+import sys
+if sys.version_info[0] < 3:
+ PY3 = False
+ def b(s):
+ return s
+ def u(s):
+ return unicode(s, 'unicode_escape')
+ import cStringIO as StringIO
+ StringIO = BytesIO = StringIO.StringIO
+ text_type = unicode
+ binary_type = str
+ string_types = (basestring,)
+ integer_types = (int, long)
+ unichr = unichr
+ reload_module = reload
+ def fromhex(s):
+ return s.decode('hex')
+
+else:
+ PY3 = True
+ if sys.version_info[:2] >= (3, 4):
+ from importlib import reload as reload_module
+ else:
+ from imp import reload as reload_module
+ import codecs
+ def b(s):
+ return codecs.latin_1_encode(s)[0]
+ def u(s):
+ return s
+ import io
+ StringIO = io.StringIO
+ BytesIO = io.BytesIO
+ text_type = str
+ binary_type = bytes
+ string_types = (str,)
+ integer_types = (int,)
+
+ def unichr(s):
+ return u(chr(s))
+
+ def fromhex(s):
+ return bytes.fromhex(s)
+
+long_type = integer_types[-1]
diff --git a/pyload/lib/simplejson/decoder.py b/pyload/lib/simplejson/decoder.py
new file mode 100644
index 000000000..545e65877
--- /dev/null
+++ b/pyload/lib/simplejson/decoder.py
@@ -0,0 +1,400 @@
+"""Implementation of JSONDecoder
+"""
+from __future__ import absolute_import
+import re
+import sys
+import struct
+from .compat import fromhex, b, u, text_type, binary_type, PY3, unichr
+from .scanner import make_scanner, JSONDecodeError
+
+def _import_c_scanstring():
+ try:
+ from ._speedups import scanstring
+ return scanstring
+ except ImportError:
+ return None
+c_scanstring = _import_c_scanstring()
+
+# NOTE (3.1.0): JSONDecodeError may still be imported from this module for
+# compatibility, but it was never in the __all__
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+ _BYTES = fromhex('7FF80000000000007FF0000000000000')
+ # The struct module in Python 2.4 would get frexp() out of range here
+ # when an endian is specified in the format string. Fixed in Python 2.5+
+ if sys.byteorder != 'big':
+ _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+ nan, inf = struct.unpack('dd', _BYTES)
+ return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+_CONSTANTS = {
+ '-Infinity': NegInf,
+ 'Infinity': PosInf,
+ 'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+ '"': u('"'), '\\': u('\u005c'), '/': u('/'),
+ 'b': u('\b'), 'f': u('\f'), 'n': u('\n'), 'r': u('\r'), 't': u('\t'),
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True,
+ _b=BACKSLASH, _m=STRINGCHUNK.match, _join=u('').join,
+ _PY3=PY3, _maxunicode=sys.maxunicode):
+ """Scan the string s for a JSON string. End is the index of the
+ character in s after the quote that started the JSON string.
+ Unescapes all valid JSON string escape sequences and raises ValueError
+ on attempt to decode an invalid string. If strict is False then literal
+ control characters are allowed in the string.
+
+ Returns a tuple of the decoded string and the index of the character in s
+ after the end quote."""
+ if encoding is None:
+ encoding = DEFAULT_ENCODING
+ chunks = []
+ _append = chunks.append
+ begin = end - 1
+ while 1:
+ chunk = _m(s, end)
+ if chunk is None:
+ raise JSONDecodeError(
+ "Unterminated string starting at", s, begin)
+ end = chunk.end()
+ content, terminator = chunk.groups()
+ # Content is contains zero or more unescaped string characters
+ if content:
+ if not _PY3 and not isinstance(content, text_type):
+ content = text_type(content, encoding)
+ _append(content)
+ # Terminator is the end of string, a literal control character,
+ # or a backslash denoting that an escape sequence follows
+ if terminator == '"':
+ break
+ elif terminator != '\\':
+ if strict:
+ msg = "Invalid control character %r at"
+ raise JSONDecodeError(msg, s, end)
+ else:
+ _append(terminator)
+ continue
+ try:
+ esc = s[end]
+ except IndexError:
+ raise JSONDecodeError(
+ "Unterminated string starting at", s, begin)
+ # If not a unicode escape sequence, must be in the lookup table
+ if esc != 'u':
+ try:
+ char = _b[esc]
+ except KeyError:
+ msg = "Invalid \\X escape sequence %r"
+ raise JSONDecodeError(msg, s, end)
+ end += 1
+ else:
+ # Unicode escape sequence
+ msg = "Invalid \\uXXXX escape sequence"
+ esc = s[end + 1:end + 5]
+ escX = esc[1:2]
+ if len(esc) != 4 or escX == 'x' or escX == 'X':
+ raise JSONDecodeError(msg, s, end - 1)
+ try:
+ uni = int(esc, 16)
+ except ValueError:
+ raise JSONDecodeError(msg, s, end - 1)
+ end += 5
+ # Check for surrogate pair on UCS-4 systems
+ # Note that this will join high/low surrogate pairs
+ # but will also pass unpaired surrogates through
+ if (_maxunicode > 65535 and
+ uni & 0xfc00 == 0xd800 and
+ s[end:end + 2] == '\\u'):
+ esc2 = s[end + 2:end + 6]
+ escX = esc2[1:2]
+ if len(esc2) == 4 and not (escX == 'x' or escX == 'X'):
+ try:
+ uni2 = int(esc2, 16)
+ except ValueError:
+ raise JSONDecodeError(msg, s, end)
+ if uni2 & 0xfc00 == 0xdc00:
+ uni = 0x10000 + (((uni - 0xd800) << 10) |
+ (uni2 - 0xdc00))
+ end += 6
+ char = unichr(uni)
+ # Append the unescaped character
+ _append(char)
+ return _join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject(state, encoding, strict, scan_once, object_hook,
+ object_pairs_hook, memo=None,
+ _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ (s, end) = state
+ # Backwards compatibility
+ if memo is None:
+ memo = {}
+ memo_get = memo.setdefault
+ pairs = []
+ # Use a slice to prevent IndexError from being raised, the following
+ # check will raise a more specific ValueError if the string is empty
+ nextchar = s[end:end + 1]
+ # Normally we expect nextchar == '"'
+ if nextchar != '"':
+ if nextchar in _ws:
+ end = _w(s, end).end()
+ nextchar = s[end:end + 1]
+ # Trivial empty object
+ if nextchar == '}':
+ if object_pairs_hook is not None:
+ result = object_pairs_hook(pairs)
+ return result, end + 1
+ pairs = {}
+ if object_hook is not None:
+ pairs = object_hook(pairs)
+ return pairs, end + 1
+ elif nextchar != '"':
+ raise JSONDecodeError(
+ "Expecting property name enclosed in double quotes",
+ s, end)
+ end += 1
+ while True:
+ key, end = scanstring(s, end, encoding, strict)
+ key = memo_get(key, key)
+
+ # To skip some function call overhead we optimize the fast paths where
+ # the JSON key separator is ": " or just ":".
+ if s[end:end + 1] != ':':
+ end = _w(s, end).end()
+ if s[end:end + 1] != ':':
+ raise JSONDecodeError("Expecting ':' delimiter", s, end)
+
+ end += 1
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ value, end = scan_once(s, end)
+ pairs.append((key, value))
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+ end += 1
+
+ if nextchar == '}':
+ break
+ elif nextchar != ',':
+ raise JSONDecodeError("Expecting ',' delimiter or '}'", s, end - 1)
+
+ try:
+ nextchar = s[end]
+ if nextchar in _ws:
+ end += 1
+ nextchar = s[end]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end]
+ except IndexError:
+ nextchar = ''
+
+ end += 1
+ if nextchar != '"':
+ raise JSONDecodeError(
+ "Expecting property name enclosed in double quotes",
+ s, end - 1)
+
+ if object_pairs_hook is not None:
+ result = object_pairs_hook(pairs)
+ return result, end
+ pairs = dict(pairs)
+ if object_hook is not None:
+ pairs = object_hook(pairs)
+ return pairs, end
+
+def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ (s, end) = state
+ values = []
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ # Look-ahead for trivial empty array
+ if nextchar == ']':
+ return values, end + 1
+ elif nextchar == '':
+ raise JSONDecodeError("Expecting value or ']'", s, end)
+ _append = values.append
+ while True:
+ value, end = scan_once(s, end)
+ _append(value)
+ nextchar = s[end:end + 1]
+ if nextchar in _ws:
+ end = _w(s, end + 1).end()
+ nextchar = s[end:end + 1]
+ end += 1
+ if nextchar == ']':
+ break
+ elif nextchar != ',':
+ raise JSONDecodeError("Expecting ',' delimiter or ']'", s, end - 1)
+
+ try:
+ if s[end] in _ws:
+ end += 1
+ if s[end] in _ws:
+ end = _w(s, end + 1).end()
+ except IndexError:
+ pass
+
+ return values, end
+
+class JSONDecoder(object):
+ """Simple JSON <http://json.org> decoder
+
+ Performs the following translations in decoding by default:
+
+ +---------------+-------------------+
+ | JSON | Python |
+ +===============+===================+
+ | object | dict |
+ +---------------+-------------------+
+ | array | list |
+ +---------------+-------------------+
+ | string | str, unicode |
+ +---------------+-------------------+
+ | number (int) | int, long |
+ +---------------+-------------------+
+ | number (real) | float |
+ +---------------+-------------------+
+ | true | True |
+ +---------------+-------------------+
+ | false | False |
+ +---------------+-------------------+
+ | null | None |
+ +---------------+-------------------+
+
+ It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+ their corresponding ``float`` values, which is outside the JSON spec.
+
+ """
+
+ def __init__(self, encoding=None, object_hook=None, parse_float=None,
+ parse_int=None, parse_constant=None, strict=True,
+ object_pairs_hook=None):
+ """
+ *encoding* determines the encoding used to interpret any
+ :class:`str` objects decoded by this instance (``'utf-8'`` by
+ default). It has no effect when decoding :class:`unicode` objects.
+
+ Note that currently only encodings that are a superset of ASCII work,
+ strings of other encodings should be passed in as :class:`unicode`.
+
+ *object_hook*, if specified, will be called with the result of every
+ JSON object decoded and its return value will be used in place of the
+ given :class:`dict`. This can be used to provide custom
+ deserializations (e.g. to support JSON-RPC class hinting).
+
+ *object_pairs_hook* is an optional function that will be called with
+ the result of any object literal decode with an ordered list of pairs.
+ The return value of *object_pairs_hook* will be used instead of the
+ :class:`dict`. This feature can be used to implement custom decoders
+ that rely on the order that the key and value pairs are decoded (for
+ example, :func:`collections.OrderedDict` will remember the order of
+ insertion). If *object_hook* is also defined, the *object_pairs_hook*
+ takes priority.
+
+ *parse_float*, if specified, will be called with the string of every
+ JSON float to be decoded. By default, this is equivalent to
+ ``float(num_str)``. This can be used to use another datatype or parser
+ for JSON floats (e.g. :class:`decimal.Decimal`).
+
+ *parse_int*, if specified, will be called with the string of every
+ JSON int to be decoded. By default, this is equivalent to
+ ``int(num_str)``. This can be used to use another datatype or parser
+ for JSON integers (e.g. :class:`float`).
+
+ *parse_constant*, if specified, will be called with one of the
+ following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``. This
+ can be used to raise an exception if invalid JSON numbers are
+ encountered.
+
+ *strict* controls the parser's behavior when it encounters an
+ invalid control character in a string. The default setting of
+ ``True`` means that unescaped control characters are parse errors, if
+ ``False`` then control characters will be allowed in strings.
+
+ """
+ if encoding is None:
+ encoding = DEFAULT_ENCODING
+ self.encoding = encoding
+ self.object_hook = object_hook
+ self.object_pairs_hook = object_pairs_hook
+ self.parse_float = parse_float or float
+ self.parse_int = parse_int or int
+ self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+ self.strict = strict
+ self.parse_object = JSONObject
+ self.parse_array = JSONArray
+ self.parse_string = scanstring
+ self.memo = {}
+ self.scan_once = make_scanner(self)
+
+ def decode(self, s, _w=WHITESPACE.match, _PY3=PY3):
+ """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+ instance containing a JSON document)
+
+ """
+ if _PY3 and isinstance(s, binary_type):
+ s = s.decode(self.encoding)
+ obj, end = self.raw_decode(s)
+ end = _w(s, end).end()
+ if end != len(s):
+ raise JSONDecodeError("Extra data", s, end, len(s))
+ return obj
+
+ def raw_decode(self, s, idx=0, _w=WHITESPACE.match, _PY3=PY3):
+ """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
+ beginning with a JSON document) and return a 2-tuple of the Python
+ representation and the index in ``s`` where the document ended.
+ Optionally, ``idx`` can be used to specify an offset in ``s`` where
+ the JSON document begins.
+
+ This can be used to decode a JSON document from a string that may
+ have extraneous data at the end.
+
+ """
+ if idx < 0:
+ # Ensure that raw_decode bails on negative indexes, the regex
+ # would otherwise mask this behavior. #98
+ raise JSONDecodeError('Expecting value', s, idx)
+ if _PY3 and not isinstance(s, text_type):
+ raise TypeError("Input string must be text, not bytes")
+ # strip UTF-8 bom
+ if len(s) > idx:
+ ord0 = ord(s[idx])
+ if ord0 == 0xfeff:
+ idx += 1
+ elif ord0 == 0xef and s[idx:idx + 3] == '\xef\xbb\xbf':
+ idx += 3
+ return self.scan_once(s, idx=_w(s, idx).end())
diff --git a/pyload/lib/simplejson/encoder.py b/pyload/lib/simplejson/encoder.py
new file mode 100644
index 000000000..db18244ec
--- /dev/null
+++ b/pyload/lib/simplejson/encoder.py
@@ -0,0 +1,648 @@
+"""Implementation of JSONEncoder
+"""
+from __future__ import absolute_import
+import re
+from operator import itemgetter
+from decimal import Decimal
+from .compat import u, unichr, binary_type, string_types, integer_types, PY3
+def _import_speedups():
+ try:
+ from . import _speedups
+ return _speedups.encode_basestring_ascii, _speedups.make_encoder
+ except ImportError:
+ return None, None
+c_encode_basestring_ascii, c_make_encoder = _import_speedups()
+
+from simplejson.decoder import PosInf
+
+#ESCAPE = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\u2028\u2029]')
+# This is required because u() will mangle the string and ur'' isn't valid
+# python3 syntax
+ESCAPE = re.compile(u'[\\x00-\\x1f\\\\"\\b\\f\\n\\r\\t\u2028\u2029]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+ '\\': '\\\\',
+ '"': '\\"',
+ '\b': '\\b',
+ '\f': '\\f',
+ '\n': '\\n',
+ '\r': '\\r',
+ '\t': '\\t',
+}
+for i in range(0x20):
+ #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+ ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+for i in [0x2028, 0x2029]:
+ ESCAPE_DCT.setdefault(unichr(i), '\\u%04x' % (i,))
+
+FLOAT_REPR = repr
+
+def encode_basestring(s, _PY3=PY3, _q=u('"')):
+ """Return a JSON representation of a Python string
+
+ """
+ if _PY3:
+ if isinstance(s, binary_type):
+ s = s.decode('utf-8')
+ else:
+ if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+ s = s.decode('utf-8')
+ def replace(match):
+ return ESCAPE_DCT[match.group(0)]
+ return _q + ESCAPE.sub(replace, s) + _q
+
+
+def py_encode_basestring_ascii(s, _PY3=PY3):
+ """Return an ASCII-only JSON representation of a Python string
+
+ """
+ if _PY3:
+ if isinstance(s, binary_type):
+ s = s.decode('utf-8')
+ else:
+ if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+ s = s.decode('utf-8')
+ def replace(match):
+ s = match.group(0)
+ try:
+ return ESCAPE_DCT[s]
+ except KeyError:
+ n = ord(s)
+ if n < 0x10000:
+ #return '\\u{0:04x}'.format(n)
+ return '\\u%04x' % (n,)
+ else:
+ # surrogate pair
+ n -= 0x10000
+ s1 = 0xd800 | ((n >> 10) & 0x3ff)
+ s2 = 0xdc00 | (n & 0x3ff)
+ #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+ return '\\u%04x\\u%04x' % (s1, s2)
+ return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = (
+ c_encode_basestring_ascii or py_encode_basestring_ascii)
+
+class JSONEncoder(object):
+ """Extensible JSON <http://json.org> encoder for Python data structures.
+
+ Supports the following objects and types by default:
+
+ +-------------------+---------------+
+ | Python | JSON |
+ +===================+===============+
+ | dict, namedtuple | object |
+ +-------------------+---------------+
+ | list, tuple | array |
+ +-------------------+---------------+
+ | str, unicode | string |
+ +-------------------+---------------+
+ | int, long, float | number |
+ +-------------------+---------------+
+ | True | true |
+ +-------------------+---------------+
+ | False | false |
+ +-------------------+---------------+
+ | None | null |
+ +-------------------+---------------+
+
+ To extend this to recognize other objects, subclass and implement a
+ ``.default()`` method with another method that returns a serializable
+ object for ``o`` if possible, otherwise it should call the superclass
+ implementation (to raise ``TypeError``).
+
+ """
+ item_separator = ', '
+ key_separator = ': '
+
+ def __init__(self, skipkeys=False, ensure_ascii=True,
+ check_circular=True, allow_nan=True, sort_keys=False,
+ indent=None, separators=None, encoding='utf-8', default=None,
+ use_decimal=True, namedtuple_as_object=True,
+ tuple_as_array=True, bigint_as_string=False,
+ item_sort_key=None, for_json=False, ignore_nan=False,
+ int_as_string_bitcount=None):
+ """Constructor for JSONEncoder, with sensible defaults.
+
+ If skipkeys is false, then it is a TypeError to attempt
+ encoding of keys that are not str, int, long, float or None. If
+ skipkeys is True, such items are simply skipped.
+
+ If ensure_ascii is true, the output is guaranteed to be str
+ objects with all incoming unicode characters escaped. If
+ ensure_ascii is false, the output will be unicode object.
+
+ If check_circular is true, then lists, dicts, and custom encoded
+ objects will be checked for circular references during encoding to
+ prevent an infinite recursion (which would cause an OverflowError).
+ Otherwise, no such check takes place.
+
+ If allow_nan is true, then NaN, Infinity, and -Infinity will be
+ encoded as such. This behavior is not JSON specification compliant,
+ but is consistent with most JavaScript based encoders and decoders.
+ Otherwise, it will be a ValueError to encode such floats.
+
+ If sort_keys is true, then the output of dictionaries will be
+ sorted by key; this is useful for regression tests to ensure
+ that JSON serializations can be compared on a day-to-day basis.
+
+ If indent is a string, then JSON array elements and object members
+ will be pretty-printed with a newline followed by that string repeated
+ for each level of nesting. ``None`` (the default) selects the most compact
+ representation without any newlines. For backwards compatibility with
+ versions of simplejson earlier than 2.1.0, an integer is also accepted
+ and is converted to a string with that many spaces.
+
+ If specified, separators should be an (item_separator, key_separator)
+ tuple. The default is (', ', ': ') if *indent* is ``None`` and
+ (',', ': ') otherwise. To get the most compact JSON representation,
+ you should specify (',', ':') to eliminate whitespace.
+
+ If specified, default is a function that gets called for objects
+ that can't otherwise be serialized. It should return a JSON encodable
+ version of the object or raise a ``TypeError``.
+
+ If encoding is not None, then all input strings will be
+ transformed into unicode using that encoding prior to JSON-encoding.
+ The default is UTF-8.
+
+ If use_decimal is true (not the default), ``decimal.Decimal`` will
+ be supported directly by the encoder. For the inverse, decode JSON
+ with ``parse_float=decimal.Decimal``.
+
+ If namedtuple_as_object is true (the default), objects with
+ ``_asdict()`` methods will be encoded as JSON objects.
+
+ If tuple_as_array is true (the default), tuple (and subclasses) will
+ be encoded as JSON arrays.
+
+ If bigint_as_string is true (not the default), ints 2**53 and higher
+ or lower than -2**53 will be encoded as strings. This is to avoid the
+ rounding that happens in Javascript otherwise.
+
+ If int_as_string_bitcount is a positive number (n), then int of size
+ greater than or equal to 2**n or lower than or equal to -2**n will be
+ encoded as strings.
+
+ If specified, item_sort_key is a callable used to sort the items in
+ each dictionary. This is useful if you want to sort items other than
+ in alphabetical order by key.
+
+ If for_json is true (not the default), objects with a ``for_json()``
+ method will use the return value of that method for encoding as JSON
+ instead of the object.
+
+ If *ignore_nan* is true (default: ``False``), then out of range
+ :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized
+ as ``null`` in compliance with the ECMA-262 specification. If true,
+ this will override *allow_nan*.
+
+ """
+
+ self.skipkeys = skipkeys
+ self.ensure_ascii = ensure_ascii
+ self.check_circular = check_circular
+ self.allow_nan = allow_nan
+ self.sort_keys = sort_keys
+ self.use_decimal = use_decimal
+ self.namedtuple_as_object = namedtuple_as_object
+ self.tuple_as_array = tuple_as_array
+ self.bigint_as_string = bigint_as_string
+ self.item_sort_key = item_sort_key
+ self.for_json = for_json
+ self.ignore_nan = ignore_nan
+ self.int_as_string_bitcount = int_as_string_bitcount
+ if indent is not None and not isinstance(indent, string_types):
+ indent = indent * ' '
+ self.indent = indent
+ if separators is not None:
+ self.item_separator, self.key_separator = separators
+ elif indent is not None:
+ self.item_separator = ','
+ if default is not None:
+ self.default = default
+ self.encoding = encoding
+
+ def default(self, o):
+ """Implement this method in a subclass such that it returns
+ a serializable object for ``o``, or calls the base implementation
+ (to raise a ``TypeError``).
+
+ For example, to support arbitrary iterators, you could
+ implement default like this::
+
+ def default(self, o):
+ try:
+ iterable = iter(o)
+ except TypeError:
+ pass
+ else:
+ return list(iterable)
+ return JSONEncoder.default(self, o)
+
+ """
+ raise TypeError(repr(o) + " is not JSON serializable")
+
+ def encode(self, o):
+ """Return a JSON string representation of a Python data structure.
+
+ >>> from simplejson import JSONEncoder
+ >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+ '{"foo": ["bar", "baz"]}'
+
+ """
+ # This is for extremely simple cases and benchmarks.
+ if isinstance(o, binary_type):
+ _encoding = self.encoding
+ if (_encoding is not None and not (_encoding == 'utf-8')):
+ o = o.decode(_encoding)
+ if isinstance(o, string_types):
+ if self.ensure_ascii:
+ return encode_basestring_ascii(o)
+ else:
+ return encode_basestring(o)
+ # This doesn't pass the iterator directly to ''.join() because the
+ # exceptions aren't as detailed. The list call should be roughly
+ # equivalent to the PySequence_Fast that ''.join() would do.
+ chunks = self.iterencode(o, _one_shot=True)
+ if not isinstance(chunks, (list, tuple)):
+ chunks = list(chunks)
+ if self.ensure_ascii:
+ return ''.join(chunks)
+ else:
+ return u''.join(chunks)
+
+ def iterencode(self, o, _one_shot=False):
+ """Encode the given object and yield each string
+ representation as available.
+
+ For example::
+
+ for chunk in JSONEncoder().iterencode(bigobject):
+ mysocket.write(chunk)
+
+ """
+ if self.check_circular:
+ markers = {}
+ else:
+ markers = None
+ if self.ensure_ascii:
+ _encoder = encode_basestring_ascii
+ else:
+ _encoder = encode_basestring
+ if self.encoding != 'utf-8':
+ def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+ if isinstance(o, binary_type):
+ o = o.decode(_encoding)
+ return _orig_encoder(o)
+
+ def floatstr(o, allow_nan=self.allow_nan, ignore_nan=self.ignore_nan,
+ _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
+ # Check for specials. Note that this type of test is processor
+ # and/or platform-specific, so do tests which don't depend on
+ # the internals.
+
+ if o != o:
+ text = 'NaN'
+ elif o == _inf:
+ text = 'Infinity'
+ elif o == _neginf:
+ text = '-Infinity'
+ else:
+ return _repr(o)
+
+ if ignore_nan:
+ text = 'null'
+ elif not allow_nan:
+ raise ValueError(
+ "Out of range float values are not JSON compliant: " +
+ repr(o))
+
+ return text
+
+ key_memo = {}
+ int_as_string_bitcount = (
+ 53 if self.bigint_as_string else self.int_as_string_bitcount)
+ if (_one_shot and c_make_encoder is not None
+ and self.indent is None):
+ _iterencode = c_make_encoder(
+ markers, self.default, _encoder, self.indent,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
+ self.namedtuple_as_object, self.tuple_as_array,
+ int_as_string_bitcount,
+ self.item_sort_key, self.encoding, self.for_json,
+ self.ignore_nan, Decimal)
+ else:
+ _iterencode = _make_iterencode(
+ markers, self.default, _encoder, self.indent, floatstr,
+ self.key_separator, self.item_separator, self.sort_keys,
+ self.skipkeys, _one_shot, self.use_decimal,
+ self.namedtuple_as_object, self.tuple_as_array,
+ int_as_string_bitcount,
+ self.item_sort_key, self.encoding, self.for_json,
+ Decimal=Decimal)
+ try:
+ return _iterencode(o, 0)
+ finally:
+ key_memo.clear()
+
+
+class JSONEncoderForHTML(JSONEncoder):
+ """An encoder that produces JSON safe to embed in HTML.
+
+ To embed JSON content in, say, a script tag on a web page, the
+ characters &, < and > should be escaped. They cannot be escaped
+ with the usual entities (e.g. &amp;) because they are not expanded
+ within <script> tags.
+ """
+
+ def encode(self, o):
+ # Override JSONEncoder.encode because it has hacks for
+ # performance that make things more complicated.
+ chunks = self.iterencode(o, True)
+ if self.ensure_ascii:
+ return ''.join(chunks)
+ else:
+ return u''.join(chunks)
+
+ def iterencode(self, o, _one_shot=False):
+ chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
+ for chunk in chunks:
+ chunk = chunk.replace('&', '\\u0026')
+ chunk = chunk.replace('<', '\\u003c')
+ chunk = chunk.replace('>', '\\u003e')
+ yield chunk
+
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
+ _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+ _use_decimal, _namedtuple_as_object, _tuple_as_array,
+ _int_as_string_bitcount, _item_sort_key,
+ _encoding,_for_json,
+ ## HACK: hand-optimized bytecode; turn globals into locals
+ _PY3=PY3,
+ ValueError=ValueError,
+ string_types=string_types,
+ Decimal=Decimal,
+ dict=dict,
+ float=float,
+ id=id,
+ integer_types=integer_types,
+ isinstance=isinstance,
+ list=list,
+ str=str,
+ tuple=tuple,
+ ):
+ if _item_sort_key and not callable(_item_sort_key):
+ raise TypeError("item_sort_key must be None or callable")
+ elif _sort_keys and not _item_sort_key:
+ _item_sort_key = itemgetter(0)
+
+ if (_int_as_string_bitcount is not None and
+ (_int_as_string_bitcount <= 0 or
+ not isinstance(_int_as_string_bitcount, integer_types))):
+ raise TypeError("int_as_string_bitcount must be a positive integer")
+
+ def _encode_int(value):
+ skip_quoting = (
+ _int_as_string_bitcount is None
+ or
+ _int_as_string_bitcount < 1
+ )
+ if (
+ skip_quoting or
+ (-1 << _int_as_string_bitcount)
+ < value <
+ (1 << _int_as_string_bitcount)
+ ):
+ return str(value)
+ return '"' + str(value) + '"'
+
+ def _iterencode_list(lst, _current_indent_level):
+ if not lst:
+ yield '[]'
+ return
+ if markers is not None:
+ markerid = id(lst)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = lst
+ buf = '['
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (_indent * _current_indent_level)
+ separator = _item_separator + newline_indent
+ buf += newline_indent
+ else:
+ newline_indent = None
+ separator = _item_separator
+ first = True
+ for value in lst:
+ if first:
+ first = False
+ else:
+ buf = separator
+ if (isinstance(value, string_types) or
+ (_PY3 and isinstance(value, binary_type))):
+ yield buf + _encoder(value)
+ elif value is None:
+ yield buf + 'null'
+ elif value is True:
+ yield buf + 'true'
+ elif value is False:
+ yield buf + 'false'
+ elif isinstance(value, integer_types):
+ yield buf + _encode_int(value)
+ elif isinstance(value, float):
+ yield buf + _floatstr(value)
+ elif _use_decimal and isinstance(value, Decimal):
+ yield buf + str(value)
+ else:
+ yield buf
+ for_json = _for_json and getattr(value, 'for_json', None)
+ if for_json and callable(for_json):
+ chunks = _iterencode(for_json(), _current_indent_level)
+ elif isinstance(value, list):
+ chunks = _iterencode_list(value, _current_indent_level)
+ else:
+ _asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
+ if _asdict and callable(_asdict):
+ chunks = _iterencode_dict(_asdict(),
+ _current_indent_level)
+ elif _tuple_as_array and isinstance(value, tuple):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (_indent * _current_indent_level)
+ yield ']'
+ if markers is not None:
+ del markers[markerid]
+
+ def _stringify_key(key):
+ if isinstance(key, string_types): # pragma: no cover
+ pass
+ elif isinstance(key, binary_type):
+ key = key.decode(_encoding)
+ elif isinstance(key, float):
+ key = _floatstr(key)
+ elif key is True:
+ key = 'true'
+ elif key is False:
+ key = 'false'
+ elif key is None:
+ key = 'null'
+ elif isinstance(key, integer_types):
+ key = str(key)
+ elif _use_decimal and isinstance(key, Decimal):
+ key = str(key)
+ elif _skipkeys:
+ key = None
+ else:
+ raise TypeError("key " + repr(key) + " is not a string")
+ return key
+
+ def _iterencode_dict(dct, _current_indent_level):
+ if not dct:
+ yield '{}'
+ return
+ if markers is not None:
+ markerid = id(dct)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = dct
+ yield '{'
+ if _indent is not None:
+ _current_indent_level += 1
+ newline_indent = '\n' + (_indent * _current_indent_level)
+ item_separator = _item_separator + newline_indent
+ yield newline_indent
+ else:
+ newline_indent = None
+ item_separator = _item_separator
+ first = True
+ if _PY3:
+ iteritems = dct.items()
+ else:
+ iteritems = dct.iteritems()
+ if _item_sort_key:
+ items = []
+ for k, v in dct.items():
+ if not isinstance(k, string_types):
+ k = _stringify_key(k)
+ if k is None:
+ continue
+ items.append((k, v))
+ items.sort(key=_item_sort_key)
+ else:
+ items = iteritems
+ for key, value in items:
+ if not (_item_sort_key or isinstance(key, string_types)):
+ key = _stringify_key(key)
+ if key is None:
+ # _skipkeys must be True
+ continue
+ if first:
+ first = False
+ else:
+ yield item_separator
+ yield _encoder(key)
+ yield _key_separator
+ if (isinstance(value, string_types) or
+ (_PY3 and isinstance(value, binary_type))):
+ yield _encoder(value)
+ elif value is None:
+ yield 'null'
+ elif value is True:
+ yield 'true'
+ elif value is False:
+ yield 'false'
+ elif isinstance(value, integer_types):
+ yield _encode_int(value)
+ elif isinstance(value, float):
+ yield _floatstr(value)
+ elif _use_decimal and isinstance(value, Decimal):
+ yield str(value)
+ else:
+ for_json = _for_json and getattr(value, 'for_json', None)
+ if for_json and callable(for_json):
+ chunks = _iterencode(for_json(), _current_indent_level)
+ elif isinstance(value, list):
+ chunks = _iterencode_list(value, _current_indent_level)
+ else:
+ _asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
+ if _asdict and callable(_asdict):
+ chunks = _iterencode_dict(_asdict(),
+ _current_indent_level)
+ elif _tuple_as_array and isinstance(value, tuple):
+ chunks = _iterencode_list(value, _current_indent_level)
+ elif isinstance(value, dict):
+ chunks = _iterencode_dict(value, _current_indent_level)
+ else:
+ chunks = _iterencode(value, _current_indent_level)
+ for chunk in chunks:
+ yield chunk
+ if newline_indent is not None:
+ _current_indent_level -= 1
+ yield '\n' + (_indent * _current_indent_level)
+ yield '}'
+ if markers is not None:
+ del markers[markerid]
+
+ def _iterencode(o, _current_indent_level):
+ if (isinstance(o, string_types) or
+ (_PY3 and isinstance(o, binary_type))):
+ yield _encoder(o)
+ elif o is None:
+ yield 'null'
+ elif o is True:
+ yield 'true'
+ elif o is False:
+ yield 'false'
+ elif isinstance(o, integer_types):
+ yield _encode_int(o)
+ elif isinstance(o, float):
+ yield _floatstr(o)
+ else:
+ for_json = _for_json and getattr(o, 'for_json', None)
+ if for_json and callable(for_json):
+ for chunk in _iterencode(for_json(), _current_indent_level):
+ yield chunk
+ elif isinstance(o, list):
+ for chunk in _iterencode_list(o, _current_indent_level):
+ yield chunk
+ else:
+ _asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
+ if _asdict and callable(_asdict):
+ for chunk in _iterencode_dict(_asdict(),
+ _current_indent_level):
+ yield chunk
+ elif (_tuple_as_array and isinstance(o, tuple)):
+ for chunk in _iterencode_list(o, _current_indent_level):
+ yield chunk
+ elif isinstance(o, dict):
+ for chunk in _iterencode_dict(o, _current_indent_level):
+ yield chunk
+ elif _use_decimal and isinstance(o, Decimal):
+ yield str(o)
+ else:
+ if markers is not None:
+ markerid = id(o)
+ if markerid in markers:
+ raise ValueError("Circular reference detected")
+ markers[markerid] = o
+ o = _default(o)
+ for chunk in _iterencode(o, _current_indent_level):
+ yield chunk
+ if markers is not None:
+ del markers[markerid]
+
+ return _iterencode
diff --git a/module/lib/simplejson/ordered_dict.py b/pyload/lib/simplejson/ordered_dict.py
index 87ad88824..87ad88824 100644
--- a/module/lib/simplejson/ordered_dict.py
+++ b/pyload/lib/simplejson/ordered_dict.py
diff --git a/pyload/lib/simplejson/scanner.py b/pyload/lib/simplejson/scanner.py
new file mode 100644
index 000000000..5abed357b
--- /dev/null
+++ b/pyload/lib/simplejson/scanner.py
@@ -0,0 +1,133 @@
+"""JSON token scanner
+"""
+import re
+def _import_c_make_scanner():
+ try:
+ from simplejson._speedups import make_scanner
+ return make_scanner
+ except ImportError:
+ return None
+c_make_scanner = _import_c_make_scanner()
+
+__all__ = ['make_scanner', 'JSONDecodeError']
+
+NUMBER_RE = re.compile(
+ r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+ (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+class JSONDecodeError(ValueError):
+ """Subclass of ValueError with the following additional properties:
+
+ msg: The unformatted error message
+ doc: The JSON document being parsed
+ pos: The start index of doc where parsing failed
+ end: The end index of doc where parsing failed (may be None)
+ lineno: The line corresponding to pos
+ colno: The column corresponding to pos
+ endlineno: The line corresponding to end (may be None)
+ endcolno: The column corresponding to end (may be None)
+
+ """
+ # Note that this exception is used from _speedups
+ def __init__(self, msg, doc, pos, end=None):
+ ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
+ self.msg = msg
+ self.doc = doc
+ self.pos = pos
+ self.end = end
+ self.lineno, self.colno = linecol(doc, pos)
+ if end is not None:
+ self.endlineno, self.endcolno = linecol(doc, end)
+ else:
+ self.endlineno, self.endcolno = None, None
+
+ def __reduce__(self):
+ return self.__class__, (self.msg, self.doc, self.pos, self.end)
+
+
+def linecol(doc, pos):
+ lineno = doc.count('\n', 0, pos) + 1
+ if lineno == 1:
+ colno = pos + 1
+ else:
+ colno = pos - doc.rindex('\n', 0, pos)
+ return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+ lineno, colno = linecol(doc, pos)
+ msg = msg.replace('%r', repr(doc[pos:pos + 1]))
+ if end is None:
+ fmt = '%s: line %d column %d (char %d)'
+ return fmt % (msg, lineno, colno, pos)
+ endlineno, endcolno = linecol(doc, end)
+ fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+ return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+def py_make_scanner(context):
+ parse_object = context.parse_object
+ parse_array = context.parse_array
+ parse_string = context.parse_string
+ match_number = NUMBER_RE.match
+ encoding = context.encoding
+ strict = context.strict
+ parse_float = context.parse_float
+ parse_int = context.parse_int
+ parse_constant = context.parse_constant
+ object_hook = context.object_hook
+ object_pairs_hook = context.object_pairs_hook
+ memo = context.memo
+
+ def _scan_once(string, idx):
+ errmsg = 'Expecting value'
+ try:
+ nextchar = string[idx]
+ except IndexError:
+ raise JSONDecodeError(errmsg, string, idx)
+
+ if nextchar == '"':
+ return parse_string(string, idx + 1, encoding, strict)
+ elif nextchar == '{':
+ return parse_object((string, idx + 1), encoding, strict,
+ _scan_once, object_hook, object_pairs_hook, memo)
+ elif nextchar == '[':
+ return parse_array((string, idx + 1), _scan_once)
+ elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+ return None, idx + 4
+ elif nextchar == 't' and string[idx:idx + 4] == 'true':
+ return True, idx + 4
+ elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+ return False, idx + 5
+
+ m = match_number(string, idx)
+ if m is not None:
+ integer, frac, exp = m.groups()
+ if frac or exp:
+ res = parse_float(integer + (frac or '') + (exp or ''))
+ else:
+ res = parse_int(integer)
+ return res, m.end()
+ elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+ return parse_constant('NaN'), idx + 3
+ elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+ return parse_constant('Infinity'), idx + 8
+ elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+ return parse_constant('-Infinity'), idx + 9
+ else:
+ raise JSONDecodeError(errmsg, string, idx)
+
+ def scan_once(string, idx):
+ if idx < 0:
+ # Ensure the same behavior as the C speedup, otherwise
+ # this would work for *some* negative string indices due
+ # to the behavior of __getitem__ for strings. #98
+ raise JSONDecodeError('Expecting value', string, idx)
+ try:
+ return _scan_once(string, idx)
+ finally:
+ memo.clear()
+
+ return scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/pyload/lib/simplejson/tests/__init__.py b/pyload/lib/simplejson/tests/__init__.py
new file mode 100644
index 000000000..c7551e820
--- /dev/null
+++ b/pyload/lib/simplejson/tests/__init__.py
@@ -0,0 +1,88 @@
+from __future__ import absolute_import
+import unittest
+import doctest
+import sys
+
+
+class NoExtensionTestSuite(unittest.TestSuite):
+ def run(self, result):
+ import simplejson
+ simplejson._toggle_speedups(False)
+ result = unittest.TestSuite.run(self, result)
+ simplejson._toggle_speedups(True)
+ return result
+
+
+class TestMissingSpeedups(unittest.TestCase):
+ def runTest(self):
+ if hasattr(sys, 'pypy_translation_info'):
+ "PyPy doesn't need speedups! :)"
+ elif hasattr(self, 'skipTest'):
+ self.skipTest('_speedups.so is missing!')
+
+
+def additional_tests(suite=None):
+ import simplejson
+ import simplejson.encoder
+ import simplejson.decoder
+ if suite is None:
+ suite = unittest.TestSuite()
+ for mod in (simplejson, simplejson.encoder, simplejson.decoder):
+ suite.addTest(doctest.DocTestSuite(mod))
+ suite.addTest(doctest.DocFileSuite('../../index.rst'))
+ return suite
+
+
+def all_tests_suite():
+ def get_suite():
+ return additional_tests(
+ unittest.TestLoader().loadTestsFromNames([
+ 'simplejson.tests.test_bitsize_int_as_string',
+ 'simplejson.tests.test_bigint_as_string',
+ 'simplejson.tests.test_check_circular',
+ 'simplejson.tests.test_decode',
+ 'simplejson.tests.test_default',
+ 'simplejson.tests.test_dump',
+ 'simplejson.tests.test_encode_basestring_ascii',
+ 'simplejson.tests.test_encode_for_html',
+ 'simplejson.tests.test_errors',
+ 'simplejson.tests.test_fail',
+ 'simplejson.tests.test_float',
+ 'simplejson.tests.test_indent',
+ 'simplejson.tests.test_pass1',
+ 'simplejson.tests.test_pass2',
+ 'simplejson.tests.test_pass3',
+ 'simplejson.tests.test_recursion',
+ 'simplejson.tests.test_scanstring',
+ 'simplejson.tests.test_separators',
+ 'simplejson.tests.test_speedups',
+ 'simplejson.tests.test_unicode',
+ 'simplejson.tests.test_decimal',
+ 'simplejson.tests.test_tuple',
+ 'simplejson.tests.test_namedtuple',
+ 'simplejson.tests.test_tool',
+ 'simplejson.tests.test_for_json',
+ ]))
+ suite = get_suite()
+ import simplejson
+ if simplejson._import_c_make_encoder() is None:
+ suite.addTest(TestMissingSpeedups())
+ else:
+ suite = unittest.TestSuite([
+ suite,
+ NoExtensionTestSuite([get_suite()]),
+ ])
+ return suite
+
+
+def main():
+ runner = unittest.TextTestRunner(verbosity=1 + sys.argv.count('-v'))
+ suite = all_tests_suite()
+ raise SystemExit(not runner.run(suite).wasSuccessful())
+
+
+if __name__ == '__main__':
+ import os
+ import sys
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+ main()
diff --git a/pyload/lib/simplejson/tests/test_bigint_as_string.py b/pyload/lib/simplejson/tests/test_bigint_as_string.py
new file mode 100644
index 000000000..2cf2cc296
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_bigint_as_string.py
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestBigintAsString(TestCase):
+ # Python 2.5, at least the one that ships on Mac OS X, calculates
+ # 2 ** 53 as 0! It manages to calculate 1 << 53 correctly.
+ values = [(200, 200),
+ ((1 << 53) - 1, 9007199254740991),
+ ((1 << 53), '9007199254740992'),
+ ((1 << 53) + 1, '9007199254740993'),
+ (-100, -100),
+ ((-1 << 53), '-9007199254740992'),
+ ((-1 << 53) - 1, '-9007199254740993'),
+ ((-1 << 53) + 1, -9007199254740991)]
+
+ options = (
+ {"bigint_as_string": True},
+ {"int_as_string_bitcount": 53}
+ )
+
+ def test_ints(self):
+ for opts in self.options:
+ for val, expect in self.values:
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, **opts)))
+
+ def test_lists(self):
+ for opts in self.options:
+ for val, expect in self.values:
+ val = [val, val]
+ expect = [expect, expect]
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, **opts)))
+
+ def test_dicts(self):
+ for opts in self.options:
+ for val, expect in self.values:
+ val = {'k': val}
+ expect = {'k': expect}
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, **opts)))
+
+ def test_dict_keys(self):
+ for opts in self.options:
+ for val, _ in self.values:
+ expect = {str(val): 'value'}
+ val = {val: 'value'}
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, **opts)))
diff --git a/pyload/lib/simplejson/tests/test_bitsize_int_as_string.py b/pyload/lib/simplejson/tests/test_bitsize_int_as_string.py
new file mode 100644
index 000000000..fd7d103a6
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_bitsize_int_as_string.py
@@ -0,0 +1,73 @@
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestBitSizeIntAsString(TestCase):
+ # Python 2.5, at least the one that ships on Mac OS X, calculates
+ # 2 ** 31 as 0! It manages to calculate 1 << 31 correctly.
+ values = [
+ (200, 200),
+ ((1 << 31) - 1, (1 << 31) - 1),
+ ((1 << 31), str(1 << 31)),
+ ((1 << 31) + 1, str((1 << 31) + 1)),
+ (-100, -100),
+ ((-1 << 31), str(-1 << 31)),
+ ((-1 << 31) - 1, str((-1 << 31) - 1)),
+ ((-1 << 31) + 1, (-1 << 31) + 1),
+ ]
+
+ def test_invalid_counts(self):
+ for n in ['foo', -1, 0, 1.0]:
+ self.assertRaises(
+ TypeError,
+ json.dumps, 0, int_as_string_bitcount=n)
+
+ def test_ints_outside_range_fails(self):
+ self.assertNotEqual(
+ str(1 << 15),
+ json.loads(json.dumps(1 << 15, int_as_string_bitcount=16)),
+ )
+
+ def test_ints(self):
+ for val, expect in self.values:
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, int_as_string_bitcount=31)),
+ )
+
+ def test_lists(self):
+ for val, expect in self.values:
+ val = [val, val]
+ expect = [expect, expect]
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, int_as_string_bitcount=31)))
+
+ def test_dicts(self):
+ for val, expect in self.values:
+ val = {'k': val}
+ expect = {'k': expect}
+ self.assertEqual(
+ val,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, int_as_string_bitcount=31)))
+
+ def test_dict_keys(self):
+ for val, _ in self.values:
+ expect = {str(val): 'value'}
+ val = {val: 'value'}
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val)))
+ self.assertEqual(
+ expect,
+ json.loads(json.dumps(val, int_as_string_bitcount=31)))
diff --git a/pyload/lib/simplejson/tests/test_check_circular.py b/pyload/lib/simplejson/tests/test_check_circular.py
new file mode 100644
index 000000000..af6463d6d
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_check_circular.py
@@ -0,0 +1,30 @@
+from unittest import TestCase
+import simplejson as json
+
+def default_iterable(obj):
+ return list(obj)
+
+class TestCheckCircular(TestCase):
+ def test_circular_dict(self):
+ dct = {}
+ dct['a'] = dct
+ self.assertRaises(ValueError, json.dumps, dct)
+
+ def test_circular_list(self):
+ lst = []
+ lst.append(lst)
+ self.assertRaises(ValueError, json.dumps, lst)
+
+ def test_circular_composite(self):
+ dct2 = {}
+ dct2['a'] = []
+ dct2['a'].append(dct2)
+ self.assertRaises(ValueError, json.dumps, dct2)
+
+ def test_circular_default(self):
+ json.dumps([set()], default=default_iterable)
+ self.assertRaises(TypeError, json.dumps, [set()])
+
+ def test_circular_off_default(self):
+ json.dumps([set()], default=default_iterable, check_circular=False)
+ self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
diff --git a/pyload/lib/simplejson/tests/test_decimal.py b/pyload/lib/simplejson/tests/test_decimal.py
new file mode 100644
index 000000000..2b0940b15
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_decimal.py
@@ -0,0 +1,71 @@
+import decimal
+from decimal import Decimal
+from unittest import TestCase
+from simplejson.compat import StringIO, reload_module
+
+import simplejson as json
+
+class TestDecimal(TestCase):
+ NUMS = "1.0", "10.00", "1.1", "1234567890.1234567890", "500"
+ def dumps(self, obj, **kw):
+ sio = StringIO()
+ json.dump(obj, sio, **kw)
+ res = json.dumps(obj, **kw)
+ self.assertEqual(res, sio.getvalue())
+ return res
+
+ def loads(self, s, **kw):
+ sio = StringIO(s)
+ res = json.loads(s, **kw)
+ self.assertEqual(res, json.load(sio, **kw))
+ return res
+
+ def test_decimal_encode(self):
+ for d in map(Decimal, self.NUMS):
+ self.assertEqual(self.dumps(d, use_decimal=True), str(d))
+
+ def test_decimal_decode(self):
+ for s in self.NUMS:
+ self.assertEqual(self.loads(s, parse_float=Decimal), Decimal(s))
+
+ def test_stringify_key(self):
+ for d in map(Decimal, self.NUMS):
+ v = {d: d}
+ self.assertEqual(
+ self.loads(
+ self.dumps(v, use_decimal=True), parse_float=Decimal),
+ {str(d): d})
+
+ def test_decimal_roundtrip(self):
+ for d in map(Decimal, self.NUMS):
+ # The type might not be the same (int and Decimal) but they
+ # should still compare equal.
+ for v in [d, [d], {'': d}]:
+ self.assertEqual(
+ self.loads(
+ self.dumps(v, use_decimal=True), parse_float=Decimal),
+ v)
+
+ def test_decimal_defaults(self):
+ d = Decimal('1.1')
+ # use_decimal=True is the default
+ self.assertRaises(TypeError, json.dumps, d, use_decimal=False)
+ self.assertEqual('1.1', json.dumps(d))
+ self.assertEqual('1.1', json.dumps(d, use_decimal=True))
+ self.assertRaises(TypeError, json.dump, d, StringIO(),
+ use_decimal=False)
+ sio = StringIO()
+ json.dump(d, sio)
+ self.assertEqual('1.1', sio.getvalue())
+ sio = StringIO()
+ json.dump(d, sio, use_decimal=True)
+ self.assertEqual('1.1', sio.getvalue())
+
+ def test_decimal_reload(self):
+ # Simulate a subinterpreter that reloads the Python modules but not
+ # the C code https://github.com/simplejson/simplejson/issues/34
+ global Decimal
+ Decimal = reload_module(decimal).Decimal
+ import simplejson.encoder
+ simplejson.encoder.Decimal = Decimal
+ self.test_decimal_roundtrip()
diff --git a/pyload/lib/simplejson/tests/test_decode.py b/pyload/lib/simplejson/tests/test_decode.py
new file mode 100644
index 000000000..30b692af2
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_decode.py
@@ -0,0 +1,99 @@
+from __future__ import absolute_import
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+from simplejson.compat import StringIO
+from simplejson import OrderedDict
+
+class TestDecode(TestCase):
+ if not hasattr(TestCase, 'assertIs'):
+ def assertIs(self, a, b):
+ self.assertTrue(a is b, '%r is %r' % (a, b))
+
+ def test_decimal(self):
+ rval = json.loads('1.1', parse_float=decimal.Decimal)
+ self.assertTrue(isinstance(rval, decimal.Decimal))
+ self.assertEqual(rval, decimal.Decimal('1.1'))
+
+ def test_float(self):
+ rval = json.loads('1', parse_int=float)
+ self.assertTrue(isinstance(rval, float))
+ self.assertEqual(rval, 1.0)
+
+ def test_decoder_optimizations(self):
+ # Several optimizations were made that skip over calls to
+ # the whitespace regex, so this test is designed to try and
+ # exercise the uncommon cases. The array cases are already covered.
+ rval = json.loads('{ "key" : "value" , "k":"v" }')
+ self.assertEqual(rval, {"key":"value", "k":"v"})
+
+ def test_empty_objects(self):
+ s = '{}'
+ self.assertEqual(json.loads(s), eval(s))
+ s = '[]'
+ self.assertEqual(json.loads(s), eval(s))
+ s = '""'
+ self.assertEqual(json.loads(s), eval(s))
+
+ def test_object_pairs_hook(self):
+ s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
+ p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4),
+ ("qrt", 5), ("pad", 6), ("hoy", 7)]
+ self.assertEqual(json.loads(s), eval(s))
+ self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
+ self.assertEqual(json.load(StringIO(s),
+ object_pairs_hook=lambda x: x), p)
+ od = json.loads(s, object_pairs_hook=OrderedDict)
+ self.assertEqual(od, OrderedDict(p))
+ self.assertEqual(type(od), OrderedDict)
+ # the object_pairs_hook takes priority over the object_hook
+ self.assertEqual(json.loads(s,
+ object_pairs_hook=OrderedDict,
+ object_hook=lambda x: None),
+ OrderedDict(p))
+
+ def check_keys_reuse(self, source, loads):
+ rval = loads(source)
+ (a, b), (c, d) = sorted(rval[0]), sorted(rval[1])
+ self.assertIs(a, c)
+ self.assertIs(b, d)
+
+ def test_keys_reuse_str(self):
+ s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'.encode('utf8')
+ self.check_keys_reuse(s, json.loads)
+
+ def test_keys_reuse_unicode(self):
+ s = u'[{"a_key": 1, "b_\xe9": 2}, {"a_key": 3, "b_\xe9": 4}]'
+ self.check_keys_reuse(s, json.loads)
+
+ def test_empty_strings(self):
+ self.assertEqual(json.loads('""'), "")
+ self.assertEqual(json.loads(u'""'), u"")
+ self.assertEqual(json.loads('[""]'), [""])
+ self.assertEqual(json.loads(u'[""]'), [u""])
+
+ def test_raw_decode(self):
+ cls = json.decoder.JSONDecoder
+ self.assertEqual(
+ ({'a': {}}, 9),
+ cls().raw_decode("{\"a\": {}}"))
+ # http://code.google.com/p/simplejson/issues/detail?id=85
+ self.assertEqual(
+ ({'a': {}}, 9),
+ cls(object_pairs_hook=dict).raw_decode("{\"a\": {}}"))
+ # https://github.com/simplejson/simplejson/pull/38
+ self.assertEqual(
+ ({'a': {}}, 11),
+ cls().raw_decode(" \n{\"a\": {}}"))
+
+ def test_bounds_checking(self):
+ # https://github.com/simplejson/simplejson/issues/98
+ j = json.decoder.JSONDecoder()
+ for i in [4, 5, 6, -1, -2, -3, -4, -5, -6]:
+ self.assertRaises(ValueError, j.scan_once, '1234', i)
+ self.assertRaises(ValueError, j.raw_decode, '1234', i)
+ x, y = sorted(['128931233', '472389423'], key=id)
+ diff = id(x) - id(y)
+ self.assertRaises(ValueError, j.scan_once, y, diff)
+ self.assertRaises(ValueError, j.raw_decode, y, i)
diff --git a/pyload/lib/simplejson/tests/test_default.py b/pyload/lib/simplejson/tests/test_default.py
new file mode 100644
index 000000000..d1eacb8c4
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_default.py
@@ -0,0 +1,9 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDefault(TestCase):
+ def test_default(self):
+ self.assertEqual(
+ json.dumps(type, default=repr),
+ json.dumps(repr(type)))
diff --git a/pyload/lib/simplejson/tests/test_dump.py b/pyload/lib/simplejson/tests/test_dump.py
new file mode 100644
index 000000000..1d118d929
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_dump.py
@@ -0,0 +1,121 @@
+from unittest import TestCase
+from simplejson.compat import StringIO, long_type, b, binary_type, PY3
+import simplejson as json
+
+def as_text_type(s):
+ if PY3 and isinstance(s, binary_type):
+ return s.decode('ascii')
+ return s
+
+class TestDump(TestCase):
+ def test_dump(self):
+ sio = StringIO()
+ json.dump({}, sio)
+ self.assertEqual(sio.getvalue(), '{}')
+
+ def test_constants(self):
+ for c in [None, True, False]:
+ self.assertTrue(json.loads(json.dumps(c)) is c)
+ self.assertTrue(json.loads(json.dumps([c]))[0] is c)
+ self.assertTrue(json.loads(json.dumps({'a': c}))['a'] is c)
+
+ def test_stringify_key(self):
+ items = [(b('bytes'), 'bytes'),
+ (1.0, '1.0'),
+ (10, '10'),
+ (True, 'true'),
+ (False, 'false'),
+ (None, 'null'),
+ (long_type(100), '100')]
+ for k, expect in items:
+ self.assertEqual(
+ json.loads(json.dumps({k: expect})),
+ {expect: expect})
+ self.assertEqual(
+ json.loads(json.dumps({k: expect}, sort_keys=True)),
+ {expect: expect})
+ self.assertRaises(TypeError, json.dumps, {json: 1})
+ for v in [{}, {'other': 1}, {b('derp'): 1, 'herp': 2}]:
+ for sort_keys in [False, True]:
+ v0 = dict(v)
+ v0[json] = 1
+ v1 = dict((as_text_type(key), val) for (key, val) in v.items())
+ self.assertEqual(
+ json.loads(json.dumps(v0, skipkeys=True, sort_keys=sort_keys)),
+ v1)
+ self.assertEqual(
+ json.loads(json.dumps({'': v0}, skipkeys=True, sort_keys=sort_keys)),
+ {'': v1})
+ self.assertEqual(
+ json.loads(json.dumps([v0], skipkeys=True, sort_keys=sort_keys)),
+ [v1])
+
+ def test_dumps(self):
+ self.assertEqual(json.dumps({}), '{}')
+
+ def test_encode_truefalse(self):
+ self.assertEqual(json.dumps(
+ {True: False, False: True}, sort_keys=True),
+ '{"false": true, "true": false}')
+ self.assertEqual(
+ json.dumps(
+ {2: 3.0,
+ 4.0: long_type(5),
+ False: 1,
+ long_type(6): True,
+ "7": 0},
+ sort_keys=True),
+ '{"2": 3.0, "4.0": 5, "6": true, "7": 0, "false": 1}')
+
+ def test_ordered_dict(self):
+ # http://bugs.python.org/issue6105
+ items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
+ s = json.dumps(json.OrderedDict(items))
+ self.assertEqual(
+ s,
+ '{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}')
+
+ def test_indent_unknown_type_acceptance(self):
+ """
+ A test against the regression mentioned at `github issue 29`_.
+
+ The indent parameter should accept any type which pretends to be
+ an instance of int or long when it comes to being multiplied by
+ strings, even if it is not actually an int or long, for
+ backwards compatibility.
+
+ .. _github issue 29:
+ http://github.com/simplejson/simplejson/issue/29
+ """
+
+ class AwesomeInt(object):
+ """An awesome reimplementation of integers"""
+
+ def __init__(self, *args, **kwargs):
+ if len(args) > 0:
+ # [construct from literals, objects, etc.]
+ # ...
+
+ # Finally, if args[0] is an integer, store it
+ if isinstance(args[0], int):
+ self._int = args[0]
+
+ # [various methods]
+
+ def __mul__(self, other):
+ # [various ways to multiply AwesomeInt objects]
+ # ... finally, if the right-hand operand is not awesome enough,
+ # try to do a normal integer multiplication
+ if hasattr(self, '_int'):
+ return self._int * other
+ else:
+ raise NotImplementedError("To do non-awesome things with"
+ " this object, please construct it from an integer!")
+
+ s = json.dumps([0, 1, 2], indent=AwesomeInt(3))
+ self.assertEqual(s, '[\n 0,\n 1,\n 2\n]')
+
+ def test_accumulator(self):
+ # the C API uses an accumulator that collects after 100,000 appends
+ lst = [0] * 100000
+ self.assertEqual(json.loads(json.dumps(lst)), lst)
diff --git a/pyload/lib/simplejson/tests/test_encode_basestring_ascii.py b/pyload/lib/simplejson/tests/test_encode_basestring_ascii.py
new file mode 100644
index 000000000..49706bfd9
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_encode_basestring_ascii.py
@@ -0,0 +1,47 @@
+from unittest import TestCase
+
+import simplejson.encoder
+from simplejson.compat import b
+
+CASES = [
+ (u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
+ (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+ (u'controls', '"controls"'),
+ (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+ (u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
+ (u' s p a c e d ', '" s p a c e d "'),
+ (u'\U0001d120', '"\\ud834\\udd20"'),
+ (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+ (b('\xce\xb1\xce\xa9'), '"\\u03b1\\u03a9"'),
+ (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+ (b('\xce\xb1\xce\xa9'), '"\\u03b1\\u03a9"'),
+ (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+ (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+ (u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
+ (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+ (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+]
+
+class TestEncodeBaseStringAscii(TestCase):
+ def test_py_encode_basestring_ascii(self):
+ self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
+
+ def test_c_encode_basestring_ascii(self):
+ if not simplejson.encoder.c_encode_basestring_ascii:
+ return
+ self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
+
+ def _test_encode_basestring_ascii(self, encode_basestring_ascii):
+ fname = encode_basestring_ascii.__name__
+ for input_string, expect in CASES:
+ result = encode_basestring_ascii(input_string)
+ #self.assertEqual(result, expect,
+ # '{0!r} != {1!r} for {2}({3!r})'.format(
+ # result, expect, fname, input_string))
+ self.assertEqual(result, expect,
+ '%r != %r for %s(%r)' % (result, expect, fname, input_string))
+
+ def test_sorted_dict(self):
+ items = [('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
+ s = simplejson.dumps(dict(items), sort_keys=True)
+ self.assertEqual(s, '{"five": 5, "four": 4, "one": 1, "three": 3, "two": 2}')
diff --git a/pyload/lib/simplejson/tests/test_encode_for_html.py b/pyload/lib/simplejson/tests/test_encode_for_html.py
new file mode 100644
index 000000000..f99525486
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_encode_for_html.py
@@ -0,0 +1,30 @@
+import unittest
+
+import simplejson as json
+
+class TestEncodeForHTML(unittest.TestCase):
+
+ def setUp(self):
+ self.decoder = json.JSONDecoder()
+ self.encoder = json.JSONEncoderForHTML()
+
+ def test_basic_encode(self):
+ self.assertEqual(r'"\u0026"', self.encoder.encode('&'))
+ self.assertEqual(r'"\u003c"', self.encoder.encode('<'))
+ self.assertEqual(r'"\u003e"', self.encoder.encode('>'))
+
+ def test_basic_roundtrip(self):
+ for char in '&<>':
+ self.assertEqual(
+ char, self.decoder.decode(
+ self.encoder.encode(char)))
+
+ def test_prevent_script_breakout(self):
+ bad_string = '</script><script>alert("gotcha")</script>'
+ self.assertEqual(
+ r'"\u003c/script\u003e\u003cscript\u003e'
+ r'alert(\"gotcha\")\u003c/script\u003e"',
+ self.encoder.encode(bad_string))
+ self.assertEqual(
+ bad_string, self.decoder.decode(
+ self.encoder.encode(bad_string)))
diff --git a/pyload/lib/simplejson/tests/test_errors.py b/pyload/lib/simplejson/tests/test_errors.py
new file mode 100644
index 000000000..8dede38fe
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_errors.py
@@ -0,0 +1,51 @@
+import sys, pickle
+from unittest import TestCase
+
+import simplejson as json
+from simplejson.compat import u, b
+
+class TestErrors(TestCase):
+ def test_string_keys_error(self):
+ data = [{'a': 'A', 'b': (2, 4), 'c': 3.0, ('d',): 'D tuple'}]
+ self.assertRaises(TypeError, json.dumps, data)
+
+ def test_decode_error(self):
+ err = None
+ try:
+ json.loads('{}\na\nb')
+ except json.JSONDecodeError:
+ err = sys.exc_info()[1]
+ else:
+ self.fail('Expected JSONDecodeError')
+ self.assertEqual(err.lineno, 2)
+ self.assertEqual(err.colno, 1)
+ self.assertEqual(err.endlineno, 3)
+ self.assertEqual(err.endcolno, 2)
+
+ def test_scan_error(self):
+ err = None
+ for t in (u, b):
+ try:
+ json.loads(t('{"asdf": "'))
+ except json.JSONDecodeError:
+ err = sys.exc_info()[1]
+ else:
+ self.fail('Expected JSONDecodeError')
+ self.assertEqual(err.lineno, 1)
+ self.assertEqual(err.colno, 10)
+
+ def test_error_is_pickable(self):
+ err = None
+ try:
+ json.loads('{}\na\nb')
+ except json.JSONDecodeError:
+ err = sys.exc_info()[1]
+ else:
+ self.fail('Expected JSONDecodeError')
+ s = pickle.dumps(err)
+ e = pickle.loads(s)
+
+ self.assertEqual(err.msg, e.msg)
+ self.assertEqual(err.doc, e.doc)
+ self.assertEqual(err.pos, e.pos)
+ self.assertEqual(err.end, e.end)
diff --git a/pyload/lib/simplejson/tests/test_fail.py b/pyload/lib/simplejson/tests/test_fail.py
new file mode 100644
index 000000000..788f3a525
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_fail.py
@@ -0,0 +1,176 @@
+import sys
+from unittest import TestCase
+
+import simplejson as json
+
+# 2007-10-05
+JSONDOCS = [
+ # http://json.org/JSON_checker/test/fail1.json
+ '"A JSON payload should be an object or array, not a string."',
+ # http://json.org/JSON_checker/test/fail2.json
+ '["Unclosed array"',
+ # http://json.org/JSON_checker/test/fail3.json
+ '{unquoted_key: "keys must be quoted"}',
+ # http://json.org/JSON_checker/test/fail4.json
+ '["extra comma",]',
+ # http://json.org/JSON_checker/test/fail5.json
+ '["double extra comma",,]',
+ # http://json.org/JSON_checker/test/fail6.json
+ '[ , "<-- missing value"]',
+ # http://json.org/JSON_checker/test/fail7.json
+ '["Comma after the close"],',
+ # http://json.org/JSON_checker/test/fail8.json
+ '["Extra close"]]',
+ # http://json.org/JSON_checker/test/fail9.json
+ '{"Extra comma": true,}',
+ # http://json.org/JSON_checker/test/fail10.json
+ '{"Extra value after close": true} "misplaced quoted value"',
+ # http://json.org/JSON_checker/test/fail11.json
+ '{"Illegal expression": 1 + 2}',
+ # http://json.org/JSON_checker/test/fail12.json
+ '{"Illegal invocation": alert()}',
+ # http://json.org/JSON_checker/test/fail13.json
+ '{"Numbers cannot have leading zeroes": 013}',
+ # http://json.org/JSON_checker/test/fail14.json
+ '{"Numbers cannot be hex": 0x14}',
+ # http://json.org/JSON_checker/test/fail15.json
+ '["Illegal backslash escape: \\x15"]',
+ # http://json.org/JSON_checker/test/fail16.json
+ '[\\naked]',
+ # http://json.org/JSON_checker/test/fail17.json
+ '["Illegal backslash escape: \\017"]',
+ # http://json.org/JSON_checker/test/fail18.json
+ '[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
+ # http://json.org/JSON_checker/test/fail19.json
+ '{"Missing colon" null}',
+ # http://json.org/JSON_checker/test/fail20.json
+ '{"Double colon":: null}',
+ # http://json.org/JSON_checker/test/fail21.json
+ '{"Comma instead of colon", null}',
+ # http://json.org/JSON_checker/test/fail22.json
+ '["Colon instead of comma": false]',
+ # http://json.org/JSON_checker/test/fail23.json
+ '["Bad value", truth]',
+ # http://json.org/JSON_checker/test/fail24.json
+ "['single quote']",
+ # http://json.org/JSON_checker/test/fail25.json
+ '["\ttab\tcharacter\tin\tstring\t"]',
+ # http://json.org/JSON_checker/test/fail26.json
+ '["tab\\ character\\ in\\ string\\ "]',
+ # http://json.org/JSON_checker/test/fail27.json
+ '["line\nbreak"]',
+ # http://json.org/JSON_checker/test/fail28.json
+ '["line\\\nbreak"]',
+ # http://json.org/JSON_checker/test/fail29.json
+ '[0e]',
+ # http://json.org/JSON_checker/test/fail30.json
+ '[0e+]',
+ # http://json.org/JSON_checker/test/fail31.json
+ '[0e+-1]',
+ # http://json.org/JSON_checker/test/fail32.json
+ '{"Comma instead if closing brace": true,',
+ # http://json.org/JSON_checker/test/fail33.json
+ '["mismatch"}',
+ # http://code.google.com/p/simplejson/issues/detail?id=3
+ u'["A\u001FZ control characters in string"]',
+ # misc based on coverage
+ '{',
+ '{]',
+ '{"foo": "bar"]',
+ '{"foo": "bar"',
+ 'nul',
+ 'nulx',
+ '-',
+ '-x',
+ '-e',
+ '-e0',
+ '-Infinite',
+ '-Inf',
+ 'Infinit',
+ 'Infinite',
+ 'NaM',
+ 'NuN',
+ 'falsy',
+ 'fal',
+ 'trug',
+ 'tru',
+ '1e',
+ '1ex',
+ '1e-',
+ '1e-x',
+]
+
+SKIPS = {
+ 1: "why not have a string payload?",
+ 18: "spec doesn't specify any nesting limitations",
+}
+
+class TestFail(TestCase):
+ def test_failures(self):
+ for idx, doc in enumerate(JSONDOCS):
+ idx = idx + 1
+ if idx in SKIPS:
+ json.loads(doc)
+ continue
+ try:
+ json.loads(doc)
+ except json.JSONDecodeError:
+ pass
+ else:
+ self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
+
+ def test_array_decoder_issue46(self):
+ # http://code.google.com/p/simplejson/issues/detail?id=46
+ for doc in [u'[,]', '[,]']:
+ try:
+ json.loads(doc)
+ except json.JSONDecodeError:
+ e = sys.exc_info()[1]
+ self.assertEqual(e.pos, 1)
+ self.assertEqual(e.lineno, 1)
+ self.assertEqual(e.colno, 2)
+ except Exception:
+ e = sys.exc_info()[1]
+ self.fail("Unexpected exception raised %r %s" % (e, e))
+ else:
+ self.fail("Unexpected success parsing '[,]'")
+
+ def test_truncated_input(self):
+ test_cases = [
+ ('', 'Expecting value', 0),
+ ('[', "Expecting value or ']'", 1),
+ ('[42', "Expecting ',' delimiter", 3),
+ ('[42,', 'Expecting value', 4),
+ ('["', 'Unterminated string starting at', 1),
+ ('["spam', 'Unterminated string starting at', 1),
+ ('["spam"', "Expecting ',' delimiter", 7),
+ ('["spam",', 'Expecting value', 8),
+ ('{', 'Expecting property name enclosed in double quotes', 1),
+ ('{"', 'Unterminated string starting at', 1),
+ ('{"spam', 'Unterminated string starting at', 1),
+ ('{"spam"', "Expecting ':' delimiter", 7),
+ ('{"spam":', 'Expecting value', 8),
+ ('{"spam":42', "Expecting ',' delimiter", 10),
+ ('{"spam":42,', 'Expecting property name enclosed in double quotes',
+ 11),
+ ('"', 'Unterminated string starting at', 0),
+ ('"spam', 'Unterminated string starting at', 0),
+ ('[,', "Expecting value", 1),
+ ]
+ for data, msg, idx in test_cases:
+ try:
+ json.loads(data)
+ except json.JSONDecodeError:
+ e = sys.exc_info()[1]
+ self.assertEqual(
+ e.msg[:len(msg)],
+ msg,
+ "%r doesn't start with %r for %r" % (e.msg, msg, data))
+ self.assertEqual(
+ e.pos, idx,
+ "pos %r != %r for %r" % (e.pos, idx, data))
+ except Exception:
+ e = sys.exc_info()[1]
+ self.fail("Unexpected exception raised %r %s" % (e, e))
+ else:
+ self.fail("Unexpected success parsing '%r'" % (data,))
diff --git a/pyload/lib/simplejson/tests/test_float.py b/pyload/lib/simplejson/tests/test_float.py
new file mode 100644
index 000000000..e382ec21a
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_float.py
@@ -0,0 +1,35 @@
+import math
+from unittest import TestCase
+from simplejson.compat import long_type, text_type
+import simplejson as json
+from simplejson.decoder import NaN, PosInf, NegInf
+
+class TestFloat(TestCase):
+ def test_degenerates_allow(self):
+ for inf in (PosInf, NegInf):
+ self.assertEqual(json.loads(json.dumps(inf)), inf)
+ # Python 2.5 doesn't have math.isnan
+ nan = json.loads(json.dumps(NaN))
+ self.assertTrue((0 + nan) != nan)
+
+ def test_degenerates_ignore(self):
+ for f in (PosInf, NegInf, NaN):
+ self.assertEqual(json.loads(json.dumps(f, ignore_nan=True)), None)
+
+ def test_degenerates_deny(self):
+ for f in (PosInf, NegInf, NaN):
+ self.assertRaises(ValueError, json.dumps, f, allow_nan=False)
+
+ def test_floats(self):
+ for num in [1617161771.7650001, math.pi, math.pi**100,
+ math.pi**-100, 3.1]:
+ self.assertEqual(float(json.dumps(num)), num)
+ self.assertEqual(json.loads(json.dumps(num)), num)
+ self.assertEqual(json.loads(text_type(json.dumps(num))), num)
+
+ def test_ints(self):
+ for num in [1, long_type(1), 1<<32, 1<<64]:
+ self.assertEqual(json.dumps(num), str(num))
+ self.assertEqual(int(json.dumps(num)), num)
+ self.assertEqual(json.loads(json.dumps(num)), num)
+ self.assertEqual(json.loads(text_type(json.dumps(num))), num)
diff --git a/pyload/lib/simplejson/tests/test_for_json.py b/pyload/lib/simplejson/tests/test_for_json.py
new file mode 100644
index 000000000..b791b883b
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_for_json.py
@@ -0,0 +1,97 @@
+import unittest
+import simplejson as json
+
+
+class ForJson(object):
+ def for_json(self):
+ return {'for_json': 1}
+
+
+class NestedForJson(object):
+ def for_json(self):
+ return {'nested': ForJson()}
+
+
+class ForJsonList(object):
+ def for_json(self):
+ return ['list']
+
+
+class DictForJson(dict):
+ def for_json(self):
+ return {'alpha': 1}
+
+
+class ListForJson(list):
+ def for_json(self):
+ return ['list']
+
+
+class TestForJson(unittest.TestCase):
+ def assertRoundTrip(self, obj, other, for_json=True):
+ if for_json is None:
+ # None will use the default
+ s = json.dumps(obj)
+ else:
+ s = json.dumps(obj, for_json=for_json)
+ self.assertEqual(
+ json.loads(s),
+ other)
+
+ def test_for_json_encodes_stand_alone_object(self):
+ self.assertRoundTrip(
+ ForJson(),
+ ForJson().for_json())
+
+ def test_for_json_encodes_object_nested_in_dict(self):
+ self.assertRoundTrip(
+ {'hooray': ForJson()},
+ {'hooray': ForJson().for_json()})
+
+ def test_for_json_encodes_object_nested_in_list_within_dict(self):
+ self.assertRoundTrip(
+ {'list': [0, ForJson(), 2, 3]},
+ {'list': [0, ForJson().for_json(), 2, 3]})
+
+ def test_for_json_encodes_object_nested_within_object(self):
+ self.assertRoundTrip(
+ NestedForJson(),
+ {'nested': {'for_json': 1}})
+
+ def test_for_json_encodes_list(self):
+ self.assertRoundTrip(
+ ForJsonList(),
+ ForJsonList().for_json())
+
+ def test_for_json_encodes_list_within_object(self):
+ self.assertRoundTrip(
+ {'nested': ForJsonList()},
+ {'nested': ForJsonList().for_json()})
+
+ def test_for_json_encodes_dict_subclass(self):
+ self.assertRoundTrip(
+ DictForJson(a=1),
+ DictForJson(a=1).for_json())
+
+ def test_for_json_encodes_list_subclass(self):
+ self.assertRoundTrip(
+ ListForJson(['l']),
+ ListForJson(['l']).for_json())
+
+ def test_for_json_ignored_if_not_true_with_dict_subclass(self):
+ for for_json in (None, False):
+ self.assertRoundTrip(
+ DictForJson(a=1),
+ {'a': 1},
+ for_json=for_json)
+
+ def test_for_json_ignored_if_not_true_with_list_subclass(self):
+ for for_json in (None, False):
+ self.assertRoundTrip(
+ ListForJson(['l']),
+ ['l'],
+ for_json=for_json)
+
+ def test_raises_typeerror_if_for_json_not_true_with_object(self):
+ self.assertRaises(TypeError, json.dumps, ForJson())
+ self.assertRaises(TypeError, json.dumps, ForJson(), for_json=False)
diff --git a/pyload/lib/simplejson/tests/test_indent.py b/pyload/lib/simplejson/tests/test_indent.py
new file mode 100644
index 000000000..cea25a575
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_indent.py
@@ -0,0 +1,86 @@
+from unittest import TestCase
+import textwrap
+
+import simplejson as json
+from simplejson.compat import StringIO
+
+class TestIndent(TestCase):
+ def test_indent(self):
+ h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh',
+ 'i-vhbjkhnth',
+ {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+ expect = textwrap.dedent("""\
+ [
+ \t[
+ \t\t"blorpie"
+ \t],
+ \t[
+ \t\t"whoops"
+ \t],
+ \t[],
+ \t"d-shtaeou",
+ \t"d-nthiouh",
+ \t"i-vhbjkhnth",
+ \t{
+ \t\t"nifty": 87
+ \t},
+ \t{
+ \t\t"field": "yes",
+ \t\t"morefield": false
+ \t}
+ ]""")
+
+
+ d1 = json.dumps(h)
+ d2 = json.dumps(h, indent='\t', sort_keys=True, separators=(',', ': '))
+ d3 = json.dumps(h, indent=' ', sort_keys=True, separators=(',', ': '))
+ d4 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
+
+ h1 = json.loads(d1)
+ h2 = json.loads(d2)
+ h3 = json.loads(d3)
+ h4 = json.loads(d4)
+
+ self.assertEqual(h1, h)
+ self.assertEqual(h2, h)
+ self.assertEqual(h3, h)
+ self.assertEqual(h4, h)
+ self.assertEqual(d3, expect.replace('\t', ' '))
+ self.assertEqual(d4, expect.replace('\t', ' '))
+ # NOTE: Python 2.4 textwrap.dedent converts tabs to spaces,
+ # so the following is expected to fail. Python 2.4 is not a
+ # supported platform in simplejson 2.1.0+.
+ self.assertEqual(d2, expect)
+
+ def test_indent0(self):
+ h = {3: 1}
+ def check(indent, expected):
+ d1 = json.dumps(h, indent=indent)
+ self.assertEqual(d1, expected)
+
+ sio = StringIO()
+ json.dump(h, sio, indent=indent)
+ self.assertEqual(sio.getvalue(), expected)
+
+ # indent=0 should emit newlines
+ check(0, '{\n"3": 1\n}')
+ # indent=None is more compact
+ check(None, '{"3": 1}')
+
+ def test_separators(self):
+ lst = [1,2,3,4]
+ expect = '[\n1,\n2,\n3,\n4\n]'
+ expect_spaces = '[\n1, \n2, \n3, \n4\n]'
+ # Ensure that separators still works
+ self.assertEqual(
+ expect_spaces,
+ json.dumps(lst, indent=0, separators=(', ', ': ')))
+ # Force the new defaults
+ self.assertEqual(
+ expect,
+ json.dumps(lst, indent=0, separators=(',', ': ')))
+ # Added in 2.1.4
+ self.assertEqual(
+ expect,
+ json.dumps(lst, indent=0))
diff --git a/pyload/lib/simplejson/tests/test_item_sort_key.py b/pyload/lib/simplejson/tests/test_item_sort_key.py
new file mode 100644
index 000000000..b05bfc814
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_item_sort_key.py
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+import simplejson as json
+from operator import itemgetter
+
+class TestItemSortKey(TestCase):
+ def test_simple_first(self):
+ a = {'a': 1, 'c': 5, 'jack': 'jill', 'pick': 'axe', 'array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
+ self.assertEqual(
+ '{"a": 1, "c": 5, "crate": "dog", "jack": "jill", "pick": "axe", "zeak": "oh", "array": [1, 5, 6, 9], "tuple": [83, 12, 3]}',
+ json.dumps(a, item_sort_key=json.simple_first))
+
+ def test_case(self):
+ a = {'a': 1, 'c': 5, 'Jack': 'jill', 'pick': 'axe', 'Array': [1, 5, 6, 9], 'tuple': (83, 12, 3), 'crate': 'dog', 'zeak': 'oh'}
+ self.assertEqual(
+ '{"Array": [1, 5, 6, 9], "Jack": "jill", "a": 1, "c": 5, "crate": "dog", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
+ json.dumps(a, item_sort_key=itemgetter(0)))
+ self.assertEqual(
+ '{"a": 1, "Array": [1, 5, 6, 9], "c": 5, "crate": "dog", "Jack": "jill", "pick": "axe", "tuple": [83, 12, 3], "zeak": "oh"}',
+ json.dumps(a, item_sort_key=lambda kv: kv[0].lower()))
diff --git a/pyload/lib/simplejson/tests/test_namedtuple.py b/pyload/lib/simplejson/tests/test_namedtuple.py
new file mode 100644
index 000000000..438789405
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_namedtuple.py
@@ -0,0 +1,122 @@
+from __future__ import absolute_import
+import unittest
+import simplejson as json
+from simplejson.compat import StringIO
+
+try:
+ from collections import namedtuple
+except ImportError:
+ class Value(tuple):
+ def __new__(cls, *args):
+ return tuple.__new__(cls, args)
+
+ def _asdict(self):
+ return {'value': self[0]}
+ class Point(tuple):
+ def __new__(cls, *args):
+ return tuple.__new__(cls, args)
+
+ def _asdict(self):
+ return {'x': self[0], 'y': self[1]}
+else:
+ Value = namedtuple('Value', ['value'])
+ Point = namedtuple('Point', ['x', 'y'])
+
+class DuckValue(object):
+ def __init__(self, *args):
+ self.value = Value(*args)
+
+ def _asdict(self):
+ return self.value._asdict()
+
+class DuckPoint(object):
+ def __init__(self, *args):
+ self.point = Point(*args)
+
+ def _asdict(self):
+ return self.point._asdict()
+
+class DeadDuck(object):
+ _asdict = None
+
+class DeadDict(dict):
+ _asdict = None
+
+CONSTRUCTORS = [
+ lambda v: v,
+ lambda v: [v],
+ lambda v: [{'key': v}],
+]
+
+class TestNamedTuple(unittest.TestCase):
+ def test_namedtuple_dumps(self):
+ for v in [Value(1), Point(1, 2), DuckValue(1), DuckPoint(1, 2)]:
+ d = v._asdict()
+ self.assertEqual(d, json.loads(json.dumps(v)))
+ self.assertEqual(
+ d,
+ json.loads(json.dumps(v, namedtuple_as_object=True)))
+ self.assertEqual(d, json.loads(json.dumps(v, tuple_as_array=False)))
+ self.assertEqual(
+ d,
+ json.loads(json.dumps(v, namedtuple_as_object=True,
+ tuple_as_array=False)))
+
+ def test_namedtuple_dumps_false(self):
+ for v in [Value(1), Point(1, 2)]:
+ l = list(v)
+ self.assertEqual(
+ l,
+ json.loads(json.dumps(v, namedtuple_as_object=False)))
+ self.assertRaises(TypeError, json.dumps, v,
+ tuple_as_array=False, namedtuple_as_object=False)
+
+ def test_namedtuple_dump(self):
+ for v in [Value(1), Point(1, 2), DuckValue(1), DuckPoint(1, 2)]:
+ d = v._asdict()
+ sio = StringIO()
+ json.dump(v, sio)
+ self.assertEqual(d, json.loads(sio.getvalue()))
+ sio = StringIO()
+ json.dump(v, sio, namedtuple_as_object=True)
+ self.assertEqual(
+ d,
+ json.loads(sio.getvalue()))
+ sio = StringIO()
+ json.dump(v, sio, tuple_as_array=False)
+ self.assertEqual(d, json.loads(sio.getvalue()))
+ sio = StringIO()
+ json.dump(v, sio, namedtuple_as_object=True,
+ tuple_as_array=False)
+ self.assertEqual(
+ d,
+ json.loads(sio.getvalue()))
+
+ def test_namedtuple_dump_false(self):
+ for v in [Value(1), Point(1, 2)]:
+ l = list(v)
+ sio = StringIO()
+ json.dump(v, sio, namedtuple_as_object=False)
+ self.assertEqual(
+ l,
+ json.loads(sio.getvalue()))
+ self.assertRaises(TypeError, json.dump, v, StringIO(),
+ tuple_as_array=False, namedtuple_as_object=False)
+
+ def test_asdict_not_callable_dump(self):
+ for f in CONSTRUCTORS:
+ self.assertRaises(TypeError,
+ json.dump, f(DeadDuck()), StringIO(), namedtuple_as_object=True)
+ sio = StringIO()
+ json.dump(f(DeadDict()), sio, namedtuple_as_object=True)
+ self.assertEqual(
+ json.dumps(f({})),
+ sio.getvalue())
+
+ def test_asdict_not_callable_dumps(self):
+ for f in CONSTRUCTORS:
+ self.assertRaises(TypeError,
+ json.dumps, f(DeadDuck()), namedtuple_as_object=True)
+ self.assertEqual(
+ json.dumps(f({})),
+ json.dumps(f(DeadDict()), namedtuple_as_object=True))
diff --git a/pyload/lib/simplejson/tests/test_pass1.py b/pyload/lib/simplejson/tests/test_pass1.py
new file mode 100644
index 000000000..f0b5b10e7
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_pass1.py
@@ -0,0 +1,71 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass1.json
+JSON = r'''
+[
+ "JSON Test Pattern pass1",
+ {"object with 1 member":["array with 1 element"]},
+ {},
+ [],
+ -42,
+ true,
+ false,
+ null,
+ {
+ "integer": 1234567890,
+ "real": -9876.543210,
+ "e": 0.123456789e-12,
+ "E": 1.234567890E+34,
+ "": 23456789012E66,
+ "zero": 0,
+ "one": 1,
+ "space": " ",
+ "quote": "\"",
+ "backslash": "\\",
+ "controls": "\b\f\n\r\t",
+ "slash": "/ & \/",
+ "alpha": "abcdefghijklmnopqrstuvwyz",
+ "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+ "digit": "0123456789",
+ "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+ "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+ "true": true,
+ "false": false,
+ "null": null,
+ "array":[ ],
+ "object":{ },
+ "address": "50 St. James Street",
+ "url": "http://www.JSON.org/",
+ "comment": "// /* <!-- --",
+ "# -- --> */": " ",
+ " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5 , 6 ,7 ],"compact": [1,2,3,4,5,6,7],
+ "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+ "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+ "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+ },
+ 0.5 ,98.6
+,
+99.44
+,
+
+1066,
+1e1,
+0.1e1,
+1e-1,
+1e00,2e+00,2e-00
+,"rosebud"]
+'''
+
+class TestPass1(TestCase):
+ def test_parse(self):
+ # test in/out equivalence and parsing
+ res = json.loads(JSON)
+ out = json.dumps(res)
+ self.assertEqual(res, json.loads(out))
diff --git a/pyload/lib/simplejson/tests/test_pass2.py b/pyload/lib/simplejson/tests/test_pass2.py
new file mode 100644
index 000000000..5d812b3bb
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_pass2.py
@@ -0,0 +1,14 @@
+from unittest import TestCase
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass2.json
+JSON = r'''
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
+'''
+
+class TestPass2(TestCase):
+ def test_parse(self):
+ # test in/out equivalence and parsing
+ res = json.loads(JSON)
+ out = json.dumps(res)
+ self.assertEqual(res, json.loads(out))
diff --git a/pyload/lib/simplejson/tests/test_pass3.py b/pyload/lib/simplejson/tests/test_pass3.py
new file mode 100644
index 000000000..821d60b22
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_pass3.py
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass3.json
+JSON = r'''
+{
+ "JSON Test Pattern pass3": {
+ "The outermost value": "must be an object or array.",
+ "In this test": "It is an object."
+ }
+}
+'''
+
+class TestPass3(TestCase):
+ def test_parse(self):
+ # test in/out equivalence and parsing
+ res = json.loads(JSON)
+ out = json.dumps(res)
+ self.assertEqual(res, json.loads(out))
diff --git a/pyload/lib/simplejson/tests/test_recursion.py b/pyload/lib/simplejson/tests/test_recursion.py
new file mode 100644
index 000000000..662eb667e
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_recursion.py
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class JSONTestObject:
+ pass
+
+
+class RecursiveJSONEncoder(json.JSONEncoder):
+ recurse = False
+ def default(self, o):
+ if o is JSONTestObject:
+ if self.recurse:
+ return [JSONTestObject]
+ else:
+ return 'JSONTestObject'
+ return json.JSONEncoder.default(o)
+
+
+class TestRecursion(TestCase):
+ def test_listrecursion(self):
+ x = []
+ x.append(x)
+ try:
+ json.dumps(x)
+ except ValueError:
+ pass
+ else:
+ self.fail("didn't raise ValueError on list recursion")
+ x = []
+ y = [x]
+ x.append(y)
+ try:
+ json.dumps(x)
+ except ValueError:
+ pass
+ else:
+ self.fail("didn't raise ValueError on alternating list recursion")
+ y = []
+ x = [y, y]
+ # ensure that the marker is cleared
+ json.dumps(x)
+
+ def test_dictrecursion(self):
+ x = {}
+ x["test"] = x
+ try:
+ json.dumps(x)
+ except ValueError:
+ pass
+ else:
+ self.fail("didn't raise ValueError on dict recursion")
+ x = {}
+ y = {"a": x, "b": x}
+ # ensure that the marker is cleared
+ json.dumps(y)
+
+ def test_defaultrecursion(self):
+ enc = RecursiveJSONEncoder()
+ self.assertEqual(enc.encode(JSONTestObject), '"JSONTestObject"')
+ enc.recurse = True
+ try:
+ enc.encode(JSONTestObject)
+ except ValueError:
+ pass
+ else:
+ self.fail("didn't raise ValueError on default recursion")
diff --git a/pyload/lib/simplejson/tests/test_scanstring.py b/pyload/lib/simplejson/tests/test_scanstring.py
new file mode 100644
index 000000000..3d98f0d82
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_scanstring.py
@@ -0,0 +1,194 @@
+import sys
+from unittest import TestCase
+
+import simplejson as json
+import simplejson.decoder
+from simplejson.compat import b, PY3
+
+class TestScanString(TestCase):
+ # The bytes type is intentionally not used in most of these tests
+ # under Python 3 because the decoder immediately coerces to str before
+ # calling scanstring. In Python 2 we are testing the code paths
+ # for both unicode and str.
+ #
+ # The reason this is done is because Python 3 would require
+ # entirely different code paths for parsing bytes and str.
+ #
+ def test_py_scanstring(self):
+ self._test_scanstring(simplejson.decoder.py_scanstring)
+
+ def test_c_scanstring(self):
+ if not simplejson.decoder.c_scanstring:
+ return
+ self._test_scanstring(simplejson.decoder.c_scanstring)
+
+ def _test_scanstring(self, scanstring):
+ if sys.maxunicode == 65535:
+ self.assertEqual(
+ scanstring(u'"z\U0001d120x"', 1, None, True),
+ (u'z\U0001d120x', 6))
+ else:
+ self.assertEqual(
+ scanstring(u'"z\U0001d120x"', 1, None, True),
+ (u'z\U0001d120x', 5))
+
+ self.assertEqual(
+ scanstring('"\\u007b"', 1, None, True),
+ (u'{', 8))
+
+ self.assertEqual(
+ scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
+ (u'A JSON payload should be an object or array, not a string.', 60))
+
+ self.assertEqual(
+ scanstring('["Unclosed array"', 2, None, True),
+ (u'Unclosed array', 17))
+
+ self.assertEqual(
+ scanstring('["extra comma",]', 2, None, True),
+ (u'extra comma', 14))
+
+ self.assertEqual(
+ scanstring('["double extra comma",,]', 2, None, True),
+ (u'double extra comma', 21))
+
+ self.assertEqual(
+ scanstring('["Comma after the close"],', 2, None, True),
+ (u'Comma after the close', 24))
+
+ self.assertEqual(
+ scanstring('["Extra close"]]', 2, None, True),
+ (u'Extra close', 14))
+
+ self.assertEqual(
+ scanstring('{"Extra comma": true,}', 2, None, True),
+ (u'Extra comma', 14))
+
+ self.assertEqual(
+ scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
+ (u'Extra value after close', 26))
+
+ self.assertEqual(
+ scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
+ (u'Illegal expression', 21))
+
+ self.assertEqual(
+ scanstring('{"Illegal invocation": alert()}', 2, None, True),
+ (u'Illegal invocation', 21))
+
+ self.assertEqual(
+ scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
+ (u'Numbers cannot have leading zeroes', 37))
+
+ self.assertEqual(
+ scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
+ (u'Numbers cannot be hex', 24))
+
+ self.assertEqual(
+ scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
+ (u'Too deep', 30))
+
+ self.assertEqual(
+ scanstring('{"Missing colon" null}', 2, None, True),
+ (u'Missing colon', 16))
+
+ self.assertEqual(
+ scanstring('{"Double colon":: null}', 2, None, True),
+ (u'Double colon', 15))
+
+ self.assertEqual(
+ scanstring('{"Comma instead of colon", null}', 2, None, True),
+ (u'Comma instead of colon', 25))
+
+ self.assertEqual(
+ scanstring('["Colon instead of comma": false]', 2, None, True),
+ (u'Colon instead of comma', 25))
+
+ self.assertEqual(
+ scanstring('["Bad value", truth]', 2, None, True),
+ (u'Bad value', 12))
+
+ for c in map(chr, range(0x00, 0x1f)):
+ self.assertEqual(
+ scanstring(c + '"', 0, None, False),
+ (c, 2))
+ self.assertRaises(
+ ValueError,
+ scanstring, c + '"', 0, None, True)
+
+ self.assertRaises(ValueError, scanstring, '', 0, None, True)
+ self.assertRaises(ValueError, scanstring, 'a', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\u', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\u0', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\u01', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\u012', 0, None, True)
+ self.assertRaises(ValueError, scanstring, '\\u0123', 0, None, True)
+ if sys.maxunicode > 65535:
+ self.assertRaises(ValueError,
+ scanstring, '\\ud834\\u"', 0, None, True)
+ self.assertRaises(ValueError,
+ scanstring, '\\ud834\\x0123"', 0, None, True)
+
+ def test_issue3623(self):
+ self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
+ "xxx")
+ self.assertRaises(UnicodeDecodeError,
+ json.encoder.encode_basestring_ascii, b("xx\xff"))
+
+ def test_overflow(self):
+ # Python 2.5 does not have maxsize, Python 3 does not have maxint
+ maxsize = getattr(sys, 'maxsize', getattr(sys, 'maxint', None))
+ assert maxsize is not None
+ self.assertRaises(OverflowError, json.decoder.scanstring, "xxx",
+ maxsize + 1)
+
+ def test_surrogates(self):
+ scanstring = json.decoder.scanstring
+
+ def assertScan(given, expect, test_utf8=True):
+ givens = [given]
+ if not PY3 and test_utf8:
+ givens.append(given.encode('utf8'))
+ for given in givens:
+ (res, count) = scanstring(given, 1, None, True)
+ self.assertEqual(len(given), count)
+ self.assertEqual(res, expect)
+
+ assertScan(
+ u'"z\\ud834\\u0079x"',
+ u'z\ud834yx')
+ assertScan(
+ u'"z\\ud834\\udd20x"',
+ u'z\U0001d120x')
+ assertScan(
+ u'"z\\ud834\\ud834\\udd20x"',
+ u'z\ud834\U0001d120x')
+ assertScan(
+ u'"z\\ud834x"',
+ u'z\ud834x')
+ assertScan(
+ u'"z\\udd20x"',
+ u'z\udd20x')
+ assertScan(
+ u'"z\ud834x"',
+ u'z\ud834x')
+ # It may look strange to join strings together, but Python is drunk.
+ # https://gist.github.com/etrepum/5538443
+ assertScan(
+ u'"z\\ud834\udd20x12345"',
+ u''.join([u'z\ud834', u'\udd20x12345']))
+ assertScan(
+ u'"z\ud834\\udd20x"',
+ u''.join([u'z\ud834', u'\udd20x']))
+ # these have different behavior given UTF8 input, because the surrogate
+ # pair may be joined (in maxunicode > 65535 builds)
+ assertScan(
+ u''.join([u'"z\ud834', u'\udd20x"']),
+ u''.join([u'z\ud834', u'\udd20x']),
+ test_utf8=False)
+
+ self.assertRaises(ValueError,
+ scanstring, u'"z\\ud83x"', 1, None, True)
+ self.assertRaises(ValueError,
+ scanstring, u'"z\\ud834\\udd2x"', 1, None, True)
diff --git a/pyload/lib/simplejson/tests/test_separators.py b/pyload/lib/simplejson/tests/test_separators.py
new file mode 100644
index 000000000..91b4d4fb6
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_separators.py
@@ -0,0 +1,42 @@
+import textwrap
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestSeparators(TestCase):
+ def test_separators(self):
+ h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+ {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+ expect = textwrap.dedent("""\
+ [
+ [
+ "blorpie"
+ ] ,
+ [
+ "whoops"
+ ] ,
+ [] ,
+ "d-shtaeou" ,
+ "d-nthiouh" ,
+ "i-vhbjkhnth" ,
+ {
+ "nifty" : 87
+ } ,
+ {
+ "field" : "yes" ,
+ "morefield" : false
+ }
+ ]""")
+
+
+ d1 = json.dumps(h)
+ d2 = json.dumps(h, indent=' ', sort_keys=True, separators=(' ,', ' : '))
+
+ h1 = json.loads(d1)
+ h2 = json.loads(d2)
+
+ self.assertEqual(h1, h)
+ self.assertEqual(h2, h)
+ self.assertEqual(d2, expect)
diff --git a/pyload/lib/simplejson/tests/test_speedups.py b/pyload/lib/simplejson/tests/test_speedups.py
new file mode 100644
index 000000000..0a2b63bff
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_speedups.py
@@ -0,0 +1,39 @@
+import sys
+import unittest
+from unittest import TestCase
+
+from simplejson import encoder, scanner
+
+
+def has_speedups():
+ return encoder.c_make_encoder is not None
+
+
+def skip_if_speedups_missing(func):
+ def wrapper(*args, **kwargs):
+ if not has_speedups():
+ if hasattr(unittest, 'SkipTest'):
+ raise unittest.SkipTest("C Extension not available")
+ else:
+ sys.stdout.write("C Extension not available")
+ return
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+class TestDecode(TestCase):
+ @skip_if_speedups_missing
+ def test_make_scanner(self):
+ self.assertRaises(AttributeError, scanner.c_make_scanner, 1)
+
+ @skip_if_speedups_missing
+ def test_make_encoder(self):
+ self.assertRaises(
+ TypeError,
+ encoder.c_make_encoder,
+ None,
+ ("\xCD\x7D\x3D\x4E\x12\x4C\xF9\x79\xD7"
+ "\x52\xBA\x82\xF2\x27\x4A\x7D\xA0\xCA\x75"),
+ None
+ )
diff --git a/pyload/lib/simplejson/tests/test_tool.py b/pyload/lib/simplejson/tests/test_tool.py
new file mode 100644
index 000000000..ac2a14c90
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_tool.py
@@ -0,0 +1,97 @@
+from __future__ import with_statement
+import os
+import sys
+import textwrap
+import unittest
+import subprocess
+import tempfile
+try:
+ # Python 3.x
+ from test.support import strip_python_stderr
+except ImportError:
+ # Python 2.6+
+ try:
+ from test.test_support import strip_python_stderr
+ except ImportError:
+ # Python 2.5
+ import re
+ def strip_python_stderr(stderr):
+ return re.sub(
+ r"\[\d+ refs\]\r?\n?$".encode(),
+ "".encode(),
+ stderr).strip()
+
+class TestTool(unittest.TestCase):
+ data = """
+
+ [["blorpie"],[ "whoops" ] , [
+ ],\t"d-shtaeou",\r"d-nthiouh",
+ "i-vhbjkhnth", {"nifty":87}, {"morefield" :\tfalse,"field"
+ :"yes"} ]
+ """
+
+ expect = textwrap.dedent("""\
+ [
+ [
+ "blorpie"
+ ],
+ [
+ "whoops"
+ ],
+ [],
+ "d-shtaeou",
+ "d-nthiouh",
+ "i-vhbjkhnth",
+ {
+ "nifty": 87
+ },
+ {
+ "field": "yes",
+ "morefield": false
+ }
+ ]
+ """)
+
+ def runTool(self, args=None, data=None):
+ argv = [sys.executable, '-m', 'simplejson.tool']
+ if args:
+ argv.extend(args)
+ proc = subprocess.Popen(argv,
+ stdin=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ out, err = proc.communicate(data)
+ self.assertEqual(strip_python_stderr(err), ''.encode())
+ self.assertEqual(proc.returncode, 0)
+ return out
+
+ def test_stdin_stdout(self):
+ self.assertEqual(
+ self.runTool(data=self.data.encode()),
+ self.expect.encode())
+
+ def test_infile_stdout(self):
+ with tempfile.NamedTemporaryFile() as infile:
+ infile.write(self.data.encode())
+ infile.flush()
+ self.assertEqual(
+ self.runTool(args=[infile.name]),
+ self.expect.encode())
+
+ def test_infile_outfile(self):
+ with tempfile.NamedTemporaryFile() as infile:
+ infile.write(self.data.encode())
+ infile.flush()
+ # outfile will get overwritten by tool, so the delete
+ # may not work on some platforms. Do it manually.
+ outfile = tempfile.NamedTemporaryFile()
+ try:
+ self.assertEqual(
+ self.runTool(args=[infile.name, outfile.name]),
+ ''.encode())
+ with open(outfile.name, 'rb') as f:
+ self.assertEqual(f.read(), self.expect.encode())
+ finally:
+ outfile.close()
+ if os.path.exists(outfile.name):
+ os.unlink(outfile.name)
diff --git a/pyload/lib/simplejson/tests/test_tuple.py b/pyload/lib/simplejson/tests/test_tuple.py
new file mode 100644
index 000000000..a6a991005
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_tuple.py
@@ -0,0 +1,51 @@
+import unittest
+
+from simplejson.compat import StringIO
+import simplejson as json
+
+class TestTuples(unittest.TestCase):
+ def test_tuple_array_dumps(self):
+ t = (1, 2, 3)
+ expect = json.dumps(list(t))
+ # Default is True
+ self.assertEqual(expect, json.dumps(t))
+ self.assertEqual(expect, json.dumps(t, tuple_as_array=True))
+ self.assertRaises(TypeError, json.dumps, t, tuple_as_array=False)
+ # Ensure that the "default" does not get called
+ self.assertEqual(expect, json.dumps(t, default=repr))
+ self.assertEqual(expect, json.dumps(t, tuple_as_array=True,
+ default=repr))
+ # Ensure that the "default" gets called
+ self.assertEqual(
+ json.dumps(repr(t)),
+ json.dumps(t, tuple_as_array=False, default=repr))
+
+ def test_tuple_array_dump(self):
+ t = (1, 2, 3)
+ expect = json.dumps(list(t))
+ # Default is True
+ sio = StringIO()
+ json.dump(t, sio)
+ self.assertEqual(expect, sio.getvalue())
+ sio = StringIO()
+ json.dump(t, sio, tuple_as_array=True)
+ self.assertEqual(expect, sio.getvalue())
+ self.assertRaises(TypeError, json.dump, t, StringIO(),
+ tuple_as_array=False)
+ # Ensure that the "default" does not get called
+ sio = StringIO()
+ json.dump(t, sio, default=repr)
+ self.assertEqual(expect, sio.getvalue())
+ sio = StringIO()
+ json.dump(t, sio, tuple_as_array=True, default=repr)
+ self.assertEqual(expect, sio.getvalue())
+ # Ensure that the "default" gets called
+ sio = StringIO()
+ json.dump(t, sio, tuple_as_array=False, default=repr)
+ self.assertEqual(
+ json.dumps(repr(t)),
+ sio.getvalue())
+
+class TestNamedTuple(unittest.TestCase):
+ def test_namedtuple_dump(self):
+ pass
diff --git a/pyload/lib/simplejson/tests/test_unicode.py b/pyload/lib/simplejson/tests/test_unicode.py
new file mode 100644
index 000000000..3b37f6599
--- /dev/null
+++ b/pyload/lib/simplejson/tests/test_unicode.py
@@ -0,0 +1,153 @@
+import sys
+import codecs
+from unittest import TestCase
+
+import simplejson as json
+from simplejson.compat import unichr, text_type, b, u, BytesIO
+
+class TestUnicode(TestCase):
+ def test_encoding1(self):
+ encoder = json.JSONEncoder(encoding='utf-8')
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ s = u.encode('utf-8')
+ ju = encoder.encode(u)
+ js = encoder.encode(s)
+ self.assertEqual(ju, js)
+
+ def test_encoding2(self):
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ s = u.encode('utf-8')
+ ju = json.dumps(u, encoding='utf-8')
+ js = json.dumps(s, encoding='utf-8')
+ self.assertEqual(ju, js)
+
+ def test_encoding3(self):
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ j = json.dumps(u)
+ self.assertEqual(j, '"\\u03b1\\u03a9"')
+
+ def test_encoding4(self):
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ j = json.dumps([u])
+ self.assertEqual(j, '["\\u03b1\\u03a9"]')
+
+ def test_encoding5(self):
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ j = json.dumps(u, ensure_ascii=False)
+ self.assertEqual(j, u'"' + u + u'"')
+
+ def test_encoding6(self):
+ u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+ j = json.dumps([u], ensure_ascii=False)
+ self.assertEqual(j, u'["' + u + u'"]')
+
+ def test_big_unicode_encode(self):
+ u = u'\U0001d120'
+ self.assertEqual(json.dumps(u), '"\\ud834\\udd20"')
+ self.assertEqual(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
+
+ def test_big_unicode_decode(self):
+ u = u'z\U0001d120x'
+ self.assertEqual(json.loads('"' + u + '"'), u)
+ self.assertEqual(json.loads('"z\\ud834\\udd20x"'), u)
+
+ def test_unicode_decode(self):
+ for i in range(0, 0xd7ff):
+ u = unichr(i)
+ #s = '"\\u{0:04x}"'.format(i)
+ s = '"\\u%04x"' % (i,)
+ self.assertEqual(json.loads(s), u)
+
+ def test_object_pairs_hook_with_unicode(self):
+ s = u'{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}'
+ p = [(u"xkd", 1), (u"kcw", 2), (u"art", 3), (u"hxm", 4),
+ (u"qrt", 5), (u"pad", 6), (u"hoy", 7)]
+ self.assertEqual(json.loads(s), eval(s))
+ self.assertEqual(json.loads(s, object_pairs_hook=lambda x: x), p)
+ od = json.loads(s, object_pairs_hook=json.OrderedDict)
+ self.assertEqual(od, json.OrderedDict(p))
+ self.assertEqual(type(od), json.OrderedDict)
+ # the object_pairs_hook takes priority over the object_hook
+ self.assertEqual(json.loads(s,
+ object_pairs_hook=json.OrderedDict,
+ object_hook=lambda x: None),
+ json.OrderedDict(p))
+
+
+ def test_default_encoding(self):
+ self.assertEqual(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
+ {'a': u'\xe9'})
+
+ def test_unicode_preservation(self):
+ self.assertEqual(type(json.loads(u'""')), text_type)
+ self.assertEqual(type(json.loads(u'"a"')), text_type)
+ self.assertEqual(type(json.loads(u'["a"]')[0]), text_type)
+
+ def test_ensure_ascii_false_returns_unicode(self):
+ # http://code.google.com/p/simplejson/issues/detail?id=48
+ self.assertEqual(type(json.dumps([], ensure_ascii=False)), text_type)
+ self.assertEqual(type(json.dumps(0, ensure_ascii=False)), text_type)
+ self.assertEqual(type(json.dumps({}, ensure_ascii=False)), text_type)
+ self.assertEqual(type(json.dumps("", ensure_ascii=False)), text_type)
+
+ def test_ensure_ascii_false_bytestring_encoding(self):
+ # http://code.google.com/p/simplejson/issues/detail?id=48
+ doc1 = {u'quux': b('Arr\xc3\xaat sur images')}
+ doc2 = {u'quux': u('Arr\xeat sur images')}
+ doc_ascii = '{"quux": "Arr\\u00eat sur images"}'
+ doc_unicode = u'{"quux": "Arr\xeat sur images"}'
+ self.assertEqual(json.dumps(doc1), doc_ascii)
+ self.assertEqual(json.dumps(doc2), doc_ascii)
+ self.assertEqual(json.dumps(doc1, ensure_ascii=False), doc_unicode)
+ self.assertEqual(json.dumps(doc2, ensure_ascii=False), doc_unicode)
+
+ def test_ensure_ascii_linebreak_encoding(self):
+ # http://timelessrepo.com/json-isnt-a-javascript-subset
+ s1 = u'\u2029\u2028'
+ s2 = s1.encode('utf8')
+ expect = '"\\u2029\\u2028"'
+ self.assertEqual(json.dumps(s1), expect)
+ self.assertEqual(json.dumps(s2), expect)
+ self.assertEqual(json.dumps(s1, ensure_ascii=False), expect)
+ self.assertEqual(json.dumps(s2, ensure_ascii=False), expect)
+
+ def test_invalid_escape_sequences(self):
+ # incomplete escape sequence
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u12')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u123')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1234')
+ # invalid escape sequence
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u123x"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u12x4"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\u1x34"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ux234"')
+ if sys.maxunicode > 65535:
+ # invalid escape sequence for low surrogate
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u0"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u00"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u000"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u000x"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u00x0"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\u0x00"')
+ self.assertRaises(json.JSONDecodeError, json.loads, '"\\ud800\\ux000"')
+
+ def test_ensure_ascii_still_works(self):
+ # in the ascii range, ensure that everything is the same
+ for c in map(unichr, range(0, 127)):
+ self.assertEqual(
+ json.dumps(c, ensure_ascii=False),
+ json.dumps(c))
+ snowman = u'\N{SNOWMAN}'
+ self.assertEqual(
+ json.dumps(c, ensure_ascii=False),
+ '"' + c + '"')
+
+ def test_strip_bom(self):
+ content = u"\u3053\u3093\u306b\u3061\u308f"
+ json_doc = codecs.BOM_UTF8 + b(json.dumps(content))
+ self.assertEqual(json.load(BytesIO(json_doc)), content)
+ for doc in json_doc, json_doc.decode('utf8'):
+ self.assertEqual(json.loads(doc), content)
diff --git a/pyload/lib/simplejson/tool.py b/pyload/lib/simplejson/tool.py
new file mode 100644
index 000000000..062e8e2c1
--- /dev/null
+++ b/pyload/lib/simplejson/tool.py
@@ -0,0 +1,42 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+ $ echo '{"json":"obj"}' | python -m simplejson.tool
+ {
+ "json": "obj"
+ }
+ $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+ Expecting property name: line 1 column 2 (char 2)
+
+"""
+from __future__ import with_statement
+import sys
+import simplejson as json
+
+def main():
+ if len(sys.argv) == 1:
+ infile = sys.stdin
+ outfile = sys.stdout
+ elif len(sys.argv) == 2:
+ infile = open(sys.argv[1], 'r')
+ outfile = sys.stdout
+ elif len(sys.argv) == 3:
+ infile = open(sys.argv[1], 'r')
+ outfile = open(sys.argv[2], 'w')
+ else:
+ raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+ with infile:
+ try:
+ obj = json.load(infile,
+ object_pairs_hook=json.OrderedDict,
+ use_decimal=True)
+ except ValueError:
+ raise SystemExit(sys.exc_info()[1])
+ with outfile:
+ json.dump(obj, outfile, sort_keys=True, indent=' ', use_decimal=True)
+ outfile.write('\n')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pyload/lib/thrift/TSCons.py b/pyload/lib/thrift/TSCons.py
new file mode 100644
index 000000000..da8d2833b
--- /dev/null
+++ b/pyload/lib/thrift/TSCons.py
@@ -0,0 +1,35 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from os import path
+from SCons.Builder import Builder
+
+
+def scons_env(env, add=''):
+ opath = path.dirname(path.abspath('$TARGET'))
+ lstr = 'thrift --gen cpp -o ' + opath + ' ' + add + ' $SOURCE'
+ cppbuild = Builder(action=lstr)
+ env.Append(BUILDERS={'ThriftCpp': cppbuild})
+
+
+def gen_cpp(env, dir, file):
+ scons_env(env)
+ suffixes = ['_types.h', '_types.cpp']
+ targets = map(lambda s: 'gen-cpp/' + file + s, suffixes)
+ return env.ThriftCpp(targets, dir + file + '.thrift')
diff --git a/pyload/lib/thrift/TSerialization.py b/pyload/lib/thrift/TSerialization.py
new file mode 100644
index 000000000..8a58d89df
--- /dev/null
+++ b/pyload/lib/thrift/TSerialization.py
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from protocol import TBinaryProtocol
+from transport import TTransport
+
+
+def serialize(thrift_object,
+ protocol_factory=TBinaryProtocol.TBinaryProtocolFactory()):
+ transport = TTransport.TMemoryBuffer()
+ protocol = protocol_factory.getProtocol(transport)
+ thrift_object.write(protocol)
+ return transport.getvalue()
+
+
+def deserialize(base,
+ buf,
+ protocol_factory=TBinaryProtocol.TBinaryProtocolFactory()):
+ transport = TTransport.TMemoryBuffer(buf)
+ protocol = protocol_factory.getProtocol(transport)
+ base.read(protocol)
+ return base
diff --git a/pyload/lib/thrift/TTornado.py b/pyload/lib/thrift/TTornado.py
new file mode 100644
index 000000000..af309c3d9
--- /dev/null
+++ b/pyload/lib/thrift/TTornado.py
@@ -0,0 +1,153 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from cStringIO import StringIO
+import logging
+import socket
+import struct
+
+from thrift.transport import TTransport
+from thrift.transport.TTransport import TTransportException
+
+from tornado import gen
+from tornado import iostream
+from tornado import netutil
+
+
+class TTornadoStreamTransport(TTransport.TTransportBase):
+ """a framed, buffered transport over a Tornado stream"""
+ def __init__(self, host, port, stream=None):
+ self.host = host
+ self.port = port
+ self.is_queuing_reads = False
+ self.read_queue = []
+ self.__wbuf = StringIO()
+
+ # servers provide a ready-to-go stream
+ self.stream = stream
+ if self.stream is not None:
+ self._set_close_callback()
+
+ # not the same number of parameters as TTransportBase.open
+ def open(self, callback):
+ logging.debug('socket connecting')
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ self.stream = iostream.IOStream(sock)
+
+ def on_close_in_connect(*_):
+ message = 'could not connect to {}:{}'.format(self.host, self.port)
+ raise TTransportException(
+ type=TTransportException.NOT_OPEN,
+ message=message)
+ self.stream.set_close_callback(on_close_in_connect)
+
+ def finish(*_):
+ self._set_close_callback()
+ callback()
+
+ self.stream.connect((self.host, self.port), callback=finish)
+
+ def _set_close_callback(self):
+ def on_close():
+ raise TTransportException(
+ type=TTransportException.END_OF_FILE,
+ message='socket closed')
+ self.stream.set_close_callback(self.close)
+
+ def close(self):
+ # don't raise if we intend to close
+ self.stream.set_close_callback(None)
+ self.stream.close()
+
+ def read(self, _):
+ # The generated code for Tornado shouldn't do individual reads -- only
+ # frames at a time
+ assert "you're doing it wrong" is True
+
+ @gen.engine
+ def readFrame(self, callback):
+ self.read_queue.append(callback)
+ logging.debug('read queue: %s', self.read_queue)
+
+ if self.is_queuing_reads:
+ # If a read is already in flight, then the while loop below should
+ # pull it from self.read_queue
+ return
+
+ self.is_queuing_reads = True
+ while self.read_queue:
+ next_callback = self.read_queue.pop()
+ result = yield gen.Task(self._readFrameFromStream)
+ next_callback(result)
+ self.is_queuing_reads = False
+
+ @gen.engine
+ def _readFrameFromStream(self, callback):
+ logging.debug('_readFrameFromStream')
+ frame_header = yield gen.Task(self.stream.read_bytes, 4)
+ frame_length, = struct.unpack('!i', frame_header)
+ logging.debug('received frame header, frame length = %i', frame_length)
+ frame = yield gen.Task(self.stream.read_bytes, frame_length)
+ logging.debug('received frame payload')
+ callback(frame)
+
+ def write(self, buf):
+ self.__wbuf.write(buf)
+
+ def flush(self, callback=None):
+ wout = self.__wbuf.getvalue()
+ wsz = len(wout)
+ # reset wbuf before write/flush to preserve state on underlying failure
+ self.__wbuf = StringIO()
+ # N.B.: Doing this string concatenation is WAY cheaper than making
+ # two separate calls to the underlying socket object. Socket writes in
+ # Python turn out to be REALLY expensive, but it seems to do a pretty
+ # good job of managing string buffer operations without excessive copies
+ buf = struct.pack("!i", wsz) + wout
+
+ logging.debug('writing frame length = %i', wsz)
+ self.stream.write(buf, callback)
+
+
+class TTornadoServer(netutil.TCPServer):
+ def __init__(self, processor, iprot_factory, oprot_factory=None,
+ *args, **kwargs):
+ super(TTornadoServer, self).__init__(*args, **kwargs)
+
+ self._processor = processor
+ self._iprot_factory = iprot_factory
+ self._oprot_factory = (oprot_factory if oprot_factory is not None
+ else iprot_factory)
+
+ def handle_stream(self, stream, address):
+ try:
+ host, port = address
+ trans = TTornadoStreamTransport(host=host, port=port, stream=stream)
+ oprot = self._oprot_factory.getProtocol(trans)
+
+ def next_pass():
+ if not trans.stream.closed():
+ self._processor.process(trans, self._iprot_factory, oprot,
+ callback=next_pass)
+
+ next_pass()
+
+ except Exception:
+ logging.exception('thrift exception in handle_stream')
+ trans.close()
diff --git a/pyload/lib/thrift/Thrift.py b/pyload/lib/thrift/Thrift.py
new file mode 100644
index 000000000..9890af7e1
--- /dev/null
+++ b/pyload/lib/thrift/Thrift.py
@@ -0,0 +1,170 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import sys
+
+
+class TType:
+ STOP = 0
+ VOID = 1
+ BOOL = 2
+ BYTE = 3
+ I08 = 3
+ DOUBLE = 4
+ I16 = 6
+ I32 = 8
+ I64 = 10
+ STRING = 11
+ UTF7 = 11
+ STRUCT = 12
+ MAP = 13
+ SET = 14
+ LIST = 15
+ UTF8 = 16
+ UTF16 = 17
+
+ _VALUES_TO_NAMES = ('STOP',
+ 'VOID',
+ 'BOOL',
+ 'BYTE',
+ 'DOUBLE',
+ None,
+ 'I16',
+ None,
+ 'I32',
+ None,
+ 'I64',
+ 'STRING',
+ 'STRUCT',
+ 'MAP',
+ 'SET',
+ 'LIST',
+ 'UTF8',
+ 'UTF16')
+
+
+class TMessageType:
+ CALL = 1
+ REPLY = 2
+ EXCEPTION = 3
+ ONEWAY = 4
+
+
+class TProcessor:
+ """Base class for procsessor, which works on two streams."""
+
+ def process(iprot, oprot):
+ pass
+
+
+class TException(Exception):
+ """Base class for all thrift exceptions."""
+
+ # BaseException.message is deprecated in Python v[2.6,3.0)
+ if (2, 6, 0) <= sys.version_info < (3, 0):
+ def _get_message(self):
+ return self._message
+
+ def _set_message(self, message):
+ self._message = message
+ message = property(_get_message, _set_message)
+
+ def __init__(self, message=None):
+ Exception.__init__(self, message)
+ self.message = message
+
+
+class TApplicationException(TException):
+ """Application level thrift exceptions."""
+
+ UNKNOWN = 0
+ UNKNOWN_METHOD = 1
+ INVALID_MESSAGE_TYPE = 2
+ WRONG_METHOD_NAME = 3
+ BAD_SEQUENCE_ID = 4
+ MISSING_RESULT = 5
+ INTERNAL_ERROR = 6
+ PROTOCOL_ERROR = 7
+ INVALID_TRANSFORM = 8
+ INVALID_PROTOCOL = 9
+ UNSUPPORTED_CLIENT_TYPE = 10
+
+ def __init__(self, type=UNKNOWN, message=None):
+ TException.__init__(self, message)
+ self.type = type
+
+ def __str__(self):
+ if self.message:
+ return self.message
+ elif self.type == self.UNKNOWN_METHOD:
+ return 'Unknown method'
+ elif self.type == self.INVALID_MESSAGE_TYPE:
+ return 'Invalid message type'
+ elif self.type == self.WRONG_METHOD_NAME:
+ return 'Wrong method name'
+ elif self.type == self.BAD_SEQUENCE_ID:
+ return 'Bad sequence ID'
+ elif self.type == self.MISSING_RESULT:
+ return 'Missing result'
+ elif self.type == self.INTERNAL_ERROR:
+ return 'Internal error'
+ elif self.type == self.PROTOCOL_ERROR:
+ return 'Protocol error'
+ elif self.type == self.INVALID_TRANSFORM:
+ return 'Invalid transform'
+ elif self.type == self.INVALID_PROTOCOL:
+ return 'Invalid protocol'
+ elif self.type == self.UNSUPPORTED_CLIENT_TYPE:
+ return 'Unsupported client type'
+ else:
+ return 'Default (unknown) TApplicationException'
+
+ def read(self, iprot):
+ iprot.readStructBegin()
+ while True:
+ (fname, ftype, fid) = iprot.readFieldBegin()
+ if ftype == TType.STOP:
+ break
+ if fid == 1:
+ if ftype == TType.STRING:
+ self.message = iprot.readString()
+ else:
+ iprot.skip(ftype)
+ elif fid == 2:
+ if ftype == TType.I32:
+ self.type = iprot.readI32()
+ else:
+ iprot.skip(ftype)
+ else:
+ iprot.skip(ftype)
+ iprot.readFieldEnd()
+ iprot.readStructEnd()
+
+ def write(self, oprot):
+ oprot.writeStructBegin('TApplicationException')
+ if self.message is not None:
+ oprot.writeFieldBegin('message', TType.STRING, 1)
+ oprot.writeString(self.message)
+ oprot.writeFieldEnd()
+ if self.type is not None:
+ oprot.writeFieldBegin('type', TType.I32, 2)
+ oprot.writeI32(self.type)
+ oprot.writeFieldEnd()
+ oprot.writeFieldStop()
+ oprot.writeStructEnd()
diff --git a/module/lib/thrift/__init__.py b/pyload/lib/thrift/__init__.py
index 48d659c40..48d659c40 100644
--- a/module/lib/thrift/__init__.py
+++ b/pyload/lib/thrift/__init__.py
diff --git a/pyload/lib/thrift/protocol/TBase.py b/pyload/lib/thrift/protocol/TBase.py
new file mode 100644
index 000000000..6cbd5f39a
--- /dev/null
+++ b/pyload/lib/thrift/protocol/TBase.py
@@ -0,0 +1,81 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from thrift.Thrift import *
+from thrift.protocol import TBinaryProtocol
+from thrift.transport import TTransport
+
+try:
+ from thrift.protocol import fastbinary
+except:
+ fastbinary = None
+
+
+class TBase(object):
+ __slots__ = []
+
+ def __repr__(self):
+ L = ['%s=%r' % (key, getattr(self, key))
+ for key in self.__slots__]
+ return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return False
+ for attr in self.__slots__:
+ my_val = getattr(self, attr)
+ other_val = getattr(other, attr)
+ if my_val != other_val:
+ return False
+ return True
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def read(self, iprot):
+ if (iprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and
+ isinstance(iprot.trans, TTransport.CReadableTransport) and
+ self.thrift_spec is not None and
+ fastbinary is not None):
+ fastbinary.decode_binary(self,
+ iprot.trans,
+ (self.__class__, self.thrift_spec))
+ return
+ iprot.readStruct(self, self.thrift_spec)
+
+ def write(self, oprot):
+ if (oprot.__class__ == TBinaryProtocol.TBinaryProtocolAccelerated and
+ self.thrift_spec is not None and
+ fastbinary is not None):
+ oprot.trans.write(
+ fastbinary.encode_binary(self, (self.__class__, self.thrift_spec)))
+ return
+ oprot.writeStruct(self, self.thrift_spec)
+
+
+class TExceptionBase(Exception):
+ # old style class so python2.4 can raise exceptions derived from this
+ # This can't inherit from TBase because of that limitation.
+ __slots__ = []
+
+ __repr__ = TBase.__repr__.im_func
+ __eq__ = TBase.__eq__.im_func
+ __ne__ = TBase.__ne__.im_func
+ read = TBase.read.im_func
+ write = TBase.write.im_func
diff --git a/pyload/lib/thrift/protocol/TBinaryProtocol.py b/pyload/lib/thrift/protocol/TBinaryProtocol.py
new file mode 100644
index 000000000..6fdd08c26
--- /dev/null
+++ b/pyload/lib/thrift/protocol/TBinaryProtocol.py
@@ -0,0 +1,260 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from TProtocol import *
+from struct import pack, unpack
+
+
+class TBinaryProtocol(TProtocolBase):
+ """Binary implementation of the Thrift protocol driver."""
+
+ # NastyHaxx. Python 2.4+ on 32-bit machines forces hex constants to be
+ # positive, converting this into a long. If we hardcode the int value
+ # instead it'll stay in 32 bit-land.
+
+ # VERSION_MASK = 0xffff0000
+ VERSION_MASK = -65536
+
+ # VERSION_1 = 0x80010000
+ VERSION_1 = -2147418112
+
+ TYPE_MASK = 0x000000ff
+
+ def __init__(self, trans, strictRead=False, strictWrite=True):
+ TProtocolBase.__init__(self, trans)
+ self.strictRead = strictRead
+ self.strictWrite = strictWrite
+
+ def writeMessageBegin(self, name, type, seqid):
+ if self.strictWrite:
+ self.writeI32(TBinaryProtocol.VERSION_1 | type)
+ self.writeString(name)
+ self.writeI32(seqid)
+ else:
+ self.writeString(name)
+ self.writeByte(type)
+ self.writeI32(seqid)
+
+ def writeMessageEnd(self):
+ pass
+
+ def writeStructBegin(self, name):
+ pass
+
+ def writeStructEnd(self):
+ pass
+
+ def writeFieldBegin(self, name, type, id):
+ self.writeByte(type)
+ self.writeI16(id)
+
+ def writeFieldEnd(self):
+ pass
+
+ def writeFieldStop(self):
+ self.writeByte(TType.STOP)
+
+ def writeMapBegin(self, ktype, vtype, size):
+ self.writeByte(ktype)
+ self.writeByte(vtype)
+ self.writeI32(size)
+
+ def writeMapEnd(self):
+ pass
+
+ def writeListBegin(self, etype, size):
+ self.writeByte(etype)
+ self.writeI32(size)
+
+ def writeListEnd(self):
+ pass
+
+ def writeSetBegin(self, etype, size):
+ self.writeByte(etype)
+ self.writeI32(size)
+
+ def writeSetEnd(self):
+ pass
+
+ def writeBool(self, bool):
+ if bool:
+ self.writeByte(1)
+ else:
+ self.writeByte(0)
+
+ def writeByte(self, byte):
+ buff = pack("!b", byte)
+ self.trans.write(buff)
+
+ def writeI16(self, i16):
+ buff = pack("!h", i16)
+ self.trans.write(buff)
+
+ def writeI32(self, i32):
+ buff = pack("!i", i32)
+ self.trans.write(buff)
+
+ def writeI64(self, i64):
+ buff = pack("!q", i64)
+ self.trans.write(buff)
+
+ def writeDouble(self, dub):
+ buff = pack("!d", dub)
+ self.trans.write(buff)
+
+ def writeString(self, str):
+ self.writeI32(len(str))
+ self.trans.write(str)
+
+ def readMessageBegin(self):
+ sz = self.readI32()
+ if sz < 0:
+ version = sz & TBinaryProtocol.VERSION_MASK
+ if version != TBinaryProtocol.VERSION_1:
+ raise TProtocolException(
+ type=TProtocolException.BAD_VERSION,
+ message='Bad version in readMessageBegin: %d' % (sz))
+ type = sz & TBinaryProtocol.TYPE_MASK
+ name = self.readString()
+ seqid = self.readI32()
+ else:
+ if self.strictRead:
+ raise TProtocolException(type=TProtocolException.BAD_VERSION,
+ message='No protocol version header')
+ name = self.trans.readAll(sz)
+ type = self.readByte()
+ seqid = self.readI32()
+ return (name, type, seqid)
+
+ def readMessageEnd(self):
+ pass
+
+ def readStructBegin(self):
+ pass
+
+ def readStructEnd(self):
+ pass
+
+ def readFieldBegin(self):
+ type = self.readByte()
+ if type == TType.STOP:
+ return (None, type, 0)
+ id = self.readI16()
+ return (None, type, id)
+
+ def readFieldEnd(self):
+ pass
+
+ def readMapBegin(self):
+ ktype = self.readByte()
+ vtype = self.readByte()
+ size = self.readI32()
+ return (ktype, vtype, size)
+
+ def readMapEnd(self):
+ pass
+
+ def readListBegin(self):
+ etype = self.readByte()
+ size = self.readI32()
+ return (etype, size)
+
+ def readListEnd(self):
+ pass
+
+ def readSetBegin(self):
+ etype = self.readByte()
+ size = self.readI32()
+ return (etype, size)
+
+ def readSetEnd(self):
+ pass
+
+ def readBool(self):
+ byte = self.readByte()
+ if byte == 0:
+ return False
+ return True
+
+ def readByte(self):
+ buff = self.trans.readAll(1)
+ val, = unpack('!b', buff)
+ return val
+
+ def readI16(self):
+ buff = self.trans.readAll(2)
+ val, = unpack('!h', buff)
+ return val
+
+ def readI32(self):
+ buff = self.trans.readAll(4)
+ val, = unpack('!i', buff)
+ return val
+
+ def readI64(self):
+ buff = self.trans.readAll(8)
+ val, = unpack('!q', buff)
+ return val
+
+ def readDouble(self):
+ buff = self.trans.readAll(8)
+ val, = unpack('!d', buff)
+ return val
+
+ def readString(self):
+ len = self.readI32()
+ str = self.trans.readAll(len)
+ return str
+
+
+class TBinaryProtocolFactory:
+ def __init__(self, strictRead=False, strictWrite=True):
+ self.strictRead = strictRead
+ self.strictWrite = strictWrite
+
+ def getProtocol(self, trans):
+ prot = TBinaryProtocol(trans, self.strictRead, self.strictWrite)
+ return prot
+
+
+class TBinaryProtocolAccelerated(TBinaryProtocol):
+ """C-Accelerated version of TBinaryProtocol.
+
+ This class does not override any of TBinaryProtocol's methods,
+ but the generated code recognizes it directly and will call into
+ our C module to do the encoding, bypassing this object entirely.
+ We inherit from TBinaryProtocol so that the normal TBinaryProtocol
+ encoding can happen if the fastbinary module doesn't work for some
+ reason. (TODO(dreiss): Make this happen sanely in more cases.)
+
+ In order to take advantage of the C module, just use
+ TBinaryProtocolAccelerated instead of TBinaryProtocol.
+
+ NOTE: This code was contributed by an external developer.
+ The internal Thrift team has reviewed and tested it,
+ but we cannot guarantee that it is production-ready.
+ Please feel free to report bugs and/or success stories
+ to the public mailing list.
+ """
+ pass
+
+
+class TBinaryProtocolAcceleratedFactory:
+ def getProtocol(self, trans):
+ return TBinaryProtocolAccelerated(trans)
diff --git a/pyload/lib/thrift/protocol/TCompactProtocol.py b/pyload/lib/thrift/protocol/TCompactProtocol.py
new file mode 100644
index 000000000..cdec60773
--- /dev/null
+++ b/pyload/lib/thrift/protocol/TCompactProtocol.py
@@ -0,0 +1,403 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from TProtocol import *
+from struct import pack, unpack
+
+__all__ = ['TCompactProtocol', 'TCompactProtocolFactory']
+
+CLEAR = 0
+FIELD_WRITE = 1
+VALUE_WRITE = 2
+CONTAINER_WRITE = 3
+BOOL_WRITE = 4
+FIELD_READ = 5
+CONTAINER_READ = 6
+VALUE_READ = 7
+BOOL_READ = 8
+
+
+def make_helper(v_from, container):
+ def helper(func):
+ def nested(self, *args, **kwargs):
+ assert self.state in (v_from, container), (self.state, v_from, container)
+ return func(self, *args, **kwargs)
+ return nested
+ return helper
+writer = make_helper(VALUE_WRITE, CONTAINER_WRITE)
+reader = make_helper(VALUE_READ, CONTAINER_READ)
+
+
+def makeZigZag(n, bits):
+ return (n << 1) ^ (n >> (bits - 1))
+
+
+def fromZigZag(n):
+ return (n >> 1) ^ -(n & 1)
+
+
+def writeVarint(trans, n):
+ out = []
+ while True:
+ if n & ~0x7f == 0:
+ out.append(n)
+ break
+ else:
+ out.append((n & 0xff) | 0x80)
+ n = n >> 7
+ trans.write(''.join(map(chr, out)))
+
+
+def readVarint(trans):
+ result = 0
+ shift = 0
+ while True:
+ x = trans.readAll(1)
+ byte = ord(x)
+ result |= (byte & 0x7f) << shift
+ if byte >> 7 == 0:
+ return result
+ shift += 7
+
+
+class CompactType:
+ STOP = 0x00
+ TRUE = 0x01
+ FALSE = 0x02
+ BYTE = 0x03
+ I16 = 0x04
+ I32 = 0x05
+ I64 = 0x06
+ DOUBLE = 0x07
+ BINARY = 0x08
+ LIST = 0x09
+ SET = 0x0A
+ MAP = 0x0B
+ STRUCT = 0x0C
+
+CTYPES = {TType.STOP: CompactType.STOP,
+ TType.BOOL: CompactType.TRUE, # used for collection
+ TType.BYTE: CompactType.BYTE,
+ TType.I16: CompactType.I16,
+ TType.I32: CompactType.I32,
+ TType.I64: CompactType.I64,
+ TType.DOUBLE: CompactType.DOUBLE,
+ TType.STRING: CompactType.BINARY,
+ TType.STRUCT: CompactType.STRUCT,
+ TType.LIST: CompactType.LIST,
+ TType.SET: CompactType.SET,
+ TType.MAP: CompactType.MAP
+ }
+
+TTYPES = {}
+for k, v in CTYPES.items():
+ TTYPES[v] = k
+TTYPES[CompactType.FALSE] = TType.BOOL
+del k
+del v
+
+
+class TCompactProtocol(TProtocolBase):
+ """Compact implementation of the Thrift protocol driver."""
+
+ PROTOCOL_ID = 0x82
+ VERSION = 1
+ VERSION_MASK = 0x1f
+ TYPE_MASK = 0xe0
+ TYPE_SHIFT_AMOUNT = 5
+
+ def __init__(self, trans):
+ TProtocolBase.__init__(self, trans)
+ self.state = CLEAR
+ self.__last_fid = 0
+ self.__bool_fid = None
+ self.__bool_value = None
+ self.__structs = []
+ self.__containers = []
+
+ def __writeVarint(self, n):
+ writeVarint(self.trans, n)
+
+ def writeMessageBegin(self, name, type, seqid):
+ assert self.state == CLEAR
+ self.__writeUByte(self.PROTOCOL_ID)
+ self.__writeUByte(self.VERSION | (type << self.TYPE_SHIFT_AMOUNT))
+ self.__writeVarint(seqid)
+ self.__writeString(name)
+ self.state = VALUE_WRITE
+
+ def writeMessageEnd(self):
+ assert self.state == VALUE_WRITE
+ self.state = CLEAR
+
+ def writeStructBegin(self, name):
+ assert self.state in (CLEAR, CONTAINER_WRITE, VALUE_WRITE), self.state
+ self.__structs.append((self.state, self.__last_fid))
+ self.state = FIELD_WRITE
+ self.__last_fid = 0
+
+ def writeStructEnd(self):
+ assert self.state == FIELD_WRITE
+ self.state, self.__last_fid = self.__structs.pop()
+
+ def writeFieldStop(self):
+ self.__writeByte(0)
+
+ def __writeFieldHeader(self, type, fid):
+ delta = fid - self.__last_fid
+ if 0 < delta <= 15:
+ self.__writeUByte(delta << 4 | type)
+ else:
+ self.__writeByte(type)
+ self.__writeI16(fid)
+ self.__last_fid = fid
+
+ def writeFieldBegin(self, name, type, fid):
+ assert self.state == FIELD_WRITE, self.state
+ if type == TType.BOOL:
+ self.state = BOOL_WRITE
+ self.__bool_fid = fid
+ else:
+ self.state = VALUE_WRITE
+ self.__writeFieldHeader(CTYPES[type], fid)
+
+ def writeFieldEnd(self):
+ assert self.state in (VALUE_WRITE, BOOL_WRITE), self.state
+ self.state = FIELD_WRITE
+
+ def __writeUByte(self, byte):
+ self.trans.write(pack('!B', byte))
+
+ def __writeByte(self, byte):
+ self.trans.write(pack('!b', byte))
+
+ def __writeI16(self, i16):
+ self.__writeVarint(makeZigZag(i16, 16))
+
+ def __writeSize(self, i32):
+ self.__writeVarint(i32)
+
+ def writeCollectionBegin(self, etype, size):
+ assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state
+ if size <= 14:
+ self.__writeUByte(size << 4 | CTYPES[etype])
+ else:
+ self.__writeUByte(0xf0 | CTYPES[etype])
+ self.__writeSize(size)
+ self.__containers.append(self.state)
+ self.state = CONTAINER_WRITE
+ writeSetBegin = writeCollectionBegin
+ writeListBegin = writeCollectionBegin
+
+ def writeMapBegin(self, ktype, vtype, size):
+ assert self.state in (VALUE_WRITE, CONTAINER_WRITE), self.state
+ if size == 0:
+ self.__writeByte(0)
+ else:
+ self.__writeSize(size)
+ self.__writeUByte(CTYPES[ktype] << 4 | CTYPES[vtype])
+ self.__containers.append(self.state)
+ self.state = CONTAINER_WRITE
+
+ def writeCollectionEnd(self):
+ assert self.state == CONTAINER_WRITE, self.state
+ self.state = self.__containers.pop()
+ writeMapEnd = writeCollectionEnd
+ writeSetEnd = writeCollectionEnd
+ writeListEnd = writeCollectionEnd
+
+ def writeBool(self, bool):
+ if self.state == BOOL_WRITE:
+ if bool:
+ ctype = CompactType.TRUE
+ else:
+ ctype = CompactType.FALSE
+ self.__writeFieldHeader(ctype, self.__bool_fid)
+ elif self.state == CONTAINER_WRITE:
+ if bool:
+ self.__writeByte(CompactType.TRUE)
+ else:
+ self.__writeByte(CompactType.FALSE)
+ else:
+ raise AssertionError("Invalid state in compact protocol")
+
+ writeByte = writer(__writeByte)
+ writeI16 = writer(__writeI16)
+
+ @writer
+ def writeI32(self, i32):
+ self.__writeVarint(makeZigZag(i32, 32))
+
+ @writer
+ def writeI64(self, i64):
+ self.__writeVarint(makeZigZag(i64, 64))
+
+ @writer
+ def writeDouble(self, dub):
+ self.trans.write(pack('!d', dub))
+
+ def __writeString(self, s):
+ self.__writeSize(len(s))
+ self.trans.write(s)
+ writeString = writer(__writeString)
+
+ def readFieldBegin(self):
+ assert self.state == FIELD_READ, self.state
+ type = self.__readUByte()
+ if type & 0x0f == TType.STOP:
+ return (None, 0, 0)
+ delta = type >> 4
+ if delta == 0:
+ fid = self.__readI16()
+ else:
+ fid = self.__last_fid + delta
+ self.__last_fid = fid
+ type = type & 0x0f
+ if type == CompactType.TRUE:
+ self.state = BOOL_READ
+ self.__bool_value = True
+ elif type == CompactType.FALSE:
+ self.state = BOOL_READ
+ self.__bool_value = False
+ else:
+ self.state = VALUE_READ
+ return (None, self.__getTType(type), fid)
+
+ def readFieldEnd(self):
+ assert self.state in (VALUE_READ, BOOL_READ), self.state
+ self.state = FIELD_READ
+
+ def __readUByte(self):
+ result, = unpack('!B', self.trans.readAll(1))
+ return result
+
+ def __readByte(self):
+ result, = unpack('!b', self.trans.readAll(1))
+ return result
+
+ def __readVarint(self):
+ return readVarint(self.trans)
+
+ def __readZigZag(self):
+ return fromZigZag(self.__readVarint())
+
+ def __readSize(self):
+ result = self.__readVarint()
+ if result < 0:
+ raise TException("Length < 0")
+ return result
+
+ def readMessageBegin(self):
+ assert self.state == CLEAR
+ proto_id = self.__readUByte()
+ if proto_id != self.PROTOCOL_ID:
+ raise TProtocolException(TProtocolException.BAD_VERSION,
+ 'Bad protocol id in the message: %d' % proto_id)
+ ver_type = self.__readUByte()
+ type = (ver_type & self.TYPE_MASK) >> self.TYPE_SHIFT_AMOUNT
+ version = ver_type & self.VERSION_MASK
+ if version != self.VERSION:
+ raise TProtocolException(TProtocolException.BAD_VERSION,
+ 'Bad version: %d (expect %d)' % (version, self.VERSION))
+ seqid = self.__readVarint()
+ name = self.__readString()
+ return (name, type, seqid)
+
+ def readMessageEnd(self):
+ assert self.state == CLEAR
+ assert len(self.__structs) == 0
+
+ def readStructBegin(self):
+ assert self.state in (CLEAR, CONTAINER_READ, VALUE_READ), self.state
+ self.__structs.append((self.state, self.__last_fid))
+ self.state = FIELD_READ
+ self.__last_fid = 0
+
+ def readStructEnd(self):
+ assert self.state == FIELD_READ
+ self.state, self.__last_fid = self.__structs.pop()
+
+ def readCollectionBegin(self):
+ assert self.state in (VALUE_READ, CONTAINER_READ), self.state
+ size_type = self.__readUByte()
+ size = size_type >> 4
+ type = self.__getTType(size_type)
+ if size == 15:
+ size = self.__readSize()
+ self.__containers.append(self.state)
+ self.state = CONTAINER_READ
+ return type, size
+ readSetBegin = readCollectionBegin
+ readListBegin = readCollectionBegin
+
+ def readMapBegin(self):
+ assert self.state in (VALUE_READ, CONTAINER_READ), self.state
+ size = self.__readSize()
+ types = 0
+ if size > 0:
+ types = self.__readUByte()
+ vtype = self.__getTType(types)
+ ktype = self.__getTType(types >> 4)
+ self.__containers.append(self.state)
+ self.state = CONTAINER_READ
+ return (ktype, vtype, size)
+
+ def readCollectionEnd(self):
+ assert self.state == CONTAINER_READ, self.state
+ self.state = self.__containers.pop()
+ readSetEnd = readCollectionEnd
+ readListEnd = readCollectionEnd
+ readMapEnd = readCollectionEnd
+
+ def readBool(self):
+ if self.state == BOOL_READ:
+ return self.__bool_value == CompactType.TRUE
+ elif self.state == CONTAINER_READ:
+ return self.__readByte() == CompactType.TRUE
+ else:
+ raise AssertionError("Invalid state in compact protocol: %d" %
+ self.state)
+
+ readByte = reader(__readByte)
+ __readI16 = __readZigZag
+ readI16 = reader(__readZigZag)
+ readI32 = reader(__readZigZag)
+ readI64 = reader(__readZigZag)
+
+ @reader
+ def readDouble(self):
+ buff = self.trans.readAll(8)
+ val, = unpack('!d', buff)
+ return val
+
+ def __readString(self):
+ len = self.__readSize()
+ return self.trans.readAll(len)
+ readString = reader(__readString)
+
+ def __getTType(self, byte):
+ return TTYPES[byte & 0x0f]
+
+
+class TCompactProtocolFactory:
+ def __init__(self):
+ pass
+
+ def getProtocol(self, trans):
+ return TCompactProtocol(trans)
diff --git a/pyload/lib/thrift/protocol/TJSONProtocol.py b/pyload/lib/thrift/protocol/TJSONProtocol.py
new file mode 100644
index 000000000..3048197d4
--- /dev/null
+++ b/pyload/lib/thrift/protocol/TJSONProtocol.py
@@ -0,0 +1,550 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from TProtocol import TType, TProtocolBase, TProtocolException
+import base64
+import json
+import math
+
+__all__ = ['TJSONProtocol',
+ 'TJSONProtocolFactory',
+ 'TSimpleJSONProtocol',
+ 'TSimpleJSONProtocolFactory']
+
+VERSION = 1
+
+COMMA = ','
+COLON = ':'
+LBRACE = '{'
+RBRACE = '}'
+LBRACKET = '['
+RBRACKET = ']'
+QUOTE = '"'
+BACKSLASH = '\\'
+ZERO = '0'
+
+ESCSEQ = '\\u00'
+ESCAPE_CHAR = '"\\bfnrt'
+ESCAPE_CHAR_VALS = ['"', '\\', '\b', '\f', '\n', '\r', '\t']
+NUMERIC_CHAR = '+-.0123456789Ee'
+
+CTYPES = {TType.BOOL: 'tf',
+ TType.BYTE: 'i8',
+ TType.I16: 'i16',
+ TType.I32: 'i32',
+ TType.I64: 'i64',
+ TType.DOUBLE: 'dbl',
+ TType.STRING: 'str',
+ TType.STRUCT: 'rec',
+ TType.LIST: 'lst',
+ TType.SET: 'set',
+ TType.MAP: 'map'}
+
+JTYPES = {}
+for key in CTYPES.keys():
+ JTYPES[CTYPES[key]] = key
+
+
+class JSONBaseContext(object):
+
+ def __init__(self, protocol):
+ self.protocol = protocol
+ self.first = True
+
+ def doIO(self, function):
+ pass
+
+ def write(self):
+ pass
+
+ def read(self):
+ pass
+
+ def escapeNum(self):
+ return False
+
+ def __str__(self):
+ return self.__class__.__name__
+
+
+class JSONListContext(JSONBaseContext):
+
+ def doIO(self, function):
+ if self.first is True:
+ self.first = False
+ else:
+ function(COMMA)
+
+ def write(self):
+ self.doIO(self.protocol.trans.write)
+
+ def read(self):
+ self.doIO(self.protocol.readJSONSyntaxChar)
+
+
+class JSONPairContext(JSONBaseContext):
+
+ def __init__(self, protocol):
+ super(JSONPairContext, self).__init__(protocol)
+ self.colon = True
+
+ def doIO(self, function):
+ if self.first:
+ self.first = False
+ self.colon = True
+ else:
+ function(COLON if self.colon else COMMA)
+ self.colon = not self.colon
+
+ def write(self):
+ self.doIO(self.protocol.trans.write)
+
+ def read(self):
+ self.doIO(self.protocol.readJSONSyntaxChar)
+
+ def escapeNum(self):
+ return self.colon
+
+ def __str__(self):
+ return '%s, colon=%s' % (self.__class__.__name__, self.colon)
+
+
+class LookaheadReader():
+ hasData = False
+ data = ''
+
+ def __init__(self, protocol):
+ self.protocol = protocol
+
+ def read(self):
+ if self.hasData is True:
+ self.hasData = False
+ else:
+ self.data = self.protocol.trans.read(1)
+ return self.data
+
+ def peek(self):
+ if self.hasData is False:
+ self.data = self.protocol.trans.read(1)
+ self.hasData = True
+ return self.data
+
+class TJSONProtocolBase(TProtocolBase):
+
+ def __init__(self, trans):
+ TProtocolBase.__init__(self, trans)
+ self.resetWriteContext()
+ self.resetReadContext()
+
+ def resetWriteContext(self):
+ self.context = JSONBaseContext(self)
+ self.contextStack = [self.context]
+
+ def resetReadContext(self):
+ self.resetWriteContext()
+ self.reader = LookaheadReader(self)
+
+ def pushContext(self, ctx):
+ self.contextStack.append(ctx)
+ self.context = ctx
+
+ def popContext(self):
+ self.contextStack.pop()
+ if self.contextStack:
+ self.context = self.contextStack[-1]
+ else:
+ self.context = JSONBaseContext(self)
+
+ def writeJSONString(self, string):
+ self.context.write()
+ self.trans.write(json.dumps(string))
+
+ def writeJSONNumber(self, number):
+ self.context.write()
+ jsNumber = str(number)
+ if self.context.escapeNum():
+ jsNumber = "%s%s%s" % (QUOTE, jsNumber, QUOTE)
+ self.trans.write(jsNumber)
+
+ def writeJSONBase64(self, binary):
+ self.context.write()
+ self.trans.write(QUOTE)
+ self.trans.write(base64.b64encode(binary))
+ self.trans.write(QUOTE)
+
+ def writeJSONObjectStart(self):
+ self.context.write()
+ self.trans.write(LBRACE)
+ self.pushContext(JSONPairContext(self))
+
+ def writeJSONObjectEnd(self):
+ self.popContext()
+ self.trans.write(RBRACE)
+
+ def writeJSONArrayStart(self):
+ self.context.write()
+ self.trans.write(LBRACKET)
+ self.pushContext(JSONListContext(self))
+
+ def writeJSONArrayEnd(self):
+ self.popContext()
+ self.trans.write(RBRACKET)
+
+ def readJSONSyntaxChar(self, character):
+ current = self.reader.read()
+ if character != current:
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Unexpected character: %s" % current)
+
+ def readJSONString(self, skipContext):
+ string = []
+ if skipContext is False:
+ self.context.read()
+ self.readJSONSyntaxChar(QUOTE)
+ while True:
+ character = self.reader.read()
+ if character == QUOTE:
+ break
+ if character == ESCSEQ[0]:
+ character = self.reader.read()
+ if character == ESCSEQ[1]:
+ self.readJSONSyntaxChar(ZERO)
+ self.readJSONSyntaxChar(ZERO)
+ character = json.JSONDecoder().decode('"\u00%s"' % self.trans.read(2))
+ else:
+ off = ESCAPE_CHAR.find(character)
+ if off == -1:
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Expected control char")
+ character = ESCAPE_CHAR_VALS[off]
+ string.append(character)
+ return ''.join(string)
+
+ def isJSONNumeric(self, character):
+ return (True if NUMERIC_CHAR.find(character) != - 1 else False)
+
+ def readJSONQuotes(self):
+ if (self.context.escapeNum()):
+ self.readJSONSyntaxChar(QUOTE)
+
+ def readJSONNumericChars(self):
+ numeric = []
+ while True:
+ character = self.reader.peek()
+ if self.isJSONNumeric(character) is False:
+ break
+ numeric.append(self.reader.read())
+ return ''.join(numeric)
+
+ def readJSONInteger(self):
+ self.context.read()
+ self.readJSONQuotes()
+ numeric = self.readJSONNumericChars()
+ self.readJSONQuotes()
+ try:
+ return int(numeric)
+ except ValueError:
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Bad data encounted in numeric data")
+
+ def readJSONDouble(self):
+ self.context.read()
+ if self.reader.peek() == QUOTE:
+ string = self.readJSONString(True)
+ try:
+ double = float(string)
+ if (self.context.escapeNum is False and
+ not math.isinf(double) and
+ not math.isnan(double)):
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Numeric data unexpectedly quoted")
+ return double
+ except ValueError:
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Bad data encounted in numeric data")
+ else:
+ if self.context.escapeNum() is True:
+ self.readJSONSyntaxChar(QUOTE)
+ try:
+ return float(self.readJSONNumericChars())
+ except ValueError:
+ raise TProtocolException(TProtocolException.INVALID_DATA,
+ "Bad data encounted in numeric data")
+
+ def readJSONBase64(self):
+ string = self.readJSONString(False)
+ return base64.b64decode(string)
+
+ def readJSONObjectStart(self):
+ self.context.read()
+ self.readJSONSyntaxChar(LBRACE)
+ self.pushContext(JSONPairContext(self))
+
+ def readJSONObjectEnd(self):
+ self.readJSONSyntaxChar(RBRACE)
+ self.popContext()
+
+ def readJSONArrayStart(self):
+ self.context.read()
+ self.readJSONSyntaxChar(LBRACKET)
+ self.pushContext(JSONListContext(self))
+
+ def readJSONArrayEnd(self):
+ self.readJSONSyntaxChar(RBRACKET)
+ self.popContext()
+
+
+class TJSONProtocol(TJSONProtocolBase):
+
+ def readMessageBegin(self):
+ self.resetReadContext()
+ self.readJSONArrayStart()
+ if self.readJSONInteger() != VERSION:
+ raise TProtocolException(TProtocolException.BAD_VERSION,
+ "Message contained bad version.")
+ name = self.readJSONString(False)
+ typen = self.readJSONInteger()
+ seqid = self.readJSONInteger()
+ return (name, typen, seqid)
+
+ def readMessageEnd(self):
+ self.readJSONArrayEnd()
+
+ def readStructBegin(self):
+ self.readJSONObjectStart()
+
+ def readStructEnd(self):
+ self.readJSONObjectEnd()
+
+ def readFieldBegin(self):
+ character = self.reader.peek()
+ ttype = 0
+ id = 0
+ if character == RBRACE:
+ ttype = TType.STOP
+ else:
+ id = self.readJSONInteger()
+ self.readJSONObjectStart()
+ ttype = JTYPES[self.readJSONString(False)]
+ return (None, ttype, id)
+
+ def readFieldEnd(self):
+ self.readJSONObjectEnd()
+
+ def readMapBegin(self):
+ self.readJSONArrayStart()
+ keyType = JTYPES[self.readJSONString(False)]
+ valueType = JTYPES[self.readJSONString(False)]
+ size = self.readJSONInteger()
+ self.readJSONObjectStart()
+ return (keyType, valueType, size)
+
+ def readMapEnd(self):
+ self.readJSONObjectEnd()
+ self.readJSONArrayEnd()
+
+ def readCollectionBegin(self):
+ self.readJSONArrayStart()
+ elemType = JTYPES[self.readJSONString(False)]
+ size = self.readJSONInteger()
+ return (elemType, size)
+ readListBegin = readCollectionBegin
+ readSetBegin = readCollectionBegin
+
+ def readCollectionEnd(self):
+ self.readJSONArrayEnd()
+ readSetEnd = readCollectionEnd
+ readListEnd = readCollectionEnd
+
+ def readBool(self):
+ return (False if self.readJSONInteger() == 0 else True)
+
+ def readNumber(self):
+ return self.readJSONInteger()
+ readByte = readNumber
+ readI16 = readNumber
+ readI32 = readNumber
+ readI64 = readNumber
+
+ def readDouble(self):
+ return self.readJSONDouble()
+
+ def readString(self):
+ return self.readJSONString(False)
+
+ def readBinary(self):
+ return self.readJSONBase64()
+
+ def writeMessageBegin(self, name, request_type, seqid):
+ self.resetWriteContext()
+ self.writeJSONArrayStart()
+ self.writeJSONNumber(VERSION)
+ self.writeJSONString(name)
+ self.writeJSONNumber(request_type)
+ self.writeJSONNumber(seqid)
+
+ def writeMessageEnd(self):
+ self.writeJSONArrayEnd()
+
+ def writeStructBegin(self, name):
+ self.writeJSONObjectStart()
+
+ def writeStructEnd(self):
+ self.writeJSONObjectEnd()
+
+ def writeFieldBegin(self, name, ttype, id):
+ self.writeJSONNumber(id)
+ self.writeJSONObjectStart()
+ self.writeJSONString(CTYPES[ttype])
+
+ def writeFieldEnd(self):
+ self.writeJSONObjectEnd()
+
+ def writeFieldStop(self):
+ pass
+
+ def writeMapBegin(self, ktype, vtype, size):
+ self.writeJSONArrayStart()
+ self.writeJSONString(CTYPES[ktype])
+ self.writeJSONString(CTYPES[vtype])
+ self.writeJSONNumber(size)
+ self.writeJSONObjectStart()
+
+ def writeMapEnd(self):
+ self.writeJSONObjectEnd()
+ self.writeJSONArrayEnd()
+
+ def writeListBegin(self, etype, size):
+ self.writeJSONArrayStart()
+ self.writeJSONString(CTYPES[etype])
+ self.writeJSONNumber(size)
+
+ def writeListEnd(self):
+ self.writeJSONArrayEnd()
+
+ def writeSetBegin(self, etype, size):
+ self.writeJSONArrayStart()
+ self.writeJSONString(CTYPES[etype])
+ self.writeJSONNumber(size)
+
+ def writeSetEnd(self):
+ self.writeJSONArrayEnd()
+
+ def writeBool(self, boolean):
+ self.writeJSONNumber(1 if boolean is True else 0)
+
+ def writeInteger(self, integer):
+ self.writeJSONNumber(integer)
+ writeByte = writeInteger
+ writeI16 = writeInteger
+ writeI32 = writeInteger
+ writeI64 = writeInteger
+
+ def writeDouble(self, dbl):
+ self.writeJSONNumber(dbl)
+
+ def writeString(self, string):
+ self.writeJSONString(string)
+
+ def writeBinary(self, binary):
+ self.writeJSONBase64(binary)
+
+
+class TJSONProtocolFactory:
+
+ def getProtocol(self, trans):
+ return TJSONProtocol(trans)
+
+
+class TSimpleJSONProtocol(TJSONProtocolBase):
+ """Simple, readable, write-only JSON protocol.
+
+ Useful for interacting with scripting languages.
+ """
+
+ def readMessageBegin(self):
+ raise NotImplementedError()
+
+ def readMessageEnd(self):
+ raise NotImplementedError()
+
+ def readStructBegin(self):
+ raise NotImplementedError()
+
+ def readStructEnd(self):
+ raise NotImplementedError()
+
+ def writeMessageBegin(self, name, request_type, seqid):
+ self.resetWriteContext()
+
+ def writeMessageEnd(self):
+ pass
+
+ def writeStructBegin(self, name):
+ self.writeJSONObjectStart()
+
+ def writeStructEnd(self):
+ self.writeJSONObjectEnd()
+
+ def writeFieldBegin(self, name, ttype, fid):
+ self.writeJSONString(name)
+
+ def writeFieldEnd(self):
+ pass
+
+ def writeMapBegin(self, ktype, vtype, size):
+ self.writeJSONObjectStart()
+
+ def writeMapEnd(self):
+ self.writeJSONObjectEnd()
+
+ def _writeCollectionBegin(self, etype, size):
+ self.writeJSONArrayStart()
+
+ def _writeCollectionEnd(self):
+ self.writeJSONArrayEnd()
+ writeListBegin = _writeCollectionBegin
+ writeListEnd = _writeCollectionEnd
+ writeSetBegin = _writeCollectionBegin
+ writeSetEnd = _writeCollectionEnd
+
+ def writeInteger(self, integer):
+ self.writeJSONNumber(integer)
+ writeByte = writeInteger
+ writeI16 = writeInteger
+ writeI32 = writeInteger
+ writeI64 = writeInteger
+
+ def writeBool(self, boolean):
+ self.writeJSONNumber(1 if boolean is True else 0)
+
+ def writeDouble(self, dbl):
+ self.writeJSONNumber(dbl)
+
+ def writeString(self, string):
+ self.writeJSONString(string)
+
+ def writeBinary(self, binary):
+ self.writeJSONBase64(binary)
+
+
+class TSimpleJSONProtocolFactory(object):
+
+ def getProtocol(self, trans):
+ return TSimpleJSONProtocol(trans)
diff --git a/pyload/lib/thrift/protocol/TProtocol.py b/pyload/lib/thrift/protocol/TProtocol.py
new file mode 100644
index 000000000..dc2b095de
--- /dev/null
+++ b/pyload/lib/thrift/protocol/TProtocol.py
@@ -0,0 +1,406 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from thrift.Thrift import *
+
+
+class TProtocolException(TException):
+ """Custom Protocol Exception class"""
+
+ UNKNOWN = 0
+ INVALID_DATA = 1
+ NEGATIVE_SIZE = 2
+ SIZE_LIMIT = 3
+ BAD_VERSION = 4
+
+ def __init__(self, type=UNKNOWN, message=None):
+ TException.__init__(self, message)
+ self.type = type
+
+
+class TProtocolBase:
+ """Base class for Thrift protocol driver."""
+
+ def __init__(self, trans):
+ self.trans = trans
+
+ def writeMessageBegin(self, name, ttype, seqid):
+ pass
+
+ def writeMessageEnd(self):
+ pass
+
+ def writeStructBegin(self, name):
+ pass
+
+ def writeStructEnd(self):
+ pass
+
+ def writeFieldBegin(self, name, ttype, fid):
+ pass
+
+ def writeFieldEnd(self):
+ pass
+
+ def writeFieldStop(self):
+ pass
+
+ def writeMapBegin(self, ktype, vtype, size):
+ pass
+
+ def writeMapEnd(self):
+ pass
+
+ def writeListBegin(self, etype, size):
+ pass
+
+ def writeListEnd(self):
+ pass
+
+ def writeSetBegin(self, etype, size):
+ pass
+
+ def writeSetEnd(self):
+ pass
+
+ def writeBool(self, bool_val):
+ pass
+
+ def writeByte(self, byte):
+ pass
+
+ def writeI16(self, i16):
+ pass
+
+ def writeI32(self, i32):
+ pass
+
+ def writeI64(self, i64):
+ pass
+
+ def writeDouble(self, dub):
+ pass
+
+ def writeString(self, str_val):
+ pass
+
+ def readMessageBegin(self):
+ pass
+
+ def readMessageEnd(self):
+ pass
+
+ def readStructBegin(self):
+ pass
+
+ def readStructEnd(self):
+ pass
+
+ def readFieldBegin(self):
+ pass
+
+ def readFieldEnd(self):
+ pass
+
+ def readMapBegin(self):
+ pass
+
+ def readMapEnd(self):
+ pass
+
+ def readListBegin(self):
+ pass
+
+ def readListEnd(self):
+ pass
+
+ def readSetBegin(self):
+ pass
+
+ def readSetEnd(self):
+ pass
+
+ def readBool(self):
+ pass
+
+ def readByte(self):
+ pass
+
+ def readI16(self):
+ pass
+
+ def readI32(self):
+ pass
+
+ def readI64(self):
+ pass
+
+ def readDouble(self):
+ pass
+
+ def readString(self):
+ pass
+
+ def skip(self, ttype):
+ if ttype == TType.STOP:
+ return
+ elif ttype == TType.BOOL:
+ self.readBool()
+ elif ttype == TType.BYTE:
+ self.readByte()
+ elif ttype == TType.I16:
+ self.readI16()
+ elif ttype == TType.I32:
+ self.readI32()
+ elif ttype == TType.I64:
+ self.readI64()
+ elif ttype == TType.DOUBLE:
+ self.readDouble()
+ elif ttype == TType.STRING:
+ self.readString()
+ elif ttype == TType.STRUCT:
+ name = self.readStructBegin()
+ while True:
+ (name, ttype, id) = self.readFieldBegin()
+ if ttype == TType.STOP:
+ break
+ self.skip(ttype)
+ self.readFieldEnd()
+ self.readStructEnd()
+ elif ttype == TType.MAP:
+ (ktype, vtype, size) = self.readMapBegin()
+ for i in xrange(size):
+ self.skip(ktype)
+ self.skip(vtype)
+ self.readMapEnd()
+ elif ttype == TType.SET:
+ (etype, size) = self.readSetBegin()
+ for i in xrange(size):
+ self.skip(etype)
+ self.readSetEnd()
+ elif ttype == TType.LIST:
+ (etype, size) = self.readListBegin()
+ for i in xrange(size):
+ self.skip(etype)
+ self.readListEnd()
+
+ # tuple of: ( 'reader method' name, is_container bool, 'writer_method' name )
+ _TTYPE_HANDLERS = (
+ (None, None, False), # 0 TType.STOP
+ (None, None, False), # 1 TType.VOID # TODO: handle void?
+ ('readBool', 'writeBool', False), # 2 TType.BOOL
+ ('readByte', 'writeByte', False), # 3 TType.BYTE and I08
+ ('readDouble', 'writeDouble', False), # 4 TType.DOUBLE
+ (None, None, False), # 5 undefined
+ ('readI16', 'writeI16', False), # 6 TType.I16
+ (None, None, False), # 7 undefined
+ ('readI32', 'writeI32', False), # 8 TType.I32
+ (None, None, False), # 9 undefined
+ ('readI64', 'writeI64', False), # 10 TType.I64
+ ('readString', 'writeString', False), # 11 TType.STRING and UTF7
+ ('readContainerStruct', 'writeContainerStruct', True), # 12 *.STRUCT
+ ('readContainerMap', 'writeContainerMap', True), # 13 TType.MAP
+ ('readContainerSet', 'writeContainerSet', True), # 14 TType.SET
+ ('readContainerList', 'writeContainerList', True), # 15 TType.LIST
+ (None, None, False), # 16 TType.UTF8 # TODO: handle utf8 types?
+ (None, None, False) # 17 TType.UTF16 # TODO: handle utf16 types?
+ )
+
+ def readFieldByTType(self, ttype, spec):
+ try:
+ (r_handler, w_handler, is_container) = self._TTYPE_HANDLERS[ttype]
+ except IndexError:
+ raise TProtocolException(type=TProtocolException.INVALID_DATA,
+ message='Invalid field type %d' % (ttype))
+ if r_handler is None:
+ raise TProtocolException(type=TProtocolException.INVALID_DATA,
+ message='Invalid field type %d' % (ttype))
+ reader = getattr(self, r_handler)
+ if not is_container:
+ return reader()
+ return reader(spec)
+
+ def readContainerList(self, spec):
+ results = []
+ ttype, tspec = spec[0], spec[1]
+ r_handler = self._TTYPE_HANDLERS[ttype][0]
+ reader = getattr(self, r_handler)
+ (list_type, list_len) = self.readListBegin()
+ if tspec is None:
+ # list values are simple types
+ for idx in xrange(list_len):
+ results.append(reader())
+ else:
+ # this is like an inlined readFieldByTType
+ container_reader = self._TTYPE_HANDLERS[list_type][0]
+ val_reader = getattr(self, container_reader)
+ for idx in xrange(list_len):
+ val = val_reader(tspec)
+ results.append(val)
+ self.readListEnd()
+ return results
+
+ def readContainerSet(self, spec):
+ results = set()
+ ttype, tspec = spec[0], spec[1]
+ r_handler = self._TTYPE_HANDLERS[ttype][0]
+ reader = getattr(self, r_handler)
+ (set_type, set_len) = self.readSetBegin()
+ if tspec is None:
+ # set members are simple types
+ for idx in xrange(set_len):
+ results.add(reader())
+ else:
+ container_reader = self._TTYPE_HANDLERS[set_type][0]
+ val_reader = getattr(self, container_reader)
+ for idx in xrange(set_len):
+ results.add(val_reader(tspec))
+ self.readSetEnd()
+ return results
+
+ def readContainerStruct(self, spec):
+ (obj_class, obj_spec) = spec
+ obj = obj_class()
+ obj.read(self)
+ return obj
+
+ def readContainerMap(self, spec):
+ results = dict()
+ key_ttype, key_spec = spec[0], spec[1]
+ val_ttype, val_spec = spec[2], spec[3]
+ (map_ktype, map_vtype, map_len) = self.readMapBegin()
+ # TODO: compare types we just decoded with thrift_spec and
+ # abort/skip if types disagree
+ key_reader = getattr(self, self._TTYPE_HANDLERS[key_ttype][0])
+ val_reader = getattr(self, self._TTYPE_HANDLERS[val_ttype][0])
+ # list values are simple types
+ for idx in xrange(map_len):
+ if key_spec is None:
+ k_val = key_reader()
+ else:
+ k_val = self.readFieldByTType(key_ttype, key_spec)
+ if val_spec is None:
+ v_val = val_reader()
+ else:
+ v_val = self.readFieldByTType(val_ttype, val_spec)
+ # this raises a TypeError with unhashable keys types
+ # i.e. this fails: d=dict(); d[[0,1]] = 2
+ results[k_val] = v_val
+ self.readMapEnd()
+ return results
+
+ def readStruct(self, obj, thrift_spec):
+ self.readStructBegin()
+ while True:
+ (fname, ftype, fid) = self.readFieldBegin()
+ if ftype == TType.STOP:
+ break
+ try:
+ field = thrift_spec[fid]
+ except IndexError:
+ self.skip(ftype)
+ else:
+ if field is not None and ftype == field[1]:
+ fname = field[2]
+ fspec = field[3]
+ val = self.readFieldByTType(ftype, fspec)
+ setattr(obj, fname, val)
+ else:
+ self.skip(ftype)
+ self.readFieldEnd()
+ self.readStructEnd()
+
+ def writeContainerStruct(self, val, spec):
+ val.write(self)
+
+ def writeContainerList(self, val, spec):
+ self.writeListBegin(spec[0], len(val))
+ r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
+ e_writer = getattr(self, w_handler)
+ if not is_container:
+ for elem in val:
+ e_writer(elem)
+ else:
+ for elem in val:
+ e_writer(elem, spec[1])
+ self.writeListEnd()
+
+ def writeContainerSet(self, val, spec):
+ self.writeSetBegin(spec[0], len(val))
+ r_handler, w_handler, is_container = self._TTYPE_HANDLERS[spec[0]]
+ e_writer = getattr(self, w_handler)
+ if not is_container:
+ for elem in val:
+ e_writer(elem)
+ else:
+ for elem in val:
+ e_writer(elem, spec[1])
+ self.writeSetEnd()
+
+ def writeContainerMap(self, val, spec):
+ k_type = spec[0]
+ v_type = spec[2]
+ ignore, ktype_name, k_is_container = self._TTYPE_HANDLERS[k_type]
+ ignore, vtype_name, v_is_container = self._TTYPE_HANDLERS[v_type]
+ k_writer = getattr(self, ktype_name)
+ v_writer = getattr(self, vtype_name)
+ self.writeMapBegin(k_type, v_type, len(val))
+ for m_key, m_val in val.iteritems():
+ if not k_is_container:
+ k_writer(m_key)
+ else:
+ k_writer(m_key, spec[1])
+ if not v_is_container:
+ v_writer(m_val)
+ else:
+ v_writer(m_val, spec[3])
+ self.writeMapEnd()
+
+ def writeStruct(self, obj, thrift_spec):
+ self.writeStructBegin(obj.__class__.__name__)
+ for field in thrift_spec:
+ if field is None:
+ continue
+ fname = field[2]
+ val = getattr(obj, fname)
+ if val is None:
+ # skip writing out unset fields
+ continue
+ fid = field[0]
+ ftype = field[1]
+ fspec = field[3]
+ # get the writer method for this value
+ self.writeFieldBegin(fname, ftype, fid)
+ self.writeFieldByTType(ftype, val, fspec)
+ self.writeFieldEnd()
+ self.writeFieldStop()
+ self.writeStructEnd()
+
+ def writeFieldByTType(self, ttype, val, spec):
+ r_handler, w_handler, is_container = self._TTYPE_HANDLERS[ttype]
+ writer = getattr(self, w_handler)
+ if is_container:
+ writer(val, spec)
+ else:
+ writer(val)
+
+
+class TProtocolFactory:
+ def getProtocol(self, trans):
+ pass
diff --git a/pyload/lib/thrift/protocol/__init__.py b/pyload/lib/thrift/protocol/__init__.py
new file mode 100644
index 000000000..7eefb458a
--- /dev/null
+++ b/pyload/lib/thrift/protocol/__init__.py
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+__all__ = ['fastbinary', 'TBase', 'TBinaryProtocol', 'TCompactProtocol', 'TJSONProtocol', 'TProtocol']
diff --git a/pyload/lib/thrift/protocol/fastbinary.c b/pyload/lib/thrift/protocol/fastbinary.c
new file mode 100644
index 000000000..2ce56603c
--- /dev/null
+++ b/pyload/lib/thrift/protocol/fastbinary.c
@@ -0,0 +1,1219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <Python.h>
+#include "cStringIO.h"
+#include <stdint.h>
+#ifndef _WIN32
+# include <stdbool.h>
+# include <netinet/in.h>
+#else
+# include <WinSock2.h>
+# pragma comment (lib, "ws2_32.lib")
+# define BIG_ENDIAN (4321)
+# define LITTLE_ENDIAN (1234)
+# define BYTE_ORDER LITTLE_ENDIAN
+# if defined(_MSC_VER) && _MSC_VER < 1600
+ typedef int _Bool;
+# define bool _Bool
+# define false 0
+# define true 1
+# endif
+# define inline __inline
+#endif
+
+/* Fix endianness issues on Solaris */
+#if defined (__SVR4) && defined (__sun)
+ #if defined(__i386) && !defined(__i386__)
+ #define __i386__
+ #endif
+
+ #ifndef BIG_ENDIAN
+ #define BIG_ENDIAN (4321)
+ #endif
+ #ifndef LITTLE_ENDIAN
+ #define LITTLE_ENDIAN (1234)
+ #endif
+
+ /* I386 is LE, even on Solaris */
+ #if !defined(BYTE_ORDER) && defined(__i386__)
+ #define BYTE_ORDER LITTLE_ENDIAN
+ #endif
+#endif
+
+// TODO(dreiss): defval appears to be unused. Look into removing it.
+// TODO(dreiss): Make parse_spec_args recursive, and cache the output
+// permanently in the object. (Malloc and orphan.)
+// TODO(dreiss): Why do we need cStringIO for reading, why not just char*?
+// Can cStringIO let us work with a BufferedTransport?
+// TODO(dreiss): Don't ignore the rv from cwrite (maybe).
+
+/* ====== BEGIN UTILITIES ====== */
+
+#define INIT_OUTBUF_SIZE 128
+
+// Stolen out of TProtocol.h.
+// It would be a huge pain to have both get this from one place.
+typedef enum TType {
+ T_STOP = 0,
+ T_VOID = 1,
+ T_BOOL = 2,
+ T_BYTE = 3,
+ T_I08 = 3,
+ T_I16 = 6,
+ T_I32 = 8,
+ T_U64 = 9,
+ T_I64 = 10,
+ T_DOUBLE = 4,
+ T_STRING = 11,
+ T_UTF7 = 11,
+ T_STRUCT = 12,
+ T_MAP = 13,
+ T_SET = 14,
+ T_LIST = 15,
+ T_UTF8 = 16,
+ T_UTF16 = 17
+} TType;
+
+#ifndef __BYTE_ORDER
+# if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+# define __BYTE_ORDER BYTE_ORDER
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __BIG_ENDIAN BIG_ENDIAN
+# else
+# error "Cannot determine endianness"
+# endif
+#endif
+
+// Same comment as the enum. Sorry.
+#if __BYTE_ORDER == __BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+# if defined(__GNUC__) && defined(__GLIBC__)
+# include <byteswap.h>
+# define ntohll(n) bswap_64(n)
+# define htonll(n) bswap_64(n)
+# else /* GNUC & GLIBC */
+# define ntohll(n) ( (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32) )
+# define htonll(n) ( (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32) )
+# endif /* GNUC & GLIBC */
+#else /* __BYTE_ORDER */
+# error "Can't define htonll or ntohll!"
+#endif
+
+// Doing a benchmark shows that interning actually makes a difference, amazingly.
+#define INTERN_STRING(value) _intern_ ## value
+
+#define INT_CONV_ERROR_OCCURRED(v) ( ((v) == -1) && PyErr_Occurred() )
+#define CHECK_RANGE(v, min, max) ( ((v) <= (max)) && ((v) >= (min)) )
+
+// Py_ssize_t was not defined before Python 2.5
+#if (PY_VERSION_HEX < 0x02050000)
+typedef int Py_ssize_t;
+#endif
+
+/**
+ * A cache of the spec_args for a set or list,
+ * so we don't have to keep calling PyTuple_GET_ITEM.
+ */
+typedef struct {
+ TType element_type;
+ PyObject* typeargs;
+} SetListTypeArgs;
+
+/**
+ * A cache of the spec_args for a map,
+ * so we don't have to keep calling PyTuple_GET_ITEM.
+ */
+typedef struct {
+ TType ktag;
+ TType vtag;
+ PyObject* ktypeargs;
+ PyObject* vtypeargs;
+} MapTypeArgs;
+
+/**
+ * A cache of the spec_args for a struct,
+ * so we don't have to keep calling PyTuple_GET_ITEM.
+ */
+typedef struct {
+ PyObject* klass;
+ PyObject* spec;
+} StructTypeArgs;
+
+/**
+ * A cache of the item spec from a struct specification,
+ * so we don't have to keep calling PyTuple_GET_ITEM.
+ */
+typedef struct {
+ int tag;
+ TType type;
+ PyObject* attrname;
+ PyObject* typeargs;
+ PyObject* defval;
+} StructItemSpec;
+
+/**
+ * A cache of the two key attributes of a CReadableTransport,
+ * so we don't have to keep calling PyObject_GetAttr.
+ */
+typedef struct {
+ PyObject* stringiobuf;
+ PyObject* refill_callable;
+} DecodeBuffer;
+
+/** Pointer to interned string to speed up attribute lookup. */
+static PyObject* INTERN_STRING(cstringio_buf);
+/** Pointer to interned string to speed up attribute lookup. */
+static PyObject* INTERN_STRING(cstringio_refill);
+
+static inline bool
+check_ssize_t_32(Py_ssize_t len) {
+ // error from getting the int
+ if (INT_CONV_ERROR_OCCURRED(len)) {
+ return false;
+ }
+ if (!CHECK_RANGE(len, 0, INT32_MAX)) {
+ PyErr_SetString(PyExc_OverflowError, "string size out of range");
+ return false;
+ }
+ return true;
+}
+
+static inline bool
+parse_pyint(PyObject* o, int32_t* ret, int32_t min, int32_t max) {
+ long val = PyInt_AsLong(o);
+
+ if (INT_CONV_ERROR_OCCURRED(val)) {
+ return false;
+ }
+ if (!CHECK_RANGE(val, min, max)) {
+ PyErr_SetString(PyExc_OverflowError, "int out of range");
+ return false;
+ }
+
+ *ret = (int32_t) val;
+ return true;
+}
+
+
+/* --- FUNCTIONS TO PARSE STRUCT SPECIFICATOINS --- */
+
+static bool
+parse_set_list_args(SetListTypeArgs* dest, PyObject* typeargs) {
+ if (PyTuple_Size(typeargs) != 2) {
+ PyErr_SetString(PyExc_TypeError, "expecting tuple of size 2 for list/set type args");
+ return false;
+ }
+
+ dest->element_type = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 0));
+ if (INT_CONV_ERROR_OCCURRED(dest->element_type)) {
+ return false;
+ }
+
+ dest->typeargs = PyTuple_GET_ITEM(typeargs, 1);
+
+ return true;
+}
+
+static bool
+parse_map_args(MapTypeArgs* dest, PyObject* typeargs) {
+ if (PyTuple_Size(typeargs) != 4) {
+ PyErr_SetString(PyExc_TypeError, "expecting 4 arguments for typeargs to map");
+ return false;
+ }
+
+ dest->ktag = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 0));
+ if (INT_CONV_ERROR_OCCURRED(dest->ktag)) {
+ return false;
+ }
+
+ dest->vtag = PyInt_AsLong(PyTuple_GET_ITEM(typeargs, 2));
+ if (INT_CONV_ERROR_OCCURRED(dest->vtag)) {
+ return false;
+ }
+
+ dest->ktypeargs = PyTuple_GET_ITEM(typeargs, 1);
+ dest->vtypeargs = PyTuple_GET_ITEM(typeargs, 3);
+
+ return true;
+}
+
+static bool
+parse_struct_args(StructTypeArgs* dest, PyObject* typeargs) {
+ if (PyTuple_Size(typeargs) != 2) {
+ PyErr_SetString(PyExc_TypeError, "expecting tuple of size 2 for struct args");
+ return false;
+ }
+
+ dest->klass = PyTuple_GET_ITEM(typeargs, 0);
+ dest->spec = PyTuple_GET_ITEM(typeargs, 1);
+
+ return true;
+}
+
+static int
+parse_struct_item_spec(StructItemSpec* dest, PyObject* spec_tuple) {
+
+ // i'd like to use ParseArgs here, but it seems to be a bottleneck.
+ if (PyTuple_Size(spec_tuple) != 5) {
+ PyErr_SetString(PyExc_TypeError, "expecting 5 arguments for spec tuple");
+ return false;
+ }
+
+ dest->tag = PyInt_AsLong(PyTuple_GET_ITEM(spec_tuple, 0));
+ if (INT_CONV_ERROR_OCCURRED(dest->tag)) {
+ return false;
+ }
+
+ dest->type = PyInt_AsLong(PyTuple_GET_ITEM(spec_tuple, 1));
+ if (INT_CONV_ERROR_OCCURRED(dest->type)) {
+ return false;
+ }
+
+ dest->attrname = PyTuple_GET_ITEM(spec_tuple, 2);
+ dest->typeargs = PyTuple_GET_ITEM(spec_tuple, 3);
+ dest->defval = PyTuple_GET_ITEM(spec_tuple, 4);
+ return true;
+}
+
+/* ====== END UTILITIES ====== */
+
+
+/* ====== BEGIN WRITING FUNCTIONS ====== */
+
+/* --- LOW-LEVEL WRITING FUNCTIONS --- */
+
+static void writeByte(PyObject* outbuf, int8_t val) {
+ int8_t net = val;
+ PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int8_t));
+}
+
+static void writeI16(PyObject* outbuf, int16_t val) {
+ int16_t net = (int16_t)htons(val);
+ PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int16_t));
+}
+
+static void writeI32(PyObject* outbuf, int32_t val) {
+ int32_t net = (int32_t)htonl(val);
+ PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int32_t));
+}
+
+static void writeI64(PyObject* outbuf, int64_t val) {
+ int64_t net = (int64_t)htonll(val);
+ PycStringIO->cwrite(outbuf, (char*)&net, sizeof(int64_t));
+}
+
+static void writeDouble(PyObject* outbuf, double dub) {
+ // Unfortunately, bitwise_cast doesn't work in C. Bad C!
+ union {
+ double f;
+ int64_t t;
+ } transfer;
+ transfer.f = dub;
+ writeI64(outbuf, transfer.t);
+}
+
+
+/* --- MAIN RECURSIVE OUTPUT FUCNTION -- */
+
+static int
+output_val(PyObject* output, PyObject* value, TType type, PyObject* typeargs) {
+ /*
+ * Refcounting Strategy:
+ *
+ * We assume that elements of the thrift_spec tuple are not going to be
+ * mutated, so we don't ref count those at all. Other than that, we try to
+ * keep a reference to all the user-created objects while we work with them.
+ * output_val assumes that a reference is already held. The *caller* is
+ * responsible for handling references
+ */
+
+ switch (type) {
+
+ case T_BOOL: {
+ int v = PyObject_IsTrue(value);
+ if (v == -1) {
+ return false;
+ }
+
+ writeByte(output, (int8_t) v);
+ break;
+ }
+ case T_I08: {
+ int32_t val;
+
+ if (!parse_pyint(value, &val, INT8_MIN, INT8_MAX)) {
+ return false;
+ }
+
+ writeByte(output, (int8_t) val);
+ break;
+ }
+ case T_I16: {
+ int32_t val;
+
+ if (!parse_pyint(value, &val, INT16_MIN, INT16_MAX)) {
+ return false;
+ }
+
+ writeI16(output, (int16_t) val);
+ break;
+ }
+ case T_I32: {
+ int32_t val;
+
+ if (!parse_pyint(value, &val, INT32_MIN, INT32_MAX)) {
+ return false;
+ }
+
+ writeI32(output, val);
+ break;
+ }
+ case T_I64: {
+ int64_t nval = PyLong_AsLongLong(value);
+
+ if (INT_CONV_ERROR_OCCURRED(nval)) {
+ return false;
+ }
+
+ if (!CHECK_RANGE(nval, INT64_MIN, INT64_MAX)) {
+ PyErr_SetString(PyExc_OverflowError, "int out of range");
+ return false;
+ }
+
+ writeI64(output, nval);
+ break;
+ }
+
+ case T_DOUBLE: {
+ double nval = PyFloat_AsDouble(value);
+ if (nval == -1.0 && PyErr_Occurred()) {
+ return false;
+ }
+
+ writeDouble(output, nval);
+ break;
+ }
+
+ case T_STRING: {
+ Py_ssize_t len = PyString_Size(value);
+
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ writeI32(output, (int32_t) len);
+ PycStringIO->cwrite(output, PyString_AsString(value), (int32_t) len);
+ break;
+ }
+
+ case T_LIST:
+ case T_SET: {
+ Py_ssize_t len;
+ SetListTypeArgs parsedargs;
+ PyObject *item;
+ PyObject *iterator;
+
+ if (!parse_set_list_args(&parsedargs, typeargs)) {
+ return false;
+ }
+
+ len = PyObject_Length(value);
+
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ writeByte(output, parsedargs.element_type);
+ writeI32(output, (int32_t) len);
+
+ iterator = PyObject_GetIter(value);
+ if (iterator == NULL) {
+ return false;
+ }
+
+ while ((item = PyIter_Next(iterator))) {
+ if (!output_val(output, item, parsedargs.element_type, parsedargs.typeargs)) {
+ Py_DECREF(item);
+ Py_DECREF(iterator);
+ return false;
+ }
+ Py_DECREF(item);
+ }
+
+ Py_DECREF(iterator);
+
+ if (PyErr_Occurred()) {
+ return false;
+ }
+
+ break;
+ }
+
+ case T_MAP: {
+ PyObject *k, *v;
+ Py_ssize_t pos = 0;
+ Py_ssize_t len;
+
+ MapTypeArgs parsedargs;
+
+ len = PyDict_Size(value);
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ if (!parse_map_args(&parsedargs, typeargs)) {
+ return false;
+ }
+
+ writeByte(output, parsedargs.ktag);
+ writeByte(output, parsedargs.vtag);
+ writeI32(output, len);
+
+ // TODO(bmaurer): should support any mapping, not just dicts
+ while (PyDict_Next(value, &pos, &k, &v)) {
+ // TODO(dreiss): Think hard about whether these INCREFs actually
+ // turn any unsafe scenarios into safe scenarios.
+ Py_INCREF(k);
+ Py_INCREF(v);
+
+ if (!output_val(output, k, parsedargs.ktag, parsedargs.ktypeargs)
+ || !output_val(output, v, parsedargs.vtag, parsedargs.vtypeargs)) {
+ Py_DECREF(k);
+ Py_DECREF(v);
+ return false;
+ }
+ Py_DECREF(k);
+ Py_DECREF(v);
+ }
+ break;
+ }
+
+ // TODO(dreiss): Consider breaking this out as a function
+ // the way we did for decode_struct.
+ case T_STRUCT: {
+ StructTypeArgs parsedargs;
+ Py_ssize_t nspec;
+ Py_ssize_t i;
+
+ if (!parse_struct_args(&parsedargs, typeargs)) {
+ return false;
+ }
+
+ nspec = PyTuple_Size(parsedargs.spec);
+
+ if (nspec == -1) {
+ return false;
+ }
+
+ for (i = 0; i < nspec; i++) {
+ StructItemSpec parsedspec;
+ PyObject* spec_tuple;
+ PyObject* instval = NULL;
+
+ spec_tuple = PyTuple_GET_ITEM(parsedargs.spec, i);
+ if (spec_tuple == Py_None) {
+ continue;
+ }
+
+ if (!parse_struct_item_spec (&parsedspec, spec_tuple)) {
+ return false;
+ }
+
+ instval = PyObject_GetAttr(value, parsedspec.attrname);
+
+ if (!instval) {
+ return false;
+ }
+
+ if (instval == Py_None) {
+ Py_DECREF(instval);
+ continue;
+ }
+
+ writeByte(output, (int8_t) parsedspec.type);
+ writeI16(output, parsedspec.tag);
+
+ if (!output_val(output, instval, parsedspec.type, parsedspec.typeargs)) {
+ Py_DECREF(instval);
+ return false;
+ }
+
+ Py_DECREF(instval);
+ }
+
+ writeByte(output, (int8_t)T_STOP);
+ break;
+ }
+
+ case T_STOP:
+ case T_VOID:
+ case T_UTF16:
+ case T_UTF8:
+ case T_U64:
+ default:
+ PyErr_SetString(PyExc_TypeError, "Unexpected TType");
+ return false;
+
+ }
+
+ return true;
+}
+
+
+/* --- TOP-LEVEL WRAPPER FOR OUTPUT -- */
+
+static PyObject *
+encode_binary(PyObject *self, PyObject *args) {
+ PyObject* enc_obj;
+ PyObject* type_args;
+ PyObject* buf;
+ PyObject* ret = NULL;
+
+ if (!PyArg_ParseTuple(args, "OO", &enc_obj, &type_args)) {
+ return NULL;
+ }
+
+ buf = PycStringIO->NewOutput(INIT_OUTBUF_SIZE);
+ if (output_val(buf, enc_obj, T_STRUCT, type_args)) {
+ ret = PycStringIO->cgetvalue(buf);
+ }
+
+ Py_DECREF(buf);
+ return ret;
+}
+
+/* ====== END WRITING FUNCTIONS ====== */
+
+
+/* ====== BEGIN READING FUNCTIONS ====== */
+
+/* --- LOW-LEVEL READING FUNCTIONS --- */
+
+static void
+free_decodebuf(DecodeBuffer* d) {
+ Py_XDECREF(d->stringiobuf);
+ Py_XDECREF(d->refill_callable);
+}
+
+static bool
+decode_buffer_from_obj(DecodeBuffer* dest, PyObject* obj) {
+ dest->stringiobuf = PyObject_GetAttr(obj, INTERN_STRING(cstringio_buf));
+ if (!dest->stringiobuf) {
+ return false;
+ }
+
+ if (!PycStringIO_InputCheck(dest->stringiobuf)) {
+ free_decodebuf(dest);
+ PyErr_SetString(PyExc_TypeError, "expecting stringio input");
+ return false;
+ }
+
+ dest->refill_callable = PyObject_GetAttr(obj, INTERN_STRING(cstringio_refill));
+
+ if(!dest->refill_callable) {
+ free_decodebuf(dest);
+ return false;
+ }
+
+ if (!PyCallable_Check(dest->refill_callable)) {
+ free_decodebuf(dest);
+ PyErr_SetString(PyExc_TypeError, "expecting callable");
+ return false;
+ }
+
+ return true;
+}
+
+static bool readBytes(DecodeBuffer* input, char** output, int len) {
+ int read;
+
+ // TODO(dreiss): Don't fear the malloc. Think about taking a copy of
+ // the partial read instead of forcing the transport
+ // to prepend it to its buffer.
+
+ read = PycStringIO->cread(input->stringiobuf, output, len);
+
+ if (read == len) {
+ return true;
+ } else if (read == -1) {
+ return false;
+ } else {
+ PyObject* newiobuf;
+
+ // using building functions as this is a rare codepath
+ newiobuf = PyObject_CallFunction(
+ input->refill_callable, "s#i", *output, read, len, NULL);
+ if (newiobuf == NULL) {
+ return false;
+ }
+
+ // must do this *AFTER* the call so that we don't deref the io buffer
+ Py_CLEAR(input->stringiobuf);
+ input->stringiobuf = newiobuf;
+
+ read = PycStringIO->cread(input->stringiobuf, output, len);
+
+ if (read == len) {
+ return true;
+ } else if (read == -1) {
+ return false;
+ } else {
+ // TODO(dreiss): This could be a valid code path for big binary blobs.
+ PyErr_SetString(PyExc_TypeError,
+ "refill claimed to have refilled the buffer, but didn't!!");
+ return false;
+ }
+ }
+}
+
+static int8_t readByte(DecodeBuffer* input) {
+ char* buf;
+ if (!readBytes(input, &buf, sizeof(int8_t))) {
+ return -1;
+ }
+
+ return *(int8_t*) buf;
+}
+
+static int16_t readI16(DecodeBuffer* input) {
+ char* buf;
+ if (!readBytes(input, &buf, sizeof(int16_t))) {
+ return -1;
+ }
+
+ return (int16_t) ntohs(*(int16_t*) buf);
+}
+
+static int32_t readI32(DecodeBuffer* input) {
+ char* buf;
+ if (!readBytes(input, &buf, sizeof(int32_t))) {
+ return -1;
+ }
+ return (int32_t) ntohl(*(int32_t*) buf);
+}
+
+
+static int64_t readI64(DecodeBuffer* input) {
+ char* buf;
+ if (!readBytes(input, &buf, sizeof(int64_t))) {
+ return -1;
+ }
+
+ return (int64_t) ntohll(*(int64_t*) buf);
+}
+
+static double readDouble(DecodeBuffer* input) {
+ union {
+ int64_t f;
+ double t;
+ } transfer;
+
+ transfer.f = readI64(input);
+ if (transfer.f == -1) {
+ return -1;
+ }
+ return transfer.t;
+}
+
+static bool
+checkTypeByte(DecodeBuffer* input, TType expected) {
+ TType got = readByte(input);
+ if (INT_CONV_ERROR_OCCURRED(got)) {
+ return false;
+ }
+
+ if (expected != got) {
+ PyErr_SetString(PyExc_TypeError, "got wrong ttype while reading field");
+ return false;
+ }
+ return true;
+}
+
+static bool
+skip(DecodeBuffer* input, TType type) {
+#define SKIPBYTES(n) \
+ do { \
+ if (!readBytes(input, &dummy_buf, (n))) { \
+ return false; \
+ } \
+ } while(0)
+
+ char* dummy_buf;
+
+ switch (type) {
+
+ case T_BOOL:
+ case T_I08: SKIPBYTES(1); break;
+ case T_I16: SKIPBYTES(2); break;
+ case T_I32: SKIPBYTES(4); break;
+ case T_I64:
+ case T_DOUBLE: SKIPBYTES(8); break;
+
+ case T_STRING: {
+ // TODO(dreiss): Find out if these check_ssize_t32s are really necessary.
+ int len = readI32(input);
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+ SKIPBYTES(len);
+ break;
+ }
+
+ case T_LIST:
+ case T_SET: {
+ TType etype;
+ int len, i;
+
+ etype = readByte(input);
+ if (etype == -1) {
+ return false;
+ }
+
+ len = readI32(input);
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (!skip(input, etype)) {
+ return false;
+ }
+ }
+ break;
+ }
+
+ case T_MAP: {
+ TType ktype, vtype;
+ int len, i;
+
+ ktype = readByte(input);
+ if (ktype == -1) {
+ return false;
+ }
+
+ vtype = readByte(input);
+ if (vtype == -1) {
+ return false;
+ }
+
+ len = readI32(input);
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (!(skip(input, ktype) && skip(input, vtype))) {
+ return false;
+ }
+ }
+ break;
+ }
+
+ case T_STRUCT: {
+ while (true) {
+ TType type;
+
+ type = readByte(input);
+ if (type == -1) {
+ return false;
+ }
+
+ if (type == T_STOP)
+ break;
+
+ SKIPBYTES(2); // tag
+ if (!skip(input, type)) {
+ return false;
+ }
+ }
+ break;
+ }
+
+ case T_STOP:
+ case T_VOID:
+ case T_UTF16:
+ case T_UTF8:
+ case T_U64:
+ default:
+ PyErr_SetString(PyExc_TypeError, "Unexpected TType");
+ return false;
+
+ }
+
+ return true;
+
+#undef SKIPBYTES
+}
+
+
+/* --- HELPER FUNCTION FOR DECODE_VAL --- */
+
+static PyObject*
+decode_val(DecodeBuffer* input, TType type, PyObject* typeargs);
+
+static bool
+decode_struct(DecodeBuffer* input, PyObject* output, PyObject* spec_seq) {
+ int spec_seq_len = PyTuple_Size(spec_seq);
+ if (spec_seq_len == -1) {
+ return false;
+ }
+
+ while (true) {
+ TType type;
+ int16_t tag;
+ PyObject* item_spec;
+ PyObject* fieldval = NULL;
+ StructItemSpec parsedspec;
+
+ type = readByte(input);
+ if (type == -1) {
+ return false;
+ }
+ if (type == T_STOP) {
+ break;
+ }
+ tag = readI16(input);
+ if (INT_CONV_ERROR_OCCURRED(tag)) {
+ return false;
+ }
+ if (tag >= 0 && tag < spec_seq_len) {
+ item_spec = PyTuple_GET_ITEM(spec_seq, tag);
+ } else {
+ item_spec = Py_None;
+ }
+
+ if (item_spec == Py_None) {
+ if (!skip(input, type)) {
+ return false;
+ } else {
+ continue;
+ }
+ }
+
+ if (!parse_struct_item_spec(&parsedspec, item_spec)) {
+ return false;
+ }
+ if (parsedspec.type != type) {
+ if (!skip(input, type)) {
+ PyErr_SetString(PyExc_TypeError, "struct field had wrong type while reading and can't be skipped");
+ return false;
+ } else {
+ continue;
+ }
+ }
+
+ fieldval = decode_val(input, parsedspec.type, parsedspec.typeargs);
+ if (fieldval == NULL) {
+ return false;
+ }
+
+ if (PyObject_SetAttr(output, parsedspec.attrname, fieldval) == -1) {
+ Py_DECREF(fieldval);
+ return false;
+ }
+ Py_DECREF(fieldval);
+ }
+ return true;
+}
+
+
+/* --- MAIN RECURSIVE INPUT FUCNTION --- */
+
+// Returns a new reference.
+static PyObject*
+decode_val(DecodeBuffer* input, TType type, PyObject* typeargs) {
+ switch (type) {
+
+ case T_BOOL: {
+ int8_t v = readByte(input);
+ if (INT_CONV_ERROR_OCCURRED(v)) {
+ return NULL;
+ }
+
+ switch (v) {
+ case 0: Py_RETURN_FALSE;
+ case 1: Py_RETURN_TRUE;
+ // Don't laugh. This is a potentially serious issue.
+ default: PyErr_SetString(PyExc_TypeError, "boolean out of range"); return NULL;
+ }
+ break;
+ }
+ case T_I08: {
+ int8_t v = readByte(input);
+ if (INT_CONV_ERROR_OCCURRED(v)) {
+ return NULL;
+ }
+
+ return PyInt_FromLong(v);
+ }
+ case T_I16: {
+ int16_t v = readI16(input);
+ if (INT_CONV_ERROR_OCCURRED(v)) {
+ return NULL;
+ }
+ return PyInt_FromLong(v);
+ }
+ case T_I32: {
+ int32_t v = readI32(input);
+ if (INT_CONV_ERROR_OCCURRED(v)) {
+ return NULL;
+ }
+ return PyInt_FromLong(v);
+ }
+
+ case T_I64: {
+ int64_t v = readI64(input);
+ if (INT_CONV_ERROR_OCCURRED(v)) {
+ return NULL;
+ }
+ // TODO(dreiss): Find out if we can take this fastpath always when
+ // sizeof(long) == sizeof(long long).
+ if (CHECK_RANGE(v, LONG_MIN, LONG_MAX)) {
+ return PyInt_FromLong((long) v);
+ }
+
+ return PyLong_FromLongLong(v);
+ }
+
+ case T_DOUBLE: {
+ double v = readDouble(input);
+ if (v == -1.0 && PyErr_Occurred()) {
+ return false;
+ }
+ return PyFloat_FromDouble(v);
+ }
+
+ case T_STRING: {
+ Py_ssize_t len = readI32(input);
+ char* buf;
+ if (!readBytes(input, &buf, len)) {
+ return NULL;
+ }
+
+ return PyString_FromStringAndSize(buf, len);
+ }
+
+ case T_LIST:
+ case T_SET: {
+ SetListTypeArgs parsedargs;
+ int32_t len;
+ PyObject* ret = NULL;
+ int i;
+
+ if (!parse_set_list_args(&parsedargs, typeargs)) {
+ return NULL;
+ }
+
+ if (!checkTypeByte(input, parsedargs.element_type)) {
+ return NULL;
+ }
+
+ len = readI32(input);
+ if (!check_ssize_t_32(len)) {
+ return NULL;
+ }
+
+ ret = PyList_New(len);
+ if (!ret) {
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ PyObject* item = decode_val(input, parsedargs.element_type, parsedargs.typeargs);
+ if (!item) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ PyList_SET_ITEM(ret, i, item);
+ }
+
+ // TODO(dreiss): Consider biting the bullet and making two separate cases
+ // for list and set, avoiding this post facto conversion.
+ if (type == T_SET) {
+ PyObject* setret;
+#if (PY_VERSION_HEX < 0x02050000)
+ // hack needed for older versions
+ setret = PyObject_CallFunctionObjArgs((PyObject*)&PySet_Type, ret, NULL);
+#else
+ // official version
+ setret = PySet_New(ret);
+#endif
+ Py_DECREF(ret);
+ return setret;
+ }
+ return ret;
+ }
+
+ case T_MAP: {
+ int32_t len;
+ int i;
+ MapTypeArgs parsedargs;
+ PyObject* ret = NULL;
+
+ if (!parse_map_args(&parsedargs, typeargs)) {
+ return NULL;
+ }
+
+ if (!checkTypeByte(input, parsedargs.ktag)) {
+ return NULL;
+ }
+ if (!checkTypeByte(input, parsedargs.vtag)) {
+ return NULL;
+ }
+
+ len = readI32(input);
+ if (!check_ssize_t_32(len)) {
+ return false;
+ }
+
+ ret = PyDict_New();
+ if (!ret) {
+ goto error;
+ }
+
+ for (i = 0; i < len; i++) {
+ PyObject* k = NULL;
+ PyObject* v = NULL;
+ k = decode_val(input, parsedargs.ktag, parsedargs.ktypeargs);
+ if (k == NULL) {
+ goto loop_error;
+ }
+ v = decode_val(input, parsedargs.vtag, parsedargs.vtypeargs);
+ if (v == NULL) {
+ goto loop_error;
+ }
+ if (PyDict_SetItem(ret, k, v) == -1) {
+ goto loop_error;
+ }
+
+ Py_DECREF(k);
+ Py_DECREF(v);
+ continue;
+
+ // Yuck! Destructors, anyone?
+ loop_error:
+ Py_XDECREF(k);
+ Py_XDECREF(v);
+ goto error;
+ }
+
+ return ret;
+
+ error:
+ Py_XDECREF(ret);
+ return NULL;
+ }
+
+ case T_STRUCT: {
+ StructTypeArgs parsedargs;
+ PyObject* ret;
+ if (!parse_struct_args(&parsedargs, typeargs)) {
+ return NULL;
+ }
+
+ ret = PyObject_CallObject(parsedargs.klass, NULL);
+ if (!ret) {
+ return NULL;
+ }
+
+ if (!decode_struct(input, ret, parsedargs.spec)) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ return ret;
+ }
+
+ case T_STOP:
+ case T_VOID:
+ case T_UTF16:
+ case T_UTF8:
+ case T_U64:
+ default:
+ PyErr_SetString(PyExc_TypeError, "Unexpected TType");
+ return NULL;
+ }
+}
+
+
+/* --- TOP-LEVEL WRAPPER FOR INPUT -- */
+
+static PyObject*
+decode_binary(PyObject *self, PyObject *args) {
+ PyObject* output_obj = NULL;
+ PyObject* transport = NULL;
+ PyObject* typeargs = NULL;
+ StructTypeArgs parsedargs;
+ DecodeBuffer input = {0, 0};
+
+ if (!PyArg_ParseTuple(args, "OOO", &output_obj, &transport, &typeargs)) {
+ return NULL;
+ }
+
+ if (!parse_struct_args(&parsedargs, typeargs)) {
+ return NULL;
+ }
+
+ if (!decode_buffer_from_obj(&input, transport)) {
+ return NULL;
+ }
+
+ if (!decode_struct(&input, output_obj, parsedargs.spec)) {
+ free_decodebuf(&input);
+ return NULL;
+ }
+
+ free_decodebuf(&input);
+
+ Py_RETURN_NONE;
+}
+
+/* ====== END READING FUNCTIONS ====== */
+
+
+/* -- PYTHON MODULE SETUP STUFF --- */
+
+static PyMethodDef ThriftFastBinaryMethods[] = {
+
+ {"encode_binary", encode_binary, METH_VARARGS, ""},
+ {"decode_binary", decode_binary, METH_VARARGS, ""},
+
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+PyMODINIT_FUNC
+initfastbinary(void) {
+#define INIT_INTERN_STRING(value) \
+ do { \
+ INTERN_STRING(value) = PyString_InternFromString(#value); \
+ if(!INTERN_STRING(value)) return; \
+ } while(0)
+
+ INIT_INTERN_STRING(cstringio_buf);
+ INIT_INTERN_STRING(cstringio_refill);
+#undef INIT_INTERN_STRING
+
+ PycString_IMPORT;
+ if (PycStringIO == NULL) return;
+
+ (void) Py_InitModule("thrift.protocol.fastbinary", ThriftFastBinaryMethods);
+}
diff --git a/pyload/lib/thrift/server/THttpServer.py b/pyload/lib/thrift/server/THttpServer.py
new file mode 100644
index 000000000..be54bab94
--- /dev/null
+++ b/pyload/lib/thrift/server/THttpServer.py
@@ -0,0 +1,87 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import BaseHTTPServer
+
+from thrift.server import TServer
+from thrift.transport import TTransport
+
+
+class ResponseException(Exception):
+ """Allows handlers to override the HTTP response
+
+ Normally, THttpServer always sends a 200 response. If a handler wants
+ to override this behavior (e.g., to simulate a misconfigured or
+ overloaded web server during testing), it can raise a ResponseException.
+ The function passed to the constructor will be called with the
+ RequestHandler as its only argument.
+ """
+ def __init__(self, handler):
+ self.handler = handler
+
+
+class THttpServer(TServer.TServer):
+ """A simple HTTP-based Thrift server
+
+ This class is not very performant, but it is useful (for example) for
+ acting as a mock version of an Apache-based PHP Thrift endpoint.
+ """
+ def __init__(self,
+ processor,
+ server_address,
+ inputProtocolFactory,
+ outputProtocolFactory=None,
+ server_class=BaseHTTPServer.HTTPServer):
+ """Set up protocol factories and HTTP server.
+
+ See BaseHTTPServer for server_address.
+ See TServer for protocol factories.
+ """
+ if outputProtocolFactory is None:
+ outputProtocolFactory = inputProtocolFactory
+
+ TServer.TServer.__init__(self, processor, None, None, None,
+ inputProtocolFactory, outputProtocolFactory)
+
+ thttpserver = self
+
+ class RequestHander(BaseHTTPServer.BaseHTTPRequestHandler):
+ def do_POST(self):
+ # Don't care about the request path.
+ itrans = TTransport.TFileObjectTransport(self.rfile)
+ otrans = TTransport.TFileObjectTransport(self.wfile)
+ itrans = TTransport.TBufferedTransport(
+ itrans, int(self.headers['Content-Length']))
+ otrans = TTransport.TMemoryBuffer()
+ iprot = thttpserver.inputProtocolFactory.getProtocol(itrans)
+ oprot = thttpserver.outputProtocolFactory.getProtocol(otrans)
+ try:
+ thttpserver.processor.process(iprot, oprot)
+ except ResponseException, exn:
+ exn.handler(self)
+ else:
+ self.send_response(200)
+ self.send_header("content-type", "application/x-thrift")
+ self.end_headers()
+ self.wfile.write(otrans.getvalue())
+
+ self.httpd = server_class(server_address, RequestHander)
+
+ def serve(self):
+ self.httpd.serve_forever()
diff --git a/pyload/lib/thrift/server/TNonblockingServer.py b/pyload/lib/thrift/server/TNonblockingServer.py
new file mode 100644
index 000000000..fa478d01f
--- /dev/null
+++ b/pyload/lib/thrift/server/TNonblockingServer.py
@@ -0,0 +1,346 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+"""Implementation of non-blocking server.
+
+The main idea of the server is to receive and send requests
+only from the main thread.
+
+The thread poool should be sized for concurrent tasks, not
+maximum connections
+"""
+import threading
+import socket
+import Queue
+import select
+import struct
+import logging
+
+from thrift.transport import TTransport
+from thrift.protocol.TBinaryProtocol import TBinaryProtocolFactory
+
+__all__ = ['TNonblockingServer']
+
+
+class Worker(threading.Thread):
+ """Worker is a small helper to process incoming connection."""
+
+ def __init__(self, queue):
+ threading.Thread.__init__(self)
+ self.queue = queue
+
+ def run(self):
+ """Process queries from task queue, stop if processor is None."""
+ while True:
+ try:
+ processor, iprot, oprot, otrans, callback = self.queue.get()
+ if processor is None:
+ break
+ processor.process(iprot, oprot)
+ callback(True, otrans.getvalue())
+ except Exception:
+ logging.exception("Exception while processing request")
+ callback(False, '')
+
+WAIT_LEN = 0
+WAIT_MESSAGE = 1
+WAIT_PROCESS = 2
+SEND_ANSWER = 3
+CLOSED = 4
+
+
+def locked(func):
+ """Decorator which locks self.lock."""
+ def nested(self, *args, **kwargs):
+ self.lock.acquire()
+ try:
+ return func(self, *args, **kwargs)
+ finally:
+ self.lock.release()
+ return nested
+
+
+def socket_exception(func):
+ """Decorator close object on socket.error."""
+ def read(self, *args, **kwargs):
+ try:
+ return func(self, *args, **kwargs)
+ except socket.error:
+ self.close()
+ return read
+
+
+class Connection:
+ """Basic class is represented connection.
+
+ It can be in state:
+ WAIT_LEN --- connection is reading request len.
+ WAIT_MESSAGE --- connection is reading request.
+ WAIT_PROCESS --- connection has just read whole request and
+ waits for call ready routine.
+ SEND_ANSWER --- connection is sending answer string (including length
+ of answer).
+ CLOSED --- socket was closed and connection should be deleted.
+ """
+ def __init__(self, new_socket, wake_up):
+ self.socket = new_socket
+ self.socket.setblocking(False)
+ self.status = WAIT_LEN
+ self.len = 0
+ self.message = ''
+ self.lock = threading.Lock()
+ self.wake_up = wake_up
+
+ def _read_len(self):
+ """Reads length of request.
+
+ It's a safer alternative to self.socket.recv(4)
+ """
+ read = self.socket.recv(4 - len(self.message))
+ if len(read) == 0:
+ # if we read 0 bytes and self.message is empty, then
+ # the client closed the connection
+ if len(self.message) != 0:
+ logging.error("can't read frame size from socket")
+ self.close()
+ return
+ self.message += read
+ if len(self.message) == 4:
+ self.len, = struct.unpack('!i', self.message)
+ if self.len < 0:
+ logging.error("negative frame size, it seems client "
+ "doesn't use FramedTransport")
+ self.close()
+ elif self.len == 0:
+ logging.error("empty frame, it's really strange")
+ self.close()
+ else:
+ self.message = ''
+ self.status = WAIT_MESSAGE
+
+ @socket_exception
+ def read(self):
+ """Reads data from stream and switch state."""
+ assert self.status in (WAIT_LEN, WAIT_MESSAGE)
+ if self.status == WAIT_LEN:
+ self._read_len()
+ # go back to the main loop here for simplicity instead of
+ # falling through, even though there is a good chance that
+ # the message is already available
+ elif self.status == WAIT_MESSAGE:
+ read = self.socket.recv(self.len - len(self.message))
+ if len(read) == 0:
+ logging.error("can't read frame from socket (get %d of "
+ "%d bytes)" % (len(self.message), self.len))
+ self.close()
+ return
+ self.message += read
+ if len(self.message) == self.len:
+ self.status = WAIT_PROCESS
+
+ @socket_exception
+ def write(self):
+ """Writes data from socket and switch state."""
+ assert self.status == SEND_ANSWER
+ sent = self.socket.send(self.message)
+ if sent == len(self.message):
+ self.status = WAIT_LEN
+ self.message = ''
+ self.len = 0
+ else:
+ self.message = self.message[sent:]
+
+ @locked
+ def ready(self, all_ok, message):
+ """Callback function for switching state and waking up main thread.
+
+ This function is the only function witch can be called asynchronous.
+
+ The ready can switch Connection to three states:
+ WAIT_LEN if request was oneway.
+ SEND_ANSWER if request was processed in normal way.
+ CLOSED if request throws unexpected exception.
+
+ The one wakes up main thread.
+ """
+ assert self.status == WAIT_PROCESS
+ if not all_ok:
+ self.close()
+ self.wake_up()
+ return
+ self.len = ''
+ if len(message) == 0:
+ # it was a oneway request, do not write answer
+ self.message = ''
+ self.status = WAIT_LEN
+ else:
+ self.message = struct.pack('!i', len(message)) + message
+ self.status = SEND_ANSWER
+ self.wake_up()
+
+ @locked
+ def is_writeable(self):
+ """Return True if connection should be added to write list of select"""
+ return self.status == SEND_ANSWER
+
+ # it's not necessary, but...
+ @locked
+ def is_readable(self):
+ """Return True if connection should be added to read list of select"""
+ return self.status in (WAIT_LEN, WAIT_MESSAGE)
+
+ @locked
+ def is_closed(self):
+ """Returns True if connection is closed."""
+ return self.status == CLOSED
+
+ def fileno(self):
+ """Returns the file descriptor of the associated socket."""
+ return self.socket.fileno()
+
+ def close(self):
+ """Closes connection"""
+ self.status = CLOSED
+ self.socket.close()
+
+
+class TNonblockingServer:
+ """Non-blocking server."""
+
+ def __init__(self,
+ processor,
+ lsocket,
+ inputProtocolFactory=None,
+ outputProtocolFactory=None,
+ threads=10):
+ self.processor = processor
+ self.socket = lsocket
+ self.in_protocol = inputProtocolFactory or TBinaryProtocolFactory()
+ self.out_protocol = outputProtocolFactory or self.in_protocol
+ self.threads = int(threads)
+ self.clients = {}
+ self.tasks = Queue.Queue()
+ self._read, self._write = socket.socketpair()
+ self.prepared = False
+ self._stop = False
+
+ def setNumThreads(self, num):
+ """Set the number of worker threads that should be created."""
+ # implement ThreadPool interface
+ assert not self.prepared, "Can't change number of threads after start"
+ self.threads = num
+
+ def prepare(self):
+ """Prepares server for serve requests."""
+ if self.prepared:
+ return
+ self.socket.listen()
+ for _ in xrange(self.threads):
+ thread = Worker(self.tasks)
+ thread.setDaemon(True)
+ thread.start()
+ self.prepared = True
+
+ def wake_up(self):
+ """Wake up main thread.
+
+ The server usualy waits in select call in we should terminate one.
+ The simplest way is using socketpair.
+
+ Select always wait to read from the first socket of socketpair.
+
+ In this case, we can just write anything to the second socket from
+ socketpair.
+ """
+ self._write.send('1')
+
+ def stop(self):
+ """Stop the server.
+
+ This method causes the serve() method to return. stop() may be invoked
+ from within your handler, or from another thread.
+
+ After stop() is called, serve() will return but the server will still
+ be listening on the socket. serve() may then be called again to resume
+ processing requests. Alternatively, close() may be called after
+ serve() returns to close the server socket and shutdown all worker
+ threads.
+ """
+ self._stop = True
+ self.wake_up()
+
+ def _select(self):
+ """Does select on open connections."""
+ readable = [self.socket.handle.fileno(), self._read.fileno()]
+ writable = []
+ for i, connection in self.clients.items():
+ if connection.is_readable():
+ readable.append(connection.fileno())
+ if connection.is_writeable():
+ writable.append(connection.fileno())
+ if connection.is_closed():
+ del self.clients[i]
+ return select.select(readable, writable, readable)
+
+ def handle(self):
+ """Handle requests.
+
+ WARNING! You must call prepare() BEFORE calling handle()
+ """
+ assert self.prepared, "You have to call prepare before handle"
+ rset, wset, xset = self._select()
+ for readable in rset:
+ if readable == self._read.fileno():
+ # don't care i just need to clean readable flag
+ self._read.recv(1024)
+ elif readable == self.socket.handle.fileno():
+ client = self.socket.accept().handle
+ self.clients[client.fileno()] = Connection(client,
+ self.wake_up)
+ else:
+ connection = self.clients[readable]
+ connection.read()
+ if connection.status == WAIT_PROCESS:
+ itransport = TTransport.TMemoryBuffer(connection.message)
+ otransport = TTransport.TMemoryBuffer()
+ iprot = self.in_protocol.getProtocol(itransport)
+ oprot = self.out_protocol.getProtocol(otransport)
+ self.tasks.put([self.processor, iprot, oprot,
+ otransport, connection.ready])
+ for writeable in wset:
+ self.clients[writeable].write()
+ for oob in xset:
+ self.clients[oob].close()
+ del self.clients[oob]
+
+ def close(self):
+ """Closes the server."""
+ for _ in xrange(self.threads):
+ self.tasks.put([None, None, None, None, None])
+ self.socket.close()
+ self.prepared = False
+
+ def serve(self):
+ """Serve requests.
+
+ Serve requests forever, or until stop() is called.
+ """
+ self._stop = False
+ self.prepare()
+ while not self._stop:
+ self.handle()
diff --git a/pyload/lib/thrift/server/TProcessPoolServer.py b/pyload/lib/thrift/server/TProcessPoolServer.py
new file mode 100644
index 000000000..7a695a883
--- /dev/null
+++ b/pyload/lib/thrift/server/TProcessPoolServer.py
@@ -0,0 +1,118 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+import logging
+from multiprocessing import Process, Value, Condition, reduction
+
+from TServer import TServer
+from thrift.transport.TTransport import TTransportException
+
+
+class TProcessPoolServer(TServer):
+ """Server with a fixed size pool of worker subprocesses to service requests
+
+ Note that if you need shared state between the handlers - it's up to you!
+ Written by Dvir Volk, doat.com
+ """
+ def __init__(self, *args):
+ TServer.__init__(self, *args)
+ self.numWorkers = 10
+ self.workers = []
+ self.isRunning = Value('b', False)
+ self.stopCondition = Condition()
+ self.postForkCallback = None
+
+ def setPostForkCallback(self, callback):
+ if not callable(callback):
+ raise TypeError("This is not a callback!")
+ self.postForkCallback = callback
+
+ def setNumWorkers(self, num):
+ """Set the number of worker threads that should be created"""
+ self.numWorkers = num
+
+ def workerProcess(self):
+ """Loop getting clients from the shared queue and process them"""
+ if self.postForkCallback:
+ self.postForkCallback()
+
+ while self.isRunning.value:
+ try:
+ client = self.serverTransport.accept()
+ self.serveClient(client)
+ except (KeyboardInterrupt, SystemExit):
+ return 0
+ except Exception, x:
+ logging.exception(x)
+
+ def serveClient(self, client):
+ """Process input/output from a client for as long as possible"""
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+ iprot = self.inputProtocolFactory.getProtocol(itrans)
+ oprot = self.outputProtocolFactory.getProtocol(otrans)
+
+ try:
+ while True:
+ self.processor.process(iprot, oprot)
+ except TTransportException, tx:
+ pass
+ except Exception, x:
+ logging.exception(x)
+
+ itrans.close()
+ otrans.close()
+
+ def serve(self):
+ """Start workers and put into queue"""
+ # this is a shared state that can tell the workers to exit when False
+ self.isRunning.value = True
+
+ # first bind and listen to the port
+ self.serverTransport.listen()
+
+ # fork the children
+ for i in range(self.numWorkers):
+ try:
+ w = Process(target=self.workerProcess)
+ w.daemon = True
+ w.start()
+ self.workers.append(w)
+ except Exception, x:
+ logging.exception(x)
+
+ # wait until the condition is set by stop()
+ while True:
+ self.stopCondition.acquire()
+ try:
+ self.stopCondition.wait()
+ break
+ except (SystemExit, KeyboardInterrupt):
+ break
+ except Exception, x:
+ logging.exception(x)
+
+ self.isRunning.value = False
+
+ def stop(self):
+ self.isRunning.value = False
+ self.stopCondition.acquire()
+ self.stopCondition.notify()
+ self.stopCondition.release()
diff --git a/pyload/lib/thrift/server/TServer.py b/pyload/lib/thrift/server/TServer.py
new file mode 100644
index 000000000..2f24842c4
--- /dev/null
+++ b/pyload/lib/thrift/server/TServer.py
@@ -0,0 +1,269 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import Queue
+import logging
+import os
+import sys
+import threading
+import traceback
+
+from thrift.Thrift import TProcessor
+from thrift.protocol import TBinaryProtocol
+from thrift.transport import TTransport
+
+
+class TServer:
+ """Base interface for a server, which must have a serve() method.
+
+ Three constructors for all servers:
+ 1) (processor, serverTransport)
+ 2) (processor, serverTransport, transportFactory, protocolFactory)
+ 3) (processor, serverTransport,
+ inputTransportFactory, outputTransportFactory,
+ inputProtocolFactory, outputProtocolFactory)
+ """
+ def __init__(self, *args):
+ if (len(args) == 2):
+ self.__initArgs__(args[0], args[1],
+ TTransport.TTransportFactoryBase(),
+ TTransport.TTransportFactoryBase(),
+ TBinaryProtocol.TBinaryProtocolFactory(),
+ TBinaryProtocol.TBinaryProtocolFactory())
+ elif (len(args) == 4):
+ self.__initArgs__(args[0], args[1], args[2], args[2], args[3], args[3])
+ elif (len(args) == 6):
+ self.__initArgs__(args[0], args[1], args[2], args[3], args[4], args[5])
+
+ def __initArgs__(self, processor, serverTransport,
+ inputTransportFactory, outputTransportFactory,
+ inputProtocolFactory, outputProtocolFactory):
+ self.processor = processor
+ self.serverTransport = serverTransport
+ self.inputTransportFactory = inputTransportFactory
+ self.outputTransportFactory = outputTransportFactory
+ self.inputProtocolFactory = inputProtocolFactory
+ self.outputProtocolFactory = outputProtocolFactory
+
+ def serve(self):
+ pass
+
+
+class TSimpleServer(TServer):
+ """Simple single-threaded server that just pumps around one transport."""
+
+ def __init__(self, *args):
+ TServer.__init__(self, *args)
+
+ def serve(self):
+ self.serverTransport.listen()
+ while True:
+ client = self.serverTransport.accept()
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+ iprot = self.inputProtocolFactory.getProtocol(itrans)
+ oprot = self.outputProtocolFactory.getProtocol(otrans)
+ try:
+ while True:
+ self.processor.process(iprot, oprot)
+ except TTransport.TTransportException, tx:
+ pass
+ except Exception, x:
+ logging.exception(x)
+
+ itrans.close()
+ otrans.close()
+
+
+class TThreadedServer(TServer):
+ """Threaded server that spawns a new thread per each connection."""
+
+ def __init__(self, *args, **kwargs):
+ TServer.__init__(self, *args)
+ self.daemon = kwargs.get("daemon", False)
+
+ def serve(self):
+ self.serverTransport.listen()
+ while True:
+ try:
+ client = self.serverTransport.accept()
+ t = threading.Thread(target=self.handle, args=(client,))
+ t.setDaemon(self.daemon)
+ t.start()
+ except KeyboardInterrupt:
+ raise
+ except Exception, x:
+ logging.exception(x)
+
+ def handle(self, client):
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+ iprot = self.inputProtocolFactory.getProtocol(itrans)
+ oprot = self.outputProtocolFactory.getProtocol(otrans)
+ try:
+ while True:
+ self.processor.process(iprot, oprot)
+ except TTransport.TTransportException, tx:
+ pass
+ except Exception, x:
+ logging.exception(x)
+
+ itrans.close()
+ otrans.close()
+
+
+class TThreadPoolServer(TServer):
+ """Server with a fixed size pool of threads which service requests."""
+
+ def __init__(self, *args, **kwargs):
+ TServer.__init__(self, *args)
+ self.clients = Queue.Queue()
+ self.threads = 10
+ self.daemon = kwargs.get("daemon", False)
+
+ def setNumThreads(self, num):
+ """Set the number of worker threads that should be created"""
+ self.threads = num
+
+ def serveThread(self):
+ """Loop around getting clients from the shared queue and process them."""
+ while True:
+ try:
+ client = self.clients.get()
+ self.serveClient(client)
+ except Exception, x:
+ logging.exception(x)
+
+ def serveClient(self, client):
+ """Process input/output from a client for as long as possible"""
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+ iprot = self.inputProtocolFactory.getProtocol(itrans)
+ oprot = self.outputProtocolFactory.getProtocol(otrans)
+ try:
+ while True:
+ self.processor.process(iprot, oprot)
+ except TTransport.TTransportException, tx:
+ pass
+ except Exception, x:
+ logging.exception(x)
+
+ itrans.close()
+ otrans.close()
+
+ def serve(self):
+ """Start a fixed number of worker threads and put client into a queue"""
+ for i in range(self.threads):
+ try:
+ t = threading.Thread(target=self.serveThread)
+ t.setDaemon(self.daemon)
+ t.start()
+ except Exception, x:
+ logging.exception(x)
+
+ # Pump the socket for clients
+ self.serverTransport.listen()
+ while True:
+ try:
+ client = self.serverTransport.accept()
+ self.clients.put(client)
+ except Exception, x:
+ logging.exception(x)
+
+
+class TForkingServer(TServer):
+ """A Thrift server that forks a new process for each request
+
+ This is more scalable than the threaded server as it does not cause
+ GIL contention.
+
+ Note that this has different semantics from the threading server.
+ Specifically, updates to shared variables will no longer be shared.
+ It will also not work on windows.
+
+ This code is heavily inspired by SocketServer.ForkingMixIn in the
+ Python stdlib.
+ """
+ def __init__(self, *args):
+ TServer.__init__(self, *args)
+ self.children = []
+
+ def serve(self):
+ def try_close(file):
+ try:
+ file.close()
+ except IOError, e:
+ logging.warning(e, exc_info=True)
+
+ self.serverTransport.listen()
+ while True:
+ client = self.serverTransport.accept()
+ try:
+ pid = os.fork()
+
+ if pid: # parent
+ # add before collect, otherwise you race w/ waitpid
+ self.children.append(pid)
+ self.collect_children()
+
+ # Parent must close socket or the connection may not get
+ # closed promptly
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+ try_close(itrans)
+ try_close(otrans)
+ else:
+ itrans = self.inputTransportFactory.getTransport(client)
+ otrans = self.outputTransportFactory.getTransport(client)
+
+ iprot = self.inputProtocolFactory.getProtocol(itrans)
+ oprot = self.outputProtocolFactory.getProtocol(otrans)
+
+ ecode = 0
+ try:
+ try:
+ while True:
+ self.processor.process(iprot, oprot)
+ except TTransport.TTransportException, tx:
+ pass
+ except Exception, e:
+ logging.exception(e)
+ ecode = 1
+ finally:
+ try_close(itrans)
+ try_close(otrans)
+
+ os._exit(ecode)
+
+ except TTransport.TTransportException, tx:
+ pass
+ except Exception, x:
+ logging.exception(x)
+
+ def collect_children(self):
+ while self.children:
+ try:
+ pid, status = os.waitpid(0, os.WNOHANG)
+ except os.error:
+ pid = None
+
+ if pid:
+ self.children.remove(pid)
+ else:
+ break
diff --git a/module/lib/thrift/server/__init__.py b/pyload/lib/thrift/server/__init__.py
index 1bf6e254e..1bf6e254e 100644
--- a/module/lib/thrift/server/__init__.py
+++ b/pyload/lib/thrift/server/__init__.py
diff --git a/pyload/lib/thrift/transport/THttpClient.py b/pyload/lib/thrift/transport/THttpClient.py
new file mode 100644
index 000000000..ea80a1ae8
--- /dev/null
+++ b/pyload/lib/thrift/transport/THttpClient.py
@@ -0,0 +1,149 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import httplib
+import os
+import socket
+import sys
+import urllib
+import urlparse
+import warnings
+
+from cStringIO import StringIO
+
+from TTransport import *
+
+
+class THttpClient(TTransportBase):
+ """Http implementation of TTransport base."""
+
+ def __init__(self, uri_or_host, port=None, path=None):
+ """THttpClient supports two different types constructor parameters.
+
+ THttpClient(host, port, path) - deprecated
+ THttpClient(uri)
+
+ Only the second supports https.
+ """
+ if port is not None:
+ warnings.warn(
+ "Please use the THttpClient('http://host:port/path') syntax",
+ DeprecationWarning,
+ stacklevel=2)
+ self.host = uri_or_host
+ self.port = port
+ assert path
+ self.path = path
+ self.scheme = 'http'
+ else:
+ parsed = urlparse.urlparse(uri_or_host)
+ self.scheme = parsed.scheme
+ assert self.scheme in ('http', 'https')
+ if self.scheme == 'http':
+ self.port = parsed.port or httplib.HTTP_PORT
+ elif self.scheme == 'https':
+ self.port = parsed.port or httplib.HTTPS_PORT
+ self.host = parsed.hostname
+ self.path = parsed.path
+ if parsed.query:
+ self.path += '?%s' % parsed.query
+ self.__wbuf = StringIO()
+ self.__http = None
+ self.__timeout = None
+ self.__custom_headers = None
+
+ def open(self):
+ if self.scheme == 'http':
+ self.__http = httplib.HTTP(self.host, self.port)
+ else:
+ self.__http = httplib.HTTPS(self.host, self.port)
+
+ def close(self):
+ self.__http.close()
+ self.__http = None
+
+ def isOpen(self):
+ return self.__http is not None
+
+ def setTimeout(self, ms):
+ if not hasattr(socket, 'getdefaulttimeout'):
+ raise NotImplementedError
+
+ if ms is None:
+ self.__timeout = None
+ else:
+ self.__timeout = ms / 1000.0
+
+ def setCustomHeaders(self, headers):
+ self.__custom_headers = headers
+
+ def read(self, sz):
+ return self.__http.file.read(sz)
+
+ def write(self, buf):
+ self.__wbuf.write(buf)
+
+ def __withTimeout(f):
+ def _f(*args, **kwargs):
+ orig_timeout = socket.getdefaulttimeout()
+ socket.setdefaulttimeout(args[0].__timeout)
+ result = f(*args, **kwargs)
+ socket.setdefaulttimeout(orig_timeout)
+ return result
+ return _f
+
+ def flush(self):
+ if self.isOpen():
+ self.close()
+ self.open()
+
+ # Pull data out of buffer
+ data = self.__wbuf.getvalue()
+ self.__wbuf = StringIO()
+
+ # HTTP request
+ self.__http.putrequest('POST', self.path)
+
+ # Write headers
+ self.__http.putheader('Host', self.host)
+ self.__http.putheader('Content-Type', 'application/x-thrift')
+ self.__http.putheader('Content-Length', str(len(data)))
+
+ if not self.__custom_headers or 'User-Agent' not in self.__custom_headers:
+ user_agent = 'Python/THttpClient'
+ script = os.path.basename(sys.argv[0])
+ if script:
+ user_agent = '%s (%s)' % (user_agent, urllib.quote(script))
+ self.__http.putheader('User-Agent', user_agent)
+
+ if self.__custom_headers:
+ for key, val in self.__custom_headers.iteritems():
+ self.__http.putheader(key, val)
+
+ self.__http.endheaders()
+
+ # Write payload
+ self.__http.send(data)
+
+ # Get reply to flush the request
+ self.code, self.message, self.headers = self.__http.getreply()
+
+ # Decorate if we know how to timeout
+ if hasattr(socket, 'getdefaulttimeout'):
+ flush = __withTimeout(flush)
diff --git a/pyload/lib/thrift/transport/TSSLSocket.py b/pyload/lib/thrift/transport/TSSLSocket.py
new file mode 100644
index 000000000..81e098426
--- /dev/null
+++ b/pyload/lib/thrift/transport/TSSLSocket.py
@@ -0,0 +1,214 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import os
+import socket
+import ssl
+
+from thrift.transport import TSocket
+from thrift.transport.TTransport import TTransportException
+
+
+class TSSLSocket(TSocket.TSocket):
+ """
+ SSL implementation of client-side TSocket
+
+ This class creates outbound sockets wrapped using the
+ python standard ssl module for encrypted connections.
+
+ The protocol used is set using the class variable
+ SSL_VERSION, which must be one of ssl.PROTOCOL_* and
+ defaults to ssl.PROTOCOL_TLSv1 for greatest security.
+ """
+ SSL_VERSION = ssl.PROTOCOL_TLSv1
+
+ def __init__(self,
+ host='localhost',
+ port=9090,
+ validate=True,
+ ca_certs=None,
+ keyfile=None,
+ certfile=None,
+ unix_socket=None):
+ """Create SSL TSocket
+
+ @param validate: Set to False to disable SSL certificate validation
+ @type validate: bool
+ @param ca_certs: Filename to the Certificate Authority pem file, possibly a
+ file downloaded from: http://curl.haxx.se/ca/cacert.pem This is passed to
+ the ssl_wrap function as the 'ca_certs' parameter.
+ @type ca_certs: str
+ @param keyfile: The private key
+ @type keyfile: str
+ @param certfile: The cert file
+ @type certfile: str
+
+ Raises an IOError exception if validate is True and the ca_certs file is
+ None, not present or unreadable.
+ """
+ self.validate = validate
+ self.is_valid = False
+ self.peercert = None
+ if not validate:
+ self.cert_reqs = ssl.CERT_NONE
+ else:
+ self.cert_reqs = ssl.CERT_REQUIRED
+ self.ca_certs = ca_certs
+ self.keyfile = keyfile
+ self.certfile = certfile
+ if validate:
+ if ca_certs is None or not os.access(ca_certs, os.R_OK):
+ raise IOError('Certificate Authority ca_certs file "%s" '
+ 'is not readable, cannot validate SSL '
+ 'certificates.' % (ca_certs))
+ TSocket.TSocket.__init__(self, host, port, unix_socket)
+
+ def open(self):
+ try:
+ res0 = self._resolveAddr()
+ for res in res0:
+ sock_family, sock_type = res[0:2]
+ ip_port = res[4]
+ plain_sock = socket.socket(sock_family, sock_type)
+ self.handle = ssl.wrap_socket(plain_sock,
+ ssl_version=self.SSL_VERSION,
+ do_handshake_on_connect=True,
+ ca_certs=self.ca_certs,
+ keyfile=self.keyfile,
+ certfile=self.certfile,
+ cert_reqs=self.cert_reqs)
+ self.handle.settimeout(self._timeout)
+ try:
+ self.handle.connect(ip_port)
+ except socket.error, e:
+ if res is not res0[-1]:
+ continue
+ else:
+ raise e
+ break
+ except socket.error, e:
+ if self._unix_socket:
+ message = 'Could not connect to secure socket %s: %s' \
+ % (self._unix_socket, e)
+ else:
+ message = 'Could not connect to %s:%d: %s' % (self.host, self.port, e)
+ raise TTransportException(type=TTransportException.NOT_OPEN,
+ message=message)
+ if self.validate:
+ self._validate_cert()
+
+ def _validate_cert(self):
+ """internal method to validate the peer's SSL certificate, and to check the
+ commonName of the certificate to ensure it matches the hostname we
+ used to make this connection. Does not support subjectAltName records
+ in certificates.
+
+ raises TTransportException if the certificate fails validation.
+ """
+ cert = self.handle.getpeercert()
+ self.peercert = cert
+ if 'subject' not in cert:
+ raise TTransportException(
+ type=TTransportException.NOT_OPEN,
+ message='No SSL certificate found from %s:%s' % (self.host, self.port))
+ fields = cert['subject']
+ for field in fields:
+ # ensure structure we get back is what we expect
+ if not isinstance(field, tuple):
+ continue
+ cert_pair = field[0]
+ if len(cert_pair) < 2:
+ continue
+ cert_key, cert_value = cert_pair[0:2]
+ if cert_key != 'commonName':
+ continue
+ certhost = cert_value
+ # this check should be performed by some sort of Access Manager
+ if certhost == self.host:
+ # success, cert commonName matches desired hostname
+ self.is_valid = True
+ return
+ else:
+ raise TTransportException(
+ type=TTransportException.UNKNOWN,
+ message='Hostname we connected to "%s" doesn\'t match certificate '
+ 'provided commonName "%s"' % (self.host, certhost))
+ raise TTransportException(
+ type=TTransportException.UNKNOWN,
+ message='Could not validate SSL certificate from '
+ 'host "%s". Cert=%s' % (self.host, cert))
+
+
+class TSSLServerSocket(TSocket.TServerSocket):
+ """SSL implementation of TServerSocket
+
+ This uses the ssl module's wrap_socket() method to provide SSL
+ negotiated encryption.
+ """
+ SSL_VERSION = ssl.PROTOCOL_TLSv1
+
+ def __init__(self,
+ host=None,
+ port=9090,
+ certfile='cert.pem',
+ unix_socket=None):
+ """Initialize a TSSLServerSocket
+
+ @param certfile: filename of the server certificate, defaults to cert.pem
+ @type certfile: str
+ @param host: The hostname or IP to bind the listen socket to,
+ i.e. 'localhost' for only allowing local network connections.
+ Pass None to bind to all interfaces.
+ @type host: str
+ @param port: The port to listen on for inbound connections.
+ @type port: int
+ """
+ self.setCertfile(certfile)
+ TSocket.TServerSocket.__init__(self, host, port)
+
+ def setCertfile(self, certfile):
+ """Set or change the server certificate file used to wrap new connections.
+
+ @param certfile: The filename of the server certificate,
+ i.e. '/etc/certs/server.pem'
+ @type certfile: str
+
+ Raises an IOError exception if the certfile is not present or unreadable.
+ """
+ if not os.access(certfile, os.R_OK):
+ raise IOError('No such certfile found: %s' % (certfile))
+ self.certfile = certfile
+
+ def accept(self):
+ plain_client, addr = self.handle.accept()
+ try:
+ client = ssl.wrap_socket(plain_client, certfile=self.certfile,
+ server_side=True, ssl_version=self.SSL_VERSION)
+ except ssl.SSLError, ssl_exc:
+ # failed handshake/ssl wrap, close socket to client
+ plain_client.close()
+ # raise ssl_exc
+ # We can't raise the exception, because it kills most TServer derived
+ # serve() methods.
+ # Instead, return None, and let the TServer instance deal with it in
+ # other exception handling. (but TSimpleServer dies anyway)
+ return None
+ result = TSocket.TSocket()
+ result.setHandle(client)
+ return result
diff --git a/pyload/lib/thrift/transport/TSocket.py b/pyload/lib/thrift/transport/TSocket.py
new file mode 100644
index 000000000..9e2b3849b
--- /dev/null
+++ b/pyload/lib/thrift/transport/TSocket.py
@@ -0,0 +1,176 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import errno
+import os
+import socket
+import sys
+
+from TTransport import *
+
+
+class TSocketBase(TTransportBase):
+ def _resolveAddr(self):
+ if self._unix_socket is not None:
+ return [(socket.AF_UNIX, socket.SOCK_STREAM, None, None,
+ self._unix_socket)]
+ else:
+ return socket.getaddrinfo(self.host,
+ self.port,
+ socket.AF_UNSPEC,
+ socket.SOCK_STREAM,
+ 0,
+ socket.AI_PASSIVE | socket.AI_ADDRCONFIG)
+
+ def close(self):
+ if self.handle:
+ self.handle.close()
+ self.handle = None
+
+
+class TSocket(TSocketBase):
+ """Socket implementation of TTransport base."""
+
+ def __init__(self, host='localhost', port=9090, unix_socket=None):
+ """Initialize a TSocket
+
+ @param host(str) The host to connect to.
+ @param port(int) The (TCP) port to connect to.
+ @param unix_socket(str) The filename of a unix socket to connect to.
+ (host and port will be ignored.)
+ """
+ self.host = host
+ self.port = port
+ self.handle = None
+ self._unix_socket = unix_socket
+ self._timeout = None
+
+ def setHandle(self, h):
+ self.handle = h
+
+ def isOpen(self):
+ return self.handle is not None
+
+ def setTimeout(self, ms):
+ if ms is None:
+ self._timeout = None
+ else:
+ self._timeout = ms / 1000.0
+
+ if self.handle is not None:
+ self.handle.settimeout(self._timeout)
+
+ def open(self):
+ try:
+ res0 = self._resolveAddr()
+ for res in res0:
+ self.handle = socket.socket(res[0], res[1])
+ self.handle.settimeout(self._timeout)
+ try:
+ self.handle.connect(res[4])
+ except socket.error, e:
+ if res is not res0[-1]:
+ continue
+ else:
+ raise e
+ break
+ except socket.error, e:
+ if self._unix_socket:
+ message = 'Could not connect to socket %s' % self._unix_socket
+ else:
+ message = 'Could not connect to %s:%d' % (self.host, self.port)
+ raise TTransportException(type=TTransportException.NOT_OPEN,
+ message=message)
+
+ def read(self, sz):
+ try:
+ buff = self.handle.recv(sz)
+ except socket.error, e:
+ if (e.args[0] == errno.ECONNRESET and
+ (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
+ # freebsd and Mach don't follow POSIX semantic of recv
+ # and fail with ECONNRESET if peer performed shutdown.
+ # See corresponding comment and code in TSocket::read()
+ # in lib/cpp/src/transport/TSocket.cpp.
+ self.close()
+ # Trigger the check to raise the END_OF_FILE exception below.
+ buff = ''
+ else:
+ raise
+ if len(buff) == 0:
+ raise TTransportException(type=TTransportException.END_OF_FILE,
+ message='TSocket read 0 bytes')
+ return buff
+
+ def write(self, buff):
+ if not self.handle:
+ raise TTransportException(type=TTransportException.NOT_OPEN,
+ message='Transport not open')
+ sent = 0
+ have = len(buff)
+ while sent < have:
+ plus = self.handle.send(buff)
+ if plus == 0:
+ raise TTransportException(type=TTransportException.END_OF_FILE,
+ message='TSocket sent 0 bytes')
+ sent += plus
+ buff = buff[plus:]
+
+ def flush(self):
+ pass
+
+
+class TServerSocket(TSocketBase, TServerTransportBase):
+ """Socket implementation of TServerTransport base."""
+
+ def __init__(self, host=None, port=9090, unix_socket=None):
+ self.host = host
+ self.port = port
+ self._unix_socket = unix_socket
+ self.handle = None
+
+ def listen(self):
+ res0 = self._resolveAddr()
+ for res in res0:
+ if res[0] is socket.AF_INET6 or res is res0[-1]:
+ break
+
+ # We need remove the old unix socket if the file exists and
+ # nobody is listening on it.
+ if self._unix_socket:
+ tmp = socket.socket(res[0], res[1])
+ try:
+ tmp.connect(res[4])
+ except socket.error, err:
+ eno, message = err.args
+ if eno == errno.ECONNREFUSED:
+ os.unlink(res[4])
+
+ self.handle = socket.socket(res[0], res[1])
+ self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if hasattr(self.handle, 'settimeout'):
+ self.handle.settimeout(None)
+ self.handle.bind(res[4])
+ self.handle.listen(128)
+
+ def accept(self):
+ client, addr = self.handle.accept()
+ result = TSocket()
+ result.setHandle(client)
+ return result
diff --git a/pyload/lib/thrift/transport/TTransport.py b/pyload/lib/thrift/transport/TTransport.py
new file mode 100644
index 000000000..4481371a6
--- /dev/null
+++ b/pyload/lib/thrift/transport/TTransport.py
@@ -0,0 +1,330 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from cStringIO import StringIO
+from struct import pack, unpack
+from thrift.Thrift import TException
+
+
+class TTransportException(TException):
+ """Custom Transport Exception class"""
+
+ UNKNOWN = 0
+ NOT_OPEN = 1
+ ALREADY_OPEN = 2
+ TIMED_OUT = 3
+ END_OF_FILE = 4
+
+ def __init__(self, type=UNKNOWN, message=None):
+ TException.__init__(self, message)
+ self.type = type
+
+
+class TTransportBase:
+ """Base class for Thrift transport layer."""
+
+ def isOpen(self):
+ pass
+
+ def open(self):
+ pass
+
+ def close(self):
+ pass
+
+ def read(self, sz):
+ pass
+
+ def readAll(self, sz):
+ buff = ''
+ have = 0
+ while (have < sz):
+ chunk = self.read(sz - have)
+ have += len(chunk)
+ buff += chunk
+
+ if len(chunk) == 0:
+ raise EOFError()
+
+ return buff
+
+ def write(self, buf):
+ pass
+
+ def flush(self):
+ pass
+
+
+# This class should be thought of as an interface.
+class CReadableTransport:
+ """base class for transports that are readable from C"""
+
+ # TODO(dreiss): Think about changing this interface to allow us to use
+ # a (Python, not c) StringIO instead, because it allows
+ # you to write after reading.
+
+ # NOTE: This is a classic class, so properties will NOT work
+ # correctly for setting.
+ @property
+ def cstringio_buf(self):
+ """A cStringIO buffer that contains the current chunk we are reading."""
+ pass
+
+ def cstringio_refill(self, partialread, reqlen):
+ """Refills cstringio_buf.
+
+ Returns the currently used buffer (which can but need not be the same as
+ the old cstringio_buf). partialread is what the C code has read from the
+ buffer, and should be inserted into the buffer before any more reads. The
+ return value must be a new, not borrowed reference. Something along the
+ lines of self._buf should be fine.
+
+ If reqlen bytes can't be read, throw EOFError.
+ """
+ pass
+
+
+class TServerTransportBase:
+ """Base class for Thrift server transports."""
+
+ def listen(self):
+ pass
+
+ def accept(self):
+ pass
+
+ def close(self):
+ pass
+
+
+class TTransportFactoryBase:
+ """Base class for a Transport Factory"""
+
+ def getTransport(self, trans):
+ return trans
+
+
+class TBufferedTransportFactory:
+ """Factory transport that builds buffered transports"""
+
+ def getTransport(self, trans):
+ buffered = TBufferedTransport(trans)
+ return buffered
+
+
+class TBufferedTransport(TTransportBase, CReadableTransport):
+ """Class that wraps another transport and buffers its I/O.
+
+ The implementation uses a (configurable) fixed-size read buffer
+ but buffers all writes until a flush is performed.
+ """
+ DEFAULT_BUFFER = 4096
+
+ def __init__(self, trans, rbuf_size=DEFAULT_BUFFER):
+ self.__trans = trans
+ self.__wbuf = StringIO()
+ self.__rbuf = StringIO("")
+ self.__rbuf_size = rbuf_size
+
+ def isOpen(self):
+ return self.__trans.isOpen()
+
+ def open(self):
+ return self.__trans.open()
+
+ def close(self):
+ return self.__trans.close()
+
+ def read(self, sz):
+ ret = self.__rbuf.read(sz)
+ if len(ret) != 0:
+ return ret
+
+ self.__rbuf = StringIO(self.__trans.read(max(sz, self.__rbuf_size)))
+ return self.__rbuf.read(sz)
+
+ def write(self, buf):
+ self.__wbuf.write(buf)
+
+ def flush(self):
+ out = self.__wbuf.getvalue()
+ # reset wbuf before write/flush to preserve state on underlying failure
+ self.__wbuf = StringIO()
+ self.__trans.write(out)
+ self.__trans.flush()
+
+ # Implement the CReadableTransport interface.
+ @property
+ def cstringio_buf(self):
+ return self.__rbuf
+
+ def cstringio_refill(self, partialread, reqlen):
+ retstring = partialread
+ if reqlen < self.__rbuf_size:
+ # try to make a read of as much as we can.
+ retstring += self.__trans.read(self.__rbuf_size)
+
+ # but make sure we do read reqlen bytes.
+ if len(retstring) < reqlen:
+ retstring += self.__trans.readAll(reqlen - len(retstring))
+
+ self.__rbuf = StringIO(retstring)
+ return self.__rbuf
+
+
+class TMemoryBuffer(TTransportBase, CReadableTransport):
+ """Wraps a cStringIO object as a TTransport.
+
+ NOTE: Unlike the C++ version of this class, you cannot write to it
+ then immediately read from it. If you want to read from a
+ TMemoryBuffer, you must either pass a string to the constructor.
+ TODO(dreiss): Make this work like the C++ version.
+ """
+
+ def __init__(self, value=None):
+ """value -- a value to read from for stringio
+
+ If value is set, this will be a transport for reading,
+ otherwise, it is for writing"""
+ if value is not None:
+ self._buffer = StringIO(value)
+ else:
+ self._buffer = StringIO()
+
+ def isOpen(self):
+ return not self._buffer.closed
+
+ def open(self):
+ pass
+
+ def close(self):
+ self._buffer.close()
+
+ def read(self, sz):
+ return self._buffer.read(sz)
+
+ def write(self, buf):
+ self._buffer.write(buf)
+
+ def flush(self):
+ pass
+
+ def getvalue(self):
+ return self._buffer.getvalue()
+
+ # Implement the CReadableTransport interface.
+ @property
+ def cstringio_buf(self):
+ return self._buffer
+
+ def cstringio_refill(self, partialread, reqlen):
+ # only one shot at reading...
+ raise EOFError()
+
+
+class TFramedTransportFactory:
+ """Factory transport that builds framed transports"""
+
+ def getTransport(self, trans):
+ framed = TFramedTransport(trans)
+ return framed
+
+
+class TFramedTransport(TTransportBase, CReadableTransport):
+ """Class that wraps another transport and frames its I/O when writing."""
+
+ def __init__(self, trans,):
+ self.__trans = trans
+ self.__rbuf = StringIO()
+ self.__wbuf = StringIO()
+
+ def isOpen(self):
+ return self.__trans.isOpen()
+
+ def open(self):
+ return self.__trans.open()
+
+ def close(self):
+ return self.__trans.close()
+
+ def read(self, sz):
+ ret = self.__rbuf.read(sz)
+ if len(ret) != 0:
+ return ret
+
+ self.readFrame()
+ return self.__rbuf.read(sz)
+
+ def readFrame(self):
+ buff = self.__trans.readAll(4)
+ sz, = unpack('!i', buff)
+ self.__rbuf = StringIO(self.__trans.readAll(sz))
+
+ def write(self, buf):
+ self.__wbuf.write(buf)
+
+ def flush(self):
+ wout = self.__wbuf.getvalue()
+ wsz = len(wout)
+ # reset wbuf before write/flush to preserve state on underlying failure
+ self.__wbuf = StringIO()
+ # N.B.: Doing this string concatenation is WAY cheaper than making
+ # two separate calls to the underlying socket object. Socket writes in
+ # Python turn out to be REALLY expensive, but it seems to do a pretty
+ # good job of managing string buffer operations without excessive copies
+ buf = pack("!i", wsz) + wout
+ self.__trans.write(buf)
+ self.__trans.flush()
+
+ # Implement the CReadableTransport interface.
+ @property
+ def cstringio_buf(self):
+ return self.__rbuf
+
+ def cstringio_refill(self, prefix, reqlen):
+ # self.__rbuf will already be empty here because fastbinary doesn't
+ # ask for a refill until the previous buffer is empty. Therefore,
+ # we can start reading new frames immediately.
+ while len(prefix) < reqlen:
+ self.readFrame()
+ prefix += self.__rbuf.getvalue()
+ self.__rbuf = StringIO(prefix)
+ return self.__rbuf
+
+
+class TFileObjectTransport(TTransportBase):
+ """Wraps a file-like object to make it work as a Thrift transport."""
+
+ def __init__(self, fileobj):
+ self.fileobj = fileobj
+
+ def isOpen(self):
+ return True
+
+ def close(self):
+ self.fileobj.close()
+
+ def read(self, sz):
+ return self.fileobj.read(sz)
+
+ def write(self, buf):
+ self.fileobj.write(buf)
+
+ def flush(self):
+ self.fileobj.flush()
diff --git a/pyload/lib/thrift/transport/TTwisted.py b/pyload/lib/thrift/transport/TTwisted.py
new file mode 100644
index 000000000..3ce3eb220
--- /dev/null
+++ b/pyload/lib/thrift/transport/TTwisted.py
@@ -0,0 +1,221 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from cStringIO import StringIO
+
+from zope.interface import implements, Interface, Attribute
+from twisted.internet.protocol import Protocol, ServerFactory, ClientFactory, \
+ connectionDone
+from twisted.internet import defer
+from twisted.protocols import basic
+from twisted.python import log
+from twisted.web import server, resource, http
+
+from thrift.transport import TTransport
+
+
+class TMessageSenderTransport(TTransport.TTransportBase):
+
+ def __init__(self):
+ self.__wbuf = StringIO()
+
+ def write(self, buf):
+ self.__wbuf.write(buf)
+
+ def flush(self):
+ msg = self.__wbuf.getvalue()
+ self.__wbuf = StringIO()
+ self.sendMessage(msg)
+
+ def sendMessage(self, message):
+ raise NotImplementedError
+
+
+class TCallbackTransport(TMessageSenderTransport):
+
+ def __init__(self, func):
+ TMessageSenderTransport.__init__(self)
+ self.func = func
+
+ def sendMessage(self, message):
+ self.func(message)
+
+
+class ThriftClientProtocol(basic.Int32StringReceiver):
+
+ MAX_LENGTH = 2 ** 31 - 1
+
+ def __init__(self, client_class, iprot_factory, oprot_factory=None):
+ self._client_class = client_class
+ self._iprot_factory = iprot_factory
+ if oprot_factory is None:
+ self._oprot_factory = iprot_factory
+ else:
+ self._oprot_factory = oprot_factory
+
+ self.recv_map = {}
+ self.started = defer.Deferred()
+
+ def dispatch(self, msg):
+ self.sendString(msg)
+
+ def connectionMade(self):
+ tmo = TCallbackTransport(self.dispatch)
+ self.client = self._client_class(tmo, self._oprot_factory)
+ self.started.callback(self.client)
+
+ def connectionLost(self, reason=connectionDone):
+ for k, v in self.client._reqs.iteritems():
+ tex = TTransport.TTransportException(
+ type=TTransport.TTransportException.END_OF_FILE,
+ message='Connection closed')
+ v.errback(tex)
+
+ def stringReceived(self, frame):
+ tr = TTransport.TMemoryBuffer(frame)
+ iprot = self._iprot_factory.getProtocol(tr)
+ (fname, mtype, rseqid) = iprot.readMessageBegin()
+
+ try:
+ method = self.recv_map[fname]
+ except KeyError:
+ method = getattr(self.client, 'recv_' + fname)
+ self.recv_map[fname] = method
+
+ method(iprot, mtype, rseqid)
+
+
+class ThriftServerProtocol(basic.Int32StringReceiver):
+
+ MAX_LENGTH = 2 ** 31 - 1
+
+ def dispatch(self, msg):
+ self.sendString(msg)
+
+ def processError(self, error):
+ self.transport.loseConnection()
+
+ def processOk(self, _, tmo):
+ msg = tmo.getvalue()
+
+ if len(msg) > 0:
+ self.dispatch(msg)
+
+ def stringReceived(self, frame):
+ tmi = TTransport.TMemoryBuffer(frame)
+ tmo = TTransport.TMemoryBuffer()
+
+ iprot = self.factory.iprot_factory.getProtocol(tmi)
+ oprot = self.factory.oprot_factory.getProtocol(tmo)
+
+ d = self.factory.processor.process(iprot, oprot)
+ d.addCallbacks(self.processOk, self.processError,
+ callbackArgs=(tmo,))
+
+
+class IThriftServerFactory(Interface):
+
+ processor = Attribute("Thrift processor")
+
+ iprot_factory = Attribute("Input protocol factory")
+
+ oprot_factory = Attribute("Output protocol factory")
+
+
+class IThriftClientFactory(Interface):
+
+ client_class = Attribute("Thrift client class")
+
+ iprot_factory = Attribute("Input protocol factory")
+
+ oprot_factory = Attribute("Output protocol factory")
+
+
+class ThriftServerFactory(ServerFactory):
+
+ implements(IThriftServerFactory)
+
+ protocol = ThriftServerProtocol
+
+ def __init__(self, processor, iprot_factory, oprot_factory=None):
+ self.processor = processor
+ self.iprot_factory = iprot_factory
+ if oprot_factory is None:
+ self.oprot_factory = iprot_factory
+ else:
+ self.oprot_factory = oprot_factory
+
+
+class ThriftClientFactory(ClientFactory):
+
+ implements(IThriftClientFactory)
+
+ protocol = ThriftClientProtocol
+
+ def __init__(self, client_class, iprot_factory, oprot_factory=None):
+ self.client_class = client_class
+ self.iprot_factory = iprot_factory
+ if oprot_factory is None:
+ self.oprot_factory = iprot_factory
+ else:
+ self.oprot_factory = oprot_factory
+
+ def buildProtocol(self, addr):
+ p = self.protocol(self.client_class, self.iprot_factory,
+ self.oprot_factory)
+ p.factory = self
+ return p
+
+
+class ThriftResource(resource.Resource):
+
+ allowedMethods = ('POST',)
+
+ def __init__(self, processor, inputProtocolFactory,
+ outputProtocolFactory=None):
+ resource.Resource.__init__(self)
+ self.inputProtocolFactory = inputProtocolFactory
+ if outputProtocolFactory is None:
+ self.outputProtocolFactory = inputProtocolFactory
+ else:
+ self.outputProtocolFactory = outputProtocolFactory
+ self.processor = processor
+
+ def getChild(self, path, request):
+ return self
+
+ def _cbProcess(self, _, request, tmo):
+ msg = tmo.getvalue()
+ request.setResponseCode(http.OK)
+ request.setHeader("content-type", "application/x-thrift")
+ request.write(msg)
+ request.finish()
+
+ def render_POST(self, request):
+ request.content.seek(0, 0)
+ data = request.content.read()
+ tmi = TTransport.TMemoryBuffer(data)
+ tmo = TTransport.TMemoryBuffer()
+
+ iprot = self.inputProtocolFactory.getProtocol(tmi)
+ oprot = self.outputProtocolFactory.getProtocol(tmo)
+
+ d = self.processor.process(iprot, oprot)
+ d.addCallback(self._cbProcess, request, tmo)
+ return server.NOT_DONE_YET
diff --git a/pyload/lib/thrift/transport/TZlibTransport.py b/pyload/lib/thrift/transport/TZlibTransport.py
new file mode 100644
index 000000000..a2f42a5d2
--- /dev/null
+++ b/pyload/lib/thrift/transport/TZlibTransport.py
@@ -0,0 +1,248 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+"""TZlibTransport provides a compressed transport and transport factory
+class, using the python standard library zlib module to implement
+data compression.
+"""
+
+from __future__ import division
+import zlib
+from cStringIO import StringIO
+from TTransport import TTransportBase, CReadableTransport
+
+
+class TZlibTransportFactory(object):
+ """Factory transport that builds zlib compressed transports.
+
+ This factory caches the last single client/transport that it was passed
+ and returns the same TZlibTransport object that was created.
+
+ This caching means the TServer class will get the _same_ transport
+ object for both input and output transports from this factory.
+ (For non-threaded scenarios only, since the cache only holds one object)
+
+ The purpose of this caching is to allocate only one TZlibTransport where
+ only one is really needed (since it must have separate read/write buffers),
+ and makes the statistics from getCompSavings() and getCompRatio()
+ easier to understand.
+ """
+ # class scoped cache of last transport given and zlibtransport returned
+ _last_trans = None
+ _last_z = None
+
+ def getTransport(self, trans, compresslevel=9):
+ """Wrap a transport, trans, with the TZlibTransport
+ compressed transport class, returning a new
+ transport to the caller.
+
+ @param compresslevel: The zlib compression level, ranging
+ from 0 (no compression) to 9 (best compression). Defaults to 9.
+ @type compresslevel: int
+
+ This method returns a TZlibTransport which wraps the
+ passed C{trans} TTransport derived instance.
+ """
+ if trans == self._last_trans:
+ return self._last_z
+ ztrans = TZlibTransport(trans, compresslevel)
+ self._last_trans = trans
+ self._last_z = ztrans
+ return ztrans
+
+
+class TZlibTransport(TTransportBase, CReadableTransport):
+ """Class that wraps a transport with zlib, compressing writes
+ and decompresses reads, using the python standard
+ library zlib module.
+ """
+ # Read buffer size for the python fastbinary C extension,
+ # the TBinaryProtocolAccelerated class.
+ DEFAULT_BUFFSIZE = 4096
+
+ def __init__(self, trans, compresslevel=9):
+ """Create a new TZlibTransport, wrapping C{trans}, another
+ TTransport derived object.
+
+ @param trans: A thrift transport object, i.e. a TSocket() object.
+ @type trans: TTransport
+ @param compresslevel: The zlib compression level, ranging
+ from 0 (no compression) to 9 (best compression). Default is 9.
+ @type compresslevel: int
+ """
+ self.__trans = trans
+ self.compresslevel = compresslevel
+ self.__rbuf = StringIO()
+ self.__wbuf = StringIO()
+ self._init_zlib()
+ self._init_stats()
+
+ def _reinit_buffers(self):
+ """Internal method to initialize/reset the internal StringIO objects
+ for read and write buffers.
+ """
+ self.__rbuf = StringIO()
+ self.__wbuf = StringIO()
+
+ def _init_stats(self):
+ """Internal method to reset the internal statistics counters
+ for compression ratios and bandwidth savings.
+ """
+ self.bytes_in = 0
+ self.bytes_out = 0
+ self.bytes_in_comp = 0
+ self.bytes_out_comp = 0
+
+ def _init_zlib(self):
+ """Internal method for setting up the zlib compression and
+ decompression objects.
+ """
+ self._zcomp_read = zlib.decompressobj()
+ self._zcomp_write = zlib.compressobj(self.compresslevel)
+
+ def getCompRatio(self):
+ """Get the current measured compression ratios (in,out) from
+ this transport.
+
+ Returns a tuple of:
+ (inbound_compression_ratio, outbound_compression_ratio)
+
+ The compression ratios are computed as:
+ compressed / uncompressed
+
+ E.g., data that compresses by 10x will have a ratio of: 0.10
+ and data that compresses to half of ts original size will
+ have a ratio of 0.5
+
+ None is returned if no bytes have yet been processed in
+ a particular direction.
+ """
+ r_percent, w_percent = (None, None)
+ if self.bytes_in > 0:
+ r_percent = self.bytes_in_comp / self.bytes_in
+ if self.bytes_out > 0:
+ w_percent = self.bytes_out_comp / self.bytes_out
+ return (r_percent, w_percent)
+
+ def getCompSavings(self):
+ """Get the current count of saved bytes due to data
+ compression.
+
+ Returns a tuple of:
+ (inbound_saved_bytes, outbound_saved_bytes)
+
+ Note: if compression is actually expanding your
+ data (only likely with very tiny thrift objects), then
+ the values returned will be negative.
+ """
+ r_saved = self.bytes_in - self.bytes_in_comp
+ w_saved = self.bytes_out - self.bytes_out_comp
+ return (r_saved, w_saved)
+
+ def isOpen(self):
+ """Return the underlying transport's open status"""
+ return self.__trans.isOpen()
+
+ def open(self):
+ """Open the underlying transport"""
+ self._init_stats()
+ return self.__trans.open()
+
+ def listen(self):
+ """Invoke the underlying transport's listen() method"""
+ self.__trans.listen()
+
+ def accept(self):
+ """Accept connections on the underlying transport"""
+ return self.__trans.accept()
+
+ def close(self):
+ """Close the underlying transport,"""
+ self._reinit_buffers()
+ self._init_zlib()
+ return self.__trans.close()
+
+ def read(self, sz):
+ """Read up to sz bytes from the decompressed bytes buffer, and
+ read from the underlying transport if the decompression
+ buffer is empty.
+ """
+ ret = self.__rbuf.read(sz)
+ if len(ret) > 0:
+ return ret
+ # keep reading from transport until something comes back
+ while True:
+ if self.readComp(sz):
+ break
+ ret = self.__rbuf.read(sz)
+ return ret
+
+ def readComp(self, sz):
+ """Read compressed data from the underlying transport, then
+ decompress it and append it to the internal StringIO read buffer
+ """
+ zbuf = self.__trans.read(sz)
+ zbuf = self._zcomp_read.unconsumed_tail + zbuf
+ buf = self._zcomp_read.decompress(zbuf)
+ self.bytes_in += len(zbuf)
+ self.bytes_in_comp += len(buf)
+ old = self.__rbuf.read()
+ self.__rbuf = StringIO(old + buf)
+ if len(old) + len(buf) == 0:
+ return False
+ return True
+
+ def write(self, buf):
+ """Write some bytes, putting them into the internal write
+ buffer for eventual compression.
+ """
+ self.__wbuf.write(buf)
+
+ def flush(self):
+ """Flush any queued up data in the write buffer and ensure the
+ compression buffer is flushed out to the underlying transport
+ """
+ wout = self.__wbuf.getvalue()
+ if len(wout) > 0:
+ zbuf = self._zcomp_write.compress(wout)
+ self.bytes_out += len(wout)
+ self.bytes_out_comp += len(zbuf)
+ else:
+ zbuf = ''
+ ztail = self._zcomp_write.flush(zlib.Z_SYNC_FLUSH)
+ self.bytes_out_comp += len(ztail)
+ if (len(zbuf) + len(ztail)) > 0:
+ self.__wbuf = StringIO()
+ self.__trans.write(zbuf + ztail)
+ self.__trans.flush()
+
+ @property
+ def cstringio_buf(self):
+ """Implement the CReadableTransport interface"""
+ return self.__rbuf
+
+ def cstringio_refill(self, partialread, reqlen):
+ """Implement the CReadableTransport interface for refill"""
+ retstring = partialread
+ if reqlen < self.DEFAULT_BUFFSIZE:
+ retstring += self.read(self.DEFAULT_BUFFSIZE)
+ while len(retstring) < reqlen:
+ retstring += self.read(reqlen - len(retstring))
+ self.__rbuf = StringIO(retstring)
+ return self.__rbuf
diff --git a/pyload/lib/thrift/transport/__init__.py b/pyload/lib/thrift/transport/__init__.py
new file mode 100644
index 000000000..c9596d9a6
--- /dev/null
+++ b/pyload/lib/thrift/transport/__init__.py
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+__all__ = ['TTransport', 'TSocket', 'THttpClient', 'TZlibTransport']
diff --git a/pyload/lib/wsgiserver.py b/pyload/lib/wsgiserver.py
new file mode 100644
index 000000000..1058b19ff
--- /dev/null
+++ b/pyload/lib/wsgiserver.py
@@ -0,0 +1,2299 @@
+"""A high-speed, production ready, thread pooled, generic HTTP server.
+
+Simplest example on how to use this module directly
+(without using CherryPy's application machinery)::
+
+ from cherrypy import wsgiserver
+
+ def my_crazy_app(environ, start_response):
+ status = '200 OK'
+ response_headers = [('Content-type','text/plain')]
+ start_response(status, response_headers)
+ return ['Hello world!']
+
+ server = wsgiserver.CherryPyWSGIServer(
+ ('0.0.0.0', 8070), my_crazy_app,
+ server_name='www.cherrypy.example')
+ server.start()
+
+The CherryPy WSGI server can serve as many WSGI applications
+as you want in one instance by using a WSGIPathInfoDispatcher::
+
+ d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
+ server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
+
+Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
+
+This won't call the CherryPy engine (application side) at all, only the
+HTTP server, which is independent from the rest of CherryPy. Don't
+let the name "CherryPyWSGIServer" throw you; the name merely reflects
+its origin, not its coupling.
+
+For those of you wanting to understand internals of this module, here's the
+basic call flow. The server's listening thread runs a very tight loop,
+sticking incoming connections onto a Queue::
+
+ server = CherryPyWSGIServer(...)
+ server.start()
+ while True:
+ tick()
+ # This blocks until a request comes in:
+ child = socket.accept()
+ conn = HTTPConnection(child, ...)
+ server.requests.put(conn)
+
+Worker threads are kept in a pool and poll the Queue, popping off and then
+handling each connection in turn. Each connection can consist of an arbitrary
+number of requests and their responses, so we run a nested loop::
+
+ while True:
+ conn = server.requests.get()
+ conn.communicate()
+ -> while True:
+ req = HTTPRequest(...)
+ req.parse_request()
+ -> # Read the Request-Line, e.g. "GET /page HTTP/1.1"
+ req.rfile.readline()
+ read_headers(req.rfile, req.inheaders)
+ req.respond()
+ -> response = app(...)
+ try:
+ for chunk in response:
+ if chunk:
+ req.write(chunk)
+ finally:
+ if hasattr(response, "close"):
+ response.close()
+ if req.close_connection:
+ return
+"""
+
+__all__ = ['HTTPRequest', 'HTTPConnection', 'HTTPServer',
+ 'SizeCheckWrapper', 'KnownLengthRFile', 'ChunkedRFile',
+ 'CP_fileobject',
+ 'MaxSizeExceeded', 'NoSSLError', 'FatalSSLAlert',
+ 'WorkerThread', 'ThreadPool', 'SSLAdapter',
+ 'CherryPyWSGIServer',
+ 'Gateway', 'WSGIGateway', 'WSGIGateway_10', 'WSGIGateway_u0',
+ 'WSGIPathInfoDispatcher']
+
+import os
+try:
+ import queue
+except:
+ import Queue as queue
+import re
+import rfc822
+import socket
+import sys
+if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
+ socket.IPPROTO_IPV6 = 41
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+DEFAULT_BUFFER_SIZE = -1
+
+_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
+
+import threading
+import time
+import traceback
+def format_exc(limit=None):
+ """Like print_exc() but return a string. Backport for Python 2.3."""
+ try:
+ etype, value, tb = sys.exc_info()
+ return ''.join(traceback.format_exception(etype, value, tb, limit))
+ finally:
+ etype = value = tb = None
+
+
+from urllib import unquote
+from urlparse import urlparse
+import warnings
+
+if sys.version_info >= (3, 0):
+ bytestr = bytes
+ unicodestr = str
+ basestring = (bytes, str)
+ def ntob(n, encoding='ISO-8859-1'):
+ """Return the given native string as a byte string in the given encoding."""
+ # In Python 3, the native string type is unicode
+ return n.encode(encoding)
+else:
+ bytestr = str
+ unicodestr = unicode
+ basestring = basestring
+ def ntob(n, encoding='ISO-8859-1'):
+ """Return the given native string as a byte string in the given encoding."""
+ # In Python 2, the native string type is bytes. Assume it's already
+ # in the given encoding, which for ISO-8859-1 is almost always what
+ # was intended.
+ return n
+
+LF = ntob('\n')
+CRLF = ntob('\r\n')
+TAB = ntob('\t')
+SPACE = ntob(' ')
+COLON = ntob(':')
+SEMICOLON = ntob(';')
+EMPTY = ntob('')
+NUMBER_SIGN = ntob('#')
+QUESTION_MARK = ntob('?')
+ASTERISK = ntob('*')
+FORWARD_SLASH = ntob('/')
+quoted_slash = re.compile(ntob("(?i)%2F"))
+
+import errno
+
+def plat_specific_errors(*errnames):
+ """Return error numbers for all errors in errnames on this platform.
+
+ The 'errno' module contains different global constants depending on
+ the specific platform (OS). This function will return the list of
+ numeric values for a given list of potential names.
+ """
+ errno_names = dir(errno)
+ nums = [getattr(errno, k) for k in errnames if k in errno_names]
+ # de-dupe the list
+ return list(dict.fromkeys(nums).keys())
+
+socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
+
+socket_errors_to_ignore = plat_specific_errors(
+ "EPIPE",
+ "EBADF", "WSAEBADF",
+ "ENOTSOCK", "WSAENOTSOCK",
+ "ETIMEDOUT", "WSAETIMEDOUT",
+ "ECONNREFUSED", "WSAECONNREFUSED",
+ "ECONNRESET", "WSAECONNRESET",
+ "ECONNABORTED", "WSAECONNABORTED",
+ "ENETRESET", "WSAENETRESET",
+ "EHOSTDOWN", "EHOSTUNREACH",
+ )
+socket_errors_to_ignore.append("timed out")
+socket_errors_to_ignore.append("The read operation timed out")
+
+socket_errors_nonblocking = plat_specific_errors(
+ 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
+
+comma_separated_headers = [ntob(h) for h in
+ ['Accept', 'Accept-Charset', 'Accept-Encoding',
+ 'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
+ 'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
+ 'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
+ 'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
+ 'WWW-Authenticate']]
+
+
+import logging
+if not hasattr(logging, 'statistics'): logging.statistics = {}
+
+
+def read_headers(rfile, hdict=None):
+ """Read headers from the given stream into the given header dict.
+
+ If hdict is None, a new header dict is created. Returns the populated
+ header dict.
+
+ Headers which are repeated are folded together using a comma if their
+ specification so dictates.
+
+ This function raises ValueError when the read bytes violate the HTTP spec.
+ You should probably return "400 Bad Request" if this happens.
+ """
+ if hdict is None:
+ hdict = {}
+
+ while True:
+ line = rfile.readline()
+ if not line:
+ # No more data--illegal end of headers
+ raise ValueError("Illegal end of headers.")
+
+ if line == CRLF:
+ # Normal end of headers
+ break
+ if not line.endswith(CRLF):
+ raise ValueError("HTTP requires CRLF terminators")
+
+ if line[0] in (SPACE, TAB):
+ # It's a continuation line.
+ v = line.strip()
+ else:
+ try:
+ k, v = line.split(COLON, 1)
+ except ValueError:
+ raise ValueError("Illegal header line.")
+ # TODO: what about TE and WWW-Authenticate?
+ k = k.strip().title()
+ v = v.strip()
+ hname = k
+
+ if k in comma_separated_headers:
+ existing = hdict.get(hname)
+ if existing:
+ v = ", ".join((existing, v))
+ hdict[hname] = v
+
+ return hdict
+
+
+class MaxSizeExceeded(Exception):
+ pass
+
+class SizeCheckWrapper(object):
+ """Wraps a file-like object, raising MaxSizeExceeded if too large."""
+
+ def __init__(self, rfile, maxlen):
+ self.rfile = rfile
+ self.maxlen = maxlen
+ self.bytes_read = 0
+
+ def _check_length(self):
+ if self.maxlen and self.bytes_read > self.maxlen:
+ raise MaxSizeExceeded()
+
+ def read(self, size=None):
+ data = self.rfile.read(size)
+ self.bytes_read += len(data)
+ self._check_length()
+ return data
+
+ def readline(self, size=None):
+ if size is not None:
+ data = self.rfile.readline(size)
+ self.bytes_read += len(data)
+ self._check_length()
+ return data
+
+ # User didn't specify a size ...
+ # We read the line in chunks to make sure it's not a 100MB line !
+ res = []
+ while True:
+ data = self.rfile.readline(256)
+ self.bytes_read += len(data)
+ self._check_length()
+ res.append(data)
+ # See http://www.cherrypy.org/ticket/421
+ if len(data) < 256 or data[-1:] == "\n":
+ return EMPTY.join(res)
+
+ def readlines(self, sizehint=0):
+ # Shamelessly stolen from StringIO
+ total = 0
+ lines = []
+ line = self.readline()
+ while line:
+ lines.append(line)
+ total += len(line)
+ if 0 < sizehint <= total:
+ break
+ line = self.readline()
+ return lines
+
+ def close(self):
+ self.rfile.close()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ data = next(self.rfile)
+ self.bytes_read += len(data)
+ self._check_length()
+ return data
+
+ def next(self):
+ data = self.rfile.next()
+ self.bytes_read += len(data)
+ self._check_length()
+ return data
+
+
+class KnownLengthRFile(object):
+ """Wraps a file-like object, returning an empty string when exhausted."""
+
+ def __init__(self, rfile, content_length):
+ self.rfile = rfile
+ self.remaining = content_length
+
+ def read(self, size=None):
+ if self.remaining == 0:
+ return ''
+ if size is None:
+ size = self.remaining
+ else:
+ size = min(size, self.remaining)
+
+ data = self.rfile.read(size)
+ self.remaining -= len(data)
+ return data
+
+ def readline(self, size=None):
+ if self.remaining == 0:
+ return ''
+ if size is None:
+ size = self.remaining
+ else:
+ size = min(size, self.remaining)
+
+ data = self.rfile.readline(size)
+ self.remaining -= len(data)
+ return data
+
+ def readlines(self, sizehint=0):
+ # Shamelessly stolen from StringIO
+ total = 0
+ lines = []
+ line = self.readline(sizehint)
+ while line:
+ lines.append(line)
+ total += len(line)
+ if 0 < sizehint <= total:
+ break
+ line = self.readline(sizehint)
+ return lines
+
+ def close(self):
+ self.rfile.close()
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ data = next(self.rfile)
+ self.remaining -= len(data)
+ return data
+
+
+class ChunkedRFile(object):
+ """Wraps a file-like object, returning an empty string when exhausted.
+
+ This class is intended to provide a conforming wsgi.input value for
+ request entities that have been encoded with the 'chunked' transfer
+ encoding.
+ """
+
+ def __init__(self, rfile, maxlen, bufsize=8192):
+ self.rfile = rfile
+ self.maxlen = maxlen
+ self.bytes_read = 0
+ self.buffer = EMPTY
+ self.bufsize = bufsize
+ self.closed = False
+
+ def _fetch(self):
+ if self.closed:
+ return
+
+ line = self.rfile.readline()
+ self.bytes_read += len(line)
+
+ if self.maxlen and self.bytes_read > self.maxlen:
+ raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
+
+ line = line.strip().split(SEMICOLON, 1)
+
+ try:
+ chunk_size = line.pop(0)
+ chunk_size = int(chunk_size, 16)
+ except ValueError:
+ raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
+
+ if chunk_size <= 0:
+ self.closed = True
+ return
+
+## if line: chunk_extension = line[0]
+
+ if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
+ raise IOError("Request Entity Too Large")
+
+ chunk = self.rfile.read(chunk_size)
+ self.bytes_read += len(chunk)
+ self.buffer += chunk
+
+ crlf = self.rfile.read(2)
+ if crlf != CRLF:
+ raise ValueError(
+ "Bad chunked transfer coding (expected '\\r\\n', "
+ "got " + repr(crlf) + ")")
+
+ def read(self, size=None):
+ data = EMPTY
+ while True:
+ if size and len(data) >= size:
+ return data
+
+ if not self.buffer:
+ self._fetch()
+ if not self.buffer:
+ # EOF
+ return data
+
+ if size:
+ remaining = size - len(data)
+ data += self.buffer[:remaining]
+ self.buffer = self.buffer[remaining:]
+ else:
+ data += self.buffer
+
+ def readline(self, size=None):
+ data = EMPTY
+ while True:
+ if size and len(data) >= size:
+ return data
+
+ if not self.buffer:
+ self._fetch()
+ if not self.buffer:
+ # EOF
+ return data
+
+ newline_pos = self.buffer.find(LF)
+ if size:
+ if newline_pos == -1:
+ remaining = size - len(data)
+ data += self.buffer[:remaining]
+ self.buffer = self.buffer[remaining:]
+ else:
+ remaining = min(size - len(data), newline_pos)
+ data += self.buffer[:remaining]
+ self.buffer = self.buffer[remaining:]
+ else:
+ if newline_pos == -1:
+ data += self.buffer
+ else:
+ data += self.buffer[:newline_pos]
+ self.buffer = self.buffer[newline_pos:]
+
+ def readlines(self, sizehint=0):
+ # Shamelessly stolen from StringIO
+ total = 0
+ lines = []
+ line = self.readline(sizehint)
+ while line:
+ lines.append(line)
+ total += len(line)
+ if 0 < sizehint <= total:
+ break
+ line = self.readline(sizehint)
+ return lines
+
+ def read_trailer_lines(self):
+ if not self.closed:
+ raise ValueError(
+ "Cannot read trailers until the request body has been read.")
+
+ while True:
+ line = self.rfile.readline()
+ if not line:
+ # No more data--illegal end of headers
+ raise ValueError("Illegal end of headers.")
+
+ self.bytes_read += len(line)
+ if self.maxlen and self.bytes_read > self.maxlen:
+ raise IOError("Request Entity Too Large")
+
+ if line == CRLF:
+ # Normal end of headers
+ break
+ if not line.endswith(CRLF):
+ raise ValueError("HTTP requires CRLF terminators")
+
+ yield line
+
+ def close(self):
+ self.rfile.close()
+
+ def __iter__(self):
+ # Shamelessly stolen from StringIO
+ total = 0
+ line = self.readline(sizehint)
+ while line:
+ yield line
+ total += len(line)
+ if 0 < sizehint <= total:
+ break
+ line = self.readline(sizehint)
+
+
+class HTTPRequest(object):
+ """An HTTP Request (and response).
+
+ A single HTTP connection may consist of multiple request/response pairs.
+ """
+
+ server = None
+ """The HTTPServer object which is receiving this request."""
+
+ conn = None
+ """The HTTPConnection object on which this request connected."""
+
+ inheaders = {}
+ """A dict of request headers."""
+
+ outheaders = []
+ """A list of header tuples to write in the response."""
+
+ ready = False
+ """When True, the request has been parsed and is ready to begin generating
+ the response. When False, signals the calling Connection that the response
+ should not be generated and the connection should close."""
+
+ close_connection = False
+ """Signals the calling Connection that the request should close. This does
+ not imply an error! The client and/or server may each request that the
+ connection be closed."""
+
+ chunked_write = False
+ """If True, output will be encoded with the "chunked" transfer-coding.
+
+ This value is set automatically inside send_headers."""
+
+ def __init__(self, server, conn):
+ self.server= server
+ self.conn = conn
+
+ self.ready = False
+ self.started_request = False
+ self.scheme = ntob("http")
+ if self.server.ssl_adapter is not None:
+ self.scheme = ntob("https")
+ # Use the lowest-common protocol in case read_request_line errors.
+ self.response_protocol = 'HTTP/1.0'
+ self.inheaders = {}
+
+ self.status = ""
+ self.outheaders = []
+ self.sent_headers = False
+ self.close_connection = self.__class__.close_connection
+ self.chunked_read = False
+ self.chunked_write = self.__class__.chunked_write
+
+ def parse_request(self):
+ """Parse the next HTTP request start-line and message-headers."""
+ self.rfile = SizeCheckWrapper(self.conn.rfile,
+ self.server.max_request_header_size)
+ try:
+ self.read_request_line()
+ except MaxSizeExceeded:
+ self.simple_response("414 Request-URI Too Long",
+ "The Request-URI sent with the request exceeds the maximum "
+ "allowed bytes.")
+ return
+
+ try:
+ success = self.read_request_headers()
+ except MaxSizeExceeded:
+ self.simple_response("413 Request Entity Too Large",
+ "The headers sent with the request exceed the maximum "
+ "allowed bytes.")
+ return
+ else:
+ if not success:
+ return
+
+ self.ready = True
+
+ def read_request_line(self):
+ # HTTP/1.1 connections are persistent by default. If a client
+ # requests a page, then idles (leaves the connection open),
+ # then rfile.readline() will raise socket.error("timed out").
+ # Note that it does this based on the value given to settimeout(),
+ # and doesn't need the client to request or acknowledge the close
+ # (although your TCP stack might suffer for it: cf Apache's history
+ # with FIN_WAIT_2).
+ request_line = self.rfile.readline()
+
+ # Set started_request to True so communicate() knows to send 408
+ # from here on out.
+ self.started_request = True
+ if not request_line:
+ # Force self.ready = False so the connection will close.
+ self.ready = False
+ return
+
+ if request_line == CRLF:
+ # RFC 2616 sec 4.1: "...if the server is reading the protocol
+ # stream at the beginning of a message and receives a CRLF
+ # first, it should ignore the CRLF."
+ # But only ignore one leading line! else we enable a DoS.
+ request_line = self.rfile.readline()
+ if not request_line:
+ self.ready = False
+ return
+
+ if not request_line.endswith(CRLF):
+ self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
+ return
+
+ try:
+ method, uri, req_protocol = request_line.strip().split(SPACE, 2)
+ rp = int(req_protocol[5]), int(req_protocol[7])
+ except (ValueError, IndexError):
+ self.simple_response("400 Bad Request", "Malformed Request-Line")
+ return
+
+ self.uri = uri
+ self.method = method
+
+ # uri may be an abs_path (including "http://host.domain.tld");
+ scheme, authority, path = self.parse_request_uri(uri)
+ if NUMBER_SIGN in path:
+ self.simple_response("400 Bad Request",
+ "Illegal #fragment in Request-URI.")
+ return
+
+ if scheme:
+ self.scheme = scheme
+
+ qs = EMPTY
+ if QUESTION_MARK in path:
+ path, qs = path.split(QUESTION_MARK, 1)
+
+ # Unquote the path+params (e.g. "/this%20path" -> "/this path").
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
+ #
+ # But note that "...a URI must be separated into its components
+ # before the escaped characters within those components can be
+ # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
+ # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
+ try:
+ atoms = [unquote(x) for x in quoted_slash.split(path)]
+ except ValueError:
+ ex = sys.exc_info()[1]
+ self.simple_response("400 Bad Request", ex.args[0])
+ return
+ path = "%2F".join(atoms)
+ self.path = path
+
+ # Note that, like wsgiref and most other HTTP servers,
+ # we "% HEX HEX"-unquote the path but not the query string.
+ self.qs = qs
+
+ # Compare request and server HTTP protocol versions, in case our
+ # server does not support the requested protocol. Limit our output
+ # to min(req, server). We want the following output:
+ # request server actual written supported response
+ # protocol protocol response protocol feature set
+ # a 1.0 1.0 1.0 1.0
+ # b 1.0 1.1 1.1 1.0
+ # c 1.1 1.0 1.0 1.0
+ # d 1.1 1.1 1.1 1.1
+ # Notice that, in (b), the response will be "HTTP/1.1" even though
+ # the client only understands 1.0. RFC 2616 10.5.6 says we should
+ # only return 505 if the _major_ version is different.
+ sp = int(self.server.protocol[5]), int(self.server.protocol[7])
+
+ if sp[0] != rp[0]:
+ self.simple_response("505 HTTP Version Not Supported")
+ return
+ self.request_protocol = req_protocol
+ self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
+
+ def read_request_headers(self):
+ """Read self.rfile into self.inheaders. Return success."""
+
+ # then all the http headers
+ try:
+ read_headers(self.rfile, self.inheaders)
+ except ValueError:
+ ex = sys.exc_info()[1]
+ self.simple_response("400 Bad Request", ex.args[0])
+ return False
+
+ mrbs = self.server.max_request_body_size
+ if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
+ self.simple_response("413 Request Entity Too Large",
+ "The entity sent with the request exceeds the maximum "
+ "allowed bytes.")
+ return False
+
+ # Persistent connection support
+ if self.response_protocol == "HTTP/1.1":
+ # Both server and client are HTTP/1.1
+ if self.inheaders.get("Connection", "") == "close":
+ self.close_connection = True
+ else:
+ # Either the server or client (or both) are HTTP/1.0
+ if self.inheaders.get("Connection", "") != "Keep-Alive":
+ self.close_connection = True
+
+ # Transfer-Encoding support
+ te = None
+ if self.response_protocol == "HTTP/1.1":
+ te = self.inheaders.get("Transfer-Encoding")
+ if te:
+ te = [x.strip().lower() for x in te.split(",") if x.strip()]
+
+ self.chunked_read = False
+
+ if te:
+ for enc in te:
+ if enc == "chunked":
+ self.chunked_read = True
+ else:
+ # Note that, even if we see "chunked", we must reject
+ # if there is an extension we don't recognize.
+ self.simple_response("501 Unimplemented")
+ self.close_connection = True
+ return False
+
+ # From PEP 333:
+ # "Servers and gateways that implement HTTP 1.1 must provide
+ # transparent support for HTTP 1.1's "expect/continue" mechanism.
+ # This may be done in any of several ways:
+ # 1. Respond to requests containing an Expect: 100-continue request
+ # with an immediate "100 Continue" response, and proceed normally.
+ # 2. Proceed with the request normally, but provide the application
+ # with a wsgi.input stream that will send the "100 Continue"
+ # response if/when the application first attempts to read from
+ # the input stream. The read request must then remain blocked
+ # until the client responds.
+ # 3. Wait until the client decides that the server does not support
+ # expect/continue, and sends the request body on its own.
+ # (This is suboptimal, and is not recommended.)
+ #
+ # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
+ # but it seems like it would be a big slowdown for such a rare case.
+ if self.inheaders.get("Expect", "") == "100-continue":
+ # Don't use simple_response here, because it emits headers
+ # we don't want. See http://www.cherrypy.org/ticket/951
+ msg = self.server.protocol + " 100 Continue\r\n\r\n"
+ try:
+ self.conn.wfile.sendall(msg)
+ except socket.error:
+ x = sys.exc_info()[1]
+ if x.args[0] not in socket_errors_to_ignore:
+ raise
+ return True
+
+ def parse_request_uri(self, uri):
+ """Parse a Request-URI into (scheme, authority, path).
+
+ Note that Request-URI's must be one of::
+
+ Request-URI = "*" | absoluteURI | abs_path | authority
+
+ Therefore, a Request-URI which starts with a double forward-slash
+ cannot be a "net_path"::
+
+ net_path = "//" authority [ abs_path ]
+
+ Instead, it must be interpreted as an "abs_path" with an empty first
+ path segment::
+
+ abs_path = "/" path_segments
+ path_segments = segment *( "/" segment )
+ segment = *pchar *( ";" param )
+ param = *pchar
+ """
+ if uri == ASTERISK:
+ return None, None, uri
+
+ i = uri.find('://')
+ if i > 0 and QUESTION_MARK not in uri[:i]:
+ # An absoluteURI.
+ # If there's a scheme (and it must be http or https), then:
+ # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
+ scheme, remainder = uri[:i].lower(), uri[i + 3:]
+ authority, path = remainder.split(FORWARD_SLASH, 1)
+ path = FORWARD_SLASH + path
+ return scheme, authority, path
+
+ if uri.startswith(FORWARD_SLASH):
+ # An abs_path.
+ return None, None, uri
+ else:
+ # An authority.
+ return None, uri, None
+
+ def respond(self):
+ """Call the gateway and write its iterable output."""
+ mrbs = self.server.max_request_body_size
+ if self.chunked_read:
+ self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
+ else:
+ cl = int(self.inheaders.get("Content-Length", 0))
+ if mrbs and mrbs < cl:
+ if not self.sent_headers:
+ self.simple_response("413 Request Entity Too Large",
+ "The entity sent with the request exceeds the maximum "
+ "allowed bytes.")
+ return
+ self.rfile = KnownLengthRFile(self.conn.rfile, cl)
+
+ self.server.gateway(self).respond()
+
+ if (self.ready and not self.sent_headers):
+ self.sent_headers = True
+ self.send_headers()
+ if self.chunked_write:
+ self.conn.wfile.sendall("0\r\n\r\n")
+
+ def simple_response(self, status, msg=""):
+ """Write a simple response back to the client."""
+ status = str(status)
+ buf = [self.server.protocol + SPACE +
+ status + CRLF,
+ "Content-Length: %s\r\n" % len(msg),
+ "Content-Type: text/plain\r\n"]
+
+ if status[:3] in ("413", "414"):
+ # Request Entity Too Large / Request-URI Too Long
+ self.close_connection = True
+ if self.response_protocol == 'HTTP/1.1':
+ # This will not be true for 414, since read_request_line
+ # usually raises 414 before reading the whole line, and we
+ # therefore cannot know the proper response_protocol.
+ buf.append("Connection: close\r\n")
+ else:
+ # HTTP/1.0 had no 413/414 status nor Connection header.
+ # Emit 400 instead and trust the message body is enough.
+ status = "400 Bad Request"
+
+ buf.append(CRLF)
+ if msg:
+ if isinstance(msg, unicodestr):
+ msg = msg.encode("ISO-8859-1")
+ buf.append(msg)
+
+ try:
+ self.conn.wfile.sendall("".join(buf))
+ except socket.error:
+ x = sys.exc_info()[1]
+ if x.args[0] not in socket_errors_to_ignore:
+ raise
+
+ def write(self, chunk):
+ """Write unbuffered data to the client."""
+ if self.chunked_write and chunk:
+ buf = [hex(len(chunk))[2:], CRLF, chunk, CRLF]
+ self.conn.wfile.sendall(EMPTY.join(buf))
+ else:
+ self.conn.wfile.sendall(chunk)
+
+ def send_headers(self):
+ """Assert, process, and send the HTTP response message-headers.
+
+ You must set self.status, and self.outheaders before calling this.
+ """
+ hkeys = [key.lower() for key, value in self.outheaders]
+ status = int(self.status[:3])
+
+ if status == 413:
+ # Request Entity Too Large. Close conn to avoid garbage.
+ self.close_connection = True
+ elif "content-length" not in hkeys:
+ # "All 1xx (informational), 204 (no content),
+ # and 304 (not modified) responses MUST NOT
+ # include a message-body." So no point chunking.
+ if status < 200 or status in (204, 205, 304):
+ pass
+ else:
+ if (self.response_protocol == 'HTTP/1.1'
+ and self.method != 'HEAD'):
+ # Use the chunked transfer-coding
+ self.chunked_write = True
+ self.outheaders.append(("Transfer-Encoding", "chunked"))
+ else:
+ # Closing the conn is the only way to determine len.
+ self.close_connection = True
+
+ if "connection" not in hkeys:
+ if self.response_protocol == 'HTTP/1.1':
+ # Both server and client are HTTP/1.1 or better
+ if self.close_connection:
+ self.outheaders.append(("Connection", "close"))
+ else:
+ # Server and/or client are HTTP/1.0
+ if not self.close_connection:
+ self.outheaders.append(("Connection", "Keep-Alive"))
+
+ if (not self.close_connection) and (not self.chunked_read):
+ # Read any remaining request body data on the socket.
+ # "If an origin server receives a request that does not include an
+ # Expect request-header field with the "100-continue" expectation,
+ # the request includes a request body, and the server responds
+ # with a final status code before reading the entire request body
+ # from the transport connection, then the server SHOULD NOT close
+ # the transport connection until it has read the entire request,
+ # or until the client closes the connection. Otherwise, the client
+ # might not reliably receive the response message. However, this
+ # requirement is not be construed as preventing a server from
+ # defending itself against denial-of-service attacks, or from
+ # badly broken client implementations."
+ remaining = getattr(self.rfile, 'remaining', 0)
+ if remaining > 0:
+ self.rfile.read(remaining)
+
+ if "date" not in hkeys:
+ self.outheaders.append(("Date", rfc822.formatdate()))
+
+ if "server" not in hkeys:
+ self.outheaders.append(("Server", self.server.server_name))
+
+ buf = [self.server.protocol + SPACE + self.status + CRLF]
+ for k, v in self.outheaders:
+ buf.append(k + COLON + SPACE + v + CRLF)
+ buf.append(CRLF)
+ self.conn.wfile.sendall(EMPTY.join(buf))
+
+
+class NoSSLError(Exception):
+ """Exception raised when a client speaks HTTP to an HTTPS socket."""
+ pass
+
+
+class FatalSSLAlert(Exception):
+ """Exception raised when the SSL implementation signals a fatal alert."""
+ pass
+
+
+class CP_fileobject(socket._fileobject):
+ """Faux file object attached to a socket object."""
+
+ def __init__(self, *args, **kwargs):
+ self.bytes_read = 0
+ self.bytes_written = 0
+ socket._fileobject.__init__(self, *args, **kwargs)
+
+ def sendall(self, data):
+ """Sendall for non-blocking sockets."""
+ while data:
+ try:
+ bytes_sent = self.send(data)
+ data = data[bytes_sent:]
+ except socket.error, e:
+ if e.args[0] not in socket_errors_nonblocking:
+ raise
+
+ def send(self, data):
+ bytes_sent = self._sock.send(data)
+ self.bytes_written += bytes_sent
+ return bytes_sent
+
+ def flush(self):
+ if self._wbuf:
+ buffer = "".join(self._wbuf)
+ self._wbuf = []
+ self.sendall(buffer)
+
+ def recv(self, size):
+ while True:
+ try:
+ data = self._sock.recv(size)
+ self.bytes_read += len(data)
+ return data
+ except socket.error, e:
+ if (e.args[0] not in socket_errors_nonblocking
+ and e.args[0] not in socket_error_eintr):
+ raise
+
+ if not _fileobject_uses_str_type:
+ def read(self, size=-1):
+ # Use max, disallow tiny reads in a loop as they are very inefficient.
+ # We never leave read() with any leftover data from a new recv() call
+ # in our internal buffer.
+ rbufsize = max(self._rbufsize, self.default_bufsize)
+ # Our use of StringIO rather than lists of string objects returned by
+ # recv() minimizes memory usage and fragmentation that occurs when
+ # rbufsize is large compared to the typical return value of recv().
+ buf = self._rbuf
+ buf.seek(0, 2) # seek end
+ if size < 0:
+ # Read until EOF
+ self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ while True:
+ data = self.recv(rbufsize)
+ if not data:
+ break
+ buf.write(data)
+ return buf.getvalue()
+ else:
+ # Read until size bytes or EOF seen, whichever comes first
+ buf_len = buf.tell()
+ if buf_len >= size:
+ # Already have size bytes in our buffer? Extract and return.
+ buf.seek(0)
+ rv = buf.read(size)
+ self._rbuf = StringIO.StringIO()
+ self._rbuf.write(buf.read())
+ return rv
+
+ self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ while True:
+ left = size - buf_len
+ # recv() will malloc the amount of memory given as its
+ # parameter even though it often returns much less data
+ # than that. The returned data string is short lived
+ # as we copy it into a StringIO and free it. This avoids
+ # fragmentation issues on many platforms.
+ data = self.recv(left)
+ if not data:
+ break
+ n = len(data)
+ if n == size and not buf_len:
+ # Shortcut. Avoid buffer data copies when:
+ # - We have no data in our buffer.
+ # AND
+ # - Our call to recv returned exactly the
+ # number of bytes we were asked to read.
+ return data
+ if n == left:
+ buf.write(data)
+ del data # explicit free
+ break
+ assert n <= left, "recv(%d) returned %d bytes" % (left, n)
+ buf.write(data)
+ buf_len += n
+ del data # explicit free
+ #assert buf_len == buf.tell()
+ return buf.getvalue()
+
+ def readline(self, size=-1):
+ buf = self._rbuf
+ buf.seek(0, 2) # seek end
+ if buf.tell() > 0:
+ # check if we already have it in our buffer
+ buf.seek(0)
+ bline = buf.readline(size)
+ if bline.endswith('\n') or len(bline) == size:
+ self._rbuf = StringIO.StringIO()
+ self._rbuf.write(buf.read())
+ return bline
+ del bline
+ if size < 0:
+ # Read until \n or EOF, whichever comes first
+ if self._rbufsize <= 1:
+ # Speed up unbuffered case
+ buf.seek(0)
+ buffers = [buf.read()]
+ self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ data = None
+ recv = self.recv
+ while data != "\n":
+ data = recv(1)
+ if not data:
+ break
+ buffers.append(data)
+ return "".join(buffers)
+
+ buf.seek(0, 2) # seek end
+ self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ while True:
+ data = self.recv(self._rbufsize)
+ if not data:
+ break
+ nl = data.find('\n')
+ if nl >= 0:
+ nl += 1
+ buf.write(data[:nl])
+ self._rbuf.write(data[nl:])
+ del data
+ break
+ buf.write(data)
+ return buf.getvalue()
+ else:
+ # Read until size bytes or \n or EOF seen, whichever comes first
+ buf.seek(0, 2) # seek end
+ buf_len = buf.tell()
+ if buf_len >= size:
+ buf.seek(0)
+ rv = buf.read(size)
+ self._rbuf = StringIO.StringIO()
+ self._rbuf.write(buf.read())
+ return rv
+ self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf.
+ while True:
+ data = self.recv(self._rbufsize)
+ if not data:
+ break
+ left = size - buf_len
+ # did we just receive a newline?
+ nl = data.find('\n', 0, left)
+ if nl >= 0:
+ nl += 1
+ # save the excess data to _rbuf
+ self._rbuf.write(data[nl:])
+ if buf_len:
+ buf.write(data[:nl])
+ break
+ else:
+ # Shortcut. Avoid data copy through buf when returning
+ # a substring of our first recv().
+ return data[:nl]
+ n = len(data)
+ if n == size and not buf_len:
+ # Shortcut. Avoid data copy through buf when
+ # returning exactly all of our first recv().
+ return data
+ if n >= left:
+ buf.write(data[:left])
+ self._rbuf.write(data[left:])
+ break
+ buf.write(data)
+ buf_len += n
+ #assert buf_len == buf.tell()
+ return buf.getvalue()
+ else:
+ def read(self, size=-1):
+ if size < 0:
+ # Read until EOF
+ buffers = [self._rbuf]
+ self._rbuf = ""
+ if self._rbufsize <= 1:
+ recv_size = self.default_bufsize
+ else:
+ recv_size = self._rbufsize
+
+ while True:
+ data = self.recv(recv_size)
+ if not data:
+ break
+ buffers.append(data)
+ return "".join(buffers)
+ else:
+ # Read until size bytes or EOF seen, whichever comes first
+ data = self._rbuf
+ buf_len = len(data)
+ if buf_len >= size:
+ self._rbuf = data[size:]
+ return data[:size]
+ buffers = []
+ if data:
+ buffers.append(data)
+ self._rbuf = ""
+ while True:
+ left = size - buf_len
+ recv_size = max(self._rbufsize, left)
+ data = self.recv(recv_size)
+ if not data:
+ break
+ buffers.append(data)
+ n = len(data)
+ if n >= left:
+ self._rbuf = data[left:]
+ buffers[-1] = data[:left]
+ break
+ buf_len += n
+ return "".join(buffers)
+
+ def readline(self, size=-1):
+ data = self._rbuf
+ if size < 0:
+ # Read until \n or EOF, whichever comes first
+ if self._rbufsize <= 1:
+ # Speed up unbuffered case
+ assert data == ""
+ buffers = []
+ while data != "\n":
+ data = self.recv(1)
+ if not data:
+ break
+ buffers.append(data)
+ return "".join(buffers)
+ nl = data.find('\n')
+ if nl >= 0:
+ nl += 1
+ self._rbuf = data[nl:]
+ return data[:nl]
+ buffers = []
+ if data:
+ buffers.append(data)
+ self._rbuf = ""
+ while True:
+ data = self.recv(self._rbufsize)
+ if not data:
+ break
+ buffers.append(data)
+ nl = data.find('\n')
+ if nl >= 0:
+ nl += 1
+ self._rbuf = data[nl:]
+ buffers[-1] = data[:nl]
+ break
+ return "".join(buffers)
+ else:
+ # Read until size bytes or \n or EOF seen, whichever comes first
+ nl = data.find('\n', 0, size)
+ if nl >= 0:
+ nl += 1
+ self._rbuf = data[nl:]
+ return data[:nl]
+ buf_len = len(data)
+ if buf_len >= size:
+ self._rbuf = data[size:]
+ return data[:size]
+ buffers = []
+ if data:
+ buffers.append(data)
+ self._rbuf = ""
+ while True:
+ data = self.recv(self._rbufsize)
+ if not data:
+ break
+ buffers.append(data)
+ left = size - buf_len
+ nl = data.find('\n', 0, left)
+ if nl >= 0:
+ nl += 1
+ self._rbuf = data[nl:]
+ buffers[-1] = data[:nl]
+ break
+ n = len(data)
+ if n >= left:
+ self._rbuf = data[left:]
+ buffers[-1] = data[:left]
+ break
+ buf_len += n
+ return "".join(buffers)
+
+
+class HTTPConnection(object):
+ """An HTTP connection (active socket).
+
+ server: the Server object which received this connection.
+ socket: the raw socket object (usually TCP) for this connection.
+ makefile: a fileobject class for reading from the socket.
+ """
+
+ remote_addr = None
+ remote_port = None
+ ssl_env = None
+ rbufsize = DEFAULT_BUFFER_SIZE
+ wbufsize = DEFAULT_BUFFER_SIZE
+ RequestHandlerClass = HTTPRequest
+
+ def __init__(self, server, sock, makefile=CP_fileobject):
+ self.server = server
+ self.socket = sock
+ self.rfile = makefile(sock, "rb", self.rbufsize)
+ self.wfile = makefile(sock, "wb", self.wbufsize)
+ self.requests_seen = 0
+
+ def communicate(self):
+ """Read each request and respond appropriately."""
+ request_seen = False
+ try:
+ while True:
+ # (re)set req to None so that if something goes wrong in
+ # the RequestHandlerClass constructor, the error doesn't
+ # get written to the previous request.
+ req = None
+ req = self.RequestHandlerClass(self.server, self)
+
+ # This order of operations should guarantee correct pipelining.
+ req.parse_request()
+ if self.server.stats['Enabled']:
+ self.requests_seen += 1
+ if not req.ready:
+ # Something went wrong in the parsing (and the server has
+ # probably already made a simple_response). Return and
+ # let the conn close.
+ return
+
+ request_seen = True
+ req.respond()
+ if req.close_connection:
+ return
+ except socket.error:
+ e = sys.exc_info()[1]
+ errnum = e.args[0]
+ # sadly SSL sockets return a different (longer) time out string
+ if errnum == 'timed out' or errnum == 'The read operation timed out':
+ # Don't error if we're between requests; only error
+ # if 1) no request has been started at all, or 2) we're
+ # in the middle of a request.
+ # See http://www.cherrypy.org/ticket/853
+ if (not request_seen) or (req and req.started_request):
+ # Don't bother writing the 408 if the response
+ # has already started being written.
+ if req and not req.sent_headers:
+ try:
+ req.simple_response("408 Request Timeout")
+ except FatalSSLAlert:
+ # Close the connection.
+ return
+ elif errnum not in socket_errors_to_ignore:
+ if req and not req.sent_headers:
+ try:
+ req.simple_response("500 Internal Server Error",
+ format_exc())
+ except FatalSSLAlert:
+ # Close the connection.
+ return
+ return
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except FatalSSLAlert:
+ # Close the connection.
+ return
+ except NoSSLError:
+ if req and not req.sent_headers:
+ # Unwrap our wfile
+ self.wfile = CP_fileobject(self.socket._sock, "wb", self.wbufsize)
+ req.simple_response("400 Bad Request",
+ "The client sent a plain HTTP request, but "
+ "this server only speaks HTTPS on this port.")
+ self.linger = True
+ except Exception:
+ if req and not req.sent_headers:
+ try:
+ req.simple_response("500 Internal Server Error", format_exc())
+ except FatalSSLAlert:
+ # Close the connection.
+ return
+
+ linger = False
+
+ def close(self):
+ """Close the socket underlying this connection."""
+ self.rfile.close()
+
+ if not self.linger:
+ # Python's socket module does NOT call close on the kernel socket
+ # when you call socket.close(). We do so manually here because we
+ # want this server to send a FIN TCP segment immediately. Note this
+ # must be called *before* calling socket.close(), because the latter
+ # drops its reference to the kernel socket.
+ if hasattr(self.socket, '_sock'):
+ self.socket._sock.close()
+ self.socket.close()
+ else:
+ # On the other hand, sometimes we want to hang around for a bit
+ # to make sure the client has a chance to read our entire
+ # response. Skipping the close() calls here delays the FIN
+ # packet until the socket object is garbage-collected later.
+ # Someday, perhaps, we'll do the full lingering_close that
+ # Apache does, but not today.
+ pass
+
+
+class TrueyZero(object):
+ """An object which equals and does math like the integer '0' but evals True."""
+ def __add__(self, other):
+ return other
+ def __radd__(self, other):
+ return other
+trueyzero = TrueyZero()
+
+
+_SHUTDOWNREQUEST = None
+
+class WorkerThread(threading.Thread):
+ """Thread which continuously polls a Queue for Connection objects.
+
+ Due to the timing issues of polling a Queue, a WorkerThread does not
+ check its own 'ready' flag after it has started. To stop the thread,
+ it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
+ (one for each running WorkerThread).
+ """
+
+ conn = None
+ """The current connection pulled off the Queue, or None."""
+
+ server = None
+ """The HTTP Server which spawned this thread, and which owns the
+ Queue and is placing active connections into it."""
+
+ ready = False
+ """A simple flag for the calling server to know when this thread
+ has begun polling the Queue."""
+
+
+ def __init__(self, server):
+ self.ready = False
+ self.server = server
+
+ self.requests_seen = 0
+ self.bytes_read = 0
+ self.bytes_written = 0
+ self.start_time = None
+ self.work_time = 0
+ self.stats = {
+ 'Requests': lambda s: self.requests_seen + ((self.start_time is None) and trueyzero or self.conn.requests_seen),
+ 'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and trueyzero or self.conn.rfile.bytes_read),
+ 'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and trueyzero or self.conn.wfile.bytes_written),
+ 'Work Time': lambda s: self.work_time + ((self.start_time is None) and trueyzero or time.time() - self.start_time),
+ 'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6),
+ 'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6),
+ }
+ threading.Thread.__init__(self)
+
+ def run(self):
+ self.server.stats['Worker Threads'][self.getName()] = self.stats
+ try:
+ self.ready = True
+ while True:
+ conn = self.server.requests.get()
+ if conn is _SHUTDOWNREQUEST:
+ return
+
+ self.conn = conn
+ if self.server.stats['Enabled']:
+ self.start_time = time.time()
+ try:
+ conn.communicate()
+ finally:
+ conn.close()
+ if self.server.stats['Enabled']:
+ self.requests_seen += self.conn.requests_seen
+ self.bytes_read += self.conn.rfile.bytes_read
+ self.bytes_written += self.conn.wfile.bytes_written
+ self.work_time += time.time() - self.start_time
+ self.start_time = None
+ self.conn = None
+ except (KeyboardInterrupt, SystemExit):
+ exc = sys.exc_info()[1]
+ self.server.interrupt = exc
+
+
+class ThreadPool(object):
+ """A Request Queue for an HTTPServer which pools threads.
+
+ ThreadPool objects must provide min, get(), put(obj), start()
+ and stop(timeout) attributes.
+ """
+
+ def __init__(self, server, min=10, max=-1):
+ self.server = server
+ self.min = min
+ self.max = max
+ self._threads = []
+ self._queue = queue.Queue()
+ self.get = self._queue.get
+
+ def start(self):
+ """Start the pool of threads."""
+ for i in range(self.min):
+ self._threads.append(WorkerThread(self.server))
+ for worker in self._threads:
+ worker.setName("CP Server " + worker.getName())
+ worker.start()
+ for worker in self._threads:
+ while not worker.ready:
+ time.sleep(.1)
+
+ def _get_idle(self):
+ """Number of worker threads which are idle. Read-only."""
+ return len([t for t in self._threads if t.conn is None])
+ idle = property(_get_idle, doc=_get_idle.__doc__)
+
+ def put(self, obj):
+ self._queue.put(obj)
+ if obj is _SHUTDOWNREQUEST:
+ return
+
+ def grow(self, amount):
+ """Spawn new worker threads (not above self.max)."""
+ for i in range(amount):
+ if self.max > 0 and len(self._threads) >= self.max:
+ break
+ worker = WorkerThread(self.server)
+ worker.setName("CP Server " + worker.getName())
+ self._threads.append(worker)
+ worker.start()
+
+ def shrink(self, amount):
+ """Kill off worker threads (not below self.min)."""
+ # Grow/shrink the pool if necessary.
+ # Remove any dead threads from our list
+ for t in self._threads:
+ if not t.isAlive():
+ self._threads.remove(t)
+ amount -= 1
+
+ if amount > 0:
+ for i in range(min(amount, len(self._threads) - self.min)):
+ # Put a number of shutdown requests on the queue equal
+ # to 'amount'. Once each of those is processed by a worker,
+ # that worker will terminate and be culled from our list
+ # in self.put.
+ self._queue.put(_SHUTDOWNREQUEST)
+
+ def stop(self, timeout=5):
+ # Must shut down threads here so the code that calls
+ # this method can know when all threads are stopped.
+ for worker in self._threads:
+ self._queue.put(_SHUTDOWNREQUEST)
+
+ # Don't join currentThread (when stop is called inside a request).
+ current = threading.currentThread()
+ if timeout and timeout >= 0:
+ endtime = time.time() + timeout
+ while self._threads:
+ worker = self._threads.pop()
+ if worker is not current and worker.isAlive():
+ try:
+ if timeout is None or timeout < 0:
+ worker.join()
+ else:
+ remaining_time = endtime - time.time()
+ if remaining_time > 0:
+ worker.join(remaining_time)
+ if worker.isAlive():
+ # We exhausted the timeout.
+ # Forcibly shut down the socket.
+ c = worker.conn
+ if c and not c.rfile.closed:
+ try:
+ c.socket.shutdown(socket.SHUT_RD)
+ except TypeError:
+ # pyOpenSSL sockets don't take an arg
+ c.socket.shutdown()
+ worker.join()
+ except (AssertionError,
+ # Ignore repeated Ctrl-C.
+ # See http://www.cherrypy.org/ticket/691.
+ KeyboardInterrupt):
+ pass
+
+ def _get_qsize(self):
+ return self._queue.qsize()
+ qsize = property(_get_qsize)
+
+
+
+try:
+ import fcntl
+except ImportError:
+ try:
+ from ctypes import windll, WinError
+ except ImportError:
+ def prevent_socket_inheritance(sock):
+ """Dummy function, since neither fcntl nor ctypes are available."""
+ pass
+ else:
+ def prevent_socket_inheritance(sock):
+ """Mark the given socket fd as non-inheritable (Windows)."""
+ if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
+ raise WinError()
+else:
+ def prevent_socket_inheritance(sock):
+ """Mark the given socket fd as non-inheritable (POSIX)."""
+ fd = sock.fileno()
+ old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+ fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
+
+
+class SSLAdapter(object):
+ """Base class for SSL driver library adapters.
+
+ Required methods:
+
+ * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
+ * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object``
+ """
+
+ def __init__(self, certificate, private_key, certificate_chain=None):
+ self.certificate = certificate
+ self.private_key = private_key
+ self.certificate_chain = certificate_chain
+
+ def wrap(self, sock):
+ raise NotImplemented
+
+ def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
+ raise NotImplemented
+
+
+class HTTPServer(object):
+ """An HTTP server."""
+
+ _bind_addr = "127.0.0.1"
+ _interrupt = None
+
+ gateway = None
+ """A Gateway instance."""
+
+ minthreads = None
+ """The minimum number of worker threads to create (default 10)."""
+
+ maxthreads = None
+ """The maximum number of worker threads to create (default -1 = no limit)."""
+
+ server_name = None
+ """The name of the server; defaults to socket.gethostname()."""
+
+ protocol = "HTTP/1.1"
+ """The version string to write in the Status-Line of all HTTP responses.
+
+ For example, "HTTP/1.1" is the default. This also limits the supported
+ features used in the response."""
+
+ request_queue_size = 5
+ """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
+
+ shutdown_timeout = 5
+ """The total time, in seconds, to wait for worker threads to cleanly exit."""
+
+ timeout = 10
+ """The timeout in seconds for accepted connections (default 10)."""
+
+ version = "CherryPy/3.2.1"
+ """A version string for the HTTPServer."""
+
+ software = None
+ """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
+
+ If None, this defaults to ``'%s Server' % self.version``."""
+
+ ready = False
+ """An internal flag which marks whether the socket is accepting connections."""
+
+ max_request_header_size = 0
+ """The maximum size, in bytes, for request headers, or 0 for no limit."""
+
+ max_request_body_size = 0
+ """The maximum size, in bytes, for request bodies, or 0 for no limit."""
+
+ nodelay = True
+ """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
+
+ ConnectionClass = HTTPConnection
+ """The class to use for handling HTTP connections."""
+
+ ssl_adapter = None
+ """An instance of SSLAdapter (or a subclass).
+
+ You must have the corresponding SSL driver library installed."""
+
+ def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
+ server_name=None):
+ self.bind_addr = bind_addr
+ self.gateway = gateway
+
+ self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
+
+ if not server_name:
+ server_name = socket.gethostname()
+ self.server_name = server_name
+ self.clear_stats()
+
+ def clear_stats(self):
+ self._start_time = None
+ self._run_time = 0
+ self.stats = {
+ 'Enabled': False,
+ 'Bind Address': lambda s: repr(self.bind_addr),
+ 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(),
+ 'Accepts': 0,
+ 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
+ 'Queue': lambda s: getattr(self.requests, "qsize", None),
+ 'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
+ 'Threads Idle': lambda s: getattr(self.requests, "idle", None),
+ 'Socket Errors': 0,
+ 'Requests': lambda s: (not s['Enabled']) and -1 or sum([w['Requests'](w) for w
+ in s['Worker Threads'].values()], 0),
+ 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Read'](w) for w
+ in s['Worker Threads'].values()], 0),
+ 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum([w['Bytes Written'](w) for w
+ in s['Worker Threads'].values()], 0),
+ 'Work Time': lambda s: (not s['Enabled']) and -1 or sum([w['Work Time'](w) for w
+ in s['Worker Threads'].values()], 0),
+ 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum(
+ [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
+ for w in s['Worker Threads'].values()], 0),
+ 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum(
+ [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
+ for w in s['Worker Threads'].values()], 0),
+ 'Worker Threads': {},
+ }
+ logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
+
+ def runtime(self):
+ if self._start_time is None:
+ return self._run_time
+ else:
+ return self._run_time + (time.time() - self._start_time)
+
+ def __str__(self):
+ return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
+ self.bind_addr)
+
+ def _get_bind_addr(self):
+ return self._bind_addr
+ def _set_bind_addr(self, value):
+ if isinstance(value, tuple) and value[0] in ('', None):
+ # Despite the socket module docs, using '' does not
+ # allow AI_PASSIVE to work. Passing None instead
+ # returns '0.0.0.0' like we want. In other words:
+ # host AI_PASSIVE result
+ # '' Y 192.168.x.y
+ # '' N 192.168.x.y
+ # None Y 0.0.0.0
+ # None N 127.0.0.1
+ # But since you can get the same effect with an explicit
+ # '0.0.0.0', we deny both the empty string and None as values.
+ raise ValueError("Host values of '' or None are not allowed. "
+ "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
+ "to listen on all active interfaces.")
+ self._bind_addr = value
+ bind_addr = property(_get_bind_addr, _set_bind_addr,
+ doc="""The interface on which to listen for connections.
+
+ For TCP sockets, a (host, port) tuple. Host values may be any IPv4
+ or IPv6 address, or any valid hostname. The string 'localhost' is a
+ synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
+ The string '0.0.0.0' is a special IPv4 entry meaning "any active
+ interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
+ IPv6. The empty string or None are not allowed.
+
+ For UNIX sockets, supply the filename as a string.""")
+
+ def start(self):
+ """Run the server forever."""
+ # We don't have to trap KeyboardInterrupt or SystemExit here,
+ # because cherrpy.server already does so, calling self.stop() for us.
+ # If you're using this server with another framework, you should
+ # trap those exceptions in whatever code block calls start().
+ self._interrupt = None
+
+ if self.software is None:
+ self.software = "%s Server" % self.version
+
+ # SSL backward compatibility
+ if (self.ssl_adapter is None and
+ getattr(self, 'ssl_certificate', None) and
+ getattr(self, 'ssl_private_key', None)):
+ warnings.warn(
+ "SSL attributes are deprecated in CherryPy 3.2, and will "
+ "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
+ "instead.",
+ DeprecationWarning
+ )
+ try:
+ from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
+ except ImportError:
+ pass
+ else:
+ self.ssl_adapter = pyOpenSSLAdapter(
+ self.ssl_certificate, self.ssl_private_key,
+ getattr(self, 'ssl_certificate_chain', None))
+
+ # Select the appropriate socket
+ if isinstance(self.bind_addr, basestring):
+ # AF_UNIX socket
+
+ # So we can reuse the socket...
+ try: os.unlink(self.bind_addr)
+ except: pass
+
+ # So everyone can access the socket...
+ try: os.chmod(self.bind_addr, 511) # 0777
+ except: pass
+
+ info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
+ else:
+ # AF_INET or AF_INET6 socket
+ # Get the correct address family for our host (allows IPv6 addresses)
+ host, port = self.bind_addr
+ try:
+ info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+ except socket.gaierror:
+ if ':' in self.bind_addr[0]:
+ info = [(socket.AF_INET6, socket.SOCK_STREAM,
+ 0, "", self.bind_addr + (0, 0))]
+ else:
+ info = [(socket.AF_INET, socket.SOCK_STREAM,
+ 0, "", self.bind_addr)]
+
+ self.socket = None
+ msg = "No socket could be created"
+ for res in info:
+ af, socktype, proto, canonname, sa = res
+ try:
+ self.bind(af, socktype, proto)
+ except socket.error:
+ if self.socket:
+ self.socket.close()
+ self.socket = None
+ continue
+ break
+ if not self.socket:
+ raise socket.error(msg)
+
+ # Timeout so KeyboardInterrupt can be caught on Win32
+ self.socket.settimeout(1)
+ self.socket.listen(self.request_queue_size)
+
+ # Create worker threads
+ self.requests.start()
+
+ self.ready = True
+ self._start_time = time.time()
+ while self.ready:
+ self.tick()
+ if self.interrupt:
+ while self.interrupt is True:
+ # Wait for self.stop() to complete. See _set_interrupt.
+ time.sleep(0.1)
+ if self.interrupt:
+ raise self.interrupt
+
+ def bind(self, family, type, proto=0):
+ """Create (or recreate) the actual socket object."""
+ self.socket = socket.socket(family, type, proto)
+ prevent_socket_inheritance(self.socket)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if self.nodelay and not isinstance(self.bind_addr, str):
+ self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+
+ if self.ssl_adapter is not None:
+ self.socket = self.ssl_adapter.bind(self.socket)
+
+ # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
+ # activate dual-stack. See http://www.cherrypy.org/ticket/871.
+ if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
+ and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
+ try:
+ self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
+ except (AttributeError, socket.error):
+ # Apparently, the socket option is not available in
+ # this machine's TCP stack
+ pass
+
+ self.socket.bind(self.bind_addr)
+
+ def tick(self):
+ """Accept a new connection and put it on the Queue."""
+ try:
+ s, addr = self.socket.accept()
+ if self.stats['Enabled']:
+ self.stats['Accepts'] += 1
+ if not self.ready:
+ return
+
+ prevent_socket_inheritance(s)
+ if hasattr(s, 'settimeout'):
+ s.settimeout(self.timeout)
+
+ makefile = CP_fileobject
+ ssl_env = {}
+ # if ssl cert and key are set, we try to be a secure HTTP server
+ if self.ssl_adapter is not None:
+ try:
+ s, ssl_env = self.ssl_adapter.wrap(s)
+ except NoSSLError:
+ msg = ("The client sent a plain HTTP request, but "
+ "this server only speaks HTTPS on this port.")
+ buf = ["%s 400 Bad Request\r\n" % self.protocol,
+ "Content-Length: %s\r\n" % len(msg),
+ "Content-Type: text/plain\r\n\r\n",
+ msg]
+
+ wfile = makefile(s, "wb", DEFAULT_BUFFER_SIZE)
+ try:
+ wfile.sendall("".join(buf))
+ except socket.error:
+ x = sys.exc_info()[1]
+ if x.args[0] not in socket_errors_to_ignore:
+ raise
+ return
+ if not s:
+ return
+ makefile = self.ssl_adapter.makefile
+ # Re-apply our timeout since we may have a new socket object
+ if hasattr(s, 'settimeout'):
+ s.settimeout(self.timeout)
+
+ conn = self.ConnectionClass(self, s, makefile)
+
+ if not isinstance(self.bind_addr, basestring):
+ # optional values
+ # Until we do DNS lookups, omit REMOTE_HOST
+ if addr is None: # sometimes this can happen
+ # figure out if AF_INET or AF_INET6.
+ if len(s.getsockname()) == 2:
+ # AF_INET
+ addr = ('0.0.0.0', 0)
+ else:
+ # AF_INET6
+ addr = ('::', 0)
+ conn.remote_addr = addr[0]
+ conn.remote_port = addr[1]
+
+ conn.ssl_env = ssl_env
+
+ self.requests.put(conn)
+ except socket.timeout:
+ # The only reason for the timeout in start() is so we can
+ # notice keyboard interrupts on Win32, which don't interrupt
+ # accept() by default
+ return
+ except socket.error:
+ x = sys.exc_info()[1]
+ if self.stats['Enabled']:
+ self.stats['Socket Errors'] += 1
+ if x.args[0] in socket_error_eintr:
+ # I *think* this is right. EINTR should occur when a signal
+ # is received during the accept() call; all docs say retry
+ # the call, and I *think* I'm reading it right that Python
+ # will then go ahead and poll for and handle the signal
+ # elsewhere. See http://www.cherrypy.org/ticket/707.
+ return
+ if x.args[0] in socket_errors_nonblocking:
+ # Just try again. See http://www.cherrypy.org/ticket/479.
+ return
+ if x.args[0] in socket_errors_to_ignore:
+ # Our socket was closed.
+ # See http://www.cherrypy.org/ticket/686.
+ return
+ raise
+
+ def _get_interrupt(self):
+ return self._interrupt
+ def _set_interrupt(self, interrupt):
+ self._interrupt = True
+ self.stop()
+ self._interrupt = interrupt
+ interrupt = property(_get_interrupt, _set_interrupt,
+ doc="Set this to an Exception instance to "
+ "interrupt the server.")
+
+ def stop(self):
+ """Gracefully shutdown a server that is serving forever."""
+ self.ready = False
+ if self._start_time is not None:
+ self._run_time += (time.time() - self._start_time)
+ self._start_time = None
+
+ sock = getattr(self, "socket", None)
+ if sock:
+ if not isinstance(self.bind_addr, basestring):
+ # Touch our own socket to make accept() return immediately.
+ try:
+ host, port = sock.getsockname()[:2]
+ except socket.error:
+ x = sys.exc_info()[1]
+ if x.args[0] not in socket_errors_to_ignore:
+ # Changed to use error code and not message
+ # See http://www.cherrypy.org/ticket/860.
+ raise
+ else:
+ # Note that we're explicitly NOT using AI_PASSIVE,
+ # here, because we want an actual IP to touch.
+ # localhost won't work if we've bound to a public IP,
+ # but it will if we bound to '0.0.0.0' (INADDR_ANY).
+ for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM):
+ af, socktype, proto, canonname, sa = res
+ s = None
+ try:
+ s = socket.socket(af, socktype, proto)
+ # See http://groups.google.com/group/cherrypy-users/
+ # browse_frm/thread/bbfe5eb39c904fe0
+ s.settimeout(1.0)
+ s.connect((host, port))
+ s.close()
+ except socket.error:
+ if s:
+ s.close()
+ if hasattr(sock, "close"):
+ sock.close()
+ self.socket = None
+
+ self.requests.stop(self.shutdown_timeout)
+
+
+class Gateway(object):
+ """A base class to interface HTTPServer with other systems, such as WSGI."""
+
+ def __init__(self, req):
+ self.req = req
+
+ def respond(self):
+ """Process the current request. Must be overridden in a subclass."""
+ raise NotImplemented
+
+
+# These may either be wsgiserver.SSLAdapter subclasses or the string names
+# of such classes (in which case they will be lazily loaded).
+ssl_adapters = {
+ 'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
+ 'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
+ }
+
+def get_ssl_adapter_class(name='pyopenssl'):
+ """Return an SSL adapter class for the given name."""
+ adapter = ssl_adapters[name.lower()]
+ if isinstance(adapter, basestring):
+ last_dot = adapter.rfind(".")
+ attr_name = adapter[last_dot + 1:]
+ mod_path = adapter[:last_dot]
+
+ try:
+ mod = sys.modules[mod_path]
+ if mod is None:
+ raise KeyError()
+ except KeyError:
+ # The last [''] is important.
+ mod = __import__(mod_path, globals(), locals(), [''])
+
+ # Let an AttributeError propagate outward.
+ try:
+ adapter = getattr(mod, attr_name)
+ except AttributeError:
+ raise AttributeError("'%s' object has no attribute '%s'"
+ % (mod_path, attr_name))
+
+ return adapter
+
+# -------------------------------- WSGI Stuff -------------------------------- #
+
+
+class CherryPyWSGIServer(HTTPServer):
+ """A subclass of HTTPServer which calls a WSGI application."""
+
+ wsgi_version = (1, 0)
+ """The version of WSGI to produce."""
+
+ def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
+ max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
+ self.requests = ThreadPool(self, min=numthreads or 1, max=max)
+ self.wsgi_app = wsgi_app
+ self.gateway = wsgi_gateways[self.wsgi_version]
+
+ self.bind_addr = bind_addr
+ if not server_name:
+ server_name = socket.gethostname()
+ self.server_name = server_name
+ self.request_queue_size = request_queue_size
+
+ self.timeout = timeout
+ self.shutdown_timeout = shutdown_timeout
+ self.clear_stats()
+
+ def _get_numthreads(self):
+ return self.requests.min
+ def _set_numthreads(self, value):
+ self.requests.min = value
+ numthreads = property(_get_numthreads, _set_numthreads)
+
+
+class WSGIGateway(Gateway):
+ """A base class to interface HTTPServer with WSGI."""
+
+ def __init__(self, req):
+ self.req = req
+ self.started_response = False
+ self.env = self.get_environ()
+ self.remaining_bytes_out = None
+
+ def get_environ(self):
+ """Return a new environ dict targeting the given wsgi.version"""
+ raise NotImplemented
+
+ def respond(self):
+ """Process the current request."""
+ response = self.req.server.wsgi_app(self.env, self.start_response)
+ try:
+ for chunk in response:
+ # "The start_response callable must not actually transmit
+ # the response headers. Instead, it must store them for the
+ # server or gateway to transmit only after the first
+ # iteration of the application return value that yields
+ # a NON-EMPTY string, or upon the application's first
+ # invocation of the write() callable." (PEP 333)
+ if chunk:
+ if isinstance(chunk, unicodestr):
+ chunk = chunk.encode('ISO-8859-1')
+ self.write(chunk)
+ finally:
+ if hasattr(response, "close"):
+ response.close()
+
+ def start_response(self, status, headers, exc_info = None):
+ """WSGI callable to begin the HTTP response."""
+ # "The application may call start_response more than once,
+ # if and only if the exc_info argument is provided."
+ if self.started_response and not exc_info:
+ raise AssertionError("WSGI start_response called a second "
+ "time with no exc_info.")
+ self.started_response = True
+
+ # "if exc_info is provided, and the HTTP headers have already been
+ # sent, start_response must raise an error, and should raise the
+ # exc_info tuple."
+ if self.req.sent_headers:
+ try:
+ raise exc_info[0], exc_info[1], exc_info[2]
+ finally:
+ exc_info = None
+
+ self.req.status = status
+ for k, v in headers:
+ if not isinstance(k, bytestr):
+ raise TypeError("WSGI response header key %r is not a byte string." % k)
+ if not isinstance(v, bytestr):
+ raise TypeError("WSGI response header value %r is not a byte string." % v)
+ if k.lower() == 'content-length':
+ self.remaining_bytes_out = int(v)
+ self.req.outheaders.extend(headers)
+
+ return self.write
+
+ def write(self, chunk):
+ """WSGI callable to write unbuffered data to the client.
+
+ This method is also used internally by start_response (to write
+ data from the iterable returned by the WSGI application).
+ """
+ if not self.started_response:
+ raise AssertionError("WSGI write called before start_response.")
+
+ chunklen = len(chunk)
+ rbo = self.remaining_bytes_out
+ if rbo is not None and chunklen > rbo:
+ if not self.req.sent_headers:
+ # Whew. We can send a 500 to the client.
+ self.req.simple_response("500 Internal Server Error",
+ "The requested resource returned more bytes than the "
+ "declared Content-Length.")
+ else:
+ # Dang. We have probably already sent data. Truncate the chunk
+ # to fit (so the client doesn't hang) and raise an error later.
+ chunk = chunk[:rbo]
+
+ if not self.req.sent_headers:
+ self.req.sent_headers = True
+ self.req.send_headers()
+
+ self.req.write(chunk)
+
+ if rbo is not None:
+ rbo -= chunklen
+ if rbo < 0:
+ raise ValueError(
+ "Response body exceeds the declared Content-Length.")
+
+
+class WSGIGateway_10(WSGIGateway):
+ """A Gateway class to interface HTTPServer with WSGI 1.0.x."""
+
+ def get_environ(self):
+ """Return a new environ dict targeting the given wsgi.version"""
+ req = self.req
+ env = {
+ # set a non-standard environ entry so the WSGI app can know what
+ # the *real* server protocol is (and what features to support).
+ # See http://www.faqs.org/rfcs/rfc2145.html.
+ 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
+ 'PATH_INFO': req.path,
+ 'QUERY_STRING': req.qs,
+ 'REMOTE_ADDR': req.conn.remote_addr or '',
+ 'REMOTE_PORT': str(req.conn.remote_port or ''),
+ 'REQUEST_METHOD': req.method,
+ 'REQUEST_URI': req.uri,
+ 'SCRIPT_NAME': '',
+ 'SERVER_NAME': req.server.server_name,
+ # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
+ 'SERVER_PROTOCOL': req.request_protocol,
+ 'SERVER_SOFTWARE': req.server.software,
+ 'wsgi.errors': sys.stderr,
+ 'wsgi.input': req.rfile,
+ 'wsgi.multiprocess': False,
+ 'wsgi.multithread': True,
+ 'wsgi.run_once': False,
+ 'wsgi.url_scheme': req.scheme,
+ 'wsgi.version': (1, 0),
+ }
+
+ if isinstance(req.server.bind_addr, basestring):
+ # AF_UNIX. This isn't really allowed by WSGI, which doesn't
+ # address unix domain sockets. But it's better than nothing.
+ env["SERVER_PORT"] = ""
+ else:
+ env["SERVER_PORT"] = str(req.server.bind_addr[1])
+
+ # Request headers
+ for k, v in req.inheaders.iteritems():
+ env["HTTP_" + k.upper().replace("-", "_")] = v
+
+ # CONTENT_TYPE/CONTENT_LENGTH
+ ct = env.pop("HTTP_CONTENT_TYPE", None)
+ if ct is not None:
+ env["CONTENT_TYPE"] = ct
+ cl = env.pop("HTTP_CONTENT_LENGTH", None)
+ if cl is not None:
+ env["CONTENT_LENGTH"] = cl
+
+ if req.conn.ssl_env:
+ env.update(req.conn.ssl_env)
+
+ return env
+
+
+class WSGIGateway_u0(WSGIGateway_10):
+ """A Gateway class to interface HTTPServer with WSGI u.0.
+
+ WSGI u.0 is an experimental protocol, which uses unicode for keys and values
+ in both Python 2 and Python 3.
+ """
+
+ def get_environ(self):
+ """Return a new environ dict targeting the given wsgi.version"""
+ req = self.req
+ env_10 = WSGIGateway_10.get_environ(self)
+ env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
+ env[u'wsgi.version'] = ('u', 0)
+
+ # Request-URI
+ env.setdefault(u'wsgi.url_encoding', u'utf-8')
+ try:
+ for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
+ env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
+ except UnicodeDecodeError:
+ # Fall back to latin 1 so apps can transcode if needed.
+ env[u'wsgi.url_encoding'] = u'ISO-8859-1'
+ for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
+ env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
+
+ for k, v in sorted(env.items()):
+ if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
+ env[k] = v.decode('ISO-8859-1')
+
+ return env
+
+wsgi_gateways = {
+ (1, 0): WSGIGateway_10,
+ ('u', 0): WSGIGateway_u0,
+}
+
+class WSGIPathInfoDispatcher(object):
+ """A WSGI dispatcher for dispatch based on the PATH_INFO.
+
+ apps: a dict or list of (path_prefix, app) pairs.
+ """
+
+ def __init__(self, apps):
+ try:
+ apps = list(apps.items())
+ except AttributeError:
+ pass
+
+ # Sort the apps by len(path), descending
+ apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
+ apps.reverse()
+
+ # The path_prefix strings must start, but not end, with a slash.
+ # Use "" instead of "/".
+ self.apps = [(p.rstrip("/"), a) for p, a in apps]
+
+ def __call__(self, environ, start_response):
+ path = environ["PATH_INFO"] or "/"
+ for p, app in self.apps:
+ # The apps list should be sorted by length, descending.
+ if path.startswith(p + "/") or path == p:
+ environ = environ.copy()
+ environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
+ environ["PATH_INFO"] = path[len(p):]
+ return app(environ, start_response)
+
+ start_response('404 Not Found', [('Content-Type', 'text/plain'),
+ ('Content-Length', '0')])
+ return ['']
diff --git a/pyload/manager/AccountManager.py b/pyload/manager/AccountManager.py
new file mode 100644
index 000000000..d1958f4b6
--- /dev/null
+++ b/pyload/manager/AccountManager.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+from os.path import exists
+from shutil import copy
+
+from threading import Lock
+
+from pyload.manager.event.PullEvents import AccountUpdateEvent
+from pyload.utils import chmod, lock
+
+ACC_VERSION = 1
+
+
+class AccountManager:
+ """manages all accounts"""
+
+ #--------------------------------------------------------------------------
+ def __init__(self, core):
+ """Constructor"""
+
+ self.core = core
+ self.lock = Lock()
+
+ self.initPlugins()
+ self.saveAccounts() # save to add categories to conf
+
+ def initPlugins(self):
+ self.accounts = {} # key = ( plugin )
+ self.plugins = {}
+
+ self.initAccountPlugins()
+ self.loadAccounts()
+
+ def getAccountPlugin(self, plugin):
+ """get account instance for plugin or None if anonymous"""
+ if plugin in self.accounts:
+ if plugin not in self.plugins:
+ try:
+ self.plugins[plugin] = self.core.pluginManager.loadClass("accounts", plugin)(self, self.accounts[plugin])
+ except TypeError: # The account class no longer exists (blacklisted plugin). Skipping the account to avoid crash
+ return None
+
+ return self.plugins[plugin]
+ else:
+ return None
+
+ def getAccountPlugins(self):
+ """ get all account instances"""
+
+ plugins = []
+ for plugin in self.accounts.keys():
+ plugins.append(self.getAccountPlugin(plugin))
+
+ return plugins
+
+ #--------------------------------------------------------------------------
+ def loadAccounts(self):
+ """loads all accounts available"""
+
+ if not exists("accounts.conf"):
+ f = open("accounts.conf", "wb")
+ f.write("version: " + str(ACC_VERSION))
+ f.close()
+
+ f = open("accounts.conf", "rb")
+ content = f.readlines()
+ version = content[0].split(":")[1].strip() if content else ""
+ f.close()
+
+ if not version or int(version) < ACC_VERSION:
+ copy("accounts.conf", "accounts.backup")
+ f = open("accounts.conf", "wb")
+ f.write("version: " + str(ACC_VERSION))
+ f.close()
+ self.core.log.warning(_("Account settings deleted, due to new config format."))
+ return
+
+ plugin = ""
+ name = ""
+
+ for line in content[1:]:
+ line = line.strip()
+
+ if not line: continue
+ if line.startswith("#"): continue
+ if line.startswith("version"): continue
+
+ if line.endswith(":") and line.count(":") == 1:
+ plugin = line[:-1]
+ self.accounts[plugin] = {}
+
+ elif line.startswith("@"):
+ try:
+ option = line[1:].split()
+ self.accounts[plugin][name]['options'][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:])
+ except:
+ pass
+
+ elif ":" in line:
+ name, sep, pw = line.partition(":")
+ self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True}
+
+ #--------------------------------------------------------------------------
+ def saveAccounts(self):
+ """save all account information"""
+
+ f = open("accounts.conf", "wb")
+ f.write("version: " + str(ACC_VERSION) + "\n")
+
+ for plugin, accounts in self.accounts.iteritems():
+ f.write("\n")
+ f.write(plugin+":\n")
+
+ for name,data in accounts.iteritems():
+ f.write("\n\t%s:%s\n" % (name,data['password']) )
+ if data['options']:
+ for option, values in data['options'].iteritems():
+ f.write("\t@%s %s\n" % (option, " ".join(values)))
+
+ f.close()
+ chmod(f.name, 0600)
+
+ #--------------------------------------------------------------------------
+ def initAccountPlugins(self):
+ """init names"""
+ for name in self.core.pluginManager.getAccountPlugins():
+ self.accounts[name] = {}
+
+ @lock
+ def updateAccount(self, plugin , user, password=None, options={}):
+ """add or update account"""
+ if plugin in self.accounts:
+ p = self.getAccountPlugin(plugin)
+ updated = p.updateAccounts(user, password, options)
+ #since accounts is a ref in plugin self.accounts doesnt need to be updated here
+
+ self.saveAccounts()
+ if updated: p.scheduleRefresh(user, force=False)
+
+ @lock
+ def removeAccount(self, plugin, user):
+ """remove account"""
+
+ if plugin in self.accounts:
+ p = self.getAccountPlugin(plugin)
+ p.removeAccount(user)
+
+ self.saveAccounts()
+
+ @lock
+ def getAccountInfos(self, force=True, refresh=False):
+ data = {}
+
+ if refresh:
+ self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
+ force = False
+
+ for p in self.accounts.keys():
+ if self.accounts[p]:
+ p = self.getAccountPlugin(p)
+ if p:
+ data[p.__name__] = p.getAllAccounts(force)
+ else: # When an account has been skipped, p is None
+ data[p] = []
+ else:
+ data[p] = []
+ e = AccountUpdateEvent()
+ self.core.pullManager.addEvent(e)
+ return data
+
+ def sendChange(self):
+ e = AccountUpdateEvent()
+ self.core.pullManager.addEvent(e)
diff --git a/pyload/manager/CaptchaManager.py b/pyload/manager/CaptchaManager.py
new file mode 100644
index 000000000..0ba876ae8
--- /dev/null
+++ b/pyload/manager/CaptchaManager.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay, RaNaN
+"""
+
+from time import time
+from traceback import print_exc
+from threading import Lock
+
+class CaptchaManager:
+ def __init__(self, core):
+ self.lock = Lock()
+ self.core = core
+ self.tasks = [] #task store, for outgoing tasks only
+
+ self.ids = 0 #only for internal purpose
+
+ def newTask(self, img, format, file, result_type):
+ task = CaptchaTask(self.ids, img, format, file, result_type)
+ self.ids += 1
+ return task
+
+ def removeTask(self, task):
+ self.lock.acquire()
+ if task in self.tasks:
+ self.tasks.remove(task)
+ self.lock.release()
+
+ def getTask(self):
+ self.lock.acquire()
+ for task in self.tasks:
+ if task.status in ("waiting", "shared-user"):
+ self.lock.release()
+ return task
+ self.lock.release()
+ return None
+
+ def getTaskByID(self, tid):
+ self.lock.acquire()
+ for task in self.tasks:
+ if task.id == str(tid): #task ids are strings
+ self.lock.release()
+ return task
+ self.lock.release()
+ return None
+
+ def handleCaptcha(self, task):
+ cli = self.core.isClientConnected()
+
+ if cli: #client connected -> should solve the captcha
+ task.setWaiting(50) #wait 50 sec for response
+
+ for plugin in self.core.hookManager.activePlugins():
+ try:
+ plugin.newCaptchaTask(task)
+ except:
+ if self.core.debug:
+ print_exc()
+
+ if task.handler or cli: #the captcha was handled
+ self.tasks.append(task)
+ return True
+
+ task.error = _("No Client connected for captcha decrypting")
+
+ return False
+
+
+class CaptchaTask:
+ def __init__(self, id, img, format, file, result_type='textual'):
+ self.id = str(id)
+ self.captchaImg = img
+ self.captchaFormat = format
+ self.captchaFile = file
+ self.captchaResultType = result_type
+ self.handler = [] #the hook plugins that will take care of the solution
+ self.result = None
+ self.waitUntil = None
+ self.error = None #error message
+
+ self.status = "init"
+ self.data = {} #handler can store data here
+
+ def getCaptcha(self):
+ return self.captchaImg, self.captchaFormat, self.captchaResultType
+
+ def setResult(self, text):
+ if self.isTextual():
+ self.result = text
+ if self.isPositional():
+ try:
+ parts = text.split(',')
+ self.result = (int(parts[0]), int(parts[1]))
+ except:
+ self.result = None
+
+ def getResult(self):
+ try:
+ res = self.result.encode("utf8", "replace")
+ except:
+ res = self.result
+
+ return res
+
+ def getStatus(self):
+ return self.status
+
+ def setWaiting(self, sec):
+ """ let the captcha wait secs for the solution """
+ self.waitUntil = max(time() + sec, self.waitUntil)
+ self.status = "waiting"
+
+ def isWaiting(self):
+ if self.result or self.error or time() > self.waitUntil:
+ return False
+
+ return True
+
+ def isTextual(self):
+ """ returns if text is written on the captcha """
+ return self.captchaResultType == 'textual'
+
+ def isPositional(self):
+ """ returns if user have to click a specific region on the captcha """
+ return self.captchaResultType == 'positional'
+
+ def setWatingForUser(self, exclusive):
+ if exclusive:
+ self.status = "user"
+ else:
+ self.status = "shared-user"
+
+ def timedOut(self):
+ return time() > self.waitUntil
+
+ def invalid(self):
+ """ indicates the captcha was not correct """
+ [x.captchaInvalid(self) for x in self.handler]
+
+ def correct(self):
+ [x.captchaCorrect(self) for x in self.handler]
+
+ def __str__(self):
+ return "<CaptchaTask '%s'>" % self.id
diff --git a/pyload/manager/HookManager.py b/pyload/manager/HookManager.py
new file mode 100644
index 000000000..6f5477aeb
--- /dev/null
+++ b/pyload/manager/HookManager.py
@@ -0,0 +1,305 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN, mkaay
+ @interface-version: 0.1
+"""
+import __builtin__
+
+import traceback
+from thread import start_new_thread
+from threading import RLock
+
+from types import MethodType
+
+from pyload.manager.thread.PluginThread import HookThread
+from pyload.manager.PluginManager import literal_eval
+from utils import lock
+
+class HookManager:
+ """Manages hooks, delegates and handles Events.
+
+ Every plugin can define events, \
+ but some very usefull events are called by the Core.
+ Contrary to overwriting hook methods you can use event listener,
+ which provides additional entry point in the control flow.
+ Only do very short tasks or use threads.
+
+ **Known Events:**
+ Most hook methods exists as events. These are the additional known events.
+
+ ===================== ============== ==================================
+ Name Arguments Description
+ ===================== ============== ==================================
+ downloadPreparing fid A download was just queued and will be prepared now.
+ downloadStarts fid A plugin will immediately starts the download afterwards.
+ linksAdded links, pid Someone just added links, you are able to modify the links.
+ allDownloadsProcessed Every link was handled, pyload would idle afterwards.
+ allDownloadsFinished Every download in queue is finished.
+ unrarFinished folder, fname An Unrar job finished
+ configChanged The config was changed via the api.
+ pluginConfigChanged The plugin config changed, due to api or internal process.
+ ===================== ============== ==================================
+
+ | Notes:
+ | allDownloadsProcessed is *always* called before allDownloadsFinished.
+ | configChanged is *always* called before pluginConfigChanged.
+
+
+ """
+
+ def __init__(self, core):
+ self.core = core
+ self.config = self.core.config
+
+ __builtin__.hookManager = self #needed to let hooks register themself
+
+ self.log = self.core.log
+ self.plugins = []
+ self.pluginMap = {}
+ self.methods = {} #dict of names and list of methods usable by rpc
+
+ self.events = {} # contains events
+
+ #registering callback for config event
+ self.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring)
+
+ self.addEvent("pluginConfigChanged", self.manageHooks)
+
+ self.lock = RLock()
+ self.createIndex()
+
+ def try_catch(func):
+ def new(*args):
+ try:
+ return func(*args)
+ except Exception, e:
+ args[0].log.error(_("Error executing hooks: %s") % str(e))
+ if args[0].core.debug:
+ traceback.print_exc()
+
+ return new
+
+
+ def addRPC(self, plugin, func, doc):
+ plugin = plugin.rpartition(".")[2]
+ doc = doc.strip() if doc else ""
+
+ if plugin in self.methods:
+ self.methods[plugin][func] = doc
+ else:
+ self.methods[plugin] = {func: doc}
+
+ def callRPC(self, plugin, func, args, parse):
+ if not args: args = tuple()
+ if parse:
+ args = tuple([literal_eval(x) for x in args])
+
+ plugin = self.pluginMap[plugin]
+ f = getattr(plugin, func)
+ return f(*args)
+
+
+ def createIndex(self):
+ plugins = []
+
+ active = []
+ deactive = []
+
+ for pluginname in self.core.pluginManager.hookPlugins:
+ try:
+ #hookClass = getattr(plugin, plugin.__name__)
+
+ if self.config.getPlugin(pluginname, "activated"):
+ pluginClass = self.core.pluginManager.loadClass("hooks", pluginname)
+ if not pluginClass: continue
+
+ plugin = pluginClass(self.core, self)
+ plugins.append(plugin)
+ self.pluginMap[pluginClass.__name__] = plugin
+ if plugin.isActivated():
+ active.append(pluginClass.__name__)
+ else:
+ deactive.append(pluginname)
+
+
+ except:
+ self.log.warning(_("Failed activating %(name)s") % {"name": pluginname})
+ if self.core.debug:
+ traceback.print_exc()
+
+ self.log.info(_("Activated plugins: %s") % ", ".join(sorted(active)))
+ self.log.info(_("Deactivate plugins: %s") % ", ".join(sorted(deactive)))
+
+ self.plugins = plugins
+
+ def manageHooks(self, plugin, name, value):
+ if name == "activated" and value:
+ self.activateHook(plugin)
+ elif name == "activated" and not value:
+ self.deactivateHook(plugin)
+
+ def activateHook(self, plugin):
+
+ #check if already loaded
+ for inst in self.plugins:
+ if inst.__name__ == plugin:
+ return
+
+ pluginClass = self.core.pluginManager.loadClass("hooks", plugin)
+
+ if not pluginClass: return
+
+ self.log.debug("Plugin loaded: %s" % plugin)
+
+ plugin = pluginClass(self.core, self)
+ self.plugins.append(plugin)
+ self.pluginMap[pluginClass.__name__] = plugin
+
+ # call core Ready
+ start_new_thread(plugin.coreReady, tuple())
+
+ def deactivateHook(self, plugin):
+
+ hook = None
+ for inst in self.plugins:
+ if inst.__name__ == plugin:
+ hook = inst
+
+ if not hook: return
+
+ self.log.debug("Plugin unloaded: %s" % plugin)
+
+ hook.unload()
+
+ #remove periodic call
+ self.log.debug("Removed callback %s" % self.core.scheduler.removeJob(hook.cb))
+ self.plugins.remove(hook)
+ del self.pluginMap[hook.__name__]
+
+
+ @try_catch
+ def coreReady(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.coreReady()
+
+ self.dispatchEvent("coreReady")
+
+ @try_catch
+ def coreExiting(self):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.coreExiting()
+
+ self.dispatchEvent("coreExiting")
+
+ @lock
+ def downloadPreparing(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadPreparing(pyfile)
+
+ self.dispatchEvent("downloadPreparing", pyfile)
+
+ @lock
+ def downloadFinished(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFinished(pyfile)
+
+ self.dispatchEvent("downloadFinished", pyfile)
+
+ @lock
+ @try_catch
+ def downloadFailed(self, pyfile):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.downloadFailed(pyfile)
+
+ self.dispatchEvent("downloadFailed", pyfile)
+
+ @lock
+ def packageFinished(self, package):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.packageFinished(package)
+
+ self.dispatchEvent("packageFinished", package)
+
+ @lock
+ def beforeReconnecting(self, ip):
+ for plugin in self.plugins:
+ plugin.beforeReconnecting(ip)
+
+ self.dispatchEvent("beforeReconnecting", ip)
+
+ @lock
+ def afterReconnecting(self, ip):
+ for plugin in self.plugins:
+ if plugin.isActivated():
+ plugin.afterReconnecting(ip)
+
+ self.dispatchEvent("afterReconnecting", ip)
+
+ def startThread(self, function, *args, **kwargs):
+ t = HookThread(self.core.threadManager, function, args, kwargs)
+
+ def activePlugins(self):
+ """ returns all active plugins """
+ return [x for x in self.plugins if x.isActivated()]
+
+ def getAllInfo(self):
+ """returns info stored by hook plugins"""
+ info = {}
+ for name, plugin in self.pluginMap.iteritems():
+ if plugin.info:
+ #copy and convert so str
+ info[name] = dict([(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()])
+ return info
+
+
+ def getInfo(self, plugin):
+ info = {}
+ if plugin in self.pluginMap and self.pluginMap[plugin].info:
+ info = dict([(x, str(y) if not isinstance(y, basestring) else y)
+ for x, y in self.pluginMap[plugin].info.iteritems()])
+
+ return info
+
+ def addEvent(self, event, func):
+ """Adds an event listener for event name"""
+ if event in self.events:
+ self.events[event].append(func)
+ else:
+ self.events[event] = [func]
+
+ def removeEvent(self, event, func):
+ """removes previously added event listener"""
+ if event in self.events:
+ self.events[event].remove(func)
+
+ def dispatchEvent(self, event, *args):
+ """dispatches event with args"""
+ if event in self.events:
+ for f in self.events[event]:
+ try:
+ f(*args)
+ except Exception, e:
+ self.log.warning("Error calling event handler %s: %s, %s, %s"
+ % (event, f, args, str(e)))
+ if self.core.debug:
+ traceback.print_exc()
diff --git a/pyload/manager/PluginManager.py b/pyload/manager/PluginManager.py
new file mode 100644
index 000000000..56e59237c
--- /dev/null
+++ b/pyload/manager/PluginManager.py
@@ -0,0 +1,356 @@
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+
+from itertools import chain
+from os import listdir, makedirs
+from os.path import isfile, join, exists, abspath
+from sys import version_info
+from traceback import print_exc
+
+from SafeEval import const_eval as literal_eval
+
+from pyload.config.Parser import IGNORE
+
+
+class PluginManager:
+ ROOT = "pyload.plugins."
+ USERROOT = "userplugins."
+ TYPES = ("accounts", "container", "crypter", "hooks", "hoster", "internal", "ocr")
+
+ PATTERN = re.compile(r'__pattern__.*=.*r("|\')([^"\']+)')
+ VERSION = re.compile(r'__version__.*=.*("|\')([0-9.]+)')
+ CONFIG = re.compile(r'__config__.*=.*\[([^\]]+)', re.MULTILINE)
+ DESC = re.compile(r'__description__.?=.?("|"""|\')([^"\']+)')
+
+
+ def __init__(self, core):
+ self.core = core
+
+ self.config = core.config
+ self.log = core.log
+
+ self.plugins = {}
+ self.createIndex()
+
+ #register for import hook
+ sys.meta_path.append(self)
+
+
+ def createIndex(self):
+ """create information for all plugins available"""
+
+ sys.path.append(abspath(""))
+
+ if not exists("userplugins"):
+ makedirs("userplugins")
+ if not exists(join("userplugins", "__init__.py")):
+ f = open(join("userplugins", "__init__.py"), "wb")
+ f.close()
+
+ self.plugins['crypter'] = self.crypterPlugins = self.parse("crypter", pattern=True)
+ self.plugins['container'] = self.containerPlugins = self.parse("container", pattern=True)
+ self.plugins['hoster'] = self.hosterPlugins = self.parse("hoster", pattern=True)
+
+ self.plugins['ocr'] = self.captchaPlugins = self.parse("ocr")
+ self.plugins['accounts'] = self.accountPlugins = self.parse("accounts")
+ self.plugins['hooks'] = self.hookPlugins = self.parse("hooks")
+ self.plugins['internal'] = self.internalPlugins = self.parse("internal")
+
+ self.log.debug("created index of plugins")
+
+ def parse(self, folder, pattern=False, home={}):
+ """
+ returns dict with information
+ home contains parsed plugins from pyload.
+
+ {
+ name : {path, version, config, (pattern, re), (plugin, class)}
+ }
+
+ """
+ plugins = {}
+ if home:
+ pfolder = join("userplugins", folder)
+ if not exists(pfolder):
+ makedirs(pfolder)
+ if not exists(join(pfolder, "__init__.py")):
+ f = open(join(pfolder, "__init__.py"), "wb")
+ f.close()
+
+ else:
+ pfolder = join(pypath, "pyload", "plugins", folder)
+
+ for f in listdir(pfolder):
+ if (isfile(join(pfolder, f)) and f.endswith(".py") or f.endswith("_25.pyc") or f.endswith(
+ "_26.pyc") or f.endswith("_27.pyc")) and not f.startswith("_"):
+ data = open(join(pfolder, f))
+ content = data.read()
+ data.close()
+
+ if f.endswith("_25.pyc") and version_info[0:2] != (2, 5):
+ continue
+ elif f.endswith("_26.pyc") and version_info[0:2] != (2, 6):
+ continue
+ elif f.endswith("_27.pyc") and version_info[0:2] != (2, 7):
+ continue
+
+ name = f[:-3]
+ if name[-1] == ".": name = name[:-4]
+
+ version = self.VERSION.findall(content)
+ if version:
+ version = float(version[0][1])
+ else:
+ version = 0
+
+ # home contains plugins from pyload root
+ if home and name in home:
+ if home[name]['v'] >= version:
+ continue
+
+ if name in IGNORE or (folder, name) in IGNORE:
+ continue
+
+ plugins[name] = {}
+ plugins[name]['v'] = version
+
+ module = f.replace(".pyc", "").replace(".py", "")
+
+ # the plugin is loaded from user directory
+ plugins[name]['user'] = True if home else False
+ plugins[name]['name'] = module
+
+ if pattern:
+ pattern = self.PATTERN.findall(content)
+
+ if pattern:
+ pattern = pattern[0][1]
+ else:
+ pattern = "^unmachtable$"
+
+ plugins[name]['pattern'] = pattern
+
+ try:
+ plugins[name]['re'] = re.compile(pattern)
+ except:
+ self.log.error(_("%s has a invalid pattern.") % name)
+
+
+ # internals have no config
+ if folder == "internal":
+ self.config.deleteConfig(name)
+ continue
+
+ config = self.CONFIG.findall(content)
+ if config:
+ config = literal_eval(config[0].strip().replace("\n", "").replace("\r", ""))
+ desc = self.DESC.findall(content)
+ desc = desc[0][1] if desc else ""
+
+ if type(config[0]) == tuple:
+ config = [list(x) for x in config]
+ else:
+ config = [list(config)]
+
+ if folder == "hooks":
+ append = True
+ for item in config:
+ if item[0] == "activated": append = False
+
+ # activated flag missing
+ if append: config.append(["activated", "bool", "Activated", False])
+
+ try:
+ self.config.addPluginConfig(name, config, desc)
+ except:
+ self.log.error("Invalid config in %s: %s" % (name, config))
+
+ elif folder == "hooks": #force config creation
+ desc = self.DESC.findall(content)
+ desc = desc[0][1] if desc else ""
+ config = (["activated", "bool", "Activated", False],)
+
+ try:
+ self.config.addPluginConfig(name, config, desc)
+ except:
+ self.log.error("Invalid config in %s: %s" % (name, config))
+
+ if not home:
+ temp = self.parse(folder, pattern, plugins)
+ plugins.update(temp)
+
+ return plugins
+
+
+ def parseUrls(self, urls):
+ """parse plugins for given list of urls"""
+
+ last = None
+ res = [] # tupels of (url, plugin)
+
+ for url in urls:
+ if type(url) not in (str, unicode, buffer): continue
+ found = False
+
+ if last and last[1]['re'].match(url):
+ res.append((url, last[0]))
+ continue
+
+ for name, value in chain(self.crypterPlugins.iteritems(), self.hosterPlugins.iteritems(),
+ self.containerPlugins.iteritems()):
+ if value['re'].match(url):
+ res.append((url, name))
+ last = (name, value)
+ found = True
+ break
+
+ if not found:
+ res.append((url, "BasePlugin"))
+
+ return res
+
+ def findPlugin(self, name, pluginlist=("hoster", "crypter", "container")):
+ for ptype in pluginlist:
+ if name in self.plugins[ptype]:
+ return self.plugins[ptype][name], ptype
+ return None, None
+
+ def getPlugin(self, name, original=False):
+ """return plugin module from hoster|decrypter|container"""
+ plugin, type = self.findPlugin(name)
+
+ if not plugin:
+ self.log.warning("Plugin %s not found." % name)
+ plugin = self.hosterPlugins['BasePlugin']
+
+ if "new_module" in plugin and not original:
+ return plugin['new_module']
+
+ return self.loadModule(type, name)
+
+ def getPluginName(self, name):
+ """ used to obtain new name if other plugin was injected"""
+ plugin, type = self.findPlugin(name)
+
+ if "new_name" in plugin:
+ return plugin['new_name']
+
+ return name
+
+ def loadModule(self, type, name):
+ """ Returns loaded module for plugin
+
+ :param type: plugin type, subfolder of pyload.plugins
+ :param name:
+ """
+ plugins = self.plugins[type]
+ if name in plugins:
+ if "module" in plugins[name]: return plugins[name]['module']
+ try:
+ module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]['name']), globals(), locals(),
+ plugins[name]['name'])
+ plugins[name]['module'] = module #cache import, maybe unneeded
+ return module
+ except Exception, e:
+ self.log.error(_("Error importing %(name)s: %(msg)s") % {"name": name, "msg": str(e)})
+ if self.core.debug:
+ print_exc()
+
+ def loadClass(self, type, name):
+ """Returns the class of a plugin with the same name"""
+ module = self.loadModule(type, name)
+ if module: return getattr(module, name)
+
+ def getAccountPlugins(self):
+ """return list of account plugin names"""
+ return self.accountPlugins.keys()
+
+ def find_module(self, fullname, path=None):
+ #redirecting imports if necesarry
+ if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #seperate pyload plugins
+ if fullname.startswith(self.USERROOT): user = 1
+ else: user = 0 #used as bool and int
+
+ split = fullname.split(".")
+ if len(split) != 4 - user: return
+ type, name = split[2 - user:4 - user]
+
+ if type in self.plugins and name in self.plugins[type]:
+ #userplugin is a newer version
+ if not user and self.plugins[type][name]['user']:
+ return self
+ #imported from userdir, but pyloads is newer
+ if user and not self.plugins[type][name]['user']:
+ return self
+
+
+ def load_module(self, name, replace=True):
+ if name not in sys.modules: #could be already in modules
+ if replace:
+ if self.ROOT in name:
+ newname = name.replace(self.ROOT, self.USERROOT)
+ else:
+ newname = name.replace(self.USERROOT, self.ROOT)
+ else: newname = name
+
+ base, plugin = newname.rsplit(".", 1)
+
+ self.log.debug("Redirected import %s -> %s" % (name, newname))
+
+ module = __import__(newname, globals(), locals(), [plugin])
+ #inject under new an old name
+ sys.modules[name] = module
+ sys.modules[newname] = module
+
+ return sys.modules[name]
+
+
+ def reloadPlugins(self, type_plugins):
+ """ reload and reindex plugins """
+ if not type_plugins:
+ return None
+
+ self.log.debug("Request reload of plugins: %s" % type_plugins)
+
+ reloaded = []
+
+ as_dict = {}
+ for t,n in type_plugins:
+ if t in ("hooks", "internal"): #: do not reload hooks or internals, because would cause to much side effects
+ continue
+ elif t in as_dict:
+ as_dict[t].append(n)
+ else:
+ as_dict[t] = [n]
+
+ for type in as_dict.iterkeys():
+ for plugin in as_dict[type]:
+ if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]:
+ self.log.debug("Reloading %s" % plugin)
+ id = (type, plugin)
+ try:
+ reload(self.plugins[type][plugin]['module'])
+ except Exception, e:
+ self.log.error("Error when reloading %s" % id, str(e))
+ continue
+ else:
+ reloaded.append(id)
+
+ #index creation
+ self.plugins['crypter'] = self.crypterPlugins = self.parse("crypter", pattern=True)
+ self.plugins['container'] = self.containerPlugins = self.parse("container", pattern=True)
+ self.plugins['hoster'] = self.hosterPlugins = self.parse("hoster", pattern=True)
+ self.plugins['ocr'] = self.captchaPlugins = self.parse("ocr")
+ self.plugins['accounts'] = self.accountPlugins = self.parse("accounts")
+
+ if "accounts" in as_dict: #: accounts needs to be reloaded
+ self.core.accountManager.initPlugins()
+ self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos)
+
+ return reloaded #: return a list of the plugins successfully reloaded
+
+ def reloadPlugin(self, type_plugin):
+ """ reload and reindex ONE plugin """
+ return True if self.reloadPlugins(type_plugin) else False
diff --git a/pyload/manager/RemoteManager.py b/pyload/manager/RemoteManager.py
new file mode 100644
index 000000000..e53e317e3
--- /dev/null
+++ b/pyload/manager/RemoteManager.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay
+"""
+
+from threading import Thread
+from traceback import print_exc
+
+class BackendBase(Thread):
+ def __init__(self, manager):
+ Thread.__init__(self)
+ self.m = manager
+ self.core = manager.core
+ self.enabled = True
+ self.running = False
+
+ def run(self):
+ self.running = True
+ try:
+ self.serve()
+ except Exception, e:
+ self.core.log.error(_("Remote backend error: %s") % e)
+ if self.core.debug:
+ print_exc()
+ finally:
+ self.running = False
+
+ def setup(self, host, port):
+ pass
+
+ def checkDeps(self):
+ return True
+
+ def serve(self):
+ pass
+
+ def shutdown(self):
+ pass
+
+ def stop(self):
+ self.enabled = False# set flag and call shutdowm message, so thread can react
+ self.shutdown()
+
+
+class RemoteManager:
+ available = []
+
+ def __init__(self, core):
+ self.core = core
+ self.backends = []
+
+ if self.core.remote:
+ self.available.append("ThriftBackend")
+# else:
+# self.available.append("SocketBackend")
+
+
+ def startBackends(self):
+ host = self.core.config["remote"]["listenaddr"]
+ port = self.core.config["remote"]["port"]
+
+ for b in self.available:
+ klass = getattr(__import__("pyload.remote.%s" % b, globals(), locals(), [b], -1), b)
+ backend = klass(self)
+ if not backend.checkDeps():
+ continue
+ try:
+ backend.setup(host, port)
+ self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port})
+ except Exception, e:
+ self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)})
+ if self.core.debug:
+ print_exc()
+ else:
+ backend.start()
+ self.backends.append(backend)
+
+ port += 1
diff --git a/pyload/manager/ThreadManager.py b/pyload/manager/ThreadManager.py
new file mode 100644
index 000000000..1073f8040
--- /dev/null
+++ b/pyload/manager/ThreadManager.py
@@ -0,0 +1,317 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from os.path import exists, join
+import re
+from subprocess import Popen
+from threading import Event, Lock
+from time import sleep, time
+from traceback import print_exc
+from random import choice
+
+import pycurl
+
+from pyload.manager.thread import PluginThread
+from pyload.datatypes.PyFile import PyFile
+from pyload.network.RequestFactory import getURL
+from pyload.utils import freeSpace, lock
+
+
+class ThreadManager:
+ """manages the download threads, assign jobs, reconnect etc"""
+
+
+ def __init__(self, core):
+ """Constructor"""
+ self.core = core
+ self.log = core.log
+
+ self.threads = [] # thread list
+ self.localThreads = [] #hook+decrypter threads
+
+ self.pause = True
+
+ self.reconnecting = Event()
+ self.reconnecting.clear()
+ self.downloaded = 0 #number of files downloaded since last cleanup
+
+ self.lock = Lock()
+
+ # some operations require to fetch url info from hoster, so we caching them so it wont be done twice
+ # contains a timestamp and will be purged after timeout
+ self.infoCache = {}
+
+ # pool of ids for online check
+ self.resultIDs = 0
+
+ # threads which are fetching hoster results
+ self.infoResults = {}
+ #timeout for cache purge
+ self.timestamp = 0
+
+ pycurl.global_init(pycurl.GLOBAL_DEFAULT)
+
+ for i in range(0, self.core.config.get("download", "max_downloads")):
+ self.createThread()
+
+
+ def createThread(self):
+ """create a download thread"""
+
+ thread = PluginThread.DownloadThread(self)
+ self.threads.append(thread)
+
+ def createInfoThread(self, data, pid):
+ """
+ start a thread whichs fetches online status and other infos
+ data = [ .. () .. ]
+ """
+ self.timestamp = time() + 5 * 60
+
+ PluginThread.InfoThread(self, data, pid)
+
+ @lock
+ def createResultThread(self, data, add=False):
+ """ creates a thread to fetch online status, returns result id """
+ self.timestamp = time() + 5 * 60
+
+ rid = self.resultIDs
+ self.resultIDs += 1
+
+ PluginThread.InfoThread(self, data, rid=rid, add=add)
+
+ return rid
+
+
+ @lock
+ def getInfoResult(self, rid):
+ """returns result and clears it"""
+ self.timestamp = time() + 5 * 60
+
+ if rid in self.infoResults:
+ data = self.infoResults[rid]
+ self.infoResults[rid] = {}
+ return data
+ else:
+ return {}
+
+ @lock
+ def setInfoResults(self, rid, result):
+ self.infoResults[rid].update(result)
+
+ def getActiveFiles(self):
+ active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)]
+
+ for t in self.localThreads:
+ active.extend(t.getActiveFiles())
+
+ return active
+
+ def processingIds(self):
+ """get a id list of all pyfiles processed"""
+ return [x.id for x in self.getActiveFiles()]
+
+
+ def work(self):
+ """run all task which have to be done (this is for repetivive call by core)"""
+ try:
+ self.tryReconnect()
+ except Exception, e:
+ self.log.error(_("Reconnect Failed: %s") % str(e) )
+ self.reconnecting.clear()
+ if self.core.debug:
+ print_exc()
+ self.checkThreadCount()
+
+ try:
+ self.assignJob()
+ except Exception, e:
+ self.log.warning("Assign job error", e)
+ if self.core.debug:
+ print_exc()
+
+ sleep(0.5)
+ self.assignJob()
+ #it may be failed non critical so we try it again
+
+ if (self.infoCache or self.infoResults) and self.timestamp < time():
+ self.infoCache.clear()
+ self.infoResults.clear()
+ self.log.debug("Cleared Result cache")
+
+ #--------------------------------------------------------------------------
+ def tryReconnect(self):
+ """checks if reconnect needed"""
+
+ if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()):
+ return False
+
+ active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active]
+
+ if not (0 < active.count(True) == len(active)):
+ return False
+
+ if not exists(self.core.config['reconnect']['method']):
+ if exists(join(pypath, self.core.config['reconnect']['method'])):
+ self.core.config['reconnect']['method'] = join(pypath, self.core.config['reconnect']['method'])
+ else:
+ self.core.config["reconnect"]["activated"] = False
+ self.log.warning(_("Reconnect script not found!"))
+ return
+
+ self.reconnecting.set()
+
+ #Do reconnect
+ self.log.info(_("Starting reconnect"))
+
+ while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0:
+ sleep(0.25)
+
+ ip = self.getIP()
+
+ self.core.hookManager.beforeReconnecting(ip)
+
+ self.log.debug("Old IP: %s" % ip)
+
+ try:
+ reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True)#, stdout=subprocess.PIPE)
+ except:
+ self.log.warning(_("Failed executing reconnect script!"))
+ self.core.config["reconnect"]["activated"] = False
+ self.reconnecting.clear()
+ if self.core.debug:
+ print_exc()
+ return
+
+ reconn.wait()
+ sleep(1)
+ ip = self.getIP()
+ self.core.hookManager.afterReconnecting(ip)
+
+ self.log.info(_("Reconnected, new IP: %s") % ip)
+
+ self.reconnecting.clear()
+
+ def getIP(self):
+ """retrieve current ip"""
+ services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"),
+ ("http://checkip.dyndns.org/",".*Current IP Address: (\S+)</body>.*")]
+
+ ip = ""
+ for i in range(10):
+ try:
+ sv = choice(services)
+ ip = getURL(sv[0])
+ ip = re.match(sv[1], ip).group(1)
+ break
+ except:
+ ip = ""
+ sleep(1)
+
+ return ip
+
+ #--------------------------------------------------------------------------
+ def checkThreadCount(self):
+ """checks if there are need for increasing or reducing thread count"""
+
+ if len(self.threads) == self.core.config.get("download", "max_downloads"):
+ return True
+ elif len(self.threads) < self.core.config.get("download", "max_downloads"):
+ self.createThread()
+ else:
+ free = [x for x in self.threads if not x.active]
+ if free:
+ free[0].put("quit")
+
+
+ def cleanPycurl(self):
+ """ make a global curl cleanup (currently ununused) """
+ if self.processingIds():
+ return False
+ pycurl.global_cleanup()
+ pycurl.global_init(pycurl.GLOBAL_DEFAULT)
+ self.downloaded = 0
+ self.log.debug("Cleaned up pycurl")
+ return True
+
+ #--------------------------------------------------------------------------
+ def assignJob(self):
+ """assing a job to a thread if possible"""
+
+ if self.pause or not self.core.api.isTimeDownload(): return
+
+ #if self.downloaded > 20:
+ # if not self.cleanPyCurl(): return
+
+ free = [x for x in self.threads if not x.active]
+
+ inuse = set([(x.active.pluginname, self.getLimit(x)) for x in self.threads if x.active and x.active.hasPlugin() and x.active.plugin.account])
+ inuse = map(lambda x: (x[0], x[1], len([y for y in self.threads if y.active and y.active.pluginname == x[0]])) ,inuse)
+ onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]]
+
+ occ = [x.active.pluginname for x in self.threads if x.active and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit
+
+ occ.sort()
+ occ = tuple(set(occ))
+ job = self.core.files.getJob(occ)
+ if job:
+ try:
+ job.initPlugin()
+ except Exception, e:
+ self.log.critical(str(e))
+ print_exc()
+ job.setStatus("failed")
+ job.error = str(e)
+ job.release()
+ return
+
+ if job.plugin.__type__ == "hoster":
+ spaceLeft = freeSpace(self.core.config["general"]["download_folder"]) / 1024 / 1024
+ if spaceLeft < self.core.config["general"]["min_free_space"]:
+ self.log.warning(_("Not enough space left on device"))
+ self.pause = True
+
+ if free and not self.pause:
+ thread = free[0]
+ #self.downloaded += 1
+
+ thread.put(job)
+ else:
+ #put job back
+ if occ not in self.core.files.jobCache:
+ self.core.files.jobCache[occ] = []
+ self.core.files.jobCache[occ].append(job.id)
+
+ #check for decrypt jobs
+ job = self.core.files.getDecryptJob()
+ if job:
+ job.initPlugin()
+ thread = PluginThread.DecrypterThread(self, job)
+
+
+ else:
+ thread = PluginThread.DecrypterThread(self, job)
+
+ def getLimit(self, thread):
+ limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL", ["0"])[0]
+ return int(limit)
+
+ def cleanup(self):
+ """do global cleanup, should be called when finished with pycurl"""
+ pycurl.global_cleanup()
diff --git a/module/plugins/accounts/__init__.py b/pyload/manager/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/accounts/__init__.py
+++ b/pyload/manager/__init__.py
diff --git a/pyload/manager/event/PullEvents.py b/pyload/manager/event/PullEvents.py
new file mode 100644
index 000000000..0739b4ec8
--- /dev/null
+++ b/pyload/manager/event/PullEvents.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay
+"""
+
+from time import time
+from pyload.utils import uniqify
+
+class PullManager:
+ def __init__(self, core):
+ self.core = core
+ self.clients = []
+
+ def newClient(self, uuid):
+ self.clients.append(Client(uuid))
+
+ def clean(self):
+ for n, client in enumerate(self.clients):
+ if client.lastActive + 30 < time():
+ del self.clients[n]
+
+ def getEvents(self, uuid):
+ events = []
+ validUuid = False
+ for client in self.clients:
+ if client.uuid == uuid:
+ client.lastActive = time()
+ validUuid = True
+ while client.newEvents():
+ events.append(client.popEvent().toList())
+ break
+ if not validUuid:
+ self.newClient(uuid)
+ events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()]
+ return uniqify(events)
+
+ def addEvent(self, event):
+ for client in self.clients:
+ client.addEvent(event)
+
+class Client:
+ def __init__(self, uuid):
+ self.uuid = uuid
+ self.lastActive = time()
+ self.events = []
+
+ def newEvents(self):
+ return len(self.events) > 0
+
+ def popEvent(self):
+ if not len(self.events):
+ return None
+ return self.events.pop(0)
+
+ def addEvent(self, event):
+ self.events.append(event)
+
+class UpdateEvent:
+ def __init__(self, itype, iid, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.destination = destination
+
+ def toList(self):
+ return ["update", self.destination, self.type, self.id]
+
+class RemoveEvent:
+ def __init__(self, itype, iid, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.destination = destination
+
+ def toList(self):
+ return ["remove", self.destination, self.type, self.id]
+
+class InsertEvent:
+ def __init__(self, itype, iid, after, destination):
+ assert itype == "pack" or itype == "file"
+ assert destination == "queue" or destination == "collector"
+ self.type = itype
+ self.id = iid
+ self.after = after
+ self.destination = destination
+
+ def toList(self):
+ return ["insert", self.destination, self.type, self.id, self.after]
+
+class ReloadAllEvent:
+ def __init__(self, destination):
+ assert destination == "queue" or destination == "collector"
+ self.destination = destination
+
+ def toList(self):
+ return ["reload", self.destination]
+
+class AccountUpdateEvent:
+ def toList(self):
+ return ["account"]
+
+class ConfigUpdateEvent:
+ def toList(self):
+ return ["config"]
diff --git a/pyload/manager/event/Scheduler.py b/pyload/manager/event/Scheduler.py
new file mode 100644
index 000000000..71b5f96af
--- /dev/null
+++ b/pyload/manager/event/Scheduler.py
@@ -0,0 +1,141 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay
+"""
+
+from time import time
+from heapq import heappop, heappush
+from thread import start_new_thread
+from threading import Lock
+
+class AlreadyCalled(Exception):
+ pass
+
+
+class Deferred:
+ def __init__(self):
+ self.call = []
+ self.result = ()
+
+ def addCallback(self, f, *cargs, **ckwargs):
+ self.call.append((f, cargs, ckwargs))
+
+ def callback(self, *args, **kwargs):
+ if self.result:
+ raise AlreadyCalled
+ self.result = (args, kwargs)
+ for f, cargs, ckwargs in self.call:
+ args += tuple(cargs)
+ kwargs.update(ckwargs)
+ f(*args ** kwargs)
+
+
+class Scheduler:
+ def __init__(self, core):
+ self.core = core
+
+ self.queue = PriorityQueue()
+
+ def addJob(self, t, call, args=[], kwargs={}, threaded=True):
+ d = Deferred()
+ t += time()
+ j = Job(t, call, args, kwargs, d, threaded)
+ self.queue.put((t, j))
+ return d
+
+
+ def removeJob(self, d):
+ """
+ :param d: defered object
+ :return: if job was deleted
+ """
+ index = -1
+
+ for i, j in enumerate(self.queue):
+ if j[1].deferred == d:
+ index = i
+
+ if index >= 0:
+ del self.queue[index]
+ return True
+
+ return False
+
+ def work(self):
+ while True:
+ t, j = self.queue.get()
+ if not j:
+ break
+ else:
+ if t <= time():
+ j.start()
+ else:
+ self.queue.put((t, j))
+ break
+
+
+class Job:
+ def __init__(self, time, call, args=[], kwargs={}, deferred=None, threaded=True):
+ self.time = float(time)
+ self.call = call
+ self.args = args
+ self.kwargs = kwargs
+ self.deferred = deferred
+ self.threaded = threaded
+
+ def run(self):
+ ret = self.call(*self.args, **self.kwargs)
+ if self.deferred is None:
+ return
+ else:
+ self.deferred.callback(ret)
+
+ def start(self):
+ if self.threaded:
+ start_new_thread(self.run, ())
+ else:
+ self.run()
+
+
+class PriorityQueue:
+ """ a non blocking priority queue """
+
+ def __init__(self):
+ self.queue = []
+ self.lock = Lock()
+
+ def __iter__(self):
+ return iter(self.queue)
+
+ def __delitem__(self, key):
+ del self.queue[key]
+
+ def put(self, element):
+ self.lock.acquire()
+ heappush(self.queue, element)
+ self.lock.release()
+
+ def get(self):
+ """ return element or None """
+ self.lock.acquire()
+ try:
+ el = heappop(self.queue)
+ return el
+ except IndexError:
+ return None, None
+ finally:
+ self.lock.release()
diff --git a/module/plugins/captcha/__init__.py b/pyload/manager/event/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/captcha/__init__.py
+++ b/pyload/manager/event/__init__.py
diff --git a/pyload/manager/thread/PluginThread.py b/pyload/manager/thread/PluginThread.py
new file mode 100644
index 000000000..5c274fa46
--- /dev/null
+++ b/pyload/manager/thread/PluginThread.py
@@ -0,0 +1,675 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from Queue import Queue
+from threading import Thread
+from os import listdir, stat
+from os.path import join
+from time import sleep, time, strftime, gmtime
+from traceback import print_exc, format_exc
+from pprint import pformat
+from sys import exc_info, exc_clear
+from copy import copy
+from types import MethodType
+
+from pycurl import error
+
+from pyload.datatypes.PyFile import PyFile
+from pyload.plugins.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload
+from pyload.utils.packagetools import parseNames
+from pyload.utils import safe_join
+from pyload.api import OnlineStatus
+
+class PluginThread(Thread):
+ """abstract base class for thread types"""
+
+ #--------------------------------------------------------------------------
+ def __init__(self, manager):
+ """Constructor"""
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.m = manager #thread manager
+
+
+ def writeDebugReport(self, pyfile):
+ """ writes a
+ :return:
+ """
+
+ dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S"))
+ dump = self.getDebugDump(pyfile)
+
+ try:
+ import zipfile
+
+ zip = zipfile.ZipFile(dump_name, "w")
+
+ for f in listdir(join("tmp", pyfile.pluginname)):
+ try:
+ # avoid encoding errors
+ zip.write(join("tmp", pyfile.pluginname, f), safe_join(pyfile.pluginname, f))
+ except:
+ pass
+
+ info = zipfile.ZipInfo(safe_join(pyfile.pluginname, "debug_Report.txt"), gmtime())
+ info.external_attr = 0644 << 16L # change permissions
+
+ zip.writestr(info, dump)
+ zip.close()
+
+ if not stat(dump_name).st_size:
+ raise Exception("Empty Zipfile")
+
+ except Exception, e:
+ self.m.log.debug("Error creating zip file: %s" % e)
+
+ dump_name = dump_name.replace(".zip", ".txt")
+ f = open(dump_name, "wb")
+ f.write(dump)
+ f.close()
+
+ self.m.core.log.info("Debug Report written to %s" % dump_name)
+
+ def getDebugDump(self, pyfile):
+ dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % (
+ self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version__, format_exc())
+
+ tb = exc_info()[2]
+ stack = []
+ while tb:
+ stack.append(tb.tb_frame)
+ tb = tb.tb_next
+
+ for frame in stack[1:]:
+ dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name,
+ frame.f_code.co_filename,
+ frame.f_lineno)
+
+ for key, value in frame.f_locals.items():
+ dump += "\t%20s = " % key
+ try:
+ dump += pformat(value) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ del frame
+
+ del stack #delete it just to be sure...
+
+ dump += "\n\nPLUGIN OBJECT DUMP: \n\n"
+
+ for name in dir(pyfile.plugin):
+ attr = getattr(pyfile.plugin, name)
+ if not name.endswith("__") and type(attr) != MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pformat(attr) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ dump += "\nPYFILE OBJECT DUMP: \n\n"
+
+ for name in dir(pyfile):
+ attr = getattr(pyfile, name)
+ if not name.endswith("__") and type(attr) != MethodType:
+ dump += "\t%20s = " % name
+ try:
+ dump += pformat(attr) + "\n"
+ except Exception, e:
+ dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n"
+
+ if pyfile.pluginname in self.m.core.config.plugin:
+ dump += "\n\nCONFIG: \n\n"
+ dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n"
+
+ return dump
+
+ def clean(self, pyfile):
+ """ set thread unactive and release pyfile """
+ self.active = False
+ pyfile.release()
+
+
+class DownloadThread(PluginThread):
+ """thread for downloading files from 'real' hoster plugins"""
+
+ #--------------------------------------------------------------------------
+ def __init__(self, manager):
+ """Constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.queue = Queue() # job queue
+ self.active = False
+
+ self.start()
+
+ #--------------------------------------------------------------------------
+ def run(self):
+ """run method"""
+ pyfile = None
+
+ while True:
+ del pyfile
+ self.active = self.queue.get()
+ pyfile = self.active
+
+ if self.active == "quit":
+ self.active = False
+ self.m.threads.remove(self)
+ return True
+
+ try:
+ if not pyfile.hasPlugin(): continue
+ #this pyfile was deleted while queueing
+
+ pyfile.plugin.checkForSameFiles(starting=True)
+ self.m.log.info(_("Download starts: %s" % pyfile.name))
+
+ # start download
+ self.m.core.hookManager.downloadPreparing(pyfile)
+ pyfile.plugin.preprocessing(self)
+
+ self.m.log.info(_("Download finished: %s") % pyfile.name)
+ self.m.core.hookManager.downloadFinished(pyfile)
+ self.m.core.files.checkPackageFinished(pyfile)
+
+ except NotImplementedError:
+ self.m.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname)
+ pyfile.setStatus("failed")
+ pyfile.error = "Plugin does not work"
+ self.clean(pyfile)
+ continue
+
+ except Abort:
+ try:
+ self.m.log.info(_("Download aborted: %s") % pyfile.name)
+ except:
+ pass
+
+ pyfile.setStatus("aborted")
+
+ self.clean(pyfile)
+ continue
+
+ except Reconnect:
+ self.queue.put(pyfile)
+ #pyfile.req.clearCookies()
+
+ while self.m.reconnecting.isSet():
+ sleep(0.5)
+
+ continue
+
+ except Retry, e:
+ reason = e.args[0]
+ self.m.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason})
+ self.queue.put(pyfile)
+ continue
+
+ except Fail, e:
+ msg = e.args[0]
+
+ if msg == "offline":
+ pyfile.setStatus("offline")
+ self.m.log.warning(_("Download is offline: %s") % pyfile.name)
+ elif msg == "temp. offline":
+ pyfile.setStatus("temp. offline")
+ self.m.log.warning(_("Download is temporary offline: %s") % pyfile.name)
+ else:
+ pyfile.setStatus("failed")
+ self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg})
+ pyfile.error = msg
+
+ self.m.core.hookManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ except error, e:
+ if len(e.args) == 2:
+ code, msg = e.args
+ else:
+ code = 0
+ msg = e.args
+
+ self.m.log.debug("pycurl exception %s: %s" % (code, msg))
+
+ if code in (7, 18, 28, 52, 56):
+ self.m.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry."))
+ wait = time() + 60
+
+ pyfile.waitUntil = wait
+ pyfile.setStatus("waiting")
+ while time() < wait:
+ sleep(1)
+ if pyfile.abort:
+ break
+
+ if pyfile.abort:
+ self.m.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ self.clean(pyfile)
+ else:
+ self.queue.put(pyfile)
+
+ continue
+
+ else:
+ pyfile.setStatus("failed")
+ self.m.log.error("pycurl error %s: %s" % (code, msg))
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.hookManager.downloadFailed(pyfile)
+
+ self.clean(pyfile)
+ continue
+
+ except SkipDownload, e:
+ pyfile.setStatus("skipped")
+
+ self.m.log.info(
+ _("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message})
+
+ self.clean(pyfile)
+
+ self.m.core.files.checkPackageFinished(pyfile)
+
+ self.active = False
+ self.m.core.files.save()
+
+ continue
+
+
+ except Exception, e:
+ pyfile.setStatus("failed")
+ self.m.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)})
+ pyfile.error = str(e)
+
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ self.m.core.hookManager.downloadFailed(pyfile)
+ self.clean(pyfile)
+ continue
+
+ finally:
+ self.m.core.files.save()
+ pyfile.checkIfProcessed()
+ exc_clear()
+
+ #pyfile.plugin.req.clean()
+
+ self.active = False
+ pyfile.finishIfDone()
+ self.m.core.files.save()
+
+
+ def put(self, job):
+ """assing job to thread"""
+ self.queue.put(job)
+
+
+ def stop(self):
+ """stops the thread"""
+ self.put("quit")
+
+
+class DecrypterThread(PluginThread):
+ """thread for decrypting"""
+
+ def __init__(self, manager, pyfile):
+ """constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.active = pyfile
+ manager.localThreads.append(self)
+
+ pyfile.setStatus("decrypting")
+
+ self.start()
+
+ def getActiveFiles(self):
+ return [self.active]
+
+ def run(self):
+ """run method"""
+
+ pyfile = self.active
+ retry = False
+
+ try:
+ self.m.log.info(_("Decrypting starts: %s") % self.active.name)
+ self.active.plugin.preprocessing(self)
+
+ except NotImplementedError:
+ self.m.log.error(_("Plugin %s is missing a function.") % self.active.pluginname)
+ return
+
+ except Fail, e:
+ msg = e.args[0]
+
+ if msg == "offline":
+ self.active.setStatus("offline")
+ self.m.log.warning(_("Download is offline: %s") % self.active.name)
+ else:
+ self.active.setStatus("failed")
+ self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": msg})
+ self.active.error = msg
+
+ return
+
+ except Abort:
+ self.m.log.info(_("Download aborted: %s") % pyfile.name)
+ pyfile.setStatus("aborted")
+
+ return
+
+ except Retry:
+ self.m.log.info(_("Retrying %s") % self.active.name)
+ retry = True
+ return self.run()
+
+ except Exception, e:
+ self.active.setStatus("failed")
+ self.m.log.error(_("Decrypting failed: %(name)s | %(msg)s") % {"name": self.active.name, "msg": str(e)})
+ self.active.error = str(e)
+
+ if self.m.core.debug:
+ print_exc()
+ self.writeDebugReport(pyfile)
+
+ return
+
+
+ finally:
+ if not retry:
+ self.active.release()
+ self.active = False
+ self.m.core.files.save()
+ self.m.localThreads.remove(self)
+ exc_clear()
+
+
+ #self.m.core.hookManager.downloadFinished(pyfile)
+
+
+ #self.m.localThreads.remove(self)
+ #self.active.finishIfDone()
+ if not retry:
+ pyfile.delete()
+
+
+class HookThread(PluginThread):
+ """thread for hooks"""
+
+ #--------------------------------------------------------------------------
+ def __init__(self, m, function, args, kwargs):
+ """Constructor"""
+ PluginThread.__init__(self, m)
+
+ self.f = function
+ self.args = args
+ self.kwargs = kwargs
+
+ self.active = []
+
+ m.localThreads.append(self)
+
+ self.start()
+
+ def getActiveFiles(self):
+ return self.active
+
+ def addActive(self, pyfile):
+ """ Adds a pyfile to active list and thus will be displayed on overview"""
+ if pyfile not in self.active:
+ self.active.append(pyfile)
+
+ def finishFile(self, pyfile):
+ if pyfile in self.active:
+ self.active.remove(pyfile)
+
+ pyfile.finishIfDone()
+
+ def run(self):
+ try:
+ try:
+ self.kwargs["thread"] = self
+ self.f(*self.args, **self.kwargs)
+ except TypeError, e:
+ #dirty method to filter out exceptions
+ if "unexpected keyword argument 'thread'" not in e.args[0]:
+ raise
+
+ del self.kwargs["thread"]
+ self.f(*self.args, **self.kwargs)
+ finally:
+ local = copy(self.active)
+ for x in local:
+ self.finishFile(x)
+
+ self.m.localThreads.remove(self)
+
+
+class InfoThread(PluginThread):
+ def __init__(self, manager, data, pid=-1, rid=-1, add=False):
+ """Constructor"""
+ PluginThread.__init__(self, manager)
+
+ self.data = data
+ self.pid = pid # package id
+ # [ .. (name, plugin) .. ]
+
+ self.rid = rid #result id
+ self.add = add #add packages instead of return result
+
+ self.cache = [] #accumulated data
+
+ self.start()
+
+ def run(self):
+ """run method"""
+
+ plugins = {}
+ container = []
+
+ for url, plugin in self.data:
+ if plugin in plugins:
+ plugins[plugin].append(url)
+ else:
+ plugins[plugin] = [url]
+
+
+ # filter out container plugins
+ for name in self.m.core.pluginManager.containerPlugins:
+ if name in plugins:
+ container.extend([(name, url) for url in plugins[name]])
+
+ del plugins[name]
+
+ #directly write to database
+ if self.pid > -1:
+ for pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateDB)
+ self.m.core.files.save()
+
+ elif self.add:
+ for pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True)
+
+ else:
+ #generate default result
+ result = [(url, 0, 3, url) for url in urls]
+
+ self.updateCache(pluginname, result)
+
+ packs = parseNames([(name, url) for name, x, y, url in self.cache])
+
+ self.m.log.debug("Fetched and generated %d packages" % len(packs))
+
+ for k, v in packs:
+ self.m.core.api.addPackage(k, v)
+
+ #empty cache
+ del self.cache[:]
+
+ else: #post the results
+
+
+ for name, url in container:
+ #attach container content
+ try:
+ data = self.decryptContainer(name, url)
+ except:
+ print_exc()
+ self.m.log.error("Could not decrypt container.")
+ data = []
+
+ for url, plugin in data:
+ if plugin in plugins:
+ plugins[plugin].append(url)
+ else:
+ plugins[plugin] = [url]
+
+ self.m.infoResults[self.rid] = {}
+
+ for pluginname, urls in plugins.iteritems():
+ plugin = self.m.core.pluginManager.getPlugin(pluginname, True)
+ if hasattr(plugin, "getInfo"):
+ self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True)
+
+ #force to process cache
+ if self.cache:
+ self.updateResult(pluginname, [], True)
+
+ else:
+ #generate default result
+ result = [(url, 0, 3, url) for url in urls]
+
+ self.updateResult(pluginname, result, True)
+
+ self.m.infoResults[self.rid]["ALL_INFO_FETCHED"] = {}
+
+ self.m.timestamp = time() + 5 * 60
+
+
+ def updateDB(self, plugin, result):
+ self.m.core.files.updateFileInfo(result, self.pid)
+
+ def updateResult(self, plugin, result, force=False):
+ #parse package name and generate result
+ #accumulate results
+
+ self.cache.extend(result)
+
+ if len(self.cache) >= 20 or force:
+ #used for package generating
+ tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size))))
+ for name, size, status, url in self.cache]
+
+ data = parseNames(tmp)
+ result = {}
+ for k, v in data.iteritems():
+ for url, status in v:
+ status.packagename = k
+ result[url] = status
+
+ self.m.setInfoResults(self.rid, result)
+
+ self.cache = []
+
+ def updateCache(self, plugin, result):
+ self.cache.extend(result)
+
+ def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None):
+ try:
+ result = [] #result loaded from cache
+ process = [] #urls to process
+ for url in urls:
+ if url in self.m.infoCache:
+ result.append(self.m.infoCache[url])
+ else:
+ process.append(url)
+
+ if result:
+ self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname))
+ cb(pluginname, result)
+
+ if process:
+ self.m.log.debug("Run Info Fetching for %s" % pluginname)
+ for result in plugin.getInfo(process):
+ #result = [ .. (name, size, status, url) .. ]
+ if not type(result) == list: result = [result]
+
+ for res in result:
+ self.m.infoCache[res[3]] = res
+
+ cb(pluginname, result)
+
+ self.m.log.debug("Finished Info Fetching for %s" % pluginname)
+ except Exception, e:
+ self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") %
+ {"name": pluginname, "err": str(e)})
+ if self.m.core.debug:
+ print_exc()
+
+ # generate default results
+ if err:
+ result = [(url, 0, 3, url) for url in urls]
+ cb(pluginname, result)
+
+
+ def decryptContainer(self, plugin, url):
+ data = []
+ # only works on container plugins
+
+ self.m.log.debug("Pre decrypting %s with %s" % (url, plugin))
+
+ # dummy pyfile
+ pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1)
+
+ pyfile.initPlugin()
+
+ # little plugin lifecycle
+ try:
+ pyfile.plugin.setup()
+ pyfile.plugin.loadToDisk()
+ pyfile.plugin.decrypt(pyfile)
+ pyfile.plugin.deleteTmp()
+
+ for pack in pyfile.plugin.packages:
+ pyfile.plugin.urls.extend(pack[1])
+
+ data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls)
+
+ self.m.log.debug("Got %d links." % len(data))
+
+ except Exception, e:
+ self.m.log.debug("Pre decrypting error: %s" % str(e))
+ finally:
+ pyfile.release()
+
+ return data
diff --git a/pyload/manager/thread/ServerThread.py b/pyload/manager/thread/ServerThread.py
new file mode 100644
index 000000000..7de3b1ca1
--- /dev/null
+++ b/pyload/manager/thread/ServerThread.py
@@ -0,0 +1,108 @@
+from __future__ import with_statement
+from os.path import exists
+
+import os
+import threading
+import logging
+
+core = None
+setup = None
+log = logging.getLogger("log")
+
+class WebServer(threading.Thread):
+ def __init__(self, pycore):
+ global core
+ threading.Thread.__init__(self)
+ self.core = pycore
+ core = pycore
+ self.running = True
+ self.server = pycore.config['webinterface']['server']
+ self.https = pycore.config['webinterface']['https']
+ self.cert = pycore.config["ssl"]["cert"]
+ self.key = pycore.config["ssl"]["key"]
+ self.host = pycore.config['webinterface']['host']
+ self.port = pycore.config['webinterface']['port']
+
+ self.setDaemon(True)
+
+ def run(self):
+ import pyload.webui as webinterface
+ global webinterface
+
+ reset = False
+
+ if self.https and (not exists(self.cert) or not exists(self.key)):
+ log.warning(_("SSL certificates not found."))
+ self.https = False
+
+ if self.server in ("lighttpd", "nginx"):
+ log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server)
+ log.warning(_("You can use the threaded server which offers good performance and ssl,"))
+ log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server)
+ log.warning(_("sample configs are located in the pyload/web/servers directory"))
+ reset = True
+ elif self.server == "fastcgi":
+ try:
+ import flup
+ except:
+ log.warning(_("Can't use %(server)s, python-flup is not installed!") % {
+ "server": self.server})
+ reset = True
+
+ if reset or self.server == "lightweight":
+ if os.name != "nt":
+ try:
+ import bjoern
+ except Exception, e:
+ log.error(_("Error importing lightweight server: %s") % e)
+ log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern"))
+ log.warning(_("Copy the boern.so to pyload/lib folder or use setup.py install"))
+ log.warning(_("Of course you need to be familiar with linux and know how to compile software"))
+ self.server = "builtin"
+ else:
+ self.core.log.info(_("Server set to threaded, due to known performance problems on windows."))
+ self.core.config['webinterface']['server'] = "threaded"
+ self.server = "threaded"
+
+ if self.server == "threaded":
+ self.start_threaded()
+ elif self.server == "fastcgi":
+ self.start_fcgi()
+ elif self.server == "lightweight":
+ self.start_lightweight()
+ else:
+ self.start_builtin()
+
+ def start_builtin(self):
+
+ if self.https:
+ log.warning(_("This server offers no SSL, please consider using threaded instead"))
+
+ self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ webinterface.run_simple(host=self.host, port=self.port)
+
+ def start_threaded(self):
+ if self.https:
+ self.core.log.info(_("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ else:
+ self.cert = ""
+ self.key = ""
+ self.core.log.info(_("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+
+ webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key)
+
+ def start_fcgi(self):
+
+ self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ webinterface.run_fcgi(host=self.host, port=self.port)
+
+
+ def start_lightweight(self):
+ if self.https:
+ log.warning(_("This server offers no SSL, please consider using threaded instead"))
+
+ self.core.log.info(_("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port})
+ webinterface.run_lightweight(host=self.host, port=self.port)
+
+ def quit(self):
+ self.running = False
diff --git a/module/plugins/container/__init__.py b/pyload/manager/thread/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/container/__init__.py
+++ b/pyload/manager/thread/__init__.py
diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py
new file mode 100644
index 000000000..e78d24688
--- /dev/null
+++ b/pyload/network/Browser.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+from logging import getLogger
+
+from HTTPRequest import HTTPRequest
+from HTTPDownload import HTTPDownload
+
+
+class Browser(object):
+ __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl")
+
+ def __init__(self, bucket=None, options={}):
+ self.log = getLogger("log")
+
+ self.options = options #holds pycurl options
+ self.bucket = bucket
+
+ self.cj = None # needs to be setted later
+ self._size = 0
+
+ self.renewHTTPRequest()
+ self.dl = None
+
+
+ def renewHTTPRequest(self):
+ if hasattr(self, "http"): self.http.close()
+ self.http = HTTPRequest(self.cj, self.options)
+
+ def setLastURL(self, val):
+ self.http.lastURL = val
+
+ # tunnel some attributes from HTTP Request to Browser
+ lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL)
+ lastURL = property(lambda self: self.http.lastURL, setLastURL)
+ code = property(lambda self: self.http.code)
+ cookieJar = property(lambda self: self.cj)
+
+ def setCookieJar(self, cj):
+ self.cj = cj
+ self.http.cj = cj
+
+ @property
+ def speed(self):
+ if self.dl:
+ return self.dl.speed
+ return 0
+
+ @property
+ def size(self):
+ if self._size:
+ return self._size
+ if self.dl:
+ return self.dl.size
+ return 0
+
+ @property
+ def arrived(self):
+ if self.dl:
+ return self.dl.arrived
+ return 0
+
+ @property
+ def percent(self):
+ if not self.size: return 0
+ return (self.arrived * 100) / self.size
+
+ def clearCookies(self):
+ if self.cj:
+ self.cj.clear()
+ self.http.clearCookies()
+
+ def clearReferer(self):
+ self.http.lastURL = None
+
+ def abortDownloads(self):
+ self.http.abort = True
+ if self.dl:
+ self._size = self.dl.size
+ self.dl.abort = True
+
+ def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False,
+ progressNotify=None, disposition=False):
+ """ this can also download ftp """
+ self._size = 0
+ self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None,
+ self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition)
+ name = self.dl.download(chunks, resume)
+ self._size = self.dl.size
+
+ self.dl = None
+
+ return name
+
+ def load(self, *args, **kwargs):
+ """ retrieves page """
+ return self.http.load(*args, **kwargs)
+
+ def putHeader(self, name, value):
+ """ add a header to the request """
+ self.http.putHeader(name, value)
+
+ def addAuth(self, pwd):
+ """Adds user and pw for http auth
+
+ :param pwd: string, user:password
+ """
+ self.options["auth"] = pwd
+ self.renewHTTPRequest() #we need a new request
+
+ def removeAuth(self):
+ if "auth" in self.options: del self.options["auth"]
+ self.renewHTTPRequest()
+
+ def setOption(self, name, value):
+ """Adds an option to the request, see HTTPRequest for existing ones"""
+ self.options[name] = value
+
+ def deleteOption(self, name):
+ if name in self.options: del self.options[name]
+
+ def clearHeaders(self):
+ self.http.clearHeaders()
+
+ def close(self):
+ """ cleanup """
+ if hasattr(self, "http"):
+ self.http.close()
+ del self.http
+ if hasattr(self, "dl"):
+ del self.dl
+ if hasattr(self, "cj"):
+ del self.cj
diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py
new file mode 100644
index 000000000..a096d644a
--- /dev/null
+++ b/pyload/network/Bucket.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from time import time
+from threading import Lock
+
+class Bucket:
+ def __init__(self):
+ self.rate = 0
+ self.tokens = 0
+ self.timestamp = time()
+ self.lock = Lock()
+
+ def __nonzero__(self):
+ return False if self.rate < 10240 else True
+
+ def setRate(self, rate):
+ self.lock.acquire()
+ self.rate = int(rate)
+ self.lock.release()
+
+ def consumed(self, amount):
+ """ return time the process have to sleep, after consumed specified amount """
+ if self.rate < 10240: return 0 #min. 10kb, may become unresponsive otherwise
+ self.lock.acquire()
+
+ self.calc_tokens()
+ self.tokens -= amount
+
+ if self.tokens < 0:
+ time = -self.tokens/float(self.rate)
+ else:
+ time = 0
+
+
+ self.lock.release()
+ return time
+
+ def calc_tokens(self):
+ if self.tokens < self.rate:
+ now = time()
+ delta = self.rate * (now - self.timestamp)
+ self.tokens = min(self.rate, self.tokens + delta)
+ self.timestamp = now
diff --git a/pyload/network/CookieJar.py b/pyload/network/CookieJar.py
new file mode 100644
index 000000000..a6ae090bc
--- /dev/null
+++ b/pyload/network/CookieJar.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay, RaNaN
+"""
+
+from time import time
+
+class CookieJar:
+ def __init__(self, pluginname, account=None):
+ self.cookies = {}
+ self.plugin = pluginname
+ self.account = account
+
+ def addCookies(self, clist):
+ for c in clist:
+ name = c.split("\t")[5]
+ self.cookies[name] = c
+
+ def getCookies(self):
+ return self.cookies.values()
+
+ def parseCookie(self, name):
+ if name in self.cookies:
+ return self.cookies[name].split("\t")[6]
+ else:
+ return None
+
+ def getCookie(self, name):
+ return self.parseCookie(name)
+
+ def setCookie(self, domain, name, value, path="/", exp=time()+3600*24*180):
+ s = ".%s TRUE %s FALSE %s %s %s" % (domain, path, exp, name, value)
+ self.cookies[name] = s
+
+ def clear(self):
+ self.cookies = {}
diff --git a/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py
new file mode 100644
index 000000000..b9d2a5379
--- /dev/null
+++ b/pyload/network/HTTPChunk.py
@@ -0,0 +1,292 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+from os import remove, stat, fsync
+from os.path import exists
+from time import sleep
+from re import search
+from pyload.utils import fs_encode
+import codecs
+import pycurl
+
+from HTTPRequest import HTTPRequest
+
+class WrongFormat(Exception):
+ pass
+
+
+class ChunkInfo:
+ def __init__(self, name):
+ self.name = unicode(name)
+ self.size = 0
+ self.resume = False
+ self.chunks = []
+
+ def __repr__(self):
+ ret = "ChunkInfo: %s, %s\n" % (self.name, self.size)
+ for i, c in enumerate(self.chunks):
+ ret += "%s# %s\n" % (i, c[1])
+
+ return ret
+
+ def setSize(self, size):
+ self.size = int(size)
+
+ def addChunk(self, name, range):
+ self.chunks.append((name, range))
+
+ def clear(self):
+ self.chunks = []
+
+ def createChunks(self, chunks):
+ self.clear()
+ chunk_size = self.size / chunks
+
+ current = 0
+ for i in range(chunks):
+ end = self.size - 1 if (i == chunks - 1) else current + chunk_size
+ self.addChunk("%s.chunk%s" % (self.name, i), (current, end))
+ current += chunk_size + 1
+
+
+ def save(self):
+ fs_name = fs_encode("%s.chunks" % self.name)
+ fh = codecs.open(fs_name, "w", "utf_8")
+ fh.write("name:%s\n" % self.name)
+ fh.write("size:%s\n" % self.size)
+ for i, c in enumerate(self.chunks):
+ fh.write("#%d:\n" % i)
+ fh.write("\tname:%s\n" % c[0])
+ fh.write("\trange:%i-%i\n" % c[1])
+ fh.close()
+
+ @staticmethod
+ def load(name):
+ fs_name = fs_encode("%s.chunks" % name)
+ if not exists(fs_name):
+ raise IOError()
+ fh = codecs.open(fs_name, "r", "utf_8")
+ name = fh.readline()[:-1]
+ size = fh.readline()[:-1]
+ if name.startswith("name:") and size.startswith("size:"):
+ name = name[5:]
+ size = size[5:]
+ else:
+ fh.close()
+ raise WrongFormat()
+ ci = ChunkInfo(name)
+ ci.loaded = True
+ ci.setSize(size)
+ while True:
+ if not fh.readline(): #skip line
+ break
+ name = fh.readline()[1:-1]
+ range = fh.readline()[1:-1]
+ if name.startswith("name:") and range.startswith("range:"):
+ name = name[5:]
+ range = range[6:].split("-")
+ else:
+ raise WrongFormat()
+
+ ci.addChunk(name, (long(range[0]), long(range[1])))
+ fh.close()
+ return ci
+
+ def remove(self):
+ fs_name = fs_encode("%s.chunks" % self.name)
+ if exists(fs_name): remove(fs_name)
+
+ def getCount(self):
+ return len(self.chunks)
+
+ def getChunkName(self, index):
+ return self.chunks[index][0]
+
+ def getChunkRange(self, index):
+ return self.chunks[index][1]
+
+
+class HTTPChunk(HTTPRequest):
+ def __init__(self, id, parent, range=None, resume=False):
+ self.id = id
+ self.p = parent # HTTPDownload instance
+ self.range = range # tuple (start, end)
+ self.resume = resume
+ self.log = parent.log
+
+ self.size = range[1] - range[0] if range else -1
+ self.arrived = 0
+ self.lastURL = self.p.referer
+
+ self.c = pycurl.Curl()
+
+ self.header = ""
+ self.headerParsed = False #indicates if the header has been processed
+
+ self.fp = None #file handle
+
+ self.initHandle()
+ self.setInterface(self.p.options)
+
+ self.BOMChecked = False # check and remove byte order mark
+
+ self.rep = None
+
+ self.sleep = 0.000
+ self.lastSize = 0
+
+ def __repr__(self):
+ return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived)
+
+ @property
+ def cj(self):
+ return self.p.cj
+
+ def getHandle(self):
+ """ returns a Curl handle ready to use for perform/multiperform """
+
+ self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj)
+ self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody)
+ self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
+
+ # request all bytes, since some servers in russia seems to have a defect arihmetic unit
+
+ fs_name = fs_encode(self.p.info.getChunkName(self.id))
+ if self.resume:
+ self.fp = open(fs_name, "ab")
+ self.arrived = self.fp.tell()
+ if not self.arrived:
+ self.arrived = stat(fs_name).st_size
+
+ if self.range:
+ #do nothing if chunk already finished
+ if self.arrived + self.range[0] >= self.range[1]: return None
+
+ if self.id == len(self.p.info.chunks) - 1: #as last chunk dont set end range, so we get everything
+ range = "%i-" % (self.arrived + self.range[0])
+ else:
+ range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1))
+
+ self.log.debug("Chunked resume with range %s" % range)
+ self.c.setopt(pycurl.RANGE, range)
+ else:
+ self.log.debug("Resume File from %i" % self.arrived)
+ self.c.setopt(pycurl.RESUME_FROM, self.arrived)
+
+ else:
+ if self.range:
+ if self.id == len(self.p.info.chunks) - 1: # see above
+ range = "%i-" % self.range[0]
+ else:
+ range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1))
+
+ self.log.debug("Chunked with range %s" % range)
+ self.c.setopt(pycurl.RANGE, range)
+
+ self.fp = open(fs_name, "wb")
+
+ return self.c
+
+ def writeHeader(self, buf):
+ self.header += buf
+ #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers
+ # as first chunk, we will parse the headers
+ if not self.range and self.header.endswith("\r\n\r\n"):
+ self.parseHeader()
+ elif not self.range and buf.startswith("150") and "data connection" in buf.lower(): #: ftp file size parsing
+ size = search(r"(\d+) bytes", buf)
+ if size:
+ self.p.size = int(size.group(1))
+ self.p.chunkSupport = True
+
+ self.headerParsed = True
+
+ def writeBody(self, buf):
+ #ignore BOM, it confuses unrar
+ if not self.BOMChecked:
+ if [ord(b) for b in buf[:3]] == [239, 187, 191]:
+ buf = buf[3:]
+ self.BOMChecked = True
+
+ size = len(buf)
+
+ self.arrived += size
+
+ self.fp.write(buf)
+
+ if self.p.bucket:
+ sleep(self.p.bucket.consumed(size))
+ else:
+ # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller
+ # otherwise reduce sleep time percentual (values are based on tests)
+ # So in general cpu time is saved without reducing bandwith too much
+
+ if size < self.lastSize:
+ self.sleep += 0.002
+ else:
+ self.sleep *= 0.7
+
+ self.lastSize = size
+
+ sleep(self.sleep)
+
+ if self.range and self.arrived > self.size:
+ return 0 #close if we have enough data
+
+
+ def parseHeader(self):
+ """parse data from recieved header"""
+ for orgline in self.decodeResponse(self.header).splitlines():
+ line = orgline.strip().lower()
+ if line.startswith("accept-ranges") and "bytes" in line:
+ self.p.chunkSupport = True
+
+ if line.startswith("content-disposition") and "filename=" in line:
+ name = orgline.partition("filename=")[2]
+ name = name.replace('"', "").replace("'", "").replace(";", "").strip()
+ self.p.nameDisposition = name
+ self.log.debug("Content-Disposition: %s" % name)
+
+ if not self.resume and line.startswith("content-length"):
+ self.p.size = int(line.split(":")[1])
+
+ self.headerParsed = True
+
+ def stop(self):
+ """The download will not proceed after next call of writeBody"""
+ self.range = [0, 0]
+ self.size = 0
+
+ def resetRange(self):
+ """ Reset the range, so the download will load all data available """
+ self.range = None
+
+ def setRange(self, range):
+ self.range = range
+ self.size = range[1] - range[0]
+
+ def flushFile(self):
+ """ flush and close file """
+ self.fp.flush()
+ fsync(self.fp.fileno()) #make sure everything was written to disk
+ self.fp.close() #needs to be closed, or merging chunks will fail
+
+ def close(self):
+ """ closes everything, unusable after this """
+ if self.fp: self.fp.close()
+ self.c.close()
+ if hasattr(self, "p"): del self.p
diff --git a/pyload/network/HTTPDownload.py b/pyload/network/HTTPDownload.py
new file mode 100644
index 000000000..50c6b4bdf
--- /dev/null
+++ b/pyload/network/HTTPDownload.py
@@ -0,0 +1,325 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from os import remove, fsync
+from os.path import dirname
+from time import sleep, time
+from shutil import move
+from logging import getLogger
+
+import pycurl
+
+from HTTPChunk import ChunkInfo, HTTPChunk
+from HTTPRequest import BadHeader
+
+from pyload.plugins.Plugin import Abort
+from pyload.utils import safe_join, fs_encode
+
+class HTTPDownload:
+ """ loads a url http + ftp """
+
+ def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None,
+ options={}, progressNotify=None, disposition=False):
+ self.url = url
+ self.filename = filename #complete file destination, not only name
+ self.get = get
+ self.post = post
+ self.referer = referer
+ self.cj = cj #cookiejar if cookies are needed
+ self.bucket = bucket
+ self.options = options
+ self.disposition = disposition
+ # all arguments
+
+ self.abort = False
+ self.size = 0
+ self.nameDisposition = None #will be parsed from content disposition
+
+ self.chunks = []
+
+ self.log = getLogger("log")
+
+ try:
+ self.info = ChunkInfo.load(filename)
+ self.info.resume = True #resume is only possible with valid info file
+ self.size = self.info.size
+ self.infoSaved = True
+ except IOError:
+ self.info = ChunkInfo(filename)
+
+ self.chunkSupport = None
+ self.m = pycurl.CurlMulti()
+
+ #needed for speed calculation
+ self.lastArrived = []
+ self.speeds = []
+ self.lastSpeeds = [0, 0]
+
+ self.progressNotify = progressNotify
+
+ @property
+ def speed(self):
+ last = [sum(x) for x in self.lastSpeeds if x]
+ return (sum(self.speeds) + sum(last)) / (1 + len(last))
+
+ @property
+ def arrived(self):
+ return sum([c.arrived for c in self.chunks])
+
+ @property
+ def percent(self):
+ if not self.size: return 0
+ return (self.arrived * 100) / self.size
+
+ def _copyChunks(self):
+ init = fs_encode(self.info.getChunkName(0)) #initial chunk name
+
+ if self.info.getCount() > 1:
+ fo = open(init, "rb+") #first chunkfile
+ for i in range(1, self.info.getCount()):
+ #input file
+ fo.seek(
+ self.info.getChunkRange(i - 1)[1] + 1) #seek to beginning of chunk, to get rid of overlapping chunks
+ fname = fs_encode("%s.chunk%d" % (self.filename, i))
+ fi = open(fname, "rb")
+ buf = 32 * 1024
+ while True: #copy in chunks, consumes less memory
+ data = fi.read(buf)
+ if not data:
+ break
+ fo.write(data)
+ fi.close()
+ if fo.tell() < self.info.getChunkRange(i)[1]:
+ fo.close()
+ remove(init)
+ self.info.remove() #there are probably invalid chunks
+ raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.")
+ remove(fname) #remove chunk
+ fo.close()
+
+ if self.nameDisposition and self.disposition:
+ self.filename = safe_join(dirname(self.filename), self.nameDisposition)
+
+ move(init, fs_encode(self.filename))
+ self.info.remove() #remove info file
+
+ def download(self, chunks=1, resume=False):
+ """ returns new filename or None """
+
+ chunks = max(1, chunks)
+ resume = self.info.resume and resume
+
+ try:
+ self._download(chunks, resume)
+ except pycurl.error, e:
+ #code 33 - no resume
+ code = e.args[0]
+ if code == 33:
+ # try again without resume
+ self.log.debug("Errno 33 -> Restart without resume")
+
+ #remove old handles
+ for chunk in self.chunks:
+ self.closeChunk(chunk)
+
+ return self._download(chunks, False)
+ else:
+ raise
+ finally:
+ self.close()
+
+ if self.nameDisposition and self.disposition: return self.nameDisposition
+ return None
+
+ def _download(self, chunks, resume):
+ if not resume:
+ self.info.clear()
+ self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #create an initial entry
+
+ self.chunks = []
+
+ init = HTTPChunk(0, self, None, resume) #initial chunk that will load complete file (if needed)
+
+ self.chunks.append(init)
+ self.m.add_handle(init.getHandle())
+
+ lastFinishCheck = 0
+ lastTimeCheck = 0
+ chunksDone = set() # list of curl handles that are finished
+ chunksCreated = False
+ done = False
+ if self.info.getCount() > 1: # This is a resume, if we were chunked originally assume still can
+ self.chunkSupport = True
+
+ while 1:
+ #need to create chunks
+ if not chunksCreated and self.chunkSupport and self.size: #will be setted later by first chunk
+
+ if not resume:
+ self.info.setSize(self.size)
+ self.info.createChunks(chunks)
+ self.info.save()
+
+ chunks = self.info.getCount()
+
+ init.setRange(self.info.getChunkRange(0))
+
+ for i in range(1, chunks):
+ c = HTTPChunk(i, self, self.info.getChunkRange(i), resume)
+
+ handle = c.getHandle()
+ if handle:
+ self.chunks.append(c)
+ self.m.add_handle(handle)
+ else:
+ #close immediatly
+ self.log.debug("Invalid curl handle -> closed")
+ c.close()
+
+ chunksCreated = True
+
+ while 1:
+ ret, num_handles = self.m.perform()
+ if ret != pycurl.E_CALL_MULTI_PERFORM:
+ break
+
+ t = time()
+
+ # reduce these calls
+ while lastFinishCheck + 0.5 < t:
+ # list of failed curl handles
+ failed = []
+ ex = None # save only last exception, we can only raise one anyway
+
+ num_q, ok_list, err_list = self.m.info_read()
+ for c in ok_list:
+ chunk = self.findChunk(c)
+ try: # check if the header implies success, else add it to failed list
+ chunk.verifyHeader()
+ except BadHeader, e:
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
+ failed.append(chunk)
+ ex = e
+ else:
+ chunksDone.add(c)
+
+ for c in err_list:
+ curl, errno, msg = c
+ chunk = self.findChunk(curl)
+ #test if chunk was finished
+ if errno != 23 or "0 !=" not in msg:
+ failed.append(chunk)
+ ex = pycurl.error(errno, msg)
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex)))
+ continue
+
+ try: # check if the header implies success, else add it to failed list
+ chunk.verifyHeader()
+ except BadHeader, e:
+ self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e)))
+ failed.append(chunk)
+ ex = e
+ else:
+ chunksDone.add(curl)
+ if not num_q: # no more infos to get
+
+ # check if init is not finished so we reset download connections
+ # note that other chunks are closed and downloaded with init too
+ if failed and init not in failed and init.c not in chunksDone:
+ self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex))))
+
+ #list of chunks to clean and remove
+ to_clean = filter(lambda x: x is not init, self.chunks)
+ for chunk in to_clean:
+ self.closeChunk(chunk)
+ self.chunks.remove(chunk)
+ remove(fs_encode(self.info.getChunkName(chunk.id)))
+
+ #let first chunk load the rest and update the info file
+ init.resetRange()
+ self.info.clear()
+ self.info.addChunk("%s.chunk0" % self.filename, (0, self.size))
+ self.info.save()
+ elif failed:
+ raise ex
+
+ lastFinishCheck = t
+
+ if len(chunksDone) >= len(self.chunks):
+ if len(chunksDone) > len(self.chunks):
+ self.log.warning("Finished download chunks size incorrect, please report bug.")
+ done = True #all chunks loaded
+
+ break
+
+ if done:
+ break #all chunks loaded
+
+ # calc speed once per second, averaging over 3 seconds
+ if lastTimeCheck + 1 < t:
+ diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in
+ enumerate(self.chunks)]
+
+ self.lastSpeeds[1] = self.lastSpeeds[0]
+ self.lastSpeeds[0] = self.speeds
+ self.speeds = [float(a) / (t - lastTimeCheck) for a in diff]
+ self.lastArrived = [c.arrived for c in self.chunks]
+ lastTimeCheck = t
+ self.updateProgress()
+
+ if self.abort:
+ raise Abort()
+
+ #sleep(0.003) #supress busy waiting - limits dl speed to (1 / x) * buffersize
+ self.m.select(1)
+
+ for chunk in self.chunks:
+ chunk.flushFile() #make sure downloads are written to disk
+
+ self._copyChunks()
+
+ def updateProgress(self):
+ if self.progressNotify:
+ self.progressNotify(self.percent)
+
+ def findChunk(self, handle):
+ """ linear search to find a chunk (should be ok since chunk size is usually low) """
+ for chunk in self.chunks:
+ if chunk.c == handle: return chunk
+
+ def closeChunk(self, chunk):
+ try:
+ self.m.remove_handle(chunk.c)
+ except pycurl.error, e:
+ self.log.debug("Error removing chunk: %s" % str(e))
+ finally:
+ chunk.close()
+
+ def close(self):
+ """ cleanup """
+ for chunk in self.chunks:
+ self.closeChunk(chunk)
+
+ self.chunks = []
+ if hasattr(self, "m"):
+ self.m.close()
+ del self.m
+ if hasattr(self, "cj"):
+ del self.cj
+ if hasattr(self, "info"):
+ del self.info
diff --git a/pyload/network/HTTPRequest.py b/pyload/network/HTTPRequest.py
new file mode 100644
index 000000000..66e355b77
--- /dev/null
+++ b/pyload/network/HTTPRequest.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+import pycurl
+
+from codecs import getincrementaldecoder, lookup, BOM_UTF8
+from urllib import quote, urlencode
+from httplib import responses
+from logging import getLogger
+from cStringIO import StringIO
+
+from pyload.plugins.Plugin import Abort
+
+def myquote(url):
+ return quote(url.encode('utf_8') if isinstance(url, unicode) else url, safe="%/:=&?~#+!$,;'@()*[]")
+
+def myurlencode(data):
+ data = dict(data)
+ return urlencode(dict((x.encode('utf_8') if isinstance(x, unicode) else x, \
+ y.encode('utf_8') if isinstance(y, unicode) else y ) for x, y in data.iteritems()))
+
+bad_headers = range(400, 404) + range(405, 418) + range(500, 506)
+
+class BadHeader(Exception):
+ def __init__(self, code, content=""):
+ Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)]))
+ self.code = code
+ self.content = content
+
+
+class HTTPRequest:
+ def __init__(self, cookies=None, options=None):
+ self.c = pycurl.Curl()
+ self.rep = StringIO()
+
+ self.cj = cookies #cookiejar
+
+ self.lastURL = None
+ self.lastEffectiveURL = None
+ self.abort = False
+ self.code = 0 # last http code
+
+ self.header = ""
+
+ self.headers = [] #temporary request header
+
+ self.initHandle()
+ self.setInterface(options)
+
+ self.c.setopt(pycurl.WRITEFUNCTION, self.write)
+ self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader)
+
+ self.log = getLogger("log")
+
+
+ def initHandle(self):
+ """ sets common options to curl handle """
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.MAXREDIRS, 5)
+ self.c.setopt(pycurl.CONNECTTIMEOUT, 30)
+ self.c.setopt(pycurl.NOSIGNAL, 1)
+ self.c.setopt(pycurl.NOPROGRESS, 1)
+ if hasattr(pycurl, "AUTOREFERER"):
+ self.c.setopt(pycurl.AUTOREFERER, 1)
+ self.c.setopt(pycurl.SSL_VERIFYPEER, 0)
+ self.c.setopt(pycurl.LOW_SPEED_TIME, 30)
+ self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5)
+
+ #self.c.setopt(pycurl.VERBOSE, 1)
+
+ self.c.setopt(pycurl.USERAGENT,
+ "Mozilla/5.0 (Windows NT 6.1; Win64; x64;en; rv:5.0) Gecko/20110619 Firefox/5.0")
+ if pycurl.version_info()[7]:
+ self.c.setopt(pycurl.ENCODING, "gzip, deflate")
+ self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*",
+ "Accept-Language: en-US, en",
+ "Accept-Charset: ISO-8859-1, utf-8;q=0.7,*;q=0.7",
+ "Connection: keep-alive",
+ "Keep-Alive: 300",
+ "Expect:"])
+
+ def setInterface(self, options):
+
+ interface, proxy, ipv6 = options["interface"], options["proxies"], options["ipv6"]
+
+ if interface and interface.lower() != "none":
+ self.c.setopt(pycurl.INTERFACE, str(interface))
+
+ if proxy:
+ if proxy["type"] == "socks4":
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4)
+ elif proxy["type"] == "socks5":
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
+ else:
+ self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
+
+ self.c.setopt(pycurl.PROXY, str(proxy["address"]))
+ self.c.setopt(pycurl.PROXYPORT, proxy["port"])
+
+ if proxy["username"]:
+ self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy["username"], proxy["password"])))
+
+ if ipv6:
+ self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)
+ else:
+ self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
+
+ if "auth" in options:
+ self.c.setopt(pycurl.USERPWD, str(options["auth"]))
+
+ if "timeout" in options:
+ self.c.setopt(pycurl.LOW_SPEED_TIME, options["timeout"])
+
+
+ def addCookies(self):
+ """ put cookies from curl handle to cj """
+ if self.cj:
+ self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST))
+
+ def getCookies(self):
+ """ add cookies from cj to curl handle """
+ if self.cj:
+ for c in self.cj.getCookies():
+ self.c.setopt(pycurl.COOKIELIST, c)
+ return
+
+ def clearCookies(self):
+ self.c.setopt(pycurl.COOKIELIST, "")
+
+ def setRequestContext(self, url, get, post, referer, cookies, multipart=False):
+ """ sets everything needed for the request """
+
+ url = myquote(url)
+
+ if get:
+ get = urlencode(get)
+ url = "%s?%s" % (url, get)
+
+ self.c.setopt(pycurl.URL, url)
+ self.c.lastUrl = url
+
+ if post:
+ self.c.setopt(pycurl.POST, 1)
+ if not multipart:
+ if type(post) == unicode:
+ post = str(post) #unicode not allowed
+ elif type(post) == str:
+ pass
+ else:
+ post = myurlencode(post)
+
+ self.c.setopt(pycurl.POSTFIELDS, post)
+ else:
+ post = [(x, y.encode('utf8') if type(y) == unicode else y ) for x, y in post.iteritems()]
+ self.c.setopt(pycurl.HTTPPOST, post)
+ else:
+ self.c.setopt(pycurl.POST, 0)
+
+ if referer and self.lastURL:
+ self.c.setopt(pycurl.REFERER, str(self.lastURL))
+
+ if cookies:
+ self.c.setopt(pycurl.COOKIEFILE, "")
+ self.c.setopt(pycurl.COOKIEJAR, "")
+ self.getCookies()
+
+
+ def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False):
+ """ load and returns a given page """
+
+ self.setRequestContext(url, get, post, referer, cookies, multipart)
+
+ self.header = ""
+
+ self.c.setopt(pycurl.HTTPHEADER, self.headers)
+
+ if just_header:
+ self.c.setopt(pycurl.FOLLOWLOCATION, 0)
+ self.c.setopt(pycurl.NOBODY, 1)
+ if post:
+ self.c.setopt(pycurl.POST, 1)
+ else:
+ self.c.setopt(pycurl.HTTPGET, 1)
+ self.c.perform()
+ rep = self.header
+
+ self.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.c.setopt(pycurl.NOBODY, 0)
+
+ else:
+ self.c.perform()
+ rep = self.getResponse()
+
+ self.c.setopt(pycurl.POSTFIELDS, "")
+ self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL)
+ self.code = self.verifyHeader()
+
+ self.addCookies()
+
+ if decode:
+ rep = self.decodeResponse(rep)
+
+ return rep
+
+ def verifyHeader(self):
+ """ raise an exceptions on bad headers """
+ code = int(self.c.getinfo(pycurl.RESPONSE_CODE))
+ if code in bad_headers:
+ #404 will NOT raise an exception
+ raise BadHeader(code, self.getResponse())
+ return code
+
+ def checkHeader(self):
+ """ check if header indicates failure"""
+ return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers
+
+ def getResponse(self):
+ """ retrieve response from string io """
+ if self.rep is None: return ""
+ value = self.rep.getvalue()
+ self.rep.close()
+ self.rep = StringIO()
+ return value
+
+ def decodeResponse(self, rep):
+ """ decode with correct encoding, relies on header """
+ header = self.header.splitlines()
+ encoding = "utf8" # default encoding
+
+ for line in header:
+ line = line.lower().replace(" ", "")
+ if not line.startswith("content-type:") or\
+ ("text" not in line and "application" not in line):
+ continue
+
+ none, delemiter, charset = line.rpartition("charset=")
+ if delemiter:
+ charset = charset.split(";")
+ if charset:
+ encoding = charset[0]
+
+ try:
+ #self.log.debug("Decoded %s" % encoding )
+ if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8):
+ encoding = 'utf-8-sig'
+
+ decoder = getincrementaldecoder(encoding)("replace")
+ rep = decoder.decode(rep, True)
+
+ #TODO: html_unescape as default
+
+ except LookupError:
+ self.log.debug("No Decoder foung for %s" % encoding)
+ except Exception:
+ self.log.debug("Error when decoding string from %s." % encoding)
+
+ return rep
+
+ def write(self, buf):
+ """ writes response """
+ if self.rep.tell() > 1000000 or self.abort:
+ rep = self.getResponse()
+ if self.abort: raise Abort()
+ f = open("response.dump", "wb")
+ f.write(rep)
+ f.close()
+ raise Exception("Loaded Url exceeded limit")
+
+ self.rep.write(buf)
+
+ def writeHeader(self, buf):
+ """ writes header """
+ self.header += buf
+
+ def putHeader(self, name, value):
+ self.headers.append("%s: %s" % (name, value))
+
+ def clearHeaders(self):
+ self.headers = []
+
+ def close(self):
+ """ cleanup, unusable after this """
+ self.rep.close()
+ if hasattr(self, "cj"):
+ del self.cj
+ if hasattr(self, "c"):
+ self.c.close()
+ del self.c
diff --git a/pyload/network/RequestFactory.py b/pyload/network/RequestFactory.py
new file mode 100644
index 000000000..6811b11d8
--- /dev/null
+++ b/pyload/network/RequestFactory.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay, RaNaN
+"""
+
+from threading import Lock
+
+from Browser import Browser
+from Bucket import Bucket
+from HTTPRequest import HTTPRequest
+from CookieJar import CookieJar
+
+from XDCCRequest import XDCCRequest
+
+class RequestFactory:
+ def __init__(self, core):
+ self.lock = Lock()
+ self.core = core
+ self.bucket = Bucket()
+ self.updateBucket()
+ self.cookiejars = {}
+
+ def iface(self):
+ return self.core.config["download"]["interface"]
+
+ def getRequest(self, pluginName, account=None, type="HTTP"):
+ self.lock.acquire()
+
+ if type == "XDCC":
+ return XDCCRequest(proxies=self.getProxies())
+
+ req = Browser(self.bucket, self.getOptions())
+
+ if account:
+ cj = self.getCookieJar(pluginName, account)
+ req.setCookieJar(cj)
+ else:
+ req.setCookieJar(CookieJar(pluginName))
+
+ self.lock.release()
+ return req
+
+ def getHTTPRequest(self, **kwargs):
+ """ returns a http request, dont forget to close it ! """
+ options = self.getOptions()
+ options.update(kwargs) # submit kwargs as additional options
+ return HTTPRequest(CookieJar(None), options)
+
+ def getURL(self, *args, **kwargs):
+ """ see HTTPRequest for argument list """
+ h = HTTPRequest(None, self.getOptions())
+ try:
+ rep = h.load(*args, **kwargs)
+ finally:
+ h.close()
+
+ return rep
+
+ def getCookieJar(self, pluginName, account=None):
+ if (pluginName, account) in self.cookiejars:
+ return self.cookiejars[(pluginName, account)]
+
+ cj = CookieJar(pluginName, account)
+ self.cookiejars[(pluginName, account)] = cj
+ return cj
+
+ def getProxies(self):
+ """ returns a proxy list for the request classes """
+ if not self.core.config["proxy"]["proxy"]:
+ return {}
+ else:
+ type = "http"
+ setting = self.core.config["proxy"]["type"].lower()
+ if setting == "socks4": type = "socks4"
+ elif setting == "socks5": type = "socks5"
+
+ username = None
+ if self.core.config["proxy"]["username"] and self.core.config["proxy"]["username"].lower() != "none":
+ username = self.core.config["proxy"]["username"]
+
+ pw = None
+ if self.core.config["proxy"]["password"] and self.core.config["proxy"]["password"].lower() != "none":
+ pw = self.core.config["proxy"]["password"]
+
+ return {
+ "type": type,
+ "address": self.core.config["proxy"]["address"],
+ "port": self.core.config["proxy"]["port"],
+ "username": username,
+ "password": pw,
+ }
+
+ def getOptions(self):
+ """returns options needed for pycurl"""
+ return {"interface": self.iface(),
+ "proxies": self.getProxies(),
+ "ipv6": self.core.config["download"]["ipv6"]}
+
+ def updateBucket(self):
+ """ set values in the bucket according to settings"""
+ if not self.core.config["download"]["limit_speed"]:
+ self.bucket.setRate(-1)
+ else:
+ self.bucket.setRate(self.core.config["download"]["max_speed"] * 1024)
+
+# needs pyreq in global namespace
+def getURL(*args, **kwargs):
+ return pyreq.getURL(*args, **kwargs)
+
+
+def getRequest(*args, **kwargs):
+ return pyreq.getHTTPRequest()
diff --git a/pyload/network/XDCCRequest.py b/pyload/network/XDCCRequest.py
new file mode 100644
index 000000000..9ae52f72b
--- /dev/null
+++ b/pyload/network/XDCCRequest.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: jeix
+"""
+
+import socket
+import re
+
+from os import remove
+from os.path import exists
+
+from time import time
+
+import struct
+from select import select
+
+from pyload.plugins.Plugin import Abort
+
+
+class XDCCRequest:
+ def __init__(self, timeout=30, proxies={}):
+
+ self.proxies = proxies
+ self.timeout = timeout
+
+ self.filesize = 0
+ self.recv = 0
+ self.speed = 0
+
+ self.abort = False
+
+ def createSocket(self):
+ # proxytype = None
+ # proxy = None
+ # if self.proxies.has_key("socks5"):
+ # proxytype = socks.PROXY_TYPE_SOCKS5
+ # proxy = self.proxies["socks5"]
+ # elif self.proxies.has_key("socks4"):
+ # proxytype = socks.PROXY_TYPE_SOCKS4
+ # proxy = self.proxies["socks4"]
+ # if proxytype:
+ # sock = socks.socksocket()
+ # t = _parse_proxy(proxy)
+ # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2])
+ # else:
+ # sock = socket.socket()
+ # return sock
+
+ return socket.socket()
+
+ def download(self, ip, port, filename, irc, progressNotify=None):
+
+ ircbuffer = ""
+ lastUpdate = time()
+ cumRecvLen = 0
+
+ dccsock = self.createSocket()
+
+ dccsock.settimeout(self.timeout)
+ dccsock.connect((ip, port))
+
+ if exists(filename):
+ i = 0
+ nameParts = filename.rpartition(".")
+ while True:
+ newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2])
+ i += 1
+
+ if not exists(newfilename):
+ filename = newfilename
+ break
+
+ fh = open(filename, "wb")
+
+ # recv loop for dcc socket
+ while True:
+ if self.abort:
+ dccsock.close()
+ fh.close()
+ remove(filename)
+ raise Abort()
+
+ self._keepAlive(irc, ircbuffer)
+
+ data = dccsock.recv(4096)
+ dataLen = len(data)
+ self.recv += dataLen
+
+ cumRecvLen += dataLen
+
+ now = time()
+ timespan = now - lastUpdate
+ if timespan > 1:
+ self.speed = cumRecvLen / timespan
+ cumRecvLen = 0
+ lastUpdate = now
+
+ if progressNotify:
+ progressNotify(self.percent)
+
+ if not data:
+ break
+
+ fh.write(data)
+
+ # acknowledge data by sending number of recceived bytes
+ dccsock.send(struct.pack('!I', self.recv))
+
+ dccsock.close()
+ fh.close()
+
+ return filename
+
+ def _keepAlive(self, sock, readbuffer):
+ fdset = select([sock], [], [], 0)
+ if sock not in fdset[0]:
+ return
+
+ readbuffer += sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ line = line.rstrip()
+ first = line.split()
+ if first[0] == "PING":
+ sock.send("PONG %s\r\n" % first[1])
+
+ def abortDownloads(self):
+ self.abort = True
+
+ @property
+ def size(self):
+ return self.filesize
+
+ @property
+ def arrived(self):
+ return self.recv
+
+ @property
+ def percent(self):
+ if not self.filesize: return 0
+ return (self.recv * 100) / self.filesize
+
+ def close(self):
+ pass
diff --git a/module/network/__init__.py b/pyload/network/__init__.py
index 8b1378917..8b1378917 100644
--- a/module/network/__init__.py
+++ b/pyload/network/__init__.py
diff --git a/pyload/plugins/Account.py b/pyload/plugins/Account.py
new file mode 100644
index 000000000..12ea494a0
--- /dev/null
+++ b/pyload/plugins/Account.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+
+from random import choice
+from time import time
+from traceback import print_exc
+from threading import RLock
+
+from pyload.plugins.Plugin import Base
+from pyload.utils import compare_time, parseFileSize, lock
+
+
+class WrongPassword(Exception):
+ pass
+
+
+class Account(Base):
+ """
+ Base class for every Account plugin.
+ Just overwrite `login` and cookies will be stored and account becomes accessible in\
+ associated hoster plugin. Plugin should also provide `loadAccountInfo`
+ """
+ __name__ = "Account"
+ __type__ = "account"
+ __version__ = "0.3"
+
+ __description__ = """Base account plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+ #: after that time (in minutes) pyload will relogin the account
+ login_timeout = 10 * 60
+ #: after that time (in minutes) account data will be reloaded
+ info_threshold = 10 * 60
+
+
+ def __init__(self, manager, accounts):
+ Base.__init__(self, manager.core)
+
+ self.manager = manager
+ self.accounts = {}
+ self.infos = {} # cache for account information
+ self.lock = RLock()
+
+ self.timestamps = {}
+ self.setAccounts(accounts)
+ self.init()
+
+ def init(self):
+ pass
+
+ def login(self, user, data, req):
+ """login into account, the cookies will be saved so user can be recognized
+
+ :param user: loginname
+ :param data: data dictionary
+ :param req: `Request` instance
+ """
+ pass
+
+ @lock
+ def _login(self, user, data):
+ # set timestamp for login
+ self.timestamps[user] = time()
+
+ req = self.getAccountRequest(user)
+ try:
+ self.login(user, data, req)
+ except WrongPassword:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": user
+ , "msg": _("Wrong Password")})
+ success = data['valid'] = False
+ except Exception, e:
+ self.logWarning(
+ _("Could not login with account %(user)s | %(msg)s") % {"user": user
+ , "msg": e})
+ success = data['valid'] = False
+ if self.core.debug:
+ print_exc()
+ else:
+ success = True
+ finally:
+ if req:
+ req.close()
+ return success
+
+ def relogin(self, user):
+ req = self.getAccountRequest(user)
+ if req:
+ req.cj.clear()
+ req.close()
+ if user in self.infos:
+ del self.infos[user] #delete old information
+
+ return self._login(user, self.accounts[user])
+
+ def setAccounts(self, accounts):
+ self.accounts = accounts
+ for user, data in self.accounts.iteritems():
+ self._login(user, data)
+ self.infos[user] = {}
+
+ def updateAccounts(self, user, password=None, options={}):
+ """ updates account and return true if anything changed """
+
+ if user in self.accounts:
+ self.accounts[user]['valid'] = True #do not remove or accounts will not login
+ if password:
+ self.accounts[user]['password'] = password
+ self.relogin(user)
+ return True
+ if options:
+ before = self.accounts[user]['options']
+ self.accounts[user]['options'].update(options)
+ return self.accounts[user]['options'] != before
+ else:
+ self.accounts[user] = {"password": password, "options": options, "valid": True}
+ self._login(user, self.accounts[user])
+ return True
+
+ def removeAccount(self, user):
+ if user in self.accounts:
+ del self.accounts[user]
+ if user in self.infos:
+ del self.infos[user]
+ if user in self.timestamps:
+ del self.timestamps[user]
+
+ @lock
+ def getAccountInfo(self, name, force=False):
+ """retrieve account infos for an user, do **not** overwrite this method!\\
+ just use it to retrieve infos in hoster plugins. see `loadAccountInfo`
+
+ :param name: username
+ :param force: reloads cached account information
+ :return: dictionary with information
+ """
+ data = Account.loadAccountInfo(self, name)
+
+ if force or name not in self.infos:
+ self.logDebug("Get Account Info for %s" % name)
+ req = self.getAccountRequest(name)
+
+ try:
+ infos = self.loadAccountInfo(name, req)
+ if not type(infos) == dict:
+ raise Exception("Wrong return format")
+ except Exception, e:
+ infos = {"error": str(e)}
+
+ if req: req.close()
+
+ self.logDebug("Account Info: %s" % str(infos))
+
+ infos['timestamp'] = time()
+ self.infos[name] = infos
+ elif "timestamp" in self.infos[name] and self.infos[name][
+ "timestamp"] + self.info_threshold * 60 < time():
+ self.logDebug("Reached timeout for account data")
+ self.scheduleRefresh(name)
+
+ data.update(self.infos[name])
+ return data
+
+ def isPremium(self, user):
+ info = self.getAccountInfo(user)
+ return info['premium']
+
+ def loadAccountInfo(self, name, req=None):
+ """this should be overwritten in account plugin,\
+ and retrieving account information for user
+
+ :param name:
+ :param req: `Request` instance
+ :return:
+ """
+ return {
+ "validuntil": None, # -1 for unlimited
+ "login": name,
+ #"password": self.accounts[name]['password'], #@XXX: security
+ "options": self.accounts[name]['options'],
+ "valid": self.accounts[name]['valid'],
+ "trafficleft": None, # in kb, -1 for unlimited
+ "maxtraffic": None,
+ "premium": True, #useful for free accounts
+ "timestamp": 0, #time this info was retrieved
+ "type": self.__name__,
+ }
+
+ def getAllAccounts(self, force=False):
+ return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()]
+
+ def getAccountRequest(self, user=None):
+ if not user:
+ user, data = self.selectAccount()
+ if not user:
+ return None
+
+ req = self.core.requestFactory.getRequest(self.__name__, user)
+ return req
+
+ def getAccountCookies(self, user=None):
+ if not user:
+ user, data = self.selectAccount()
+ if not user:
+ return None
+
+ cj = self.core.requestFactory.getCookieJar(self.__name__, user)
+ return cj
+
+ def getAccountData(self, user):
+ return self.accounts[user]
+
+ def selectAccount(self):
+ """ returns an valid account name and data"""
+ usable = []
+ for user, data in self.accounts.iteritems():
+ if not data['valid']: continue
+
+ if "time" in data['options'] and data['options']['time']:
+ time_data = ""
+ try:
+ time_data = data['options']['time'][0]
+ start, end = time_data.split("-")
+ if not compare_time(start.split(":"), end.split(":")):
+ continue
+ except:
+ self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data)
+
+ if user in self.infos:
+ if "validuntil" in self.infos[user]:
+ if self.infos[user]['validuntil'] > 0 and time() > self.infos[user]['validuntil']:
+ continue
+ if "trafficleft" in self.infos[user]:
+ if self.infos[user]['trafficleft'] == 0:
+ continue
+
+ usable.append((user, data))
+
+ if not usable: return None, None
+ return choice(usable)
+
+ def canUse(self):
+ return False if self.selectAccount() == (None, None) else True
+
+ def parseTraffic(self, string): #returns kbyte
+ return parseFileSize(string) / 1024
+
+ def wrongPassword(self):
+ raise WrongPassword
+
+ def empty(self, user):
+ if user in self.infos:
+ self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user)
+
+ self.infos[user].update({"trafficleft": 0})
+ self.scheduleRefresh(user, 30 * 60)
+
+ def expired(self, user):
+ if user in self.infos:
+ self.logWarning(_("Account %s is expired, checking again in 1h") % user)
+
+ self.infos[user].update({"validuntil": time() - 1})
+ self.scheduleRefresh(user, 60 * 60)
+
+ def scheduleRefresh(self, user, time=0, force=True):
+ """ add task to refresh account info to sheduler """
+ self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time))
+ self.core.scheduler.addJob(time, self.getAccountInfo, [user, force])
+
+ @lock
+ def checkLogin(self, user):
+ """ checks if user is still logged in """
+ if user in self.timestamps:
+ if self.login_timeout > 0 and self.timestamps[user] + self.login_timeout * 60 < time():
+ self.logDebug("Reached login timeout for %s" % user)
+ return self.relogin(user)
+ else:
+ return True
+ else:
+ return False
diff --git a/pyload/plugins/Container.py b/pyload/plugins/Container.py
new file mode 100644
index 000000000..747232c18
--- /dev/null
+++ b/pyload/plugins/Container.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from os import remove
+from os.path import basename, exists
+
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import safe_join
+
+
+class Container(Crypter):
+ __name__ = "Container"
+ __type__ = "container"
+ __version__ = "0.1"
+
+ __pattern__ = None
+
+ __description__ = """Base container decrypter plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def preprocessing(self, thread):
+ """prepare"""
+
+ self.setup()
+ self.thread = thread
+
+ self.loadToDisk()
+
+ self.decrypt(self.pyfile)
+ self.deleteTmp()
+
+ self.createPackages()
+
+
+ def loadToDisk(self):
+ """loads container to disk if its stored remotely and overwrite url,
+ or check existent on several places at disk"""
+
+ if self.pyfile.url.startswith("http"):
+ self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1]
+ content = self.load(self.pyfile.url)
+ self.pyfile.url = safe_join(self.config['general']['download_folder'], self.pyfile.name)
+ f = open(self.pyfile.url, "wb" )
+ f.write(content)
+ f.close()
+
+ else:
+ self.pyfile.name = basename(self.pyfile.url)
+ if not exists(self.pyfile.url):
+ if exists(safe_join(pypath, self.pyfile.url)):
+ self.pyfile.url = safe_join(pypath, self.pyfile.url)
+ else:
+ self.fail(_("File not exists."))
+
+
+ def deleteTmp(self):
+ if self.pyfile.name.startswith("tmp_"):
+ remove(self.pyfile.url)
diff --git a/pyload/plugins/Crypter.py b/pyload/plugins/Crypter.py
new file mode 100644
index 000000000..ed72c57c1
--- /dev/null
+++ b/pyload/plugins/Crypter.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Plugin import Plugin
+
+
+class Crypter(Plugin):
+ __name__ = "Crypter"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = None
+
+ __description__ = """Base decrypter plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def __init__(self, pyfile):
+ Plugin.__init__(self, pyfile)
+
+ #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder )
+ self.packages = []
+
+ #: List of urls, pyLoad will generate packagenames
+ self.urls = []
+
+ self.multiDL = True
+ self.limitDL = 0
+
+
+ def preprocessing(self, thread):
+ """prepare"""
+ self.setup()
+ self.thread = thread
+
+ self.decrypt(self.pyfile)
+
+ self.createPackages()
+
+
+ def decrypt(self, pyfile):
+ raise NotImplementedError
+
+ def createPackages(self):
+ """ create new packages from self.packages """
+ for pack in self.packages:
+
+ name, links, folder = pack
+
+ self.logDebug("Parsed package %(name)s with %(len)d links" % {"name": name, "len": len(links)})
+
+ links = [x.decode("utf-8") for x in links]
+
+ pid = self.api.addPackage(name, links, self.pyfile.package().queue)
+
+ if name != folder is not None:
+ self.api.setPackageData(pid, {"folder": folder}) #: Due to not break API addPackage method right now
+ self.logDebug("Set package %(name)s folder to %(folder)s" % {"name": name, "folder": folder})
+
+ if self.pyfile.package().password:
+ self.api.setPackageData(pid, {"password": self.pyfile.package().password})
+
+ if self.urls:
+ self.api.generateAndAddPackages(self.urls)
diff --git a/pyload/plugins/Hook.py b/pyload/plugins/Hook.py
new file mode 100644
index 000000000..b9ffbc647
--- /dev/null
+++ b/pyload/plugins/Hook.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+from traceback import print_exc
+
+from pyload.plugins.Plugin import Base
+
+
+class Expose(object):
+ """ used for decoration to declare rpc services """
+
+ def __new__(cls, f, *args, **kwargs):
+ hookManager.addRPC(f.__module__, f.func_name, f.func_doc)
+ return f
+
+
+def threaded(f):
+
+ def run(*args,**kwargs):
+ hookManager.startThread(f, *args, **kwargs)
+ return run
+
+
+class Hook(Base):
+ """
+ Base class for hook plugins.
+ """
+ __name__ = "Hook"
+ __type__ = "hook"
+ __version__ = "0.2"
+
+ __config__ = [("name", "type", "desc", "default")]
+
+ __description__ = """Interface for hook"""
+ __author_name__ = ("mkaay", "RaNaN")
+ __author_mail__ = ("mkaay@mkaay.de", "RaNaN@pyload.org")
+
+ #: automatically register event listeners for functions, attribute will be deleted dont use it yourself
+ event_map = None
+
+ # Alternative to event_map
+ #: List of events the plugin can handle, name the functions exactly like eventname.
+ event_list = None # dont make duplicate entries in event_map
+
+ #: periodic call interval in secondc
+ interval = 60
+
+
+ def __init__(self, core, manager):
+ Base.__init__(self, core)
+
+ #: Provide information in dict here, usable by API `getInfo`
+ self.info = None
+
+ #: Callback of periodical job task, used by hookmanager
+ self.cb = None
+
+ #: `HookManager`
+ self.manager = manager
+
+ #register events
+ if self.event_map:
+ for event, funcs in self.event_map.iteritems():
+ if type(funcs) in (list, tuple):
+ for f in funcs:
+ self.manager.addEvent(event, getattr(self,f))
+ else:
+ self.manager.addEvent(event, getattr(self,funcs))
+
+ #delete for various reasons
+ self.event_map = None
+
+ if self.event_list:
+ for f in self.event_list:
+ self.manager.addEvent(f, getattr(self,f))
+
+ self.event_list = None
+
+ self.setup()
+ self.initPeriodical()
+
+
+ def initPeriodical(self):
+ if self.interval >=1:
+ self.cb = self.core.scheduler.addJob(0, self._periodical, threaded=False)
+
+ def _periodical(self):
+ try:
+ if self.isActivated(): self.periodical()
+ except Exception, e:
+ self.logError(_("Error executing hooks: %s") % str(e))
+ if self.core.debug:
+ print_exc()
+
+ self.cb = self.core.scheduler.addJob(self.interval, self._periodical, threaded=False)
+
+
+ def __repr__(self):
+ return "<Hook %s>" % self.__name__
+
+ def setup(self):
+ """ more init stuff if needed """
+ pass
+
+ def unload(self):
+ """ called when hook was deactivated """
+ pass
+
+ def isActivated(self):
+ """ checks if hook is activated"""
+ return self.config.getPlugin(self.__name__, "activated")
+
+
+ #event methods - overwrite these if needed
+ def coreReady(self):
+ pass
+
+ def coreExiting(self):
+ pass
+
+ def downloadPreparing(self, pyfile):
+ pass
+
+ def downloadFinished(self, pyfile):
+ pass
+
+ def downloadFailed(self, pyfile):
+ pass
+
+ def packageFinished(self, pypack):
+ pass
+
+ def beforeReconnecting(self, ip):
+ pass
+
+ def afterReconnecting(self, ip):
+ pass
+
+ def periodical(self):
+ pass
+
+ def newCaptchaTask(self, task):
+ """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """
+ pass
+
+ def captchaCorrect(self, task):
+ pass
+
+ def captchaInvalid(self, task):
+ pass
diff --git a/pyload/plugins/Hoster.py b/pyload/plugins/Hoster.py
new file mode 100644
index 000000000..23369deec
--- /dev/null
+++ b/pyload/plugins/Hoster.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Plugin import Plugin
+
+
+def getInfo(self):
+ #result = [ .. (name, size, status, url) .. ]
+ return
+
+
+class Hoster(Plugin):
+ __name__ = "Hoster"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = None
+
+ __description__ = """Base hoster plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
diff --git a/pyload/plugins/OCR.py b/pyload/plugins/OCR.py
new file mode 100644
index 000000000..0991184f3
--- /dev/null
+++ b/pyload/plugins/OCR.py
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+import os
+import logging
+import subprocess
+
+from os.path import abspath, join
+from PIL import Image
+from PIL import TiffImagePlugin
+from PIL import PngImagePlugin
+from PIL import GifImagePlugin
+from PIL import JpegImagePlugin
+
+
+class OCR(object):
+ __name__ = "OCR"
+ __type__ = "ocr"
+ __version__ = "0.1"
+
+ __description__ = """OCR base plugin"""
+ __author_name__ = "pyLoad Team"
+ __author_mail__ = "admin@pyload.org"
+
+
+ def __init__(self):
+ self.logger = logging.getLogger("log")
+
+ def load_image(self, image):
+ self.image = Image.open(image)
+ self.pixels = self.image.load()
+ self.result_captcha = ''
+
+ def unload(self):
+ """delete all tmp images"""
+ pass
+
+ def threshold(self, value):
+ self.image = self.image.point(lambda a: a * value + 10)
+
+ def run(self, command):
+ """Run a command"""
+
+ popen = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ popen.wait()
+ output = popen.stdout.read() + " | " + popen.stderr.read()
+ popen.stdout.close()
+ popen.stderr.close()
+ self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output))
+
+ def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True):
+ #self.logger.debug("create tmp tif")
+ #tmp = tempfile.NamedTemporaryFile(suffix=".tif")
+ tmp = open(join("tmp", "tmpTif_%s.tif" % self.__name__), "wb")
+ tmp.close()
+ #self.logger.debug("create tmp txt")
+ #tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt")
+ tmpTxt = open(join("tmp", "tmpTxt_%s.txt" % self.__name__), "wb")
+ tmpTxt.close()
+
+ self.logger.debug("save tiff")
+ self.image.save(tmp.name, 'TIFF')
+
+ if os.name == "nt":
+ tessparams = [join(pypath, "tesseract", "tesseract.exe")]
+ else:
+ tessparams = ['tesseract']
+
+ tessparams.extend([abspath(tmp.name), abspath(tmpTxt.name).replace(".txt", "")])
+
+ if subset and (digits or lowercase or uppercase):
+ #self.logger.debug("create temp subset config")
+ #tmpSub = tempfile.NamedTemporaryFile(suffix=".subset")
+ tmpSub = open(join("tmp", "tmpSub_%s.subset" % self.__name__), "wb")
+ tmpSub.write("tessedit_char_whitelist ")
+ if digits:
+ tmpSub.write("0123456789")
+ if lowercase:
+ tmpSub.write("abcdefghijklmnopqrstuvwxyz")
+ if uppercase:
+ tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ tmpSub.write("\n")
+ tessparams.append("nobatch")
+ tessparams.append(abspath(tmpSub.name))
+ tmpSub.close()
+
+ self.logger.debug("run tesseract")
+ self.run(tessparams)
+ self.logger.debug("read txt")
+
+ try:
+ with open(tmpTxt.name, 'r') as f:
+ self.result_captcha = f.read().replace("\n", "")
+ except:
+ self.result_captcha = ""
+
+ self.logger.debug(self.result_captcha)
+ try:
+ os.remove(tmp.name)
+ os.remove(tmpTxt.name)
+ if subset and (digits or lowercase or uppercase):
+ os.remove(tmpSub.name)
+ except:
+ pass
+
+ def get_captcha(self, name):
+ raise NotImplementedError
+
+ def to_greyscale(self):
+ if self.image.mode != 'L':
+ self.image = self.image.convert('L')
+
+ self.pixels = self.image.load()
+
+ def eval_black_white(self, limit):
+ self.pixels = self.image.load()
+ w, h = self.image.size
+ for x in xrange(w):
+ for y in xrange(h):
+ if self.pixels[x, y] > limit:
+ self.pixels[x, y] = 255
+ else:
+ self.pixels[x, y] = 0
+
+ def clean(self, allowed):
+ pixels = self.pixels
+
+ w, h = self.image.size
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 255:
+ continue
+ # No point in processing white pixels since we only want to remove black pixel
+ count = 0
+
+ try:
+ if pixels[x - 1, y - 1] != 255:
+ count += 1
+ if pixels[x - 1, y] != 255:
+ count += 1
+ if pixels[x - 1, y + 1] != 255:
+ count += 1
+ if pixels[x, y + 1] != 255:
+ count += 1
+ if pixels[x + 1, y + 1] != 255:
+ count += 1
+ if pixels[x + 1, y] != 255:
+ count += 1
+ if pixels[x + 1, y - 1] != 255:
+ count += 1
+ if pixels[x, y - 1] != 255:
+ count += 1
+ except:
+ pass
+
+ # not enough neighbors are dark pixels so mark this pixel
+ # to be changed to white
+ if count < allowed:
+ pixels[x, y] = 1
+
+ # second pass: this time set all 1's to 255 (white)
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 1:
+ pixels[x, y] = 255
+
+ self.pixels = pixels
+
+ def derotate_by_average(self):
+ """rotate by checking each angle and guess most suitable"""
+
+ w, h = self.image.size
+ pixels = self.pixels
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 155
+
+ highest = {}
+ counts = {}
+
+ for angle in xrange(-45, 45):
+
+ tmpimage = self.image.rotate(angle)
+
+ pixels = tmpimage.load()
+
+ w, h = self.image.size
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 255
+
+ count = {}
+
+ for x in xrange(w):
+ count[x] = 0
+ for y in xrange(h):
+ if pixels[x, y] == 155:
+ count[x] += 1
+
+ sum = 0
+ cnt = 0
+
+ for x in count.values():
+ if x != 0:
+ sum += x
+ cnt += 1
+
+ avg = sum / cnt
+ counts[angle] = cnt
+ highest[angle] = 0
+ for x in count.values():
+ if x > highest[angle]:
+ highest[angle] = x
+
+ highest[angle] = highest[angle] - avg
+
+ hkey = 0
+ hvalue = 0
+
+ for key, value in highest.iteritems():
+ if value > hvalue:
+ hkey = key
+ hvalue = value
+
+ self.image = self.image.rotate(hkey)
+ pixels = self.image.load()
+
+ for x in xrange(w):
+ for y in xrange(h):
+ if pixels[x, y] == 0:
+ pixels[x, y] = 255
+
+ if pixels[x, y] == 155:
+ pixels[x, y] = 0
+
+ self.pixels = pixels
+
+ def split_captcha_letters(self):
+ captcha = self.image
+ started = False
+ letters = []
+ width, height = captcha.size
+ bottomY, topY = 0, height
+ pixels = captcha.load()
+
+ for x in xrange(width):
+ black_pixel_in_col = False
+ for y in xrange(height):
+ if pixels[x, y] != 255:
+ if not started:
+ started = True
+ firstX = x
+ lastX = x
+
+ if y > bottomY:
+ bottomY = y
+ if y < topY:
+ topY = y
+ if x > lastX:
+ lastX = x
+
+ black_pixel_in_col = True
+
+ if black_pixel_in_col is False and started is True:
+ rect = (firstX, topY, lastX, bottomY)
+ new_captcha = captcha.crop(rect)
+
+ w, h = new_captcha.size
+ if w > 5 and h > 5:
+ letters.append(new_captcha)
+
+ started = False
+ bottomY, topY = 0, height
+
+ return letters
+
+ def correct(self, values, var=None):
+ if var:
+ result = var
+ else:
+ result = self.result_captcha
+
+ for key, item in values.iteritems():
+
+ if key.__class__ == str:
+ result = result.replace(key, item)
+ else:
+ for expr in key:
+ result = result.replace(expr, item)
+
+ if var:
+ return result
+ else:
+ self.result_captcha = result
diff --git a/pyload/plugins/Plugin.py b/pyload/plugins/Plugin.py
new file mode 100644
index 000000000..31cbfca57
--- /dev/null
+++ b/pyload/plugins/Plugin.py
@@ -0,0 +1,629 @@
+# -*- coding: utf-8 -*-
+
+from time import time, sleep
+from random import randint
+
+import os
+from os import remove, makedirs, chmod, stat
+from os.path import exists, join
+
+if os.name != "nt":
+ from os import chown
+ from pwd import getpwnam
+ from grp import getgrnam
+
+from itertools import islice
+
+from pyload.utils import safe_join, safe_filename, fs_encode, fs_decode
+
+def chunks(iterable, size):
+ it = iter(iterable)
+ item = list(islice(it, size))
+ while item:
+ yield item
+ item = list(islice(it, size))
+
+
+class Abort(Exception):
+ """ raised when aborted """
+
+
+class Fail(Exception):
+ """ raised when failed """
+
+
+class Reconnect(Exception):
+ """ raised when reconnected """
+
+
+class Retry(Exception):
+ """ raised when start again from beginning """
+
+
+class SkipDownload(Exception):
+ """ raised when download should be skipped """
+
+
+class Base(object):
+ """
+ A Base class with log/config/db methods *all* plugin types can use
+ """
+
+ def __init__(self, core):
+ #: Core instance
+ self.core = core
+ #: logging instance
+ self.log = core.log
+ #: core config
+ self.config = core.config
+
+ #log functions
+ def logInfo(self, *args):
+ self.log.info("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
+
+ def logWarning(self, *args):
+ self.log.warning("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
+
+ def logError(self, *args):
+ self.log.error("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
+
+ def logDebug(self, *args):
+ self.log.debug("%s: %s" % (self.__name__, " | ".join([a if isinstance(a, basestring) else str(a) for a in args])))
+
+
+ def setConf(self, option, value):
+ """ see `setConfig` """
+ self.config.setPlugin(self.__name__, option, value)
+
+ def setConfig(self, option, value):
+ """ Set config value for current plugin
+
+ :param option:
+ :param value:
+ :return:
+ """
+ self.setConf(option, value)
+
+ #: Deprecated method
+ def getConf(self, option):
+ """ see `getConfig` """
+ return self.getConfig(option)
+
+ def getConfig(self, option):
+ """ Returns config value for current plugin
+
+ :param option:
+ :return:
+ """
+ return self.config.getPlugin(self.__name__, option)
+
+ def setStorage(self, key, value):
+ """ Saves a value persistently to the database """
+ self.core.db.setStorage(self.__name__, key, value)
+
+ def store(self, key, value):
+ """ same as `setStorage` """
+ self.core.db.setStorage(self.__name__, key, value)
+
+ def getStorage(self, key=None, default=None):
+ """ Retrieves saved value or dict of all saved entries if key is None """
+ if key is not None:
+ return self.core.db.getStorage(self.__name__, key) or default
+ return self.core.db.getStorage(self.__name__, key)
+
+ def retrieve(self, *args, **kwargs):
+ """ same as `getStorage` """
+ return self.getStorage(*args, **kwargs)
+
+ def delStorage(self, key):
+ """ Delete entry in db """
+ self.core.db.delStorage(self.__name__, key)
+
+
+class Plugin(Base):
+ """
+ Base plugin for hoster/crypter.
+ Overwrite `process` / `decrypt` in your subclassed plugin.
+ """
+ __name__ = "Plugin"
+ __type__ = "hoster"
+ __version__ = "0.5"
+
+ __pattern__ = None
+ __config__ = [("name", "type", "desc", "default")]
+
+ __description__ = """Base plugin"""
+ __author_name__ = ("RaNaN", "spoob", "mkaay")
+ __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org", "mkaay@mkaay.de")
+
+
+ def __init__(self, pyfile):
+ Base.__init__(self, pyfile.m.core)
+
+ #: engage wan reconnection
+ self.wantReconnect = False
+
+ #: enable simultaneous processing of multiple downloads
+ self.multiDL = True
+ self.limitDL = 0
+
+ #: chunk limit
+ self.chunkLimit = 1
+ self.resumeDownload = False
+
+ #: time() + wait in seconds
+ self.waitUntil = 0
+ self.waiting = False
+
+ #: captcha reader instance
+ self.ocr = None
+
+ #: account handler instance, see :py:class:`Account`
+ self.account = pyfile.m.core.accountManager.getAccountPlugin(self.__name__)
+
+ #: premium status
+ self.premium = False
+ #: username/login
+ self.user = None
+
+ if self.account and not self.account.canUse():
+ self.account = None
+
+ if self.account:
+ self.user, data = self.account.selectAccount()
+ #: Browser instance, see `network.Browser`
+ self.req = self.account.getAccountRequest(self.user)
+ self.chunkLimit = -1 # chunk limit, -1 for unlimited
+ #: enables resume (will be ignored if server dont accept chunks)
+ self.resumeDownload = True
+ self.multiDL = True #every hoster with account should provide multiple downloads
+ #: premium status
+ self.premium = self.account.isPremium(self.user)
+ else:
+ self.req = pyfile.m.core.requestFactory.getRequest(self.__name__)
+
+ #: associated pyfile instance, see `PyFile`
+ self.pyfile = pyfile
+
+ self.thread = None # holds thread in future
+
+ #: location where the last call to download was saved
+ self.lastDownload = ""
+ #: re match of the last call to `checkDownload`
+ self.lastCheck = None
+
+ #: js engine, see `JsEngine`
+ self.js = self.core.js
+
+ #: captcha task
+ self.cTask = None
+
+ #: amount of retries already made
+ self.retries = 0
+
+ #: some plugins store html code here
+ self.html = None
+
+ #: quick caller for API
+ self.api = self.core.api
+
+ self.init()
+
+ def getChunkCount(self):
+ if self.chunkLimit <= 0:
+ return self.config['download']['chunks']
+ return min(self.config['download']['chunks'], self.chunkLimit)
+
+ def __call__(self):
+ return self.__name__
+
+ def init(self):
+ """initialize the plugin (in addition to `__init__`)"""
+ pass
+
+ def setup(self):
+ """ setup for enviroment and other things, called before downloading (possibly more than one time)"""
+ pass
+
+ def preprocessing(self, thread):
+ """ handles important things to do before starting """
+ self.thread = thread
+
+ if self.account:
+ self.account.checkLogin(self.user)
+ else:
+ self.req.clearCookies()
+
+ self.setup()
+
+ self.pyfile.setStatus("starting")
+
+ return self.process(self.pyfile)
+
+
+ def process(self, pyfile):
+ """the 'main' method of every plugin, you **have to** overwrite it"""
+ raise NotImplementedError
+
+ def resetAccount(self):
+ """ dont use account and retry download """
+ self.account = None
+ self.req = self.core.requestFactory.getRequest(self.__name__)
+ self.retry()
+
+ def checksum(self, local_file=None):
+ """
+ return codes:
+ 0 - checksum ok
+ 1 - checksum wrong
+ 5 - can't get checksum
+ 10 - not implemented
+ 20 - unknown error
+ """
+ #@TODO checksum check hook
+
+ return True, 10
+
+
+ def setWait(self, seconds, reconnect=None):
+ """Set a specific wait time later used with `wait`
+
+ :param seconds: wait time in seconds
+ :param reconnect: True if a reconnect would avoid wait time
+ """
+ if reconnect:
+ self.wantReconnect = True
+ self.pyfile.waitUntil = time() + int(seconds)
+
+ def wait(self, seconds=None, reconnect=None):
+ """ Waits the time previously set or use these from arguments. See `setWait`
+ """
+ if seconds:
+ self.setWait(seconds, reconnect)
+
+ self._wait()
+
+ def _wait(self):
+ self.waiting = True
+ self.pyfile.setStatus("waiting")
+
+ while self.pyfile.waitUntil > time():
+ self.thread.m.reconnecting.wait(2)
+
+ if self.pyfile.abort:
+ raise Abort
+ if self.thread.m.reconnecting.isSet():
+ self.waiting = False
+ self.wantReconnect = False
+ raise Reconnect
+
+ self.waiting = False
+ self.pyfile.setStatus("starting")
+
+ def fail(self, reason):
+ """ fail and give reason """
+ raise Fail(reason)
+
+ def offline(self):
+ """ fail and indicate file is offline """
+ raise Fail("offline")
+
+ def tempOffline(self):
+ """ fail and indicates file ist temporary offline, the core may take consequences """
+ raise Fail("temp. offline")
+
+ def retry(self, max_tries=3, wait_time=1, reason=""):
+ """Retries and begin again from the beginning
+
+ :param max_tries: number of maximum retries
+ :param wait_time: time to wait in seconds
+ :param reason: reason for retrying, will be passed to fail if max_tries reached
+ """
+ if 0 < max_tries <= self.retries:
+ if not reason: reason = "Max retries reached"
+ raise Fail(reason)
+
+ self.wantReconnect = False
+ self.setWait(wait_time)
+ self.wait()
+
+ self.retries += 1
+ raise Retry(reason)
+
+ def invalidCaptcha(self):
+ if self.cTask:
+ self.cTask.invalid()
+
+ def correctCaptcha(self):
+ if self.cTask:
+ self.cTask.correct()
+
+ def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg',
+ result_type='textual'):
+ """ Loads a captcha and decrypts it with ocr, plugin, user input
+
+ :param url: url of captcha image
+ :param get: get part for request
+ :param post: post part for request
+ :param cookies: True if cookies should be enabled
+ :param forceUser: if True, ocr is not used
+ :param imgtype: Type of the Image
+ :param result_type: 'textual' if text is written on the captcha\
+ or 'positional' for captcha where the user have to click\
+ on a specific region on the captcha
+
+ :return: result of decrypting
+ """
+
+ img = self.load(url, get=get, post=post, cookies=cookies)
+
+ id = ("%.2f" % time())[-6:].replace(".", "")
+ temp_file = open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.__name__, id, imgtype)), "wb")
+ temp_file.write(img)
+ temp_file.close()
+
+ has_plugin = self.__name__ in self.core.pluginManager.captchaPlugins
+
+ if self.core.captcha:
+ Ocr = self.core.pluginManager.loadClass("captcha", self.__name__)
+ else:
+ Ocr = None
+
+ if Ocr and not forceUser:
+ sleep(randint(3000, 5000) / 1000.0)
+ if self.pyfile.abort: raise Abort
+
+ ocr = Ocr()
+ result = ocr.get_captcha(temp_file.name)
+ else:
+ captchaManager = self.core.captchaManager
+ task = captchaManager.newTask(img, imgtype, temp_file.name, result_type)
+ self.cTask = task
+ captchaManager.handleCaptcha(task)
+
+ while task.isWaiting():
+ if self.pyfile.abort:
+ captchaManager.removeTask(task)
+ raise Abort
+ sleep(1)
+
+ captchaManager.removeTask(task)
+
+ if task.error and has_plugin: #ignore default error message since the user could use OCR
+ self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting"))
+ elif task.error:
+ self.fail(task.error)
+ elif not task.result:
+ self.fail(_("No captcha result obtained in appropiate time by any of the plugins."))
+
+ result = task.result
+ self.logDebug("Received captcha result: %s" % str(result))
+
+ if not self.core.debug:
+ try:
+ remove(temp_file.name)
+ except:
+ pass
+
+ return result
+
+
+ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
+ """Load content at url and returns it
+
+ :param url:
+ :param get:
+ :param post:
+ :param ref:
+ :param cookies:
+ :param just_header: if True only the header will be retrieved and returned as dict
+ :param decode: Wether to decode the output according to http header, should be True in most cases
+ :return: Loaded content
+ """
+ if self.pyfile.abort: raise Abort
+ #utf8 vs decode -> please use decode attribute in all future plugins
+ if type(url) == unicode: url = str(url)
+
+ res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode)
+
+ if self.core.debug:
+ from inspect import currentframe
+
+ frame = currentframe()
+ if not exists(join("tmp", self.__name__)):
+ makedirs(join("tmp", self.__name__))
+
+ f = open(
+ join("tmp", self.__name__, "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno))
+ , "wb")
+ del frame # delete the frame or it wont be cleaned
+
+ try:
+ tmp = res.encode("utf8")
+ except:
+ tmp = res
+
+ f.write(tmp)
+ f.close()
+
+ if just_header:
+ #parse header
+ header = {"code": self.req.code}
+ for line in res.splitlines():
+ line = line.strip()
+ if not line or ":" not in line: continue
+
+ key, none, value = line.partition(":")
+ key = key.lower().strip()
+ value = value.strip()
+
+ if key in header:
+ if type(header[key]) == list:
+ header[key].append(value)
+ else:
+ header[key] = [header[key], value]
+ else:
+ header[key] = value
+ res = header
+
+ return res
+
+ def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False):
+ """Downloads the content at url to download folder
+
+ :param url:
+ :param get:
+ :param post:
+ :param ref:
+ :param cookies:
+ :param disposition: if True and server provides content-disposition header\
+ the filename will be changed if needed
+ :return: The location where the file was saved
+ """
+
+ self.checkForSameFiles()
+
+ self.pyfile.setStatus("downloading")
+
+ download_folder = self.config['general']['download_folder']
+
+ location = safe_join(download_folder, self.pyfile.package().folder)
+
+ if not exists(location):
+ makedirs(location, int(self.config['permission']['folder'], 8))
+
+ if self.config['permission']['change_dl'] and os.name != "nt":
+ try:
+ uid = getpwnam(self.config['permission']['user'])[2]
+ gid = getgrnam(self.config['permission']['group'])[2]
+
+ chown(location, uid, gid)
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed: %s") % str(e))
+
+ # convert back to unicode
+ location = fs_decode(location)
+ name = safe_filename(self.pyfile.name)
+
+ filename = join(location, name)
+
+ self.core.hookManager.dispatchEvent("downloadStarts", self.pyfile, url, filename)
+
+ try:
+ newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies,
+ chunks=self.getChunkCount(), resume=self.resumeDownload,
+ progressNotify=self.pyfile.setProgress, disposition=disposition)
+ finally:
+ self.pyfile.size = self.req.size
+
+ if disposition and newname and newname != name: #triple check, just to be sure
+ self.logInfo("%(name)s saved as %(newname)s" % {"name": name, "newname": newname})
+ self.pyfile.name = newname
+ filename = join(location, newname)
+
+ fs_filename = fs_encode(filename)
+
+ if self.config['permission']['change_file']:
+ chmod(fs_filename, int(self.config['permission']['file'], 8))
+
+ if self.config['permission']['change_dl'] and os.name != "nt":
+ try:
+ uid = getpwnam(self.config['permission']['user'])[2]
+ gid = getgrnam(self.config['permission']['group'])[2]
+
+ chown(fs_filename, uid, gid)
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed: %s") % str(e))
+
+ self.lastDownload = filename
+ return self.lastDownload
+
+ def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0):
+ """ checks the content of the last downloaded file, re match is saved to `lastCheck`
+
+ :param rules: dict with names and rules to match (compiled regexp or strings)
+ :param api_size: expected file size
+ :param max_size: if the file is larger then it wont be checked
+ :param delete: delete if matched
+ :param read_size: amount of bytes to read from files larger then max_size
+ :return: dictionary key of the first rule that matched
+ """
+ lastDownload = fs_encode(self.lastDownload)
+ if not exists(lastDownload): return None
+
+ size = stat(lastDownload)
+ size = size.st_size
+
+ if api_size and api_size <= size: return None
+ elif size > max_size and not read_size: return None
+ self.logDebug("Download Check triggered")
+ f = open(lastDownload, "rb")
+ content = f.read(read_size if read_size else -1)
+ f.close()
+ #produces encoding errors, better log to other file in the future?
+ #self.logDebug("Content: %s" % content)
+ for name, rule in rules.iteritems():
+ if type(rule) in (str, unicode):
+ if rule in content:
+ if delete:
+ remove(lastDownload)
+ return name
+ elif hasattr(rule, "search"):
+ m = rule.search(content)
+ if m:
+ if delete:
+ remove(lastDownload)
+ self.lastCheck = m
+ return name
+
+
+ def getPassword(self):
+ """ get the password the user provided in the package"""
+ password = self.pyfile.package().password
+ if not password: return ""
+ return password
+
+
+ def checkForSameFiles(self, starting=False):
+ """ checks if same file was/is downloaded within same package
+
+ :param starting: indicates that the current download is going to start
+ :raises SkipDownload:
+ """
+
+ pack = self.pyfile.package()
+
+ for pyfile in self.core.files.cache.values():
+ if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder:
+ if pyfile.status in (0, 12): #finished or downloading
+ raise SkipDownload(pyfile.pluginname)
+ elif pyfile.status in (
+ 5, 7) and starting: #a download is waiting/starting and was appenrently started before
+ raise SkipDownload(pyfile.pluginname)
+
+ download_folder = self.config['general']['download_folder']
+ location = safe_join(download_folder, pack.folder, self.pyfile.name)
+
+ if starting and self.config['download']['skip_existing'] and exists(location):
+ size = os.stat(location).st_size
+ if size >= self.pyfile.size:
+ raise SkipDownload("File exists.")
+
+ pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name)
+ if pyfile:
+ if exists(location):
+ raise SkipDownload(pyfile[0])
+
+ self.logDebug("File %s not skipped, because it does not exists." % self.pyfile.name)
+
+ def clean(self):
+ """ clean everything and remove references """
+ if hasattr(self, "pyfile"):
+ del self.pyfile
+ if hasattr(self, "req"):
+ self.req.close()
+ del self.req
+ if hasattr(self, "thread"):
+ del self.thread
+ if hasattr(self, "html"):
+ del self.html
diff --git a/pyload/plugins/README.md b/pyload/plugins/README.md
new file mode 100644
index 000000000..fa2a4c5b2
--- /dev/null
+++ b/pyload/plugins/README.md
@@ -0,0 +1,16 @@
+Licensing
+---------
+
+According to the terms of the GNU General Public License,
+pyload's plugins must be treated as an extension of the main program.
+This means the plugins must be released under the GPL or a GPL-compatible
+free software license, and that the terms of the GPL must be followed when
+those plugins are distributed.
+
+ * Any plugin published **without a license notice** is intend published under the **GNU GPLv3**.
+ * A different license can be used but it **must be GPL-compatible** and the license notice must be put in the plugin
+ file.
+ * Any plugin published **with a GPL incompatible license** will be rejected.
+ This includes *copyright all right reserved*.
+ * Is recommended to put the license notice at the top of the plugin file.
+ * Is recommended to **not** put the license notice when plugin is published under the GNU GPLv3.
diff --git a/module/plugins/crypter/__init__.py b/pyload/plugins/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/crypter/__init__.py
+++ b/pyload/plugins/__init__.py
diff --git a/pyload/plugins/accounts/AlldebridCom.py b/pyload/plugins/accounts/AlldebridCom.py
new file mode 100644
index 000000000..928e81fe5
--- /dev/null
+++ b/pyload/plugins/accounts/AlldebridCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+import xml.dom.minidom as dom
+
+from time import time
+from urllib import urlencode
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugins.Account import Account
+
+
+class AlldebridCom(Account):
+ __name__ = "AlldebridCom"
+ __type__ = "account"
+ __version__ = "0.22"
+
+ __description__ = """AllDebrid.com account plugin"""
+ __author_name__ = "Andy Voigt"
+ __author_mail__ = "spamsales@online.de"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("http://www.alldebrid.com/account/")
+ soup = BeautifulSoup(page)
+ #Try to parse expiration date directly from the control panel page (better accuracy)
+ try:
+ time_text = soup.find('div', attrs={'class': 'remaining_time_text'}).strong.string
+ self.logDebug("Account expires in: %s" % time_text)
+ p = re.compile('\d+')
+ exp_data = p.findall(time_text)
+ exp_time = time() + int(exp_data[0]) * 24 * 60 * 60 + int(
+ exp_data[1]) * 60 * 60 + (int(exp_data[2]) - 1) * 60
+ #Get expiration date from API
+ except:
+ data = self.getAccountData(user)
+ page = req.load("http://www.alldebrid.com/api.php?action=info_user&login=%s&pw=%s" % (user,
+ data['password']))
+ self.logDebug(page)
+ xml = dom.parseString(page)
+ exp_time = time() + int(xml.getElementsByTagName("date")[0].childNodes[0].nodeValue) * 24 * 60 * 60
+ account_info = {"validuntil": exp_time, "trafficleft": -1}
+ return account_info
+
+ def login(self, user, data, req):
+ urlparams = urlencode({'action': 'login', 'login_login': user, 'login_password': data['password']})
+ page = req.load("http://www.alldebrid.com/register/?%s" % urlparams)
+
+ if "This login doesn't exist" in page:
+ self.wrongPassword()
+
+ if "The password is not valid" in page:
+ self.wrongPassword()
+
+ if "Invalid captcha" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/BayfilesCom.py b/pyload/plugins/accounts/BayfilesCom.py
new file mode 100644
index 000000000..38537da0e
--- /dev/null
+++ b/pyload/plugins/accounts/BayfilesCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class BayfilesCom(Account):
+ __name__ = "BayfilesCom"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """Bayfiles.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ for _ in xrange(2):
+ response = json_loads(req.load("http://api.bayfiles.com/v1/account/info"))
+ self.logDebug(response)
+ if not response['error']:
+ break
+ self.logWarning(response['error'])
+ self.relogin(user)
+
+ return {"premium": bool(response['premium']), "trafficleft": -1,
+ "validuntil": response['expires'] if response['expires'] >= int(time()) else -1}
+
+ def login(self, user, data, req):
+ response = json_loads(req.load("http://api.bayfiles.com/v1/account/login/%s/%s" % (user, data['password'])))
+ self.logDebug(response)
+ if response['error']:
+ self.logError(response['error'])
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/BitshareCom.py b/pyload/plugins/accounts/BitshareCom.py
new file mode 100644
index 000000000..7a982aea5
--- /dev/null
+++ b/pyload/plugins/accounts/BitshareCom.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class BitshareCom(Account):
+ __name__ = "BitshareCom"
+ __type__ = "account"
+ __version__ = "0.12"
+
+ __description__ = """Bitshare account plugin"""
+ __author_name__ = "Paul King"
+ __author_mail__ = None
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://bitshare.com/mysettings.html")
+
+ if "\"http://bitshare.com/myupgrade.html\">Free" in page:
+ return {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ if not '<input type="checkbox" name="directdownload" checked="checked" />' in page:
+ self.logWarning(_("Activate direct Download in your Bitshare Account"))
+
+ return {"validuntil": -1, "trafficleft": -1, "premium": True}
+
+ def login(self, user, data, req):
+ page = req.load("http://bitshare.com/login.html",
+ post={"user": user, "password": data['password'], "submit": "Login"}, cookies=True)
+ if "login" in req.lastEffectiveURL:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/CramitIn.py b/pyload/plugins/accounts/CramitIn.py
new file mode 100644
index 000000000..5bf7a3141
--- /dev/null
+++ b/pyload/plugins/accounts/CramitIn.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class CramitIn(XFSPAccount):
+ __name__ = "CramitIn"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Cramit.in account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ MAIN_PAGE = "http://cramit.in/"
diff --git a/pyload/plugins/accounts/CyberlockerCh.py b/pyload/plugins/accounts/CyberlockerCh.py
new file mode 100644
index 000000000..94cc0d8c4
--- /dev/null
+++ b/pyload/plugins/accounts/CyberlockerCh.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+from pyload.plugins.internal.SimpleHoster import parseHtmlForm
+
+
+class CyberlockerCh(XFSPAccount):
+ __name__ = "CyberlockerCh"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Cyberlocker.ch account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ MAIN_PAGE = "http://cyberlocker.ch/"
+
+
+ def login(self, user, data, req):
+ html = req.load(self.MAIN_PAGE + 'login.html', decode=True)
+
+ action, inputs = parseHtmlForm('name="FL"', html)
+ if not inputs:
+ inputs = {"op": "login",
+ "redirect": self.MAIN_PAGE}
+
+ inputs.update({"login": user,
+ "password": data['password']})
+
+ # Without this a 403 Forbidden is returned
+ req.http.lastURL = self.MAIN_PAGE + 'login.html'
+ html = req.load(self.MAIN_PAGE, post=inputs, decode=True)
+
+ if 'Incorrect Login or Password' in html or '>Error<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/CzshareCom.py b/pyload/plugins/accounts/CzshareCom.py
new file mode 100644
index 000000000..584b9a3a2
--- /dev/null
+++ b/pyload/plugins/accounts/CzshareCom.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+import re
+
+from pyload.plugins.Account import Account
+
+
+class CzshareCom(Account):
+ __name__ = "CzshareCom"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __description__ = """Czshare.com account plugin, now Sdilej.cz"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([0-9 ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://sdilej.cz/prehled_kreditu/")
+
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ if m is None:
+ return {"validuntil": 0, "trafficleft": 0}
+ else:
+ credits = float(m.group(1).replace(' ', '').replace(',', '.'))
+ credits = credits * 1024 ** {'KiB': 0, 'MiB': 1, 'GiB': 2}[m.group(2)]
+ validuntil = mktime(strptime(m.group(3), '%d.%m.%y %H:%M'))
+ return {"validuntil": validuntil, "trafficleft": credits}
+
+ def login(self, user, data, req):
+ html = req.load('https://sdilej.cz/index.php', post={
+ "Prihlasit": "Prihlasit",
+ "login-password": data['password'],
+ "login-name": user
+ })
+
+ if '<div class="login' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/DebridItaliaCom.py b/pyload/plugins/accounts/DebridItaliaCom.py
new file mode 100644
index 000000000..cff0be018
--- /dev/null
+++ b/pyload/plugins/accounts/DebridItaliaCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.Account import Account
+
+
+class DebridItaliaCom(Account):
+ __name__ = "DebridItaliaCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Debriditalia.com account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ WALID_UNTIL_PATTERN = r"Premium valid till: (?P<D>[^|]+) \|"
+
+
+ def loadAccountInfo(self, user, req):
+ if 'Account premium not activated' in self.html:
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+ m = re.search(self.WALID_UNTIL_PATTERN, self.html)
+ if m:
+ validuntil = int(time.mktime(time.strptime(m.group('D'), "%d/%m/%Y %H:%M")))
+ return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
+ else:
+ self.logError('Unable to retrieve account information - Plugin may be out of date')
+
+ def login(self, user, data, req):
+ self.html = req.load("http://debriditalia.com/login.php",
+ get={"u": user, "p": data['password']})
+ if 'NO' in self.html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/DepositfilesCom.py b/pyload/plugins/accounts/DepositfilesCom.py
new file mode 100644
index 000000000..a17493cc1
--- /dev/null
+++ b/pyload/plugins/accounts/DepositfilesCom.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime
+
+from pyload.plugins.Account import Account
+
+
+class DepositfilesCom(Account):
+ __name__ = "DepositfilesCom"
+ __type__ = "account"
+ __version__ = "0.3"
+
+ __description__ = """Depositfiles.com account plugin"""
+ __author_name__ = ("mkaay", "stickell", "Walter Purcaro")
+ __author_mail__ = ("mkaay@mkaay.de", "l.stickell@yahoo.it", "vuolter@gmail.com")
+
+
+ def loadAccountInfo(self, user, req):
+ src = req.load("https://dfiles.eu/de/gold/")
+ validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", src).group(1)
+
+ validuntil = int(mktime(strptime(validuntil, "%Y-%m-%d %H:%M:%S")))
+
+ return {"validuntil": validuntil, "trafficleft": -1}
+
+ def login(self, user, data, req):
+ src = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"},
+ post={"login": user, "password": data['password']})
+ if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in src:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/EasybytezCom.py b/pyload/plugins/accounts/EasybytezCom.py
new file mode 100644
index 000000000..595e95ec4
--- /dev/null
+++ b/pyload/plugins/accounts/EasybytezCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime, gmtime
+
+from pyload.plugins.Account import Account
+from pyload.plugins.internal.SimpleHoster import parseHtmlForm
+from pyload.utils import parseFileSize
+
+
+class EasybytezCom(Account):
+ __name__ = "EasybytezCom"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __description__ = """EasyBytez.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ VALID_UNTIL_PATTERN = r'Premium account expire:</TD><TD><b>([^<]+)</b>'
+ TRAFFIC_LEFT_PATTERN = r'<TR><TD>Traffic available today:</TD><TD><b>(?P<S>[^<]+)</b>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.easybytez.com/?op=my_account", decode=True)
+
+ validuntil = trafficleft = None
+ premium = False
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ try:
+ self.logDebug("Expire date: " + m.group(1))
+ validuntil = mktime(strptime(m.group(1), "%d %B %Y"))
+ except Exception, e:
+ self.logError(e)
+ if validuntil > mktime(gmtime()):
+ premium = True
+ trafficleft = -1
+ else:
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = m.group(1)
+ if "Unlimited" in trafficleft:
+ trafficleft = -1
+ else:
+ trafficleft = parseFileSize(trafficleft) / 1024
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ html = req.load('http://www.easybytez.com/login.html', decode=True)
+ action, inputs = parseHtmlForm('name="FL"', html)
+ inputs.update({"login": user,
+ "password": data['password'],
+ "redirect": "http://www.easybytez.com/"})
+
+ html = req.load(action, post=inputs, decode=True)
+
+ if 'Incorrect Login or Password' in html or '>Error<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/EgoFilesCom.py b/pyload/plugins/accounts/EgoFilesCom.py
new file mode 100644
index 000000000..3886d053a
--- /dev/null
+++ b/pyload/plugins/accounts/EgoFilesCom.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.Account import Account
+from pyload.utils import parseFileSize
+
+
+class EgoFilesCom(Account):
+ __name__ = "EgoFilesCom"
+ __type__ = "account"
+ __version__ = "0.2"
+
+ __description__ = """Egofiles.com account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ PREMIUM_ACCOUNT_PATTERN = '<br/>\s*Premium: (?P<P>[^/]*) / Traffic left: (?P<T>[\d.]*) (?P<U>\w*)\s*\\n\s*<br/>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://egofiles.com")
+ if 'You are logged as a Free User' in html:
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+ m = re.search(self.PREMIUM_ACCOUNT_PATTERN, html)
+ if m:
+ validuntil = int(time.mktime(time.strptime(m.group('P'), "%Y-%m-%d %H:%M:%S")))
+ trafficleft = parseFileSize(m.group('T'), m.group('U')) / 1024
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+ else:
+ self.logError('Unable to retrieve account information - Plugin may be out of date')
+
+ def login(self, user, data, req):
+ # Set English language
+ req.load("https://egofiles.com/ajax/lang.php?lang=en", just_header=True)
+
+ html = req.load("http://egofiles.com/ajax/register.php",
+ post={"log": 1,
+ "loginV": user,
+ "passV": data['password']})
+ if 'Login successful' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/EuroshareEu.py b/pyload/plugins/accounts/EuroshareEu.py
new file mode 100644
index 000000000..d74d4526b
--- /dev/null
+++ b/pyload/plugins/accounts/EuroshareEu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+import re
+
+from pyload.plugins.Account import Account
+
+
+class EuroshareEu(Account):
+ __name__ = "EuroshareEu"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Euroshare.eu account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ self.relogin(user)
+ html = req.load("http://euroshare.eu/customer-zone/settings/")
+
+ m = re.search('id="input_expire_date" value="(\d+\.\d+\.\d+ \d+:\d+)"', html)
+ if m is None:
+ premium, validuntil = False, -1
+ else:
+ premium = True
+ validuntil = mktime(strptime(m.group(1), "%d.%m.%Y %H:%M"))
+
+ return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
+
+ def login(self, user, data, req):
+
+ html = req.load('http://euroshare.eu/customer-zone/login/', post={
+ "trvale": "1",
+ "login": user,
+ "password": data['password']
+ }, decode=True)
+
+ if u">Nesprávne prihlasovacie meno alebo heslo" in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FastixRu.py b/pyload/plugins/accounts/FastixRu.py
new file mode 100644
index 000000000..69840fa2c
--- /dev/null
+++ b/pyload/plugins/accounts/FastixRu.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class FastixRu(Account):
+ __name__ = "FastixRu"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Fastix account plugin"""
+ __author_name__ = "Massimo Rosamilia"
+ __author_mail__ = "max@spiritix.eu"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("http://fastix.ru/api_v2/?apikey=%s&sub=getaccountdetails" % (data['api']))
+ page = json_loads(page)
+ points = page['points']
+ kb = float(points)
+ kb = kb * 1024 ** 2 / 1000
+ if points > 0:
+ account_info = {"validuntil": -1, "trafficleft": kb}
+ else:
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+ return account_info
+
+ def login(self, user, data, req):
+ page = req.load("http://fastix.ru/api_v2/?sub=get_apikey&email=%s&password=%s" % (user, data['password']))
+ api = json_loads(page)
+ api = api['apikey']
+ data['api'] = api
+ if "error_code" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FastshareCz.py b/pyload/plugins/accounts/FastshareCz.py
new file mode 100644
index 000000000..6e86f60fa
--- /dev/null
+++ b/pyload/plugins/accounts/FastshareCz.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Account import Account
+from pyload.utils import parseFileSize
+
+
+class FastshareCz(Account):
+ __name__ = "FastshareCz"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """Fastshare.cz account plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ CREDIT_PATTERN = r'(?:Kredit|Credit)\s*</td>\s*<td[^>]*>([\d. \w]+)&nbsp;'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.fastshare.cz/user", decode=True)
+
+ m = re.search(self.CREDIT_PATTERN, html)
+ if m:
+ trafficleft = parseFileSize(m.group(1)) / 1024
+ premium = True if trafficleft else False
+ else:
+ trafficleft = None
+ premium = False
+
+ return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ req.load('http://www.fastshare.cz/login') # Do not remove or it will not login
+ html = req.load('http://www.fastshare.cz/sql.php', post={
+ "heslo": data['password'],
+ "login": user
+ }, decode=True)
+
+ if u'>Špatné uşivatelské jméno nebo heslo.<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/File4safeCom.py b/pyload/plugins/accounts/File4safeCom.py
new file mode 100644
index 000000000..4da721193
--- /dev/null
+++ b/pyload/plugins/accounts/File4safeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class File4safeCom(XFSPAccount):
+ __name__ = "File4safeCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """File4safe.com account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ MAIN_PAGE = "http://file4safe.com/"
+
+ LOGIN_FAIL_PATTERN = r'input_login'
+ PREMIUM_PATTERN = r'Extend Premium'
diff --git a/pyload/plugins/accounts/FilecloudIo.py b/pyload/plugins/accounts/FilecloudIo.py
new file mode 100644
index 000000000..dc764d218
--- /dev/null
+++ b/pyload/plugins/accounts/FilecloudIo.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class FilecloudIo(Account):
+ __name__ = "FilecloudIo"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """FilecloudIo account plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+ def loadAccountInfo(self, user, req):
+ # It looks like the first API request always fails, so we retry 5 times, it should work on the second try
+ for _ in xrange(5):
+ rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api",
+ post={"username": user, "password": self.accounts[user]['password']})
+ rep = json_loads(rep)
+ if rep['status'] == 'ok':
+ break
+ elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password':
+ self.logError("Wrong username or password")
+ return {"valid": False, "premium": False}
+ else:
+ return {"premium": False}
+
+ akey = rep['akey']
+ self.accounts[user]['akey'] = akey # Saved for hoster plugin
+ rep = req.load("http://api.filecloud.io/api-fetch_account_details.api",
+ post={"akey": akey})
+ rep = json_loads(rep)
+
+ if rep['is_premium'] == 1:
+ return {"validuntil": int(rep['premium_until']), "trafficleft": -1}
+ else:
+ return {"premium": False}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("secure.filecloud.io", "lang", "en")
+ html = req.load('https://secure.filecloud.io/user-login.html')
+
+ if not hasattr(self, "form_data"):
+ self.form_data = {}
+
+ self.form_data['username'] = user
+ self.form_data['password'] = data['password']
+
+ html = req.load('https://secure.filecloud.io/user-login_p.html',
+ post=self.form_data,
+ multipart=True)
+
+ self.logged_in = True if "you have successfully logged in - filecloud.io" in html else False
+ self.form_data = {}
diff --git a/pyload/plugins/accounts/FilefactoryCom.py b/pyload/plugins/accounts/FilefactoryCom.py
new file mode 100644
index 000000000..1e2115ac3
--- /dev/null
+++ b/pyload/plugins/accounts/FilefactoryCom.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pycurl import REFERER
+
+from pyload.plugins.Account import Account
+
+
+class FilefactoryCom(Account):
+ __name__ = "FilefactoryCom"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __description__ = """Filefactory.com account plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ VALID_UNTIL_PATTERN = r'Premium valid until: <strong>(?P<d>\d{1,2})\w{1,2} (?P<m>\w{3}), (?P<y>\d{4})</strong>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.filefactory.com/account/")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<d> \g<m> \g<y>', m.group(0))
+ validuntil = mktime(strptime(validuntil, "%d %b %Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+ def login(self, user, data, req):
+ req.http.c.setopt(REFERER, "http://www.filefactory.com/member/login.php")
+
+ html = req.load("http://www.filefactory.com/member/signin.php", post={
+ "loginEmail": user,
+ "loginPassword": data['password'],
+ "Submit": "Sign In"})
+
+ if req.lastEffectiveURL != "http://www.filefactory.com/account/":
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FilejungleCom.py b/pyload/plugins/accounts/FilejungleCom.py
new file mode 100644
index 000000000..ab52ffc04
--- /dev/null
+++ b/pyload/plugins/accounts/FilejungleCom.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+
+
+class FilejungleCom(Account):
+ __name__ = "FilejungleCom"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __description__ = """Filejungle.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ login_timeout = 60
+
+ URL = "http://filejungle.com/"
+ TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ [A-Za-z]+ \d+)<br'
+ LOGIN_FAILED_PATTERN = r'<span htmlfor="loginUser(Name|Password)" generated="true" class="fail_info">'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load(self.URL + "dashboard.php")
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = mktime(strptime(m.group(1), "%d %b %Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+ def login(self, user, data, req):
+ html = req.load(self.URL + "login.php", post={
+ "loginUserName": user,
+ "loginUserPassword": data['password'],
+ "loginFormSubmit": "Login",
+ "recaptcha_challenge_field": "",
+ "recaptcha_response_field": "",
+ "recaptcha_shortencode_field": ""})
+
+ if re.search(self.LOGIN_FAILED_PATTERN, html):
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FilerNet.py b/pyload/plugins/accounts/FilerNet.py
new file mode 100644
index 000000000..51c2e5d75
--- /dev/null
+++ b/pyload/plugins/accounts/FilerNet.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.Account import Account
+from pyload.utils import parseFileSize
+
+
+class FilerNet(Account):
+ __name__ = "FilerNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Filer.net account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ TOKEN_PATTERN = r'_csrf_token" value="([^"]+)" />'
+ WALID_UNTIL_PATTERN = r"Der Premium-Zugang ist gÃŒltig bis (.+)\.\s*</td>"
+ TRAFFIC_PATTERN = r'Traffic</th>\s*<td>([^<]+)</td>'
+ FREE_PATTERN = r'Account Status</th>\s*<td>\s*Free'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("https://filer.net/profile")
+
+ # Free user
+ if re.search(self.FREE_PATTERN, html):
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+ until = re.search(self.WALID_UNTIL_PATTERN, html)
+ traffic = re.search(self.TRAFFIC_PATTERN, html)
+ if until and traffic:
+ validuntil = int(time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S")))
+ trafficleft = parseFileSize(traffic.group(1)) / 1024
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+ else:
+ self.logError('Unable to retrieve account information - Plugin may be out of date')
+ return {"premium": False, "validuntil": None, "trafficleft": None}
+
+ def login(self, user, data, req):
+ html = req.load("https://filer.net/login")
+ token = re.search(self.TOKEN_PATTERN, html).group(1)
+ html = req.load("https://filer.net/login_check",
+ post={"_username": user, "_password": data['password'],
+ "_remember_me": "on", "_csrf_token": token, "_target_path": "https://filer.net/"})
+ if 'Logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FilerioCom.py b/pyload/plugins/accounts/FilerioCom.py
new file mode 100644
index 000000000..0a8bc10cd
--- /dev/null
+++ b/pyload/plugins/accounts/FilerioCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class FilerioCom(XFSPAccount):
+ __name__ = "FilerioCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """FileRio.in account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ MAIN_PAGE = "http://filerio.in/"
diff --git a/pyload/plugins/accounts/FilesMailRu.py b/pyload/plugins/accounts/FilesMailRu.py
new file mode 100644
index 000000000..a3ef4b348
--- /dev/null
+++ b/pyload/plugins/accounts/FilesMailRu.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class FilesMailRu(Account):
+ __name__ = "FilesMailRu"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Filesmail.ru account plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def loadAccountInfo(self, user, req):
+ return {"validuntil": None, "trafficleft": None}
+
+ def login(self, user, data, req):
+ user, domain = user.split("@")
+
+ page = req.load("http://swa.mail.ru/cgi-bin/auth", None,
+ {"Domain": domain, "Login": user, "Password": data['password'],
+ "Page": "http://files.mail.ru/"}, cookies=True)
+
+ if "НеверМПе ОЌя пПльзПвателя ОлО парПль" in page: # @TODO seems not to work
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FileserveCom.py b/pyload/plugins/accounts/FileserveCom.py
new file mode 100644
index 000000000..99f4430c2
--- /dev/null
+++ b/pyload/plugins/accounts/FileserveCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class FileserveCom(Account):
+ __name__ = "FileserveCom"
+ __type__ = "account"
+ __version__ = "0.2"
+
+ __description__ = """Fileserve.com account plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+
+ page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(page)
+
+ if res['type'] == "premium":
+ validuntil = mktime(strptime(res['expireTime'], "%Y-%m-%d %H:%M:%S"))
+ return {"trafficleft": res['traffic'], "validuntil": validuntil}
+ else:
+ return {"premium": False, "trafficleft": None, "validuntil": None}
+
+ def login(self, user, data, req):
+ page = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'],
+ "submit": "Submit+Query"})
+ res = json_loads(page)
+
+ if not res['type']:
+ self.wrongPassword()
+
+ #login at fileserv page
+ req.load("http://www.fileserve.com/login.php",
+ post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked",
+ "loginFormSubmit": "Login"})
diff --git a/pyload/plugins/accounts/FourSharedCom.py b/pyload/plugins/accounts/FourSharedCom.py
new file mode 100644
index 000000000..e4df62956
--- /dev/null
+++ b/pyload/plugins/accounts/FourSharedCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class FourSharedCom(Account):
+ __name__ = "FourSharedCom"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """FourShared.com account plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ def loadAccountInfo(self, user, req):
+ # Free mode only for now
+ return {"premium": False}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("4shared.com", "4langcookie", "en")
+ response = req.load('http://www.4shared.com/web/login',
+ post={"login": user,
+ "password": data['password'],
+ "remember": "on",
+ "_remember": "on",
+ "returnTo": "http://www.4shared.com/account/home.jsp"})
+
+ if 'Please log in to access your 4shared account' in response:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FreakshareCom.py b/pyload/plugins/accounts/FreakshareCom.py
new file mode 100644
index 000000000..2484a2da1
--- /dev/null
+++ b/pyload/plugins/accounts/FreakshareCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import strptime, mktime
+
+from pyload.plugins.Account import Account
+
+
+class FreakshareCom(Account):
+ __name__ = "FreakshareCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Freakshare.com account plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://freakshare.com/")
+
+ validuntil = r"ltig bis:</td>\s*<td><b>([0-9 \-:.]+)</b></td>"
+ validuntil = re.search(validuntil, page, re.MULTILINE)
+ validuntil = validuntil.group(1).strip()
+ validuntil = mktime(strptime(validuntil, "%d.%m.%Y - %H:%M"))
+
+ traffic = r"Traffic verbleibend:</td>\s*<td>([^<]+)"
+ traffic = re.search(traffic, page, re.MULTILINE)
+ traffic = traffic.group(1).strip()
+ traffic = self.parseTraffic(traffic)
+
+ return {"validuntil": validuntil, "trafficleft": traffic}
+
+ def login(self, user, data, req):
+ page = req.load("http://freakshare.com/login.html", None,
+ {"submit": "Login", "user": user, "pass": data['password']}, cookies=True)
+
+ if "Falsche Logindaten!" in page or "Wrong Username or Password!" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/FreeWayMe.py b/pyload/plugins/accounts/FreeWayMe.py
new file mode 100644
index 000000000..5106067a9
--- /dev/null
+++ b/pyload/plugins/accounts/FreeWayMe.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class FreeWayMe(Account):
+ __name__ = "FreeWayMe"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __description__ = """FreeWayMe account plugin"""
+ __author_name__ = "Nicolas Giese"
+ __author_mail__ = "james@free-way.me"
+
+
+ def loadAccountInfo(self, user, req):
+ status = self.getAccountStatus(user, req)
+ if not status:
+ return False
+ self.logDebug(status)
+
+ account_info = {"validuntil": -1, "premium": False}
+ if status['premium'] == "Free":
+ account_info['trafficleft'] = int(status['guthaben']) * 1024
+ elif status['premium'] == "Spender":
+ account_info['trafficleft'] = -1
+ elif status['premium'] == "Flatrate":
+ account_info = {"validuntil": int(status['Flatrate']),
+ "trafficleft": -1,
+ "premium": True}
+
+ return account_info
+
+ def getpw(self, user):
+ return self.accounts[user]['password']
+
+ def login(self, user, data, req):
+ status = self.getAccountStatus(user, req)
+
+ # Check if user and password are valid
+ if not status:
+ self.wrongPassword()
+
+ def getAccountStatus(self, user, req):
+ answer = req.load("https://www.free-way.me/ajax/jd.php",
+ get={"id": 4, "user": user, "pass": self.accounts[user]['password']})
+ self.logDebug("login: %s" % answer)
+ if answer == "Invalid login":
+ self.wrongPassword()
+ return False
+ return json_loads(answer)
diff --git a/pyload/plugins/accounts/FshareVn.py b/pyload/plugins/accounts/FshareVn.py
new file mode 100644
index 000000000..3d664629b
--- /dev/null
+++ b/pyload/plugins/accounts/FshareVn.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+from pycurl import REFERER
+import re
+
+from pyload.plugins.Account import Account
+
+
+class FshareVn(Account):
+ __name__ = "FshareVn"
+ __type__ = "account"
+ __version__ = "0.07"
+
+ __description__ = """Fshare.vn account plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ VALID_UNTIL_PATTERN = ur'<dt>Thời hạn dùng:</dt>\s*<dd>([^<]+)</dd>'
+ LIFETIME_PATTERN = ur'<dt>Lần đăng nhập trước:</dt>\s*<dd>[^<]+</dd>'
+ TRAFFIC_LEFT_PATTERN = ur'<dt>Tổng Dung Lượng Tài Khoản</dt>\s*<dd[^>]*>([0-9.]+) ([kKMG])B</dd>'
+ DIRECT_DOWNLOAD_PATTERN = ur'<input type="checkbox"\s*([^=>]*)[^>]*/>Kích hoạt download trực tiếp</dt>'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.fshare.vn/account_info.php", decode=True)
+
+ if re.search(self.LIFETIME_PATTERN, html):
+ self.logDebug("Lifetime membership detected")
+ trafficleft = self.getTrafficLeft()
+ return {"validuntil": -1, "trafficleft": trafficleft, "premium": True}
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ premium = True
+ validuntil = mktime(strptime(m.group(1), '%I:%M:%S %p %d-%m-%Y'))
+ trafficleft = self.getTrafficLeft()
+ else:
+ premium = False
+ validuntil = None
+ trafficleft = None
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ req.http.c.setopt(REFERER, "https://www.fshare.vn/login.php")
+
+ html = req.load('https://www.fshare.vn/login.php', post={
+ "login_password": data['password'],
+ "login_useremail": user,
+ "url_refe": "http://www.fshare.vn/index.php"
+ }, referer=True, decode=True)
+
+ if not re.search(r'<img\s+alt="VIP"', html):
+ self.wrongPassword()
+
+ def getTrafficLeft(self):
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ return float(m.group(1)) * 1024 ** {'k': 0, 'K': 0, 'M': 1, 'G': 2}[m.group(2)] if m else 0
diff --git a/pyload/plugins/accounts/Ftp.py b/pyload/plugins/accounts/Ftp.py
new file mode 100644
index 000000000..23b637050
--- /dev/null
+++ b/pyload/plugins/accounts/Ftp.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class Ftp(Account):
+ __name__ = "Ftp"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Ftp dummy account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ login_timeout = info_threshold = -1 #: Unlimited
diff --git a/pyload/plugins/accounts/HellshareCz.py b/pyload/plugins/accounts/HellshareCz.py
new file mode 100644
index 000000000..ae3f974a1
--- /dev/null
+++ b/pyload/plugins/accounts/HellshareCz.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.plugins.Account import Account
+
+
+class HellshareCz(Account):
+ __name__ = "HellshareCz"
+ __type__ = "account"
+ __version__ = "0.14"
+
+ __description__ = """Hellshare.cz account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ CREDIT_LEFT_PATTERN = r'<div class="credit-link">\s*<table>\s*<tr>\s*<th>(\d+|\d\d\.\d\d\.)</th>'
+
+
+ def loadAccountInfo(self, user, req):
+ self.relogin(user)
+ html = req.load("http://www.hellshare.com/")
+
+ m = re.search(self.CREDIT_LEFT_PATTERN, html)
+ if m is None:
+ trafficleft = None
+ validuntil = None
+ premium = False
+ else:
+ credit = m.group(1)
+ premium = True
+ try:
+ if "." in credit:
+ #Time-based account
+ vt = [int(x) for x in credit.split('.')[:2]]
+ lt = time.localtime()
+ year = lt.tm_year + int(vt[1] < lt.tm_mon or (vt[1] == lt.tm_mon and vt[0] < lt.tm_mday))
+ validuntil = time.mktime(time.strptime("%s%d 23:59:59" % (credit, year), "%d.%m.%Y %H:%M:%S"))
+ trafficleft = -1
+ else:
+ #Traffic-based account
+ trafficleft = int(credit) * 1024
+ validuntil = -1
+ except Exception, e:
+ self.logError('Unable to parse credit info', e)
+ validuntil = -1
+ trafficleft = -1
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ html = req.load('http://www.hellshare.com/')
+ if req.lastEffectiveURL != 'http://www.hellshare.com/':
+ #Switch to English
+ self.logDebug('Switch lang - URL: %s' % req.lastEffectiveURL)
+ json = req.load("%s?do=locRouter-show" % req.lastEffectiveURL)
+ hash = re.search(r"(--[0-9a-f]+-)", json).group(1)
+ self.logDebug('Switch lang - HASH: %s' % hash)
+ html = req.load('http://www.hellshare.com/%s/' % hash)
+
+ if re.search(self.CREDIT_LEFT_PATTERN, html):
+ self.logDebug('Already logged in')
+ return
+
+ html = req.load('http://www.hellshare.com/login?do=loginForm-submit', post={
+ "login": "Log in",
+ "password": data['password'],
+ "username": user,
+ "perm_login": "on"
+ })
+
+ if "<p>You input a wrong user name or wrong password</p>" in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/HotfileCom.py b/pyload/plugins/accounts/HotfileCom.py
new file mode 100644
index 000000000..ec164d14f
--- /dev/null
+++ b/pyload/plugins/accounts/HotfileCom.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+from time import strptime, mktime
+import hashlib
+
+from pyload.plugins.Account import Account
+
+
+class HotfileCom(Account):
+ __name__ = "HotfileCom"
+ __type__ = "account"
+ __version__ = "0.2"
+
+ __description__ = """Hotfile.com account plugin"""
+ __author_name__ = ("mkaay", "JoKoT3")
+ __author_mail__ = ("mkaay@mkaay.de", "jokot3@gmail.com")
+
+
+ def loadAccountInfo(self, user, req):
+ resp = self.apiCall("getuserinfo", user=user)
+ if resp.startswith("."):
+ self.core.debug("HotfileCom API Error: %s" % resp)
+ raise Exception
+ info = {}
+ for p in resp.split("&"):
+ key, value = p.split("=")
+ info[key] = value
+
+ if info['is_premium'] == '1':
+ info['premium_until'] = info['premium_until'].replace("T", " ")
+ zone = info['premium_until'][19:]
+ info['premium_until'] = info['premium_until'][:19]
+ zone = int(zone[:3])
+
+ validuntil = int(mktime(strptime(info['premium_until'], "%Y-%m-%d %H:%M:%S"))) + (zone * 60 * 60)
+ tmp = {"validuntil": validuntil, "trafficleft": -1, "premium": True}
+
+ elif info['is_premium'] == '0':
+ tmp = {"premium": False}
+
+ return tmp
+
+ def apiCall(self, method, post={}, user=None):
+ if user:
+ data = self.getAccountData(user)
+ else:
+ user, data = self.selectAccount()
+
+ req = self.getAccountRequest(user)
+
+ digest = req.load("http://api.hotfile.com/", post={"action": "getdigest"})
+ h = hashlib.md5()
+ h.update(data['password'])
+ hp = h.hexdigest()
+ h = hashlib.md5()
+ h.update(hp)
+ h.update(digest)
+ pwhash = h.hexdigest()
+
+ post.update({"action": method})
+ post.update({"username": user, "passwordmd5dig": pwhash, "digest": digest})
+ resp = req.load("http://api.hotfile.com/", post=post)
+ req.close()
+ return resp
+
+ def login(self, user, data, req):
+ cj = self.getAccountCookies(user)
+ cj.setCookie("hotfile.com", "lang", "en")
+ req.load("http://hotfile.com/", cookies=True)
+ page = req.load("http://hotfile.com/login.php", post={"returnto": "/", "user": user, "pass": data['password']},
+ cookies=True)
+
+ if "Bad username/password" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/Http.py b/pyload/plugins/accounts/Http.py
new file mode 100644
index 000000000..eda087c91
--- /dev/null
+++ b/pyload/plugins/accounts/Http.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class Http(Account):
+ __name__ = "Http"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Http dummy account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ login_timeout = info_threshold = -1 #: Unlimited
diff --git a/pyload/plugins/accounts/LetitbitNet.py b/pyload/plugins/accounts/LetitbitNet.py
new file mode 100644
index 000000000..bce97d378
--- /dev/null
+++ b/pyload/plugins/accounts/LetitbitNet.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+# from pyload.utils import json_loads, json_dumps
+
+
+class LetitbitNet(Account):
+ __name__ = "LetitbitNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Letitbit.net account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def loadAccountInfo(self, user, req):
+ ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ##
+ # api_key = self.accounts[user]['password']
+ # json_data = [api_key, ['key/info']]
+ # api_rep = req.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
+ # self.logDebug('API Key Info: ' + api_rep)
+ # api_rep = json_loads(api_rep)
+ #
+ # if api_rep['status'] == 'FAIL':
+ # self.logWarning(api_rep['data'])
+ # return {'valid': False, 'premium': False}
+
+ return {"premium": True}
+
+ def login(self, user, data, req):
+ # API_KEY is the username and the PREMIUM_KEY is the password
+ self.logInfo('You must use your API KEY as username and the PREMIUM KEY as password.')
diff --git a/pyload/plugins/accounts/LinksnappyCom.py b/pyload/plugins/accounts/LinksnappyCom.py
new file mode 100644
index 000000000..35fc881b0
--- /dev/null
+++ b/pyload/plugins/accounts/LinksnappyCom.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+from hashlib import md5
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class LinksnappyCom(Account):
+ __name__ = "LinksnappyCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Linksnappy.com account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ r = req.load('http://gen.linksnappy.com/lseAPI.php',
+ get={'act': 'USERDETAILS', 'username': user, 'password': md5(data['password']).hexdigest()})
+ self.logDebug("JSON data: " + r)
+ j = json_loads(r)
+
+ if j['error']:
+ return {"premium": False}
+
+ validuntil = j['return']['expire']
+ if validuntil == 'lifetime':
+ validuntil = -1
+ elif validuntil == 'expired':
+ return {"premium": False}
+ else:
+ validuntil = float(validuntil)
+
+ if 'trafficleft' not in j['return'] or isinstance(j['return']['trafficleft'], str):
+ trafficleft = -1
+ else:
+ trafficleft = int(j['return']['trafficleft']) * 1024
+
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+ def login(self, user, data, req):
+ r = req.load('http://gen.linksnappy.com/lseAPI.php',
+ get={'act': 'USERDETAILS', 'username': user, 'password': md5(data['password']).hexdigest()})
+
+ if 'Invalid Account Details' in r:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/MegaDebridEu.py b/pyload/plugins/accounts/MegaDebridEu.py
new file mode 100644
index 000000000..5ba8d3577
--- /dev/null
+++ b/pyload/plugins/accounts/MegaDebridEu.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class MegaDebridEu(Account):
+ __name__ = "MegaDebridEu"
+ __type__ = "account"
+ __version__ = "0.2"
+
+ __description__ = """mega-debrid.eu account plugin"""
+ __author_name__ = "D.Ducatel"
+ __author_mail__ = "dducatel@je-geek.fr"
+
+ # Define the base URL of MegaDebrid api
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ jsonResponse = req.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ response = json_loads(jsonResponse)
+
+ if response['response_code'] == "ok":
+ return {"premium": True, "validuntil": float(response['vip_end']), "status": True}
+ else:
+ self.logError(response)
+ return {"status": False, "premium": False}
+
+ def login(self, user, data, req):
+ jsonResponse = req.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ response = json_loads(jsonResponse)
+ if response['response_code'] != "ok":
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/MegasharesCom.py b/pyload/plugins/accounts/MegasharesCom.py
new file mode 100644
index 000000000..2032d0578
--- /dev/null
+++ b/pyload/plugins/accounts/MegasharesCom.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+
+
+class MegasharesCom(Account):
+ __name__ = "MegasharesCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Megashares.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ VALID_UNTIL_PATTERN = r'<p class="premium_info_box">Period Ends: (\w{3} \d{1,2}, \d{4})</p>'
+
+
+ def loadAccountInfo(self, user, req):
+ #self.relogin(user)
+ html = req.load("http://d01.megashares.com/myms.php", decode=True)
+
+ premium = False if '>Premium Upgrade<' in html else True
+
+ validuntil = trafficleft = -1
+ try:
+ timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1)
+ self.logDebug(timestr)
+ validuntil = mktime(strptime(timestr, "%b %d, %Y"))
+ except Exception, e:
+ self.logError(e)
+
+ return {"validuntil": validuntil, "trafficleft": -1, "premium": premium}
+
+ def login(self, user, data, req):
+ html = req.load('http://d01.megashares.com/myms_login.php', post={
+ "httpref": "",
+ "myms_login": "Login",
+ "mymslogin_name": user,
+ "mymspassword": data['password']
+ }, decode=True)
+
+ if not '<span class="b ml">%s</span>' % user in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/MovReelCom.py b/pyload/plugins/accounts/MovReelCom.py
new file mode 100644
index 000000000..0f80b1aa8
--- /dev/null
+++ b/pyload/plugins/accounts/MovReelCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class MovReelCom(XFSPAccount):
+ __name__ = "MovReelCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Movreel.com account plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ login_timeout = 60
+ info_threshold = 30
+
+ MAIN_PAGE = "http://movreel.com/"
+
+ TRAFFIC_LEFT_PATTERN = r'Traffic.*?<b>([^<]+)</b>'
+ LOGIN_FAIL_PATTERN = r'<b[^>]*>Incorrect Login or Password</b><br>'
diff --git a/pyload/plugins/accounts/MultishareCz.py b/pyload/plugins/accounts/MultishareCz.py
new file mode 100644
index 000000000..7e72ff513
--- /dev/null
+++ b/pyload/plugins/accounts/MultishareCz.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+#from time import mktime, strptime
+#from pycurl import REFERER
+import re
+from pyload.utils import parseFileSize
+
+
+class MultishareCz(Account):
+ __name__ = "MultishareCz"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Multishare.cz account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[0-9,]+)&nbsp;(?P<U>\w+)</strong>'
+ ACCOUNT_INFO_PATTERN = r'<input type="hidden" id="(u_ID|u_hash)" name="[^"]*" value="([^"]+)">'
+
+
+ def loadAccountInfo(self, user, req):
+ #self.relogin(user)
+ html = req.load("http://www.multishare.cz/profil/", decode=True)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ trafficleft = parseFileSize(m.group('S'), m.group('U')) / 1024 if m else 0
+ self.premium = True if trafficleft else False
+
+ html = req.load("http://www.multishare.cz/", decode=True)
+ mms_info = dict(re.findall(self.ACCOUNT_INFO_PATTERN, html))
+
+ return dict(mms_info, **{"validuntil": -1, "trafficleft": trafficleft})
+
+ def login(self, user, data, req):
+ html = req.load('http://www.multishare.cz/html/prihlaseni_process.php', post={
+ "akce": "Přihlásit",
+ "heslo": data['password'],
+ "jmeno": user
+ }, decode=True)
+
+ if '<div class="akce-chyba akce">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/MyfastfileCom.py b/pyload/plugins/accounts/MyfastfileCom.py
new file mode 100644
index 000000000..b5d35d326
--- /dev/null
+++ b/pyload/plugins/accounts/MyfastfileCom.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Account):
+ __name__ = "MyfastfileCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """Myfastfile.com account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def loadAccountInfo(self, user, req):
+ if 'days_left' in self.json_data:
+ validuntil = int(time() + self.json_data['days_left'] * 24 * 60 * 60)
+ return {"premium": True, "validuntil": validuntil, "trafficleft": -1}
+ else:
+ self.logError('Unable to get account information')
+
+ def login(self, user, data, req):
+ # Password to use is the API-Password written in http://myfastfile.com/myaccount
+ html = req.load("http://myfastfile.com/api.php",
+ get={"user": user, "pass": data['password']})
+ self.logDebug('JSON data: ' + html)
+ self.json_data = json_loads(html)
+ if self.json_data['status'] != 'ok':
+ self.logError('Invalid login. The password to use is the API-Password you find in your "My Account" page')
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/NetloadIn.py b/pyload/plugins/accounts/NetloadIn.py
new file mode 100644
index 000000000..988affb51
--- /dev/null
+++ b/pyload/plugins/accounts/NetloadIn.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+
+from pyload.plugins.Account import Account
+
+
+class NetloadIn(Account):
+ __name__ = "NetloadIn"
+ __type__ = "account"
+ __version__ = "0.22"
+
+ __description__ = """Netload.in account plugin"""
+ __author_name__ = ("RaNaN", "CryNickSystems")
+ __author_mail__ = ("RaNaN@pyload.org", "webmaster@pcProfil.de")
+
+
+ def loadAccountInfo(self, user, req):
+ page = req.load("http://netload.in/index.php?id=2&lang=de")
+ left = r">(\d+) (Tag|Tage), (\d+) Stunden<"
+ left = re.search(left, page)
+ if left:
+ validuntil = time() + int(left.group(1)) * 24 * 60 * 60 + int(left.group(3)) * 60 * 60
+ trafficleft = -1
+ premium = True
+ else:
+ validuntil = None
+ premium = False
+ trafficleft = None
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ page = req.load("http://netload.in/index.php", None,
+ {"txtuser": user, "txtpass": data['password'], "txtcheck": "login", "txtlogin": "Login"},
+ cookies=True)
+ if "password or it might be invalid!" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/OboomCom.py b/pyload/plugins/accounts/OboomCom.py
new file mode 100644
index 000000000..b281a289a
--- /dev/null
+++ b/pyload/plugins/accounts/OboomCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from beaker.crypto.pbkdf2 import PBKDF2
+
+from pyload.utils import json_loads
+from pyload.plugins.Account import Account
+
+
+class OboomCom(Account):
+ __name__ = "OboomCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Oboom.com account plugin"""
+ __author_name__ = "stanley"
+ __author_mail__ = "stanley.foerster@gmail.com"
+
+
+ def loadAccountData(self, user, req):
+ passwd = self.getAccountData(user)['password']
+ salt = passwd[::-1]
+ pbkdf2 = PBKDF2(passwd, salt, 1000).hexread(16)
+ result = json_loads(req.load("https://www.oboom.com/1.0/login", get={"auth": user, "pass": pbkdf2}))
+ if not result[0] == 200:
+ self.logWarning("Failed to log in: %s" % result[1])
+ self.wrongPassword()
+ return result[1]
+
+ def loadAccountInfo(self, name, req):
+ accountData = self.loadAccountData(name, req)
+ userData = accountData['user']
+
+ if "premium_unix" in userData:
+ validUntilUtc = int(userData['premium_unix'])
+ if validUntilUtc > int(time.time()):
+ premium = True
+ validUntil = validUntilUtc
+ traffic = userData['traffic']
+ trafficLeft = traffic['current']
+ maxTraffic = traffic['max']
+ session = accountData['session']
+ return {"premium": premium,
+ "validuntil": validUntil,
+ "trafficleft": trafficLeft / 1024,
+ "maxtraffic": maxTraffic / 1024,
+ "session": session
+ }
+ return {"premium": False, "validuntil": -1}
+
+ def login(self, user, data, req):
+ self.loadAccountData(user, req)
diff --git a/pyload/plugins/accounts/OneFichierCom.py b/pyload/plugins/accounts/OneFichierCom.py
new file mode 100644
index 000000000..36899e2a5
--- /dev/null
+++ b/pyload/plugins/accounts/OneFichierCom.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import strptime, mktime
+from pycurl import REFERER
+
+from pyload.plugins.Account import Account
+
+
+class OneFichierCom(Account):
+ __name__ = "OneFichierCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """1fichier.com account plugin"""
+ __author_name__ = "Elrick69"
+ __author_mail__ = "elrick69[AT]rocketmail[DOT]com"
+
+ VALID_UNTIL_PATTERN = r'You are a premium user until (?P<d>\d{2})/(?P<m>\d{2})/(?P<y>\d{4})'
+
+
+ def loadAccountInfo(self, user, req):
+
+ html = req.load("http://1fichier.com/console/abo.pl")
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+
+ if m:
+ premium = True
+ validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<d>/\g<m>/\g<y>', m.group(0))
+ validuntil = int(mktime(strptime(validuntil, "%d/%m/%Y")))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+ def login(self, user, data, req):
+
+ req.http.c.setopt(REFERER, "http://1fichier.com/login.pl?lg=en")
+
+ html = req.load("http://1fichier.com/login.pl?lg=en", post={
+ "mail": user,
+ "pass": data['password'],
+ "Login": "Login"})
+
+ if r'<div class="error_message">Invalid username or password.</div>' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/OverLoadMe.py b/pyload/plugins/accounts/OverLoadMe.py
new file mode 100644
index 000000000..18a3db645
--- /dev/null
+++ b/pyload/plugins/accounts/OverLoadMe.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class OverLoadMe(Account):
+ __name__ = "OverLoadMe"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Over-Load.me account plugin"""
+ __author_name__ = "marley"
+ __author_mail__ = "marley@over-load.me"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("https://api.over-load.me/account.php", get={"user": user, "auth": data['password']}).strip()
+ data = json_loads(page)
+
+ # Check for premium
+ if data['membership'] == "Free":
+ return {"premium": False}
+
+ account_info = {"validuntil": data['expirationunix'], "trafficleft": -1}
+ return account_info
+
+ def login(self, user, data, req):
+ jsondata = req.load("https://api.over-load.me/account.php",
+ get={"user": user, "auth": data['password']}).strip()
+ data = json_loads(jsondata)
+
+ if data['err'] == 1:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/PremiumTo.py b/pyload/plugins/accounts/PremiumTo.py
new file mode 100644
index 000000000..3b757bdf2
--- /dev/null
+++ b/pyload/plugins/accounts/PremiumTo.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class PremiumTo(Account):
+ __name__ = "PremiumTo"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __description__ = """Premium.to account plugin"""
+ __author_name__ = ("RaNaN", "zoidberg", "stickell")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+ def loadAccountInfo(self, user, req):
+ api_r = req.load("http://premium.to/api/straffic.php",
+ get={'username': self.username, 'password': self.password})
+ traffic = sum(map(int, api_r.split(';')))
+
+ return {"trafficleft": int(traffic) / 1024, "validuntil": -1}
+
+ def login(self, user, data, req):
+ self.username = user
+ self.password = data['password']
+ authcode = req.load("http://premium.to/api/getauthcode.php?username=%s&password=%s" % (
+ user, self.password)).strip()
+
+ if "wrong username" in authcode:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/PremiumizeMe.py b/pyload/plugins/accounts/PremiumizeMe.py
new file mode 100644
index 000000000..ecb03afda
--- /dev/null
+++ b/pyload/plugins/accounts/PremiumizeMe.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+from pyload.utils import json_loads
+
+
+class PremiumizeMe(Account):
+ __name__ = "PremiumizeMe"
+ __type__ = "account"
+ __version__ = "0.11"
+
+ __description__ = """Premiumize.me account plugin"""
+ __author_name__ = "Florian Franzen"
+ __author_mail__ = "FlorianFranzen@gmail.com"
+
+
+ def loadAccountInfo(self, user, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+ self.logDebug(status)
+
+ # Parse account info
+ account_info = {"validuntil": float(status['result']['expires']),
+ "trafficleft": max(0, status['result']['trafficleft_bytes'] / 1024)}
+
+ if status['result']['type'] == 'free':
+ account_info['premium'] = False
+
+ return account_info
+
+ def login(self, user, data, req):
+ # Get user data from premiumize.me
+ status = self.getAccountStatus(user, req)
+
+ # Check if user and password are valid
+ if status['status'] != 200:
+ self.wrongPassword()
+
+ def getAccountStatus(self, user, req):
+ # Use premiumize.me API v1 (see https://secure.premiumize.me/?show=api)
+ # to retrieve account info and return the parsed json answer
+ answer = req.load(
+ "https://api.premiumize.me/pm-api/v1.php?method=accountstatus&params[login]=%s&params[pass]=%s" % (
+ user, self.accounts[user]['password']))
+ return json_loads(answer)
diff --git a/pyload/plugins/accounts/QuickshareCz.py b/pyload/plugins/accounts/QuickshareCz.py
new file mode 100644
index 000000000..6abc02b1c
--- /dev/null
+++ b/pyload/plugins/accounts/QuickshareCz.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Account import Account
+from pyload.utils import parseFileSize
+
+
+class QuickshareCz(Account):
+ __name__ = "QuickshareCz"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Quickshare.cz account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.quickshare.cz/premium", decode=True)
+
+ m = re.search(r'Stav kreditu: <strong>(.+?)</strong>', html)
+ if m:
+ trafficleft = parseFileSize(m.group(1)) / 1024
+ premium = True if trafficleft else False
+ else:
+ trafficleft = None
+ premium = False
+
+ return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ html = req.load('http://www.quickshare.cz/html/prihlaseni_process.php', post={
+ "akce": u'Přihlásit',
+ "heslo": data['password'],
+ "jmeno": user
+ }, decode=True)
+
+ if u'>TakovÜ uşivatel neexistuje.<' in html or u'>Špatné heslo.<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/RPNetBiz.py b/pyload/plugins/accounts/RPNetBiz.py
new file mode 100644
index 000000000..c2b7cad21
--- /dev/null
+++ b/pyload/plugins/accounts/RPNetBiz.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Account):
+ __name__ = "RPNetBiz"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """RPNet.biz account plugin"""
+ __author_name__ = "Dman"
+ __author_mail__ = "dmanugm@gmail.com"
+
+
+ def loadAccountInfo(self, user, req):
+ # Get account information from rpnet.biz
+ response = self.getAccountStatus(user, req)
+ try:
+ if response['accountInfo']['isPremium']:
+ # Parse account info. Change the trafficleft later to support per host info.
+ account_info = {"validuntil": int(response['accountInfo']['premiumExpiry']),
+ "trafficleft": -1, "premium": True}
+ else:
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ except KeyError:
+ #handle wrong password exception
+ account_info = {"validuntil": None, "trafficleft": None, "premium": False}
+
+ return account_info
+
+ def login(self, user, data, req):
+ # Get account information from rpnet.biz
+ response = self.getAccountStatus(user, req)
+
+ # If we have an error in the response, we have wrong login information
+ if 'error' in response:
+ self.wrongPassword()
+
+ def getAccountStatus(self, user, req):
+ # Using the rpnet API, check if valid premium account
+ response = req.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": self.accounts[user]['password'],
+ "action": "showAccountInformation"})
+ self.logDebug("JSON data: %s" % response)
+
+ return json_loads(response)
diff --git a/pyload/plugins/accounts/RapidgatorNet.py b/pyload/plugins/accounts/RapidgatorNet.py
new file mode 100644
index 000000000..c8f129c5b
--- /dev/null
+++ b/pyload/plugins/accounts/RapidgatorNet.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class RapidgatorNet(Account):
+ __name__ = "RapidgatorNet"
+ __type__ = "account"
+ __version__ = "0.04"
+
+ __description__ = """Rapidgator.net account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ API_URL = 'http://rapidgator.net/api/user'
+
+
+ def loadAccountInfo(self, user, req):
+ try:
+ sid = self.getAccountData(user).get('SID')
+ assert sid
+
+ json = req.load("%s/info?sid=%s" % (self.API_URL, sid))
+ self.logDebug("API:USERINFO", json)
+ json = json_loads(json)
+
+ if json['response_status'] == 200:
+ if "reset_in" in json['response']:
+ self.scheduleRefresh(user, json['response']['reset_in'])
+
+ return {"validuntil": json['response']['expire_date'],
+ "trafficleft": int(json['response']['traffic_left']) / 1024,
+ "premium": True}
+ else:
+ self.logError(json['response_details'])
+ except Exception, e:
+ self.logError(e)
+
+ return {"validuntil": None, "trafficleft": None, "premium": False}
+
+ def login(self, user, data, req):
+ try:
+ json = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']})
+ self.logDebug("API:LOGIN", json)
+ json = json_loads(json)
+
+ if json['response_status'] == 200:
+ data['SID'] = str(json['response']['session_id'])
+ return
+ else:
+ self.logError(json['response_details'])
+ except Exception, e:
+ self.logError(e)
+
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/RapidshareCom.py b/pyload/plugins/accounts/RapidshareCom.py
new file mode 100644
index 000000000..38db62200
--- /dev/null
+++ b/pyload/plugins/accounts/RapidshareCom.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class RapidshareCom(Account):
+ __name__ = "RapidshareCom"
+ __type__ = "account"
+ __version__ = "0.22"
+
+ __description__ = """Rapidshare.com account plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
+ api_param_prem = {"sub": "getaccountdetails", "type": "prem", "login": user,
+ "password": data['password'], "withcookie": 1}
+ src = req.load(api_url_base, cookies=False, get=api_param_prem)
+ if src.startswith("ERROR"):
+ raise Exception(src)
+ fields = src.split("\n")
+ info = {}
+ for t in fields:
+ if not t.strip():
+ continue
+ k, v = t.split("=")
+ info[k] = v
+
+ validuntil = int(info['billeduntil'])
+ premium = True if validuntil else False
+
+ tmp = {"premium": premium, "validuntil": validuntil, "trafficleft": -1, "maxtraffic": -1}
+
+ return tmp
+
+ def login(self, user, data, req):
+ api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
+ api_param_prem = {"sub": "getaccountdetails", "type": "prem", "login": user,
+ "password": data['password'], "withcookie": 1}
+ src = req.load(api_url_base, cookies=False, get=api_param_prem)
+ if src.startswith("ERROR"):
+ raise Exception(src + "### Note you have to use your account number for login, instead of name.")
+ fields = src.split("\n")
+ info = {}
+ for t in fields:
+ if not t.strip():
+ continue
+ k, v = t.split("=")
+ info[k] = v
+ cj = self.getAccountCookies(user)
+ cj.setCookie("rapidshare.com", "enc", info['cookie'])
diff --git a/pyload/plugins/accounts/RarefileNet.py b/pyload/plugins/accounts/RarefileNet.py
new file mode 100644
index 000000000..68e2595e2
--- /dev/null
+++ b/pyload/plugins/accounts/RarefileNet.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class RarefileNet(XFSPAccount):
+ __name__ = "RarefileNet"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """RareFile.net account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ MAIN_PAGE = "http://rarefile.net/"
diff --git a/pyload/plugins/accounts/RealdebridCom.py b/pyload/plugins/accounts/RealdebridCom.py
new file mode 100644
index 000000000..8ab0234a9
--- /dev/null
+++ b/pyload/plugins/accounts/RealdebridCom.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import xml.dom.minidom as dom
+
+from pyload.plugins.Account import Account
+
+
+class RealdebridCom(Account):
+ __name__ = "RealdebridCom"
+ __type__ = "account"
+ __version__ = "0.43"
+
+ __description__ = """Real-Debrid.com account plugin"""
+ __author_name__ = "Devirex Hazzard"
+ __author_mail__ = "naibaf_11@yahoo.de"
+
+
+ def loadAccountInfo(self, user, req):
+ if self.pin_code:
+ return {"premium": False}
+ page = req.load("https://real-debrid.com/api/account.php")
+ xml = dom.parseString(page)
+ account_info = {"validuntil": int(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue),
+ "trafficleft": -1}
+
+ return account_info
+
+ def login(self, user, data, req):
+ self.pin_code = False
+ page = req.load("https://real-debrid.com/ajax/login.php", get={"user": user, "pass": data['password']})
+ if "Your login informations are incorrect" in page:
+ self.wrongPassword()
+ elif "PIN Code required" in page:
+ self.logWarning('PIN code required. Please login to https://real-debrid.com using the PIN or disable the double authentication in your control panel on https://real-debrid.com.')
+ self.pin_code = True
diff --git a/pyload/plugins/accounts/RehostTo.py b/pyload/plugins/accounts/RehostTo.py
new file mode 100644
index 000000000..3bda118f4
--- /dev/null
+++ b/pyload/plugins/accounts/RehostTo.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class RehostTo(Account):
+ __name__ = "RehostTo"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Rehost.to account plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAccountData(user)
+ page = req.load("http://rehost.to/api.php?cmd=login&user=%s&pass=%s" % (user, data['password']))
+ data = [x.split("=") for x in page.split(",")]
+ ses = data[0][1]
+ long_ses = data[1][1]
+
+ page = req.load("http://rehost.to/api.php?cmd=get_premium_credits&long_ses=%s" % long_ses)
+ traffic, valid = page.split(",")
+
+ account_info = {"trafficleft": int(traffic) * 1024,
+ "validuntil": int(valid),
+ "long_ses": long_ses,
+ "ses": ses}
+
+ return account_info
+
+ def login(self, user, data, req):
+ page = req.load("http://rehost.to/api.php?cmd=login&user=%s&pass=%s" % (user, data['password']))
+
+ if "Login failed." in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/RyushareCom.py b/pyload/plugins/accounts/RyushareCom.py
new file mode 100644
index 000000000..74258e984
--- /dev/null
+++ b/pyload/plugins/accounts/RyushareCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class RyushareCom(XFSPAccount):
+ __name__ = "RyushareCom"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """Ryushare.com account plugin"""
+ __author_name__ = ("zoidberg", "trance4us")
+ __author_mail__ = ("zoidberg@mujmail.cz", "")
+
+ MAIN_PAGE = "http://ryushare.com/"
+
+
+ def login(self, user, data, req):
+ req.lastURL = "http://ryushare.com/login.python"
+ html = req.load("http://ryushare.com/login.python",
+ post={"login": user, "password": data['password'], "op": "login"})
+ if 'Incorrect Login or Password' in html or '>Error<' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/ShareRapidCom.py b/pyload/plugins/accounts/ShareRapidCom.py
new file mode 100644
index 000000000..92e6c7988
--- /dev/null
+++ b/pyload/plugins/accounts/ShareRapidCom.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime
+from pyload.plugins.Account import Account
+
+
+class ShareRapidCom(Account):
+ __name__ = "ShareRapidCom"
+ __type__ = "account"
+ __version__ = "0.34"
+
+ __description__ = """MegaRapid.cz account plugin"""
+ __author_name__ = ("MikyWoW", "zoidberg")
+ __author_mail__ = ("mikywow@seznam.cz", "zoidberg@mujmail.cz")
+
+ login_timeout = 60
+
+
+ def loadAccountInfo(self, user, req):
+ src = req.load("http://megarapid.cz/mujucet/", decode=True)
+
+ m = re.search(ur'<td>Max. počet paralelních stahování: </td><td>(\d+)', src)
+ if m:
+ data = self.getAccountData(user)
+ data['options']['limitDL'] = [int(m.group(1))]
+
+ m = re.search(ur'<td>Paušální stahování aktivní. Vyprší </td><td><strong>(.*?)</strong>', src)
+ if m:
+ validuntil = mktime(strptime(m.group(1), "%d.%m.%Y - %H:%M"))
+ return {"premium": True, "trafficleft": -1, "validuntil": validuntil}
+
+ m = re.search(r'<tr><td>Kredit</td><td>(.*?) GiB', src)
+ if m:
+ trafficleft = float(m.group(1)) * (1 << 20)
+ return {"premium": True, "trafficleft": trafficleft, "validuntil": -1}
+
+ return {"premium": False, "trafficleft": None, "validuntil": None}
+
+ def login(self, user, data, req):
+ htm = req.load("http://megarapid.cz/prihlaseni/", cookies=True)
+ if "Heslo:" in htm:
+ start = htm.index('id="inp_hash" name="hash" value="')
+ htm = htm[start + 33:]
+ hashes = htm[0:32]
+ htm = req.load("http://megarapid.cz/prihlaseni/",
+ post={"hash": hashes,
+ "login": user,
+ "pass1": data['password'],
+ "remember": 0,
+ "sbmt": u"Přihlásit"}, cookies=True)
diff --git a/pyload/plugins/accounts/ShareonlineBiz.py b/pyload/plugins/accounts/ShareonlineBiz.py
new file mode 100644
index 000000000..0f6e61fab
--- /dev/null
+++ b/pyload/plugins/accounts/ShareonlineBiz.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+
+
+class ShareonlineBiz(Account):
+ __name__ = "ShareonlineBiz"
+ __type__ = "account"
+ __version__ = "0.24"
+
+ __description__ = """Share-online.biz account plugin"""
+ __author_name__ = ("mkaay", "zoidberg")
+ __author_mail__ = ("mkaay@mkaay.de", "zoidberg@mujmail.cz")
+
+
+ def getUserAPI(self, user, req):
+ return req.load("http://api.share-online.biz/account.php",
+ {"username": user, "password": self.accounts[user]['password'], "act": "userDetails"})
+
+ def loadAccountInfo(self, user, req):
+ src = self.getUserAPI(user, req)
+
+ info = {}
+ for line in src.splitlines():
+ if "=" in line:
+ key, value = line.split("=")
+ info[key] = value
+ self.logDebug(info)
+
+ if "dl" in info and info['dl'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", "dl", info['dl'])
+ if "a" in info and info['a'].lower() != "not_available":
+ req.cj.setCookie("share-online.biz", "a", info['a'])
+
+ return {"validuntil": int(info['expire_date']) if "expire_date" in info else -1,
+ "trafficleft": -1,
+ "premium": True if ("dl" in info or "a" in info) and (info['group'] != "Sammler") else False}
+
+ def login(self, user, data, req):
+ src = self.getUserAPI(user, req)
+ if "EXCEPTION" in src:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/SimplyPremiumCom.py b/pyload/plugins/accounts/SimplyPremiumCom.py
new file mode 100644
index 000000000..3a385c477
--- /dev/null
+++ b/pyload/plugins/accounts/SimplyPremiumCom.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugins.Account import Account
+
+
+class SimplyPremiumCom(Account):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """Simply-Premium.com account plugin"""
+ __author_name__ = "EvolutionClip"
+ __author_mail__ = "evolutionclip@live.de"
+
+
+ def loadAccountInfo(self, user, req):
+ json_data = req.load('http://www.simply-premium.com/api/user.php?format=json')
+ self.logDebug("JSON data: " + json_data)
+ json_data = json_loads(json_data)
+
+ if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
+ return {"premium": False}
+
+ #Time package
+ validuntil = float(json_data['result']['timeend'])
+ #Traffic package
+ # {"trafficleft": int(traffic) / 1024, "validuntil": -1}
+ #trafficleft = int(json_data['result']['traffic'] / 1024)
+
+ #return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+ return {"premium": True, "validuntil": validuntil}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("simply-premium.com", "lang", "EN")
+
+ if data['password'] == '' or data['password'] == '0':
+ post_data = {"key": user}
+ else:
+ post_data = {"login_name": user, "login_pass": data['password']}
+
+ html = req.load("http://www.simply-premium.com/login.php", post=post_data)
+
+ if 'logout' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/SimplydebridCom.py b/pyload/plugins/accounts/SimplydebridCom.py
new file mode 100644
index 000000000..169b27e0b
--- /dev/null
+++ b/pyload/plugins/accounts/SimplydebridCom.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+
+
+class SimplydebridCom(Account):
+ __name__ = "SimplydebridCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Simply-Debrid.com account plugin"""
+ __author_name__ = "Kagenoshin"
+ __author_mail__ = "kagenoshin@gmx.ch"
+
+
+ def loadAccountInfo(self, user, req):
+ get_data = {'login': 2, 'u': self.loginname, 'p': self.password}
+ response = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ data = [x.strip() for x in response.split(";")]
+ if str(data[0]) != "1":
+ return {"premium": False}
+ else:
+ return {"trafficleft": -1, "validuntil": mktime(strptime(str(data[2]), "%d/%m/%Y"))}
+
+ def login(self, user, data, req):
+ self.loginname = user
+ self.password = data['password']
+ get_data = {'login': 1, 'u': self.loginname, 'p': self.password}
+ response = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True)
+ if response != "02: loggin success":
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/StahnuTo.py b/pyload/plugins/accounts/StahnuTo.py
new file mode 100644
index 000000000..9d4cc6994
--- /dev/null
+++ b/pyload/plugins/accounts/StahnuTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Account import Account
+from pyload.utils import parseFileSize
+
+
+class StahnuTo(Account):
+ __name__ = "StahnuTo"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """StahnuTo account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://www.stahnu.to/")
+
+ m = re.search(r'>VIP: (\d+.*)<', html)
+ trafficleft = parseFileSize(m.group(1)) * 1024 if m else 0
+
+ return {"premium": trafficleft > (512 * 1024), "trafficleft": trafficleft, "validuntil": -1}
+
+ def login(self, user, data, req):
+ html = req.load("http://www.stahnu.to/login.php", post={
+ "username": user,
+ "password": data['password'],
+ "submit": "Login"})
+
+ if not '<a href="logout.php">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/TurbobitNet.py b/pyload/plugins/accounts/TurbobitNet.py
new file mode 100644
index 000000000..d4221a97a
--- /dev/null
+++ b/pyload/plugins/accounts/TurbobitNet.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+
+
+class TurbobitNet(Account):
+ __name__ = "TurbobitNet"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """TurbobitNet account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load("http://turbobit.net")
+
+ m = re.search(r'<u>Turbo Access</u> to ([0-9.]+)', html)
+ if m:
+ premium = True
+ validuntil = mktime(strptime(m.group(1), "%d.%m.%Y"))
+ else:
+ premium = False
+ validuntil = -1
+
+ return {"premium": premium, "trafficleft": -1, "validuntil": validuntil}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("turbobit.net", "user_lang", "en")
+
+ html = req.load("http://turbobit.net/user/login", post={
+ "user[login]": user,
+ "user[pass]": data['password'],
+ "user[submit]": "Login"})
+
+ if not '<div class="menu-item user-name">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/UlozTo.py b/pyload/plugins/accounts/UlozTo.py
new file mode 100644
index 000000000..01fb134e8
--- /dev/null
+++ b/pyload/plugins/accounts/UlozTo.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Account import Account
+
+
+class UlozTo(Account):
+ __name__ = "UlozTo"
+ __type__ = "account"
+ __version__ = "0.06"
+
+ __description__ = """Uloz.to account plugin"""
+ __author_name__ = ("zoidberg", "pulpe")
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a href="/kredit" title="[^"]*?GB = ([0-9.]+) MB"'
+
+
+ def loadAccountInfo(self, user, req):
+ #this cookie gets lost somehow after each request
+ self.phpsessid = req.cj.getCookie("ULOSESSID")
+ html = req.load("http://www.ulozto.net/", decode=True)
+ req.cj.setCookie("www.ulozto.net", "ULOSESSID", self.phpsessid)
+
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ trafficleft = int(float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048) if m else 0
+ self.premium = True if trafficleft else False
+
+ return {"validuntil": -1, "trafficleft": trafficleft}
+
+ def login(self, user, data, req):
+ login_page = req.load('http://www.ulozto.net/?do=web-login', decode=True)
+ action = re.findall('<form action="(.+?)"', login_page)[1].replace('&amp;', '&')
+ token = re.search('_token_" value="(.+?)"', login_page).group(1)
+
+ html = req.load('http://www.ulozto.net'+action, post={
+ "_token_": token,
+ "login": "Submit",
+ "password": data['password'],
+ "username": user
+ }, decode=True)
+
+ if '<div class="flash error">' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/UnrestrictLi.py b/pyload/plugins/accounts/UnrestrictLi.py
new file mode 100644
index 000000000..6e878b556
--- /dev/null
+++ b/pyload/plugins/accounts/UnrestrictLi.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Account import Account
+from pyload.utils import json_loads
+
+
+class UnrestrictLi(Account):
+ __name__ = "UnrestrictLi"
+ __type__ = "account"
+ __version__ = "0.03"
+
+ __description__ = """Unrestrict.li account plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def loadAccountInfo(self, user, req):
+ json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json')
+ self.logDebug("JSON data: " + json_data)
+ json_data = json_loads(json_data)
+
+ if 'vip' in json_data['result'] and json_data['result']['vip'] == 0:
+ return {"premium": False}
+
+ validuntil = json_data['result']['expires']
+ trafficleft = int(json_data['result']['traffic'] / 1024)
+
+ return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("unrestrict.li", "lang", "EN")
+ html = req.load("https://unrestrict.li/sign_in")
+
+ if 'solvemedia' in html:
+ self.logError("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry")
+ return
+
+ post_data = {"username": user, "password": data['password'],
+ "remember_me": "remember", "signin": "Sign in"}
+ html = req.load("https://unrestrict.li/sign_in", post=post_data)
+
+ if 'sign_out' not in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/UploadedTo.py b/pyload/plugins/accounts/UploadedTo.py
new file mode 100644
index 000000000..64bbeac6e
--- /dev/null
+++ b/pyload/plugins/accounts/UploadedTo.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+
+from pyload.plugins.Account import Account
+
+
+class UploadedTo(Account):
+ __name__ = "UploadedTo"
+ __type__ = "account"
+ __version__ = "0.26"
+
+ __description__ = """Uploaded.to account plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def loadAccountInfo(self, user, req):
+
+ req.load("http://uploaded.net/language/en")
+ html = req.load("http://uploaded.net/me")
+
+ premium = '<a href="register"><em>Premium</em>' in html or '<em>Premium</em></th>' in html
+
+ if premium:
+ raw_traffic = re.search(r'<th colspan="2"><b class="cB">([^<]+)', html).group(1).replace('.', '')
+ raw_valid = re.search(r"<td>Duration:</td>\s*<th>([^<]+)", html, re.MULTILINE).group(1).strip()
+
+ traffic = int(self.parseTraffic(raw_traffic))
+
+ if raw_valid == "unlimited":
+ validuntil = -1
+ else:
+ raw_valid = re.findall(r"(\d+) (Week|weeks|days|day|hours|hour)", raw_valid)
+ validuntil = time()
+ for n, u in raw_valid:
+ validuntil += int(n) * 60 * 60 * {"Week": 168, "weeks": 168, "days": 24,
+ "day": 24, "hours": 1, "hour": 1}[u]
+
+ return {"validuntil": validuntil, "trafficleft": traffic, "maxtraffic": 50 * 1024 * 1024}
+ else:
+ return {"premium": False, "validuntil": -1}
+
+ def login(self, user, data, req):
+
+ req.load("http://uploaded.net/language/en")
+ req.cj.setCookie("uploaded.net", "lang", "en")
+
+ page = req.load("http://uploaded.net/io/login", post={"id": user, "pw": data['password'], "_": ""})
+
+ if "User and password do not match!" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/UploadheroCom.py b/pyload/plugins/accounts/UploadheroCom.py
new file mode 100644
index 000000000..1cb0ab698
--- /dev/null
+++ b/pyload/plugins/accounts/UploadheroCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+import datetime
+import time
+
+from pyload.plugins.Account import Account
+
+
+class UploadheroCom(Account):
+ __name__ = "UploadheroCom"
+ __type__ = "account"
+ __version__ = "0.2"
+
+ __description__ = """Uploadhero.co account plugin"""
+ __author_name__ = "mcmyst"
+ __author_mail__ = "mcmyst@hotmail.fr"
+
+
+ def loadAccountInfo(self, user, req):
+ premium_pattern = re.compile('Il vous reste <span class="bleu">([0-9]+)</span> jours premium.')
+
+ data = self.getAccountData(user)
+ page = req.load("http://uploadhero.co/my-account")
+
+ if premium_pattern.search(page):
+ end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(page).group(1)))
+ end_date = time.mktime(future.timetuple())
+ account_info = {"validuntil": end_date, "trafficleft": -1, "premium": True}
+ else:
+ account_info = {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ return account_info
+
+ def login(self, user, data, req):
+ page = req.load("http://uploadhero.co/lib/connexion.php",
+ post={"pseudo_login": user, "password_login": data['password']})
+
+ if "mot de passe invalide" in page:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/UploadingCom.py b/pyload/plugins/accounts/UploadingCom.py
new file mode 100644
index 000000000..9ac674b71
--- /dev/null
+++ b/pyload/plugins/accounts/UploadingCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+from time import time, strptime, mktime
+import re
+
+from pyload.plugins.Account import Account
+
+
+class UploadingCom(Account):
+ __name__ = "UploadingCom"
+ __type__ = "account"
+ __version__ = "0.1"
+
+ __description__ = """Uploading.com account plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def loadAccountInfo(self, user, req):
+ src = req.load("http://uploading.com/")
+ premium = True
+ if "UPGRADE TO PREMIUM" in src:
+ return {"validuntil": -1, "trafficleft": -1, "premium": False}
+
+ m = re.search("Valid Until:(.*?)<", src)
+ if m:
+ validuntil = int(mktime(strptime(m.group(1).strip(), "%b %d, %Y")))
+ else:
+ validuntil = -1
+
+ return {"validuntil": validuntil, "trafficleft": -1, "premium": True}
+
+ def login(self, user, data, req):
+ req.cj.setCookie("uploading.com", "lang", "1")
+ req.cj.setCookie("uploading.com", "language", "1")
+ req.cj.setCookie("uploading.com", "setlang", "en")
+ req.cj.setCookie("uploading.com", "_lang", "en")
+ req.load("http://uploading.com/")
+ req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time() * 1000),
+ post={"email": user, "password": data['password'], "remember": "on"})
diff --git a/pyload/plugins/accounts/UptoboxCom.py b/pyload/plugins/accounts/UptoboxCom.py
new file mode 100644
index 000000000..7f9618da8
--- /dev/null
+++ b/pyload/plugins/accounts/UptoboxCom.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.XFSPAccount import XFSPAccount
+
+
+class UptoboxCom(XFSPAccount):
+ __name__ = "UptoboxCom"
+ __type__ = "account"
+ __version__ = "0.02"
+
+ __description__ = """DDLStorage.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ MAIN_PAGE = "http://uptobox.com/"
+
+ VALID_UNTIL_PATTERN = r'>Premium.[Aa]ccount expire: ([^<]+)</strong>'
diff --git a/pyload/plugins/accounts/YibaishiwuCom.py b/pyload/plugins/accounts/YibaishiwuCom.py
new file mode 100644
index 000000000..3898c3cef
--- /dev/null
+++ b/pyload/plugins/accounts/YibaishiwuCom.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Account import Account
+
+
+class YibaishiwuCom(Account):
+ __name__ = "YibaishiwuCom"
+ __type__ = "account"
+ __version__ = "0.01"
+
+ __description__ = """115.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ ACCOUNT_INFO_PATTERN = r'var USER_PERMISSION = {(.*?)}'
+
+
+ def loadAccountInfo(self, user, req):
+ #self.relogin(user)
+ html = req.load("http://115.com/", decode=True)
+
+ m = re.search(self.ACCOUNT_INFO_PATTERN, html, re.S)
+ premium = True if (m and 'is_vip: 1' in m.group(1)) else False
+ validuntil = trafficleft = (-1 if m else 0)
+ return dict({"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium})
+
+ def login(self, user, data, req):
+ html = req.load('http://passport.115.com/?ac=login', post={
+ "back": "http://www.115.com/",
+ "goto": "http://115.com/",
+ "login[account]": user,
+ "login[passwd]": data['password']
+ }, decode=True)
+
+ if not 'var USER_PERMISSION = {' in html:
+ self.wrongPassword()
diff --git a/pyload/plugins/accounts/ZeveraCom.py b/pyload/plugins/accounts/ZeveraCom.py
new file mode 100644
index 000000000..d84000359
--- /dev/null
+++ b/pyload/plugins/accounts/ZeveraCom.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+
+
+class ZeveraCom(Account):
+ __name__ = "ZeveraCom"
+ __type__ = "account"
+ __version__ = "0.21"
+
+ __description__ = """Zevera.com account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def loadAccountInfo(self, user, req):
+ data = self.getAPIData(req)
+ if data == "No traffic":
+ account_info = {"trafficleft": 0, "validuntil": 0, "premium": False}
+ else:
+ account_info = {
+ "trafficleft": int(data['availabletodaytraffic']) * 1024,
+ "validuntil": mktime(strptime(data['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S")),
+ "premium": True
+ }
+ return account_info
+
+ def login(self, user, data, req):
+ self.loginname = user
+ self.password = data['password']
+ if self.getAPIData(req) == "No traffic":
+ self.wrongPassword()
+
+ def getAPIData(self, req, just_header=False, **kwargs):
+ get_data = {
+ 'cmd': 'accountinfo',
+ 'login': self.loginname,
+ 'pass': self.password
+ }
+ get_data.update(kwargs)
+
+ response = req.load("http://www.zevera.com/jDownloader.ashx", get=get_data,
+ decode=True, just_header=just_header)
+ self.logDebug(response)
+
+ if ':' in response:
+ if not just_header:
+ response = response.replace(',', '\n')
+ return dict((y.strip().lower(), z.strip()) for (y, z) in
+ [x.split(':', 1) for x in response.splitlines() if ':' in x])
+ else:
+ return response
diff --git a/module/plugins/hooks/__init__.py b/pyload/plugins/accounts/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/hooks/__init__.py
+++ b/pyload/plugins/accounts/__init__.py
diff --git a/pyload/plugins/container/CCF.py b/pyload/plugins/container/CCF.py
new file mode 100644
index 000000000..1e647b486
--- /dev/null
+++ b/pyload/plugins/container/CCF.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from os import makedirs
+from os.path import exists
+from urllib2 import build_opener
+
+from MultipartPostHandler import MultipartPostHandler
+
+from pyload.plugins.Container import Container
+from pyload.utils import safe_join
+
+
+class CCF(Container):
+ __name__ = "CCF"
+ __version__ = "0.2"
+
+ __pattern__ = r'.+\.ccf'
+
+ __description__ = """CCF container decrypter plugin"""
+ __author_name__ = "Willnix"
+ __author_mail__ = "Willnix@pyload.org"
+
+
+ def decrypt(self, pyfile):
+
+ infile = pyfile.url.replace("\n", "")
+
+ opener = build_opener(MultipartPostHandler)
+ params = {"src": "ccf",
+ "filename": "test.ccf",
+ "upload": open(infile, "rb")}
+ tempdlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', params).read()
+
+ download_folder = self.config['general']['download_folder']
+
+ tempdlc_name = safe_join(download_folder, "tmp_%s.dlc" % pyfile.name)
+ tempdlc = open(tempdlc_name, "w")
+ tempdlc.write(re.search(r'<dlc>(.*)</dlc>', tempdlc_content, re.DOTALL).group(1))
+ tempdlc.close()
+
+ self.urls = [tempdlc_name]
diff --git a/module/plugins/container/DLC_25.pyc b/pyload/plugins/container/DLC_25.pyc
index b8fde0051..b8fde0051 100644
--- a/module/plugins/container/DLC_25.pyc
+++ b/pyload/plugins/container/DLC_25.pyc
Binary files differ
diff --git a/module/plugins/container/DLC_26.pyc b/pyload/plugins/container/DLC_26.pyc
index 41a4e0cb8..41a4e0cb8 100644
--- a/module/plugins/container/DLC_26.pyc
+++ b/pyload/plugins/container/DLC_26.pyc
Binary files differ
diff --git a/module/plugins/container/DLC_27.pyc b/pyload/plugins/container/DLC_27.pyc
index a6bffaf74..a6bffaf74 100644
--- a/module/plugins/container/DLC_27.pyc
+++ b/pyload/plugins/container/DLC_27.pyc
Binary files differ
diff --git a/pyload/plugins/container/LinkList.py b/pyload/plugins/container/LinkList.py
new file mode 100644
index 000000000..b8941ee29
--- /dev/null
+++ b/pyload/plugins/container/LinkList.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import codecs
+
+from pyload.plugins.Container import Container
+from pyload.utils import fs_encode
+
+
+class LinkList(Container):
+ __name__ = "LinkList"
+ __version__ = "0.12"
+
+ __pattern__ = r'.+\.txt'
+ __config__ = [("clear", "bool", "Clear Linklist after adding", False),
+ ("encoding", "string", "File encoding (default utf-8)", "")]
+
+ __description__ = """Read link lists in txt format"""
+ __author_name__ = ("spoob", "jeix")
+ __author_mail__ = ("spoob@pyload.org", "jeix@hasnomail.com")
+
+
+ def decrypt(self, pyfile):
+ try:
+ file_enc = codecs.lookup(self.getConfig("encoding")).name
+ except:
+ file_enc = "utf-8"
+
+ print repr(pyfile.url)
+ print pyfile.url
+
+ file_name = fs_encode(pyfile.url)
+
+ txt = codecs.open(file_name, 'r', file_enc)
+ links = txt.readlines()
+ curPack = "Parsed links from %s" % pyfile.name
+
+ packages = {curPack:[],}
+
+ for link in links:
+ link = link.strip()
+ if not link:
+ continue
+
+ if link.startswith(";"):
+ continue
+ if link.startswith("[") and link.endswith("]"):
+ # new package
+ curPack = link[1:-1]
+ packages[curPack] = []
+ continue
+ packages[curPack].append(link)
+ txt.close()
+
+ # empty packages fix
+
+ delete = []
+
+ for key,value in packages.iteritems():
+ if not value:
+ delete.append(key)
+
+ for key in delete:
+ del packages[key]
+
+ if self.getConfig("clear"):
+ try:
+ txt = open(file_name, 'wb')
+ txt.close()
+ except:
+ self.logWarning(_("LinkList could not be cleared."))
+
+ for name, links in packages.iteritems():
+ self.packages.append((name, links, name))
diff --git a/pyload/plugins/container/RSDF.py b/pyload/plugins/container/RSDF.py
new file mode 100644
index 000000000..2b4d4c686
--- /dev/null
+++ b/pyload/plugins/container/RSDF.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from pyload.plugins.Container import Container
+
+
+class RSDF(Container):
+ __name__ = "RSDF"
+ __version__ = "0.22"
+
+ __pattern__ = r'.+\.rsdf'
+
+ __description__ = """RSDF container decrypter plugin"""
+ __author_name__ = ("RaNaN", "spoob")
+ __author_mail__ = ("RaNaN@pyload.org", "spoob@pyload.org")
+
+
+ def decrypt(self, pyfile):
+
+ from Crypto.Cipher import AES
+
+ infile = pyfile.url.replace("\n", "")
+ Key = binascii.unhexlify('8C35192D964DC3182C6F84F3252239EB4A320D2500000000')
+
+ IV = binascii.unhexlify('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
+ IV_Cipher = AES.new(Key, AES.MODE_ECB)
+ IV = IV_Cipher.encrypt(IV)
+
+ obj = AES.new(Key, AES.MODE_CFB, IV)
+
+ rsdf = open(infile, 'r')
+
+ data = rsdf.read()
+ rsdf.close()
+
+ if re.search(r"<title>404 - Not Found</title>", data) is None:
+ data = binascii.unhexlify(''.join(data.split()))
+ data = data.splitlines()
+
+ for link in data:
+ if not link:
+ continue
+ link = base64.b64decode(link)
+ link = obj.decrypt(link)
+ decryptedUrl = link.replace('CCF: ', '')
+ self.urls.append(decryptedUrl)
+
+ self.log.debug("%s: adding package %s with %d links" % (self.__name__,pyfile.package().name,len(links)))
diff --git a/module/plugins/hoster/__init__.py b/pyload/plugins/container/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/hoster/__init__.py
+++ b/pyload/plugins/container/__init__.py
diff --git a/pyload/plugins/crypter/BitshareComFolder.py b/pyload/plugins/crypter/BitshareComFolder.py
new file mode 100644
index 000000000..cfb6fc1a0
--- /dev/null
+++ b/pyload/plugins/crypter/BitshareComFolder.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class BitshareComFolder(SimpleCrypter):
+ __name__ = "BitshareComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?bitshare\.com/\?d=\w+'
+
+ __description__ = """Bitshare.com folder decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<a href="(http://bitshare.com/files/.+)">.+</a></td>'
+ TITLE_PATTERN = r'View public folder "(?P<title>.+)"</h1>'
diff --git a/pyload/plugins/crypter/C1neonCom.py b/pyload/plugins/crypter/C1neonCom.py
new file mode 100644
index 000000000..2d1e91ef6
--- /dev/null
+++ b/pyload/plugins/crypter/C1neonCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class C1neonCom(DeadCrypter):
+ __name__ = "C1neonCom"
+ __type__ = "crypter"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?c1neon.com/.*?'
+
+ __description__ = """C1neon.com decrypter plugin"""
+ __author_name__ = "godofdream"
+ __author_mail__ = "soilfiction@gmail.com"
diff --git a/pyload/plugins/crypter/ChipDe.py b/pyload/plugins/crypter/ChipDe.py
new file mode 100644
index 000000000..29a248693
--- /dev/null
+++ b/pyload/plugins/crypter/ChipDe.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class ChipDe(Crypter):
+ __name__ = "ChipDe"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?chip.de/video/.*\.html'
+
+ __description__ = """Chip.de decrypter plugin"""
+ __author_name__ = "4Christopher"
+ __author_mail__ = "4Christopher@gmx.de"
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ try:
+ f = re.search(r'"(http://video.chip.de/\d+?/.*)"', self.html)
+ except:
+ self.fail('Failed to find the URL')
+ else:
+ self.urls = [f.group(1)]
+ self.logDebug('The file URL is %s' % self.urls[0])
diff --git a/pyload/plugins/crypter/CrockoComFolder.py b/pyload/plugins/crypter/CrockoComFolder.py
new file mode 100644
index 000000000..200b3333e
--- /dev/null
+++ b/pyload/plugins/crypter/CrockoComFolder.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class CrockoComFolder(SimpleCrypter):
+ __name__ = "CrockoComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?crocko.com/f/.*'
+
+ __description__ = """Crocko.com folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'<td class="last"><a href="([^"]+)">download</a>'
diff --git a/pyload/plugins/crypter/CryptItCom.py b/pyload/plugins/crypter/CryptItCom.py
new file mode 100644
index 000000000..3de00847e
--- /dev/null
+++ b/pyload/plugins/crypter/CryptItCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class CryptItCom(DeadCrypter):
+ __name__ = "CryptItCom"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/[\w]+'
+
+ __description__ = """Crypt-it.com decrypter plugin"""
+ __author_name__ = "jeix"
+ __author_mail__ = "jeix@hasnomail.de"
diff --git a/pyload/plugins/crypter/CzshareComFolder.py b/pyload/plugins/crypter/CzshareComFolder.py
new file mode 100644
index 000000000..94e4f07b3
--- /dev/null
+++ b/pyload/plugins/crypter/CzshareComFolder.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class CzshareComFolder(Crypter):
+ __name__ = "CzshareComFolder"
+ __type__ = "crypter"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.*'
+
+ __description__ = """Czshare.com folder decrypter plugin, now Sdilej.cz"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FOLDER_PATTERN = r'<tr class="subdirectory">\s*<td>\s*<table>(.*?)</table>'
+ LINK_PATTERN = r'<td class="col2"><a href="([^"]+)">info</a></td>'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
+ if m is None:
+ self.fail("Parse error (FOLDER)")
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
+ if not self.urls:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/DDLMusicOrg.py b/pyload/plugins/crypter/DDLMusicOrg.py
new file mode 100644
index 000000000..be4a92617
--- /dev/null
+++ b/pyload/plugins/crypter/DDLMusicOrg.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import sleep
+
+from pyload.plugins.Crypter import Crypter
+
+
+class DDLMusicOrg(Crypter):
+ __name__ = "DDLMusicOrg"
+ __type__ = "crypter"
+ __version__ = "0.3"
+
+ __pattern__ = r'http://(?:www\.)?ddl-music\.org/captcha/ddlm_cr\d\.php\?\d+\?\d+'
+
+ __description__ = """Ddl-music.org decrypter plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def setup(self):
+ self.multiDL = False
+
+ def decrypt(self, pyfile):
+ html = self.req.load(pyfile.url, cookies=True)
+
+ if re.search(r"Wer dies nicht rechnen kann", html) is not None:
+ self.offline()
+
+ math = re.search(r"(\d+) ([\+-]) (\d+) =\s+<inp", self.html)
+ id = re.search(r"name=\"id\" value=\"(\d+)\"", self.html).group(1)
+ linknr = re.search(r"name=\"linknr\" value=\"(\d+)\"", self.html).group(1)
+
+ solve = ""
+ if math.group(2) == "+":
+ solve = int(math.group(1)) + int(math.group(3))
+ else:
+ solve = int(math.group(1)) - int(math.group(3))
+ sleep(3)
+ htmlwithlink = self.req.load(pyfile.url, cookies=True,
+ post={"calc%s" % linknr: solve, "send%s" % linknr: "Send", "id": id,
+ "linknr": linknr})
+ m = re.search(r"<form id=\"ff\" action=\"(.*?)\" method=\"post\">", htmlwithlink)
+ if m:
+ self.urls = [m.group(1)]
+ else:
+ self.retry()
diff --git a/pyload/plugins/crypter/DailymotionBatch.py b/pyload/plugins/crypter/DailymotionBatch.py
new file mode 100644
index 000000000..2c818d8bc
--- /dev/null
+++ b/pyload/plugins/crypter/DailymotionBatch.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.utils import json_loads
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import safe_join
+
+
+class DailymotionBatch(Crypter):
+ __name__ = "DailymotionBatch"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)'
+
+ __description__ = """Dailymotion.com channel & playlist decrypter"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+ def api_response(self, ref, req=None):
+ url = urljoin("https://api.dailymotion.com/", ref)
+ page = self.load(url, get=req)
+ return json_loads(page)
+
+ def getPlaylistInfo(self, id):
+ ref = "playlist/" + id
+ req = {"fields": "name,owner.screenname"}
+ playlist = self.api_response(ref, req)
+
+ if "error" in playlist:
+ return
+
+ name = playlist['name']
+ owner = playlist['owner.screenname']
+ return name, owner
+
+ def _getPlaylists(self, user_id, page=1):
+ ref = "user/%s/playlists" % user_id
+ req = {"fields": "id", "page": page, "limit": 100}
+ user = self.api_response(ref, req)
+
+ if "error" in user:
+ return
+
+ for playlist in user['list']:
+ yield playlist['id']
+
+ if user['has_more']:
+ for item in self._getPlaylists(user_id, page + 1):
+ yield item
+
+ def getPlaylists(self, user_id):
+ return [(id,) + self.getPlaylistInfo(id) for id in self._getPlaylists(user_id)]
+
+ def _getVideos(self, id, page=1):
+ ref = "playlist/%s/videos" % id
+ req = {"fields": "url", "page": page, "limit": 100}
+ playlist = self.api_response(ref, req)
+
+ if "error" in playlist:
+ return
+
+ for video in playlist['list']:
+ yield video['url']
+
+ if playlist['has_more']:
+ for item in self._getVideos(id, page + 1):
+ yield item
+
+ def getVideos(self, playlist_id):
+ return list(self._getVideos(playlist_id))[::-1]
+
+ def decrypt(self, pyfile):
+ m = re.match(self.__pattern__, pyfile.url)
+ m_id = m.group("ID")
+ m_type = m.group("TYPE")
+
+ if m_type == "playlist":
+ self.logDebug("Url recognized as Playlist")
+ p_info = self.getPlaylistInfo(m_id)
+ playlists = [(m_id,) + p_info] if p_info else None
+ else:
+ self.logDebug("Url recognized as Channel")
+ playlists = self.getPlaylists(m_id)
+ self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), m_id))
+
+ if not playlists:
+ self.fail("No playlist available")
+
+ for p_id, p_name, p_owner in playlists:
+ p_videos = self.getVideos(p_id)
+ p_folder = safe_join(self.config['general']['download_folder'], p_owner, p_name)
+ self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
+ self.packages.append((p_name, p_videos, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
diff --git a/pyload/plugins/crypter/DataHuFolder.py b/pyload/plugins/crypter/DataHuFolder.py
new file mode 100644
index 000000000..49dab9159
--- /dev/null
+++ b/pyload/plugins/crypter/DataHuFolder.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DataHuFolder(SimpleCrypter):
+ __name__ = "DataHuFolder"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?data.hu/dir/\w+'
+
+ __description__ = """Data.hu folder decrypter plugin"""
+ __author_name__ = ("crash", "stickell")
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r"<a href='(http://data\.hu/get/.+)' target='_blank'>\1</a>"
+ TITLE_PATTERN = ur'<title>(?P<title>.+) Let\xf6lt\xe9se</title>'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: # Password protected
+ password = self.getPassword()
+ if password is '':
+ self.fail("No password specified, please set right password on Add package form and retry")
+ self.logDebug('The folder is password protected', 'Using password: ' + password)
+ self.html = self.load(pyfile.url, post={'mappa_pass': password}, decode=True)
+ if u'Hib\xe1s jelsz\xf3' in self.html: # Wrong password
+ self.fail("Incorrect password, please set right password on Add package form and retry")
+
+ package_name, folder_name = self.getPackageNameAndFolder()
+
+ package_links = re.findall(self.LINK_PATTERN, self.html)
+ self.logDebug('Package has %d links' % len(package_links))
+
+ if package_links:
+ self.packages = [(package_name, package_links, folder_name)]
+ else:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/DdlstorageComFolder.py b/pyload/plugins/crypter/DdlstorageComFolder.py
new file mode 100644
index 000000000..7469610f1
--- /dev/null
+++ b/pyload/plugins/crypter/DdlstorageComFolder.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter, create_getInfo
+
+
+class DdlstorageComFolder(DeadCrypter):
+ __name__ = "DdlstorageComFolder"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+'
+
+ __description__ = """DDLStorage.com folder decrypter plugin"""
+ __author_name__ = ("godofdream", "stickell")
+ __author_mail__ = ("soilfiction@gmail.com", "l.stickell@yahoo.it")
+
+
+getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/pyload/plugins/crypter/DepositfilesComFolder.py b/pyload/plugins/crypter/DepositfilesComFolder.py
new file mode 100644
index 000000000..e308305ae
--- /dev/null
+++ b/pyload/plugins/crypter/DepositfilesComFolder.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DepositfilesComFolder(SimpleCrypter):
+ __name__ = "DepositfilesComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?depositfiles.com/folders/\w+'
+
+ __description__ = """Depositfiles.com folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'<div class="progressName"[^>]*>\s*<a href="([^"]+)" title="[^"]*" target="_blank">'
diff --git a/pyload/plugins/crypter/Dereferer.py b/pyload/plugins/crypter/Dereferer.py
new file mode 100644
index 000000000..6a7ac8c67
--- /dev/null
+++ b/pyload/plugins/crypter/Dereferer.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.Crypter import Crypter
+
+
+class Dereferer(Crypter):
+ __name__ = "Dereferer"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://([^/]+)/.*?(?P<url>(ht|f)tps?(://|%3A%2F%2F).*)'
+
+ __description__ = """Crypter for dereferers"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def decrypt(self, pyfile):
+ link = re.match(self.__pattern__, pyfile.url).group('url')
+ self.urls = [unquote(link).rstrip('+')]
diff --git a/pyload/plugins/crypter/DlProtectCom.py b/pyload/plugins/crypter/DlProtectCom.py
new file mode 100644
index 000000000..2c9e282be
--- /dev/null
+++ b/pyload/plugins/crypter/DlProtectCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from base64 import urlsafe_b64encode
+from time import time
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DlProtectCom(SimpleCrypter):
+ __name__ = "DlProtectCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?dl-protect\.com/((en|fr)/)?(?P<ID>\w+)'
+
+ __description__ = """Dl-protect.com decrypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ OFFLINE_PATTERN = r'>Unfortunately, the link you are looking for is not found'
+
+
+ def getLinks(self):
+ # Direct link with redirect
+ if not re.match(r"http://(?:www\.)?dl-protect\.com", self.req.http.lastEffectiveURL):
+ return [self.req.http.lastEffectiveURL]
+
+ #id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ key = re.search(r'name="id_key" value="(.+?)"', self.html).group(1)
+
+ post_req = {"id_key": key, "submitform": ""}
+
+ if self.OFFLINE_PATTERN in self.html:
+ self.offline()
+ elif ">Please click on continue to see the content" in self.html:
+ post_req.update({"submitform": "Continue"})
+ else:
+ mstime = int(round(time() * 1000))
+ b64time = "_" + urlsafe_b64encode(str(mstime)).replace("=", "%3D")
+
+ post_req.update({"i": b64time, "submitform": "Decrypt+link"})
+
+ if ">Password :" in self.html:
+ post_req['pwd'] = self.getPassword()
+
+ if ">Security Code" in self.html:
+ captcha_id = re.search(r'/captcha\.php\?uid=(.+?)"', self.html).group(1)
+ captcha_url = "http://www.dl-protect.com/captcha.php?uid=" + captcha_id
+ captcha_code = self.decryptCaptcha(captcha_url, imgtype="gif")
+
+ post_req['secure'] = captcha_code
+
+ self.html = self.load(self.pyfile.url, post=post_req)
+
+ for errmsg in (">The password is incorrect", ">The security code is incorrect"):
+ if errmsg in self.html:
+ self.fail(errmsg[1:])
+
+ pattern = r'<a href="([^/].+?)" target="_blank">'
+ return re.findall(pattern, self.html)
diff --git a/pyload/plugins/crypter/DontKnowMe.py b/pyload/plugins/crypter/DontKnowMe.py
new file mode 100644
index 000000000..b16992b27
--- /dev/null
+++ b/pyload/plugins/crypter/DontKnowMe.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.Crypter import Crypter
+
+
+class DontKnowMe(Crypter):
+ __name__ = "DontKnowMe"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?dontknow.me/at/\?.+$'
+
+ __description__ = """DontKnow.me decrypter plugin"""
+ __author_name__ = "selaux"
+ __author_mail__ = None
+
+ LINK_PATTERN = r'http://dontknow.me/at/\?(.+)$'
+
+
+ def decrypt(self, pyfile):
+ link = re.findall(self.LINK_PATTERN, pyfile.url)[0]
+ self.urls = [unquote(link)]
diff --git a/pyload/plugins/crypter/DuckCryptInfo.py b/pyload/plugins/crypter/DuckCryptInfo.py
new file mode 100644
index 000000000..63340f2ed
--- /dev/null
+++ b/pyload/plugins/crypter/DuckCryptInfo.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugins.Crypter import Crypter
+
+
+class DuckCryptInfo(Crypter):
+ __name__ = "DuckCryptInfo"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?duckcrypt.info/(folder|wait|link)/(\w+)/?(\w*)'
+
+ __description__ = """DuckCrypt.info decrypter plugin"""
+ __author_name__ = "godofdream"
+ __author_mail__ = "soilfiction@gmail.com"
+
+ TIMER_PATTERN = r'<span id="timer">(.*)</span>'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+ # seems we don't need to wait
+ #src = self.req.load(str(url))
+ #m = re.search(self.TIMER_PATTERN, src)
+ #if m:
+ # self.logDebug("Sleeping for" % m.group(1))
+ # self.setWait(int(m.group(1)) ,False)
+ m = re.match(self.__pattern__, url)
+ if m is None:
+ self.fail('Weird error in link')
+ if str(m.group(1)) == "link":
+ self.handleLink(url)
+ else:
+ self.handleFolder(m)
+
+ def handleFolder(self, m):
+ src = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2)))
+ m = re.match(self.__pattern__, src)
+ self.logDebug("Redirectet to " + str(m.group(0)))
+ src = self.load(str(m.group(0)))
+ soup = BeautifulSoup(src)
+ cryptlinks = soup.findAll("div", attrs={"class": "folderbox"})
+ self.logDebug("Redirectet to " + str(cryptlinks))
+ if not cryptlinks:
+ self.fail('no links m - (Plugin out of date?)')
+ for clink in cryptlinks:
+ if clink.find("a"):
+ self.handleLink(clink.find("a")['href'])
+
+ def handleLink(self, url):
+ src = self.load(url)
+ soup = BeautifulSoup(src)
+ self.urls = [soup.find("iframe")['src']]
+ if not self.urls:
+ self.logDebug('no links m - (Plugin out of date?)')
diff --git a/pyload/plugins/crypter/DuploadOrgFolder.py b/pyload/plugins/crypter/DuploadOrgFolder.py
new file mode 100644
index 000000000..ca76cff75
--- /dev/null
+++ b/pyload/plugins/crypter/DuploadOrgFolder.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class DuploadOrgFolder(SimpleCrypter):
+ __name__ = "DuploadOrgFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?dupload\.org/folder/\d+/'
+
+ __description__ = """Dupload.org folder decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<td style="[^"]+"><a href="(http://[^"]+)" target="_blank">[^<]+</a></td>'
diff --git a/pyload/plugins/crypter/EasybytezComFolder.py b/pyload/plugins/crypter/EasybytezComFolder.py
new file mode 100644
index 000000000..163f2bdf3
--- /dev/null
+++ b/pyload/plugins/crypter/EasybytezComFolder.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class EasybytezComFolder(SimpleCrypter):
+ __name__ = "EasybytezComFolder"
+ __type__ = "crypter"
+ __version__ = "0.06"
+
+ __pattern__ = r'http://(?:www\.)?easybytez\.com/users/(?P<ID>\d+/\d+)'
+
+ __description__ = """Easybytez.com decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ URL_REPLACEMENTS = [(__pattern__, r"http://www.easybytez.com/users/\g<ID>?per_page=10000")]
+
+ LINK_PATTERN = r'<td><a href="(http://www\.easybytez\.com/\w+)" target="_blank">.+(?:</a>)?</td>'
+ TITLE_PATTERN = r'<Title>Files of \d+: (?P<title>.+) folder</Title>'
diff --git a/pyload/plugins/crypter/EmbeduploadCom.py b/pyload/plugins/crypter/EmbeduploadCom.py
new file mode 100644
index 000000000..476767f94
--- /dev/null
+++ b/pyload/plugins/crypter/EmbeduploadCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+from pyload.network.HTTPRequest import BadHeader
+
+
+class EmbeduploadCom(Crypter):
+ __name__ = "EmbeduploadCom"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?embedupload.com/\?d=.*'
+ __config__ = [("preferedHoster", "str", "Prefered hoster list (bar-separated) ", "embedupload"),
+ ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
+
+ __description__ = """EmbedUpload.com decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'<div id="([^"]+)"[^>]*>\s*<a href="([^"]+)" target="_blank" (?:class="DownloadNow"|style="color:red")>'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ tmp_links = []
+
+ m = re.findall(self.LINK_PATTERN, self.html)
+ if m:
+ prefered_set = set(self.getConfig("preferedHoster").split('|'))
+ prefered_set = map(lambda s: s.lower().split('.')[0], prefered_set)
+ print "PF", prefered_set
+ tmp_links.extend([x[1] for x in m if x[0] in prefered_set])
+ self.urls = self.getLocation(tmp_links)
+
+ if not self.urls:
+ ignored_set = set(self.getConfig("ignoredHoster").split('|'))
+ ignored_set = map(lambda s: s.lower().split('.')[0], ignored_set)
+ print "IG", ignored_set
+ tmp_links.extend([x[1] for x in m if x[0] not in ignored_set])
+ self.urls = self.getLocation(tmp_links)
+
+ if not self.urls:
+ self.fail('Could not extract any links')
+
+ def getLocation(self, tmp_links):
+ new_links = []
+ for link in tmp_links:
+ try:
+ header = self.load(link, just_header=True)
+ if "location" in header:
+ new_links.append(header['location'])
+ except BadHeader:
+ pass
+ return new_links
diff --git a/pyload/plugins/crypter/FilebeerInfoFolder.py b/pyload/plugins/crypter/FilebeerInfoFolder.py
new file mode 100644
index 000000000..ee577a865
--- /dev/null
+++ b/pyload/plugins/crypter/FilebeerInfoFolder.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class FilebeerInfoFolder(DeadCrypter):
+ __name__ = "FilebeerInfoFolder"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?filebeer\.info/(\d+~f).*'
+
+ __description__ = """Filebeer.info folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
diff --git a/pyload/plugins/crypter/FilecloudIoFolder.py b/pyload/plugins/crypter/FilecloudIoFolder.py
new file mode 100644
index 000000000..577dd43a3
--- /dev/null
+++ b/pyload/plugins/crypter/FilecloudIoFolder.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilecloudIoFolder(SimpleCrypter):
+ __name__ = "FilecloudIoFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+'
+
+ __description__ = """Filecloud.io folder decrypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ LINK_PATTERN = r'href="(http://filecloud.io/\w+)" title'
+ TITLE_PATTERN = r'>(?P<title>.+?) - filecloud.io<'
diff --git a/pyload/plugins/crypter/FilefactoryComFolder.py b/pyload/plugins/crypter/FilefactoryComFolder.py
new file mode 100644
index 000000000..6886fa5b1
--- /dev/null
+++ b/pyload/plugins/crypter/FilefactoryComFolder.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilefactoryComFolder(SimpleCrypter):
+ __name__ = "FilefactoryComFolder"
+ __type__ = "crypter"
+ __version__ = "0.2"
+
+ __pattern__ = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+'
+
+ __description__ = """Filefactory.com folder decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<td><a href="([^"]+)">'
+ TITLE_PATTERN = r'<h1>Files in <span>(?P<title>.+)</span></h1>'
+ PAGES_PATTERN = r'data-paginator-totalPages="(?P<pages>\d+)"'
+
+ SH_COOKIES = [('.filefactory.com', 'locale', 'en_US.utf8')]
+
+
+ def loadPage(self, page_n):
+ return self.load(self.pyfile.url, get={'page': page_n})
diff --git a/pyload/plugins/crypter/FilerNetFolder.py b/pyload/plugins/crypter/FilerNetFolder.py
new file mode 100644
index 000000000..4acb7e165
--- /dev/null
+++ b/pyload/plugins/crypter/FilerNetFolder.py
@@ -0,0 +1,22 @@
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilerNetFolder(SimpleCrypter):
+ __name__ = "FilerNetFolder"
+ __type__ = "crypter"
+ __version__ = "0.3"
+
+ __pattern__ = r'https?://filer\.net/folder/\w{16}'
+
+ __description__ = """Filer.net decrypter plugin"""
+ __author_name_ = ("nath_schwarz", "stickell")
+ __author_mail_ = ("nathan.notwhite@gmail.com", "l.stickell@yahoo.it")
+
+ LINK_PATTERN = r'href="(/get/\w{16})">(?!<)'
+ TITLE_PATTERN = r'<h3>(?P<title>.+) - <small'
+
+
+ def getLinks(self):
+ return ['http://filer.net%s' % link for link in re.findall(self.LINK_PATTERN, self.html)]
diff --git a/pyload/plugins/crypter/FileserveComFolder.py b/pyload/plugins/crypter/FileserveComFolder.py
new file mode 100644
index 000000000..52e1df6b4
--- /dev/null
+++ b/pyload/plugins/crypter/FileserveComFolder.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Crypter import Crypter
+
+
+class FileserveComFolder(Crypter):
+ __name__ = "FileserveComFolder"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?fileserve.com/list/\w+'
+
+ __description__ = """FileServe.com folder decrypter plugin"""
+ __author_name__ = "fionnc"
+ __author_mail__ = "fionnc@gmail.com"
+
+ FOLDER_PATTERN = r'<table class="file_list">(.*?)</table>'
+ LINK_PATTERN = r'<a href="([^"]+)" class="sheet_icon wbold">'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ new_links = []
+
+ folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
+ if folder is None:
+ self.fail("Parse error (FOLDER)")
+
+ new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1)))
+
+ if new_links:
+ self.urls = [map(lambda s: "http://fileserve.com%s" % s, new_links)]
+ else:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/FilestubeCom.py b/pyload/plugins/crypter/FilestubeCom.py
new file mode 100644
index 000000000..fc80762d1
--- /dev/null
+++ b/pyload/plugins/crypter/FilestubeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FilestubeCom(SimpleCrypter):
+ __name__ = "FilestubeCom"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?filestube\.(?:com|to)/\w+'
+
+ __description__ = """Filestube.com decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)'
+ TITLE_PATTERN = r'<h1\s*> (?P<title>.+) download\s*</h1>'
diff --git a/pyload/plugins/crypter/FiletramCom.py b/pyload/plugins/crypter/FiletramCom.py
new file mode 100644
index 000000000..6620adc12
--- /dev/null
+++ b/pyload/plugins/crypter/FiletramCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FiletramCom(SimpleCrypter):
+ __name__ = "FiletramCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?filetram.com/[^/]+/.+'
+
+ __description__ = """Filetram.com decrypter plugin"""
+ __author_name__ = ("igel", "stickell")
+ __author_mail__ = ("igelkun@myopera.com", "l.stickell@yahoo.it")
+
+ LINK_PATTERN = r'\s+(http://.+)'
+ TITLE_PATTERN = r'<title>(?P<title>[^<]+) - Free Download[^<]*</title>'
diff --git a/pyload/plugins/crypter/FiredriveComFolder.py b/pyload/plugins/crypter/FiredriveComFolder.py
new file mode 100644
index 000000000..072a548a2
--- /dev/null
+++ b/pyload/plugins/crypter/FiredriveComFolder.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FiredriveComFolder(SimpleCrypter):
+ __name__ = "FiredriveComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+'
+
+ __description__ = """Firedrive.com folder decrypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ LINK_PATTERN = r'<div class="pf_item pf_(file|folder).+?public=\'(.+?)\''
+ TITLE_PATTERN = r'>Shared Folder "(?P<title>.+)" | Firedrive<'
+ OFFLINE_PATTERN = r'class="sad_face_image"|>No such page here.<'
+ TEMP_OFFLINE_PATTERN = r'>(File Temporarily Unavailable|Server Error. Try again later)'
+
+
+ def getLinks(self):
+ return map(lambda x: "http://www.firedrive.com/%s/%s" %
+ ("share" if x[0] == "folder" else "file", x[1]),
+ re.findall(self.LINK_PATTERN, self.html))
diff --git a/pyload/plugins/crypter/FourChanOrg.py b/pyload/plugins/crypter/FourChanOrg.py
new file mode 100644
index 000000000..2d3bfa07a
--- /dev/null
+++ b/pyload/plugins/crypter/FourChanOrg.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599)
+
+import re
+
+from pyload.plugins.Crypter import Crypter
+
+
+class FourChanOrg(Crypter):
+ __name__ = "FourChanOrg"
+ __type__ = "crypter"
+ __version__ = "0.3"
+
+ __pattern__ = r'http://(?:www\.)?boards\.4chan.org/\w+/res/(\d+)'
+
+ __description__ = """4chan.org folder decrypter plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+
+ def decrypt(self, pyfile):
+ pagehtml = self.load(pyfile.url)
+ images = set(re.findall(r'(images\.4chan\.org/[^/]*/src/[^"<]*)', pagehtml))
+ self.urls = ["http://" + image for image in images]
diff --git a/pyload/plugins/crypter/FreakhareComFolder.py b/pyload/plugins/crypter/FreakhareComFolder.py
new file mode 100644
index 000000000..fca1b26a1
--- /dev/null
+++ b/pyload/plugins/crypter/FreakhareComFolder.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreakhareComFolder(SimpleCrypter):
+ __name__ = "FreakhareComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?freakshare\.com/folder/.+'
+
+ __description__ = """Freakhare.com folder decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<a href="(http://freakshare.com/files/[^"]+)" target="_blank">'
+ TITLE_PATTERN = r'Folder:</b> (?P<title>.+)'
+ PAGES_PATTERN = r'Pages: +(?P<pages>\d+)'
+
+
+ def loadPage(self, page_n):
+ if not hasattr(self, 'f_id') and not hasattr(self, 'f_md5'):
+ m = re.search(r'http://freakshare.com/\?x=folder&f_id=(\d+)&f_md5=(\w+)', self.html)
+ if m:
+ self.f_id = m.group(1)
+ self.f_md5 = m.group(2)
+ return self.load('http://freakshare.com/', get={'x': 'folder',
+ 'f_id': self.f_id,
+ 'f_md5': self.f_md5,
+ 'entrys': '20',
+ 'page': page_n - 1,
+ 'order': ''}, decode=True)
diff --git a/pyload/plugins/crypter/FreetexthostCom.py b/pyload/plugins/crypter/FreetexthostCom.py
new file mode 100644
index 000000000..e56d638f0
--- /dev/null
+++ b/pyload/plugins/crypter/FreetexthostCom.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FreetexthostCom(SimpleCrypter):
+ __name__ = "FreetexthostCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?freetexthost\.com/\w+'
+
+ __description__ = """Freetexthost.com decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def getLinks(self):
+ m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.DOTALL)
+ if m is None:
+ self.fail('Unable to extract links | Plugin may be out-of-date')
+ links = m.group(1)
+ return links.strip().split("<br />\r\n")
diff --git a/pyload/plugins/crypter/FshareVnFolder.py b/pyload/plugins/crypter/FshareVnFolder.py
new file mode 100644
index 000000000..1706d97e0
--- /dev/null
+++ b/pyload/plugins/crypter/FshareVnFolder.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class FshareVnFolder(SimpleCrypter):
+ __name__ = "FshareVnFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?fshare.vn/folder/.*'
+
+ __description__ = """Fshare.vn folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'<li class="w_80pc"><a href="([^"]+)" target="_blank">'
diff --git a/pyload/plugins/crypter/GooGl.py b/pyload/plugins/crypter/GooGl.py
new file mode 100644
index 000000000..93f3456cc
--- /dev/null
+++ b/pyload/plugins/crypter/GooGl.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import json_loads
+
+
+class GooGl(Crypter):
+ __name__ = "GooGl"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?goo\.gl/\w+'
+
+ __description__ = """Goo.gl decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ API_URL = "https://www.googleapis.com/urlshortener/v1/url"
+
+
+ def decrypt(self, pyfile):
+ rep = self.load(self.API_URL, get={'shortUrl': pyfile.url})
+ self.logDebug('JSON data: ' + rep)
+ rep = json_loads(rep)
+
+ if 'longUrl' in rep:
+ self.urls = [rep['longUrl']]
+ else:
+ self.fail('Unable to expand shortened link')
diff --git a/pyload/plugins/crypter/HoerbuchIn.py b/pyload/plugins/crypter/HoerbuchIn.py
new file mode 100644
index 000000000..fa8e1dc34
--- /dev/null
+++ b/pyload/plugins/crypter/HoerbuchIn.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
+
+from pyload.plugins.Crypter import Crypter
+
+
+class HoerbuchIn(Crypter):
+ __name__ = "HoerbuchIn"
+ __type__ = "crypter"
+ __version__ = "0.6"
+
+ __pattern__ = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out.php\?.+|protection/folder_\d+\.html)'
+
+ __description__ = """Hoerbuch.in decrypter plugin"""
+ __author_name__ = ("spoob", "mkaay")
+ __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de")
+
+ article = re.compile("http://(?:www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/")
+ protection = re.compile("http://(?:www\.)?hoerbuch\.in/protection/folder_\d+.html")
+
+
+ def decrypt(self, pyfile):
+ self.pyfile = pyfile
+
+ if self.article.match(pyfile.url):
+ src = self.load(pyfile.url)
+ soup = BeautifulSoup(src, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
+
+ abookname = soup.find("a", attrs={"rel": "bookmark"}).text
+ for a in soup.findAll("a", attrs={"href": self.protection}):
+ package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1])
+ links = self.decryptFolder(a['href'])
+
+ self.packages.append((package, links, package))
+ else:
+ self.urls = self.decryptFolder(pyfile.url)
+
+ def decryptFolder(self, url):
+ m = self.protection.search(url)
+ if m is None:
+ self.fail("Bad URL")
+ url = m.group(0)
+
+ self.pyfile.url = url
+ src = self.req.load(url, post={"viewed": "adpg"})
+
+ links = []
+ pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"")
+ for hoster, lid in pattern.findall(src):
+ self.req.lastURL = url
+ self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid))
+ links.append(self.req.lastEffectiveURL)
+
+ return links
diff --git a/pyload/plugins/crypter/HotfileFolderCom.py b/pyload/plugins/crypter/HotfileFolderCom.py
new file mode 100644
index 000000000..1b5ce28b6
--- /dev/null
+++ b/pyload/plugins/crypter/HotfileFolderCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Crypter import Crypter
+
+
+class HotfileFolderCom(Crypter):
+ __name__ = "HotfileFolderCom"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?hotfile.com/list/\w+/\w+'
+
+ __description__ = """Hotfile.com folder decrypter plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ name = re.findall(
+ r'<img src="/i/folder.gif" width="23" height="14" style="margin-bottom: -2px;" />([^<]+)', html,
+ re.MULTILINE)[0].replace("/", "")
+ new_links = re.findall(r'href="(http://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+[^"]+)', html)
+
+ new_links = [x[0] for x in new_links]
+
+ self.packages = [(name, new_links, name)]
diff --git a/pyload/plugins/crypter/ILoadTo.py b/pyload/plugins/crypter/ILoadTo.py
new file mode 100644
index 000000000..16f813926
--- /dev/null
+++ b/pyload/plugins/crypter/ILoadTo.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class ILoadTo(DeadCrypter):
+ __name__ = "ILoadTo"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?iload\.to/go/\d+-[\w\.-]+/'
+
+ __description__ = """Iload.to decrypter plugin"""
+ __author_name__ = "hzpz"
+ __author_mail__ = None
diff --git a/pyload/plugins/crypter/ImgurComAlbum.py b/pyload/plugins/crypter/ImgurComAlbum.py
new file mode 100644
index 000000000..5e8be3a5d
--- /dev/null
+++ b/pyload/plugins/crypter/ImgurComAlbum.py
@@ -0,0 +1,24 @@
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import uniqify
+
+
+class ImgurComAlbum(SimpleCrypter):
+ __name__ = "ImgurComAlbum"
+ __type__ = "crypter"
+ __version__ = "0.4"
+
+ __pattern__ = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}'
+
+ __description__ = """Imgur.com decrypter plugin"""
+ __author_name_ = "nath_schwarz"
+ __author_mail_ = "nathan.notwhite@gmail.com"
+
+ TITLE_PATTERN = r'(?P<title>.+) - Imgur'
+ LINK_PATTERN = r'i\.imgur\.com/\w{7}s?\.(?:jpeg|jpg|png|gif|apng)'
+
+
+ def getLinks(self):
+ f = lambda url: "http://" + re.sub(r'(\w{7})s\.', r'\1.', url)
+ return uniqify(map(f, re.findall(self.LINK_PATTERN, self.html)))
diff --git a/pyload/plugins/crypter/LetitbitNetFolder.py b/pyload/plugins/crypter/LetitbitNetFolder.py
new file mode 100644
index 000000000..b03ea27b2
--- /dev/null
+++ b/pyload/plugins/crypter/LetitbitNetFolder.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class LetitbitNetFolder(Crypter):
+ __name__ = "LetitbitNetFolder"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?letitbit.net/folder/\w+'
+
+ __description__ = """Letitbit.net folder decrypter plugin"""
+ __author_name__ = ("DHMH", "z00nx")
+ __author_mail__ = ("webmaster@pcProfil.de", "z00nx0@gmail.com")
+
+ FOLDER_PATTERN = r'<table>(.*)</table>'
+ LINK_PATTERN = r'<a href="([^"]+)" target="_blank">'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ folder = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
+ if folder is None:
+ self.fail("Parse error (FOLDER)")
+
+ self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0)))
+
+ if not self.urls:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/LinkSaveIn.py b/pyload/plugins/crypter/LinkSaveIn.py
new file mode 100644
index 000000000..4a56606c8
--- /dev/null
+++ b/pyload/plugins/crypter/LinkSaveIn.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+#
+# * cnl2 and web links are skipped if JS is not available (instead of failing the package)
+# * only best available link source is used (priority: cnl2>rsdf>ccf>dlc>web
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import html_unescape
+
+
+class LinkSaveIn(Crypter):
+ __name__ = "LinkSaveIn"
+ __type__ = "crypter"
+ __version__ = "2.01"
+
+ __pattern__ = r'http://(?:www\.)?linksave.in/(?P<id>\w+)$'
+
+ __description__ = """LinkSave.in decrypter plugin"""
+ __author_name__ = "fragonib"
+ __author_mail__ = "fragonib[AT]yahoo[DOT]es"
+
+ # Constants
+ _JK_KEY_ = "jk"
+ _CRYPTED_KEY_ = "crypted"
+ HOSTER_NAME = "linksave.in"
+
+
+ def setup(self):
+ self.html = None
+ self.fileid = None
+ self.captcha = False
+ self.package = None
+ self.preferred_sources = ["cnl2", "rsdf", "ccf", "dlc", "web"]
+
+ def decrypt(self, pyfile):
+ # Init
+ self.package = pyfile.package()
+ self.fileid = re.match(self.__pattern__, pyfile.url).group('id')
+ self.req.cj.setCookie(self.HOSTER_NAME, "Linksave_Language", "english")
+
+ # Request package
+ self.html = self.load(pyfile.url)
+ if not self.isOnline():
+ self.offline()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleErrors()
+
+ # Get package name and folder
+ (package_name, folder_name) = self.getPackageInfo()
+
+ # Extract package links
+ package_links = []
+ for type_ in self.preferred_sources:
+ package_links.extend(self.handleLinkSource(type_))
+ if package_links: # use only first source which provides links
+ break
+ package_links = set(package_links)
+
+ # Pack
+ if package_links:
+ self.packages = [(package_name, package_links, folder_name)]
+ else:
+ self.fail('Could not extract any links')
+
+ def isOnline(self):
+ if "<big>Error 404 - Folder not found!</big>" in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+ def isPasswordProtected(self):
+ if re.search(r'''<input.*?type="password"''', self.html):
+ self.logDebug("Links are password protected")
+ return True
+
+ def isCaptchaProtected(self):
+ if "<b>Captcha:</b>" in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ return False
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ post = {"id": self.fileid, "besucherpasswort": password, 'login': 'submit'}
+ self.html = self.load(self.pyfile.url, post=post)
+
+ def unlockCaptchaProtection(self):
+ captcha_hash = re.search(r'name="hash" value="([^"]+)', self.html).group(1)
+ captcha_url = re.search(r'src=".(/captcha/cap.php\?hsh=[^"]+)', self.html).group(1)
+ captcha_code = self.decryptCaptcha("http://linksave.in" + captcha_url, forceUser=True)
+ self.html = self.load(self.pyfile.url, post={"id": self.fileid, "hash": captcha_hash, "code": captcha_code})
+
+ def getPackageInfo(self):
+ name = self.pyfile.package().name
+ folder = self.pyfile.package().folder
+ self.logDebug("Defaulting to pyfile name [%s] and folder [%s] for package" % (name, folder))
+ return name, folder
+
+ def handleErrors(self):
+ if "The visitorpassword you have entered is wrong" in self.html:
+ self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
+ self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
+
+ if self.captcha:
+ if "Wrong code. Please retry" in self.html:
+ self.logDebug("Invalid captcha, retrying")
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+ def handleLinkSource(self, type_):
+ if type_ == "cnl2":
+ return self.handleCNL2()
+ elif type_ in ("rsdf", "ccf", "dlc"):
+ return self.handleContainer(type_)
+ elif type_ == "web":
+ return self.handleWebLinks()
+ else:
+ self.fail('unknown source type "%s" (this is probably a bug)' % type_)
+
+ def handleWebLinks(self):
+ package_links = []
+ self.logDebug("Search for Web links")
+ if not self.js:
+ self.logDebug("no JS -> skip Web links")
+ else:
+ #@TODO: Gather paginated web links
+ pattern = r'<a href="http://linksave\.in/(\w{43})"'
+ ids = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Web links" % len(ids))
+ for i, weblink_id in enumerate(ids):
+ try:
+ webLink = "http://linksave.in/%s" % weblink_id
+ self.logDebug("Decrypting Web link %d, %s" % (i + 1, webLink))
+ fwLink = "http://linksave.in/fw-%s" % weblink_id
+ response = self.load(fwLink)
+ jscode = re.findall(r'<script type="text/javascript">(.*)</script>', response)[-1]
+ jseval = self.js.eval("document = { write: function(e) { return e; } }; %s" % jscode)
+ dlLink = re.search(r'http://linksave\.in/dl-\w+', jseval).group(0)
+ self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
+ response = self.load(dlLink)
+ link = html_unescape(re.search(r'<iframe src="(.+?)"', response).group(1))
+ package_links.append(link)
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (webLink, detail))
+ return package_links
+
+ def handleContainer(self, type_):
+ package_links = []
+ type_ = type_.lower()
+ self.logDebug('Seach for %s Container links' % type_.upper())
+ if not type_.isalnum(): # check to prevent broken re-pattern (cnl2,rsdf,ccf,dlc,web are all alpha-numeric)
+ self.fail('unknown container type "%s" (this is probably a bug)' % type_)
+ pattern = r"\('%s_link'\).href=unescape\('(.*?\.%s)'\)" % (type_, type_)
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Found %d %s Container links" % (len(containersLinks), type_.upper()))
+ for containerLink in containersLinks:
+ link = "http://linksave.in/%s" % html_unescape(containerLink)
+ package_links.append(link)
+ return package_links
+
+ def handleCNL2(self):
+ package_links = []
+ self.logDebug("Search for CNL2 links")
+ if not self.js:
+ self.logDebug("no JS -> skip CNL2 links")
+ elif 'cnl2_load' in self.html:
+ try:
+ (vcrypted, vjk) = self._getCipherParams()
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except:
+ self.fail("Unable to decrypt CNL2 links")
+ return package_links
+
+ def _getCipherParams(self):
+ # Get jk
+ jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._JK_KEY_
+ vjk = re.findall(jk_re, self.html)
+
+ # Get crypted
+ crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkSaveIn._CRYPTED_KEY_
+ vcrypted = re.findall(crypted_re, self.html)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/LinkdecrypterCom.py b/pyload/plugins/crypter/LinkdecrypterCom.py
new file mode 100644
index 000000000..b6ca2ec4f
--- /dev/null
+++ b/pyload/plugins/crypter/LinkdecrypterCom.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class LinkdecrypterCom(Crypter):
+ __name__ = "LinkdecrypterCom"
+ __type__ = "crypter"
+ __version__ = "0.27"
+
+ __pattern__ = None
+
+ __description__ = """Linkdecrypter.com"""
+ __author_name__ = ("zoidberg", "flowlee")
+ __author_mail__ = ("zoidberg@mujmail.cz", "")
+
+ TEXTAREA_PATTERN = r'<textarea name="links" wrap="off" readonly="1" class="caja_des">(.+)</textarea>'
+ PASSWORD_PATTERN = r'<input type="text" name="password"'
+ CAPTCHA_PATTERN = r'<img class="captcha" src="(.+?)"(.*?)>'
+ REDIR_PATTERN = r'<i>(Click <a href="./">here</a> if your browser does not redirect you).</i>'
+
+
+ def decrypt(self, pyfile):
+
+ self.passwords = self.getPassword().splitlines()
+
+ # API not working anymore
+ self.urls = self.decryptHTML()
+ if not self.urls:
+ self.fail('Could not extract any links')
+
+ def decryptAPI(self):
+
+ get_dict = {"t": "link", "url": self.pyfile.url, "lcache": "1"}
+ self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
+ if self.html.startswith('http://'):
+ return self.html.splitlines()
+
+ if self.html == 'INTERRUPTION(PASSWORD)':
+ for get_dict['pass'] in self.passwords:
+ self.html = self.load('http://linkdecrypter.com/api', get=get_dict)
+ if self.html.startswith('http://'):
+ return self.html.splitlines()
+
+ self.logError('API', self.html)
+ if self.html == 'INTERRUPTION(PASSWORD)':
+ self.fail("No or incorrect password")
+
+ return None
+
+ def decryptHTML(self):
+
+ retries = 5
+
+ post_dict = {"link_cache": "on", "pro_links": self.pyfile.url, "modo_links": "text"}
+ self.html = self.load('http://linkdecrypter.com/', post=post_dict, cookies=True, decode=True)
+
+ while self.passwords or retries:
+ m = re.search(self.TEXTAREA_PATTERN, self.html, flags=re.DOTALL)
+ if m:
+ return [x for x in m.group(1).splitlines() if '[LINK-ERROR]' not in x]
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_url = 'http://linkdecrypter.com/' + m.group(1)
+ result_type = "positional" if "getPos" in m.group(2) else "textual"
+
+ m = re.search(r"<p><i><b>([^<]+)</b></i></p>", self.html)
+ msg = m.group(1) if m else ""
+ self.logInfo("Captcha protected link", result_type, msg)
+
+ captcha = self.decryptCaptcha(captcha_url, result_type=result_type)
+ if result_type == "positional":
+ captcha = "%d|%d" % captcha
+ self.html = self.load('http://linkdecrypter.com/', post={"captcha": captcha}, decode=True)
+ retries -= 1
+
+ elif self.PASSWORD_PATTERN in self.html:
+ if self.passwords:
+ password = self.passwords.pop(0)
+ self.logInfo("Password protected link, trying " + password)
+ self.html = self.load('http://linkdecrypter.com/', post={'password': password}, decode=True)
+ else:
+ self.fail("No or incorrect password")
+
+ else:
+ retries -= 1
+ self.html = self.load('http://linkdecrypter.com/', cookies=True, decode=True)
+
+ return None
diff --git a/pyload/plugins/crypter/LixIn.py b/pyload/plugins/crypter/LixIn.py
new file mode 100644
index 000000000..1d812b0e3
--- /dev/null
+++ b/pyload/plugins/crypter/LixIn.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Crypter import Crypter
+
+
+class LixIn(Crypter):
+ __name__ = "LixIn"
+ __type__ = "crypter"
+ __version__ = "0.22"
+
+ __pattern__ = r'http://(www.)?lix.in/(?P<id>.*)'
+
+ __description__ = """Lix.in decrypter plugin"""
+ __author_name__ = "spoob"
+ __author_mail__ = "spoob@pyload.org"
+
+ CAPTCHA_PATTERN = r'<img src="(?P<image>captcha_img.php\?.*?)"'
+ SUBMIT_PATTERN = r"value='continue.*?'"
+ LINK_PATTERN = r'name="ifram" src="(?P<link>.*?)"'
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+
+ m = re.match(self.__pattern__, url)
+ if m is None:
+ self.fail("couldn't identify file id")
+
+ id = m.group("id")
+ self.logDebug("File id is %s" % id)
+
+ self.html = self.req.load(url, decode=True)
+
+ m = re.search(self.SUBMIT_PATTERN, self.html)
+ if m is None:
+ self.fail("link doesn't seem valid")
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ for _ in xrange(5):
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ self.logDebug("trying captcha")
+ captcharesult = self.decryptCaptcha("http://lix.in/" + m.group("image"))
+ self.html = self.req.load(url, decode=True,
+ post={"capt": captcharesult, "submit": "submit", "tiny": id})
+ else:
+ self.logDebug("no captcha/captcha solved")
+ else:
+ self.html = self.req.load(url, decode=True, post={"submit": "submit", "tiny": id})
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.fail("can't find destination url")
+ else:
+ self.urls = [m.group("link")]
+ self.logDebug("Found link %s, adding to package" % self.urls[0])
diff --git a/pyload/plugins/crypter/LofCc.py b/pyload/plugins/crypter/LofCc.py
new file mode 100644
index 000000000..6c91a55ec
--- /dev/null
+++ b/pyload/plugins/crypter/LofCc.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class LofCc(DeadCrypter):
+ __name__ = "LofCc"
+ __type__ = "crypter"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?lof.cc/(.*)'
+
+ __description__ = """Lof.cc decrypter plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
diff --git a/pyload/plugins/crypter/MBLinkInfo.py b/pyload/plugins/crypter/MBLinkInfo.py
new file mode 100644
index 000000000..8516ff6e4
--- /dev/null
+++ b/pyload/plugins/crypter/MBLinkInfo.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class MBLinkInfo(DeadCrypter):
+ __name__ = "MBLinkInfo"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)'
+
+ __description__ = """MBLink.info decrypter plugin"""
+ __author_name__ = ("Gummibaer", "stickell")
+ __author_mail__ = ("Gummibaer@wiki-bierkiste.de", "l.stickell@yahoo.it")
diff --git a/pyload/plugins/crypter/MediafireComFolder.py b/pyload/plugins/crypter/MediafireComFolder.py
new file mode 100644
index 000000000..98c05f450
--- /dev/null
+++ b/pyload/plugins/crypter/MediafireComFolder.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+from pyload.plugins.hoster.MediafireCom import checkHTMLHeader
+from pyload.utils import json_loads
+
+
+class MediafireComFolder(Crypter):
+ __name__ = "MediafireComFolder"
+ __type__ = "crypter"
+ __version__ = "0.14"
+
+ __pattern__ = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))'
+
+ __description__ = """Mediafire.com folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FOLDER_KEY_PATTERN = r"var afI= '(\w+)';"
+ FILE_URL_PATTERN = r'<meta property="og:url" content="http://www.mediafire.com/\?(\w+)"/>'
+
+
+ def decrypt(self, pyfile):
+ url, result = checkHTMLHeader(pyfile.url)
+ self.logDebug('Location (%d): %s' % (result, url))
+
+ if result == 0:
+ # load and parse html
+ html = self.load(pyfile.url)
+ m = re.search(self.FILE_URL_PATTERN, html)
+ if m:
+ # file page
+ self.urls.append("http://www.mediafire.com/file/%s" % m.group(1))
+ else:
+ # folder page
+ m = re.search(self.FOLDER_KEY_PATTERN, html)
+ if m:
+ folder_key = m.group(1)
+ self.logDebug("FOLDER KEY: %s" % folder_key)
+
+ json_resp = json_loads(self.load(
+ "http://www.mediafire.com/api/folder/get_info.php?folder_key=%s&response_format=json&version=1" % folder_key))
+ #self.logInfo(json_resp)
+ if json_resp['response']['result'] == "Success":
+ for link in json_resp['response']['folder_info']['files']:
+ self.urls.append("http://www.mediafire.com/file/%s" % link['quickkey'])
+ else:
+ self.fail(json_resp['response']['message'])
+ elif result == 1:
+ self.offline()
+ else:
+ self.urls.append(url)
+
+ if not self.urls:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/Movie2kTo.py b/pyload/plugins/crypter/Movie2kTo.py
new file mode 100644
index 000000000..b6a554758
--- /dev/null
+++ b/pyload/plugins/crypter/Movie2kTo.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class Movie2kTo(DeadCrypter):
+ __name__ = "Movie2kTo"
+ __type__ = "crypter"
+ __version__ = "0.51"
+
+ __pattern__ = r'http://(?:www\.)?movie2k\.to/(.*)\.html'
+
+ __description__ = """Movie2k.to decrypter plugin"""
+ __author_name__ = "4Christopher"
+ __author_mail__ = "4Christopher@gmx.de"
diff --git a/pyload/plugins/crypter/MultiUpOrg.py b/pyload/plugins/crypter/MultiUpOrg.py
new file mode 100644
index 000000000..96553a09a
--- /dev/null
+++ b/pyload/plugins/crypter/MultiUpOrg.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import re
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class MultiUpOrg(SimpleCrypter):
+ __name__ = "MultiUpOrg"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?'
+
+ __description__ = """MultiUp.org crypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ TITLE_PATTERN = r'<title>.*(Project|Projet|ownload|élécharger) (?P<title>.+?) (\(|- )'
+
+
+ def getLinks(self):
+ m_type = re.match(self.__pattern__, self.pyfile.url).group("TYPE")
+
+ if m_type == "project":
+ pattern = r'\n(http://www\.multiup\.org/(?:en|fr)/download/.*)'
+ else:
+ pattern = r'style="width:97%;text-align:left".*\n.*href="(.*)"'
+ if m_type == "download":
+ dl_pattern = r'href="(.*)">.*\n.*<h5>DOWNLOAD</h5>'
+ miror_page = urljoin("http://www.multiup.org", re.search(dl_pattern, self.html).group(1))
+ self.html = self.load(miror_page)
+
+ return re.findall(pattern, self.html)
diff --git a/pyload/plugins/crypter/MultiloadCz.py b/pyload/plugins/crypter/MultiloadCz.py
new file mode 100644
index 000000000..be7950e98
--- /dev/null
+++ b/pyload/plugins/crypter/MultiloadCz.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class MultiloadCz(Crypter):
+ __name__ = "MultiloadCz"
+ __type__ = "crypter"
+ __version__ = "0.4"
+
+ __pattern__ = r'http://(?:[^/]*\.)?multiload.cz/(stahnout|slozka)/.*'
+ __config__ = [("usedHoster", "str", "Prefered hoster list (bar-separated) ", ""),
+ ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
+
+ __description__ = """Multiload.cz decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FOLDER_PATTERN = r'<form action="" method="get"><textarea[^>]*>([^>]*)</textarea></form>'
+ LINK_PATTERN = r'<p class="manager-server"><strong>([^<]+)</strong></p><p class="manager-linky"><a href="([^"]+)">'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+
+ if re.match(self.__pattern__, pyfile.url).group(1) == "slozka":
+ m = re.search(self.FOLDER_PATTERN, self.html)
+ if m:
+ self.urls.extend(m.group(1).split())
+ else:
+ m = re.findall(self.LINK_PATTERN, self.html)
+ if m:
+ prefered_set = set(self.getConfig("usedHoster").split('|'))
+ self.urls.extend([x[1] for x in m if x[0] in prefered_set])
+
+ if not self.urls:
+ ignored_set = set(self.getConfig("ignoredHoster").split('|'))
+ self.urls.extend([x[1] for x in m if x[0] not in ignored_set])
+
+ if not self.urls:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/MultiuploadCom.py b/pyload/plugins/crypter/MultiuploadCom.py
new file mode 100644
index 000000000..b1650b647
--- /dev/null
+++ b/pyload/plugins/crypter/MultiuploadCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+from time import time
+
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import json_loads
+
+
+class MultiuploadCom(Crypter):
+ __name__ = "MultiuploadCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?multiupload.com/(\w+)'
+ __config__ = [("preferedHoster", "str", "Prefered hoster list (bar-separated) ", "multiupload"),
+ ("ignoredHoster", "str", "Ignored hoster list (bar-separated) ", "")]
+
+ __description__ = """MultiUpload.com decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ ML_LINK_PATTERN = r'<div id="downloadbutton_" style=""><a href="([^"]+)"'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+ m = re.search(self.ML_LINK_PATTERN, self.html)
+ ml_url = m.group(1) if m else None
+
+ json_list = json_loads(self.load("http://multiupload.com/progress/", get={
+ "d": re.match(self.__pattern__, pyfile.url).group(1),
+ "r": str(int(time() * 1000))
+ }))
+
+ prefered_set = map(lambda s: s.lower().split('.')[0], set(self.getConfig("preferedHoster").split('|')))
+
+ if ml_url and 'multiupload' in prefered_set:
+ self.urls.append(ml_url)
+
+ for link in json_list:
+ if link['service'].lower() in prefered_set and int(link['status']) and not int(link['deleted']):
+ url = self.getLocation(link['url'])
+ if url:
+ self.urls.append(url)
+
+ if not self.urls:
+ ignored_set = map(lambda s: s.lower().split('.')[0], set(self.getConfig("ignoredHoster").split('|')))
+
+ if 'multiupload' not in ignored_set:
+ self.urls.append(ml_url)
+
+ for link in json_list:
+ if link['service'].lower() not in ignored_set and int(link['status']) and not int(link['deleted']):
+ url = self.getLocation(link['url'])
+ if url:
+ self.urls.append(url)
+
+ if not self.urls:
+ self.fail('Could not extract any links')
+
+ def getLocation(self, url):
+ header = self.load(url, just_header=True)
+ return header['location'] if "location" in header else None
diff --git a/pyload/plugins/crypter/NCryptIn.py b/pyload/plugins/crypter/NCryptIn.py
new file mode 100644
index 000000000..70c541d02
--- /dev/null
+++ b/pyload/plugins/crypter/NCryptIn.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+
+from pyload.plugins.Crypter import Crypter
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+
+
+class NCryptIn(Crypter):
+ __name__ = "NCryptIn"
+ __type__ = "crypter"
+ __version__ = "1.32"
+
+ __pattern__ = r'http://(?:www\.)?ncrypt.in/(?P<type>folder|link|frame)-([^/\?]+)'
+
+ __description__ = """NCrypt.in decrypter plugin"""
+ __author_name__ = ("fragonib", "stickell")
+ __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "l.stickell@yahoo.it")
+
+ JK_KEY = "jk"
+ CRYPTED_KEY = "crypted"
+
+ NAME_PATTERN = r'<meta name="description" content="(?P<N>[^"]+)"'
+
+
+ def setup(self):
+ self.package = None
+ self.html = None
+ self.cleanedHtml = None
+ self.links_source_order = ["cnl2", "rsdf", "ccf", "dlc", "web"]
+ self.protection_type = None
+
+ def decrypt(self, pyfile):
+ # Init
+ self.package = pyfile.package()
+ package_links = []
+ package_name = self.package.name
+ folder_name = self.package.folder
+
+ # Deal with single links
+ if self.isSingleLink():
+ package_links.extend(self.handleSingleLink())
+
+ # Deal with folders
+ else:
+
+ # Request folder home
+ self.html = self.requestFolderHome()
+ self.cleanedHtml = self.removeHtmlCrap(self.html)
+ if not self.isOnline():
+ self.offline()
+
+ # Check for folder protection
+ if self.isProtected():
+ self.html = self.unlockProtection()
+ self.cleanedHtml = self.removeHtmlCrap(self.html)
+ self.handleErrors()
+
+ # Prepare package name and folder
+ (package_name, folder_name) = self.getPackageInfo()
+
+ # Extract package links
+ for link_source_type in self.links_source_order:
+ package_links.extend(self.handleLinkSource(link_source_type))
+ if package_links: # use only first source which provides links
+ break
+ package_links = set(package_links)
+
+ # Pack and return links
+ if not package_links:
+ self.fail('Could not extract any links')
+ self.packages = [(package_name, package_links, folder_name)]
+
+ def isSingleLink(self):
+ link_type = re.match(self.__pattern__, self.pyfile.url).group('type')
+ return link_type in ("link", "frame")
+
+ def requestFolderHome(self):
+ return self.load(self.pyfile.url, decode=True)
+
+ def removeHtmlCrap(self, content):
+ patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")',
+ r'display:none;">(.*?)</(div|span)>',
+ r'<div\s+class="jdownloader"(.*?)</div>',
+ r'<table class="global">(.*?)</table>',
+ r'<iframe\s+style="display:none(.*?)</iframe>')
+ for pattern in patterns:
+ rexpr = re.compile(pattern, re.DOTALL)
+ content = re.sub(rexpr, "", content)
+ return content
+
+ def isOnline(self):
+ if "Your folder does not exist" in self.cleanedHtml:
+ self.logDebug("File not m")
+ return False
+ return True
+
+ def isProtected(self):
+ form = re.search(r'<form.*?name.*?protected.*?>(.*?)</form>', self.cleanedHtml, re.DOTALL)
+ if form is not None:
+ content = form.group(1)
+ for keyword in ("password", "captcha"):
+ if keyword in content:
+ self.protection_type = keyword
+ self.logDebug("Links are %s protected" % self.protection_type)
+ return True
+ return False
+
+ def getPackageInfo(self):
+ m = re.search(self.NAME_PATTERN, self.html)
+ if m:
+ name = folder = m.group('N').strip()
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+ else:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not m, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+ return name, folder
+
+ def unlockProtection(self):
+
+ postData = {}
+
+ form = re.search(r'<form name="protected"(.*?)</form>', self.cleanedHtml, re.DOTALL).group(1)
+
+ # Submit package password
+ if "password" in form:
+ password = self.getPassword()
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ postData['password'] = password
+
+ # Resolve anicaptcha
+ if "anicaptcha" in form:
+ self.logDebug("Captcha protected")
+ captchaUri = re.search(r'src="(/temp/anicaptcha/[^"]+)', form).group(1)
+ captcha = self.decryptCaptcha("http://ncrypt.in" + captchaUri)
+ self.logDebug("Captcha resolved [%s]" % captcha)
+ postData['captcha'] = captcha
+
+ # Resolve recaptcha
+ if "recaptcha" in form:
+ self.logDebug("ReCaptcha protected")
+ captcha_key = re.search(r'\?k=(.*?)"', form).group(1)
+ self.logDebug("Resolving ReCaptcha with key [%s]" % captcha_key)
+ recaptcha = ReCaptcha(self)
+ challenge, code = recaptcha.challenge(captcha_key)
+ postData['recaptcha_challenge_field'] = challenge
+ postData['recaptcha_response_field'] = code
+
+ # Resolve circlecaptcha
+ if "circlecaptcha" in form:
+ self.logDebug("CircleCaptcha protected")
+ captcha_img_url = "http://ncrypt.in/classes/captcha/circlecaptcha.php"
+ coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+ postData['circle.x'] = coords[0]
+ postData['circle.y'] = coords[1]
+
+ # Unlock protection
+ postData['submit_protected'] = 'Continue to folder'
+ return self.load(self.pyfile.url, post=postData, decode=True)
+
+ def handleErrors(self):
+ if self.protection_type == "password":
+ if "This password is invalid!" in self.cleanedHtml:
+ self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
+ self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
+
+ if self.protection_type == "captcha":
+ if "The securitycheck was wrong!" in self.cleanedHtml:
+ self.logDebug("Invalid captcha, retrying")
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+ def handleLinkSource(self, link_source_type):
+ # Check for JS engine
+ require_js_engine = link_source_type in ("cnl2", "rsdf", "ccf", "dlc")
+ if require_js_engine and not self.js:
+ self.logDebug("No JS engine available, skip %s links" % link_source_type)
+ return []
+
+ # Select suitable handler
+ if link_source_type == 'single':
+ return self.handleSingleLink()
+ if link_source_type == 'cnl2':
+ return self.handleCNL2()
+ elif link_source_type in ("rsdf", "ccf", "dlc"):
+ return self.handleContainer(link_source_type)
+ elif link_source_type == "web":
+ return self.handleWebLinks()
+ else:
+ self.fail('unknown source type "%s" (this is probably a bug)' % link_source_type)
+
+ def handleSingleLink(self):
+
+ self.logDebug("Handling Single link")
+ package_links = []
+
+ # Decrypt single link
+ decrypted_link = self.decryptLink(self.pyfile.url)
+ if decrypted_link:
+ package_links.append(decrypted_link)
+
+ return package_links
+
+ def handleCNL2(self):
+
+ self.logDebug("Handling CNL2 links")
+ package_links = []
+
+ if 'cnl2_output' in self.cleanedHtml:
+ try:
+ (vcrypted, vjk) = self._getCipherParams()
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except:
+ self.fail("Unable to decrypt CNL2 links")
+
+ return package_links
+
+ def handleContainers(self):
+
+ self.logDebug("Handling Container links")
+ package_links = []
+
+ pattern = r"/container/(rsdf|dlc|ccf)/([a-z0-9]+)"
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Container links" % len(containersLinks))
+ for containerLink in containersLinks:
+ link = "http://ncrypt.in/container/%s/%s.%s" % (containerLink[0], containerLink[1], containerLink[0])
+ package_links.append(link)
+
+ return package_links
+
+ def handleWebLinks(self):
+
+ self.logDebug("Handling Web links")
+ pattern = r"(http://ncrypt\.in/link-.*?=)"
+ links = re.findall(pattern, self.html)
+
+ package_links = []
+ self.logDebug("Decrypting %d Web links" % len(links))
+ for i, link in enumerate(links):
+ self.logDebug("Decrypting Web link %d, %s" % (i + 1, link))
+ decrypted_link = self.decrypt(link)
+ if decrypted_link:
+ package_links.append(decrypted_link)
+
+ return package_links
+
+ def decryptLink(self, link):
+ try:
+ url = link.replace("link-", "frame-")
+ link = self.load(url, just_header=True)['location']
+ return link
+ except Exception, detail:
+ self.logDebug("Error decrypting link %s, %s" % (link, detail))
+
+ def _getCipherParams(self):
+
+ pattern = r'<input.*?name="%s".*?value="(.*?)"'
+
+ # Get jk
+ jk_re = pattern % NCryptIn.JK_KEY
+ vjk = re.findall(jk_re, self.html)
+
+ # Get crypted
+ crypted_re = pattern % NCryptIn.CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, self.html)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/NetfolderIn.py b/pyload/plugins/crypter/NetfolderIn.py
new file mode 100644
index 000000000..858755e5c
--- /dev/null
+++ b/pyload/plugins/crypter/NetfolderIn.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class NetfolderIn(SimpleCrypter):
+ __name__ = "NetfolderIn"
+ __type__ = "crypter"
+ __version__ = "0.6"
+
+ __pattern__ = r'http://(?:www\.)?netfolder.in/((?P<id1>\w+)/\w+|folder.php\?folder_id=(?P<id2>\w+))'
+
+ __description__ = """NetFolder.in decrypter plugin"""
+ __author_name__ = ("RaNaN", "fragonib")
+ __author_mail__ = ("RaNaN@pyload.org", "fragonib[AT]yahoo[DOT]es")
+
+ TITLE_PATTERN = r'<div class="Text">Inhalt des Ordners <span(.*)>(?P<title>.+)</span></div>'
+
+
+ def decrypt(self, pyfile):
+ # Request package
+ self.html = self.load(pyfile.url)
+
+ # Check for password protection
+ if self.isPasswordProtected():
+ self.html = self.submitPassword()
+ if not self.html:
+ self.fail("Incorrect password, please set right password on Add package form and retry")
+
+ # Get package name and folder
+ (package_name, folder_name) = self.getPackageNameAndFolder()
+
+ # Get package links
+ package_links = self.getLinks()
+
+ # Set package
+ self.packages = [(package_name, package_links, folder_name)]
+
+ def isPasswordProtected(self):
+ if '<input type="password" name="password"' in self.html:
+ self.logDebug("Links are password protected")
+ return True
+ return False
+
+ def submitPassword(self):
+ # Gather data
+ try:
+ m = re.match(self.__pattern__, self.pyfile.url)
+ id = max(m.group('id1'), m.group('id2'))
+ except AttributeError:
+ self.logDebug("Unable to get package id from url [%s]" % self.pyfile.url)
+ return
+ url = "http://netfolder.in/folder.php?folder_id=" + id
+ password = self.getPassword()
+
+ # Submit package password
+ post = {'password': password, 'save': 'Absenden'}
+ self.logDebug("Submitting password [%s] for protected links with id [%s]" % (password, id))
+ html = self.load(url, {}, post)
+
+ # Check for invalid password
+ if '<div class="InPage_Error">' in html:
+ self.logDebug("Incorrect password, please set right password on Edit package form and retry")
+ return None
+
+ return html
+
+ def getLinks(self):
+ links = re.search(r'name="list" value="(.*?)"', self.html).group(1).split(",")
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/NosvideoCom.py b/pyload/plugins/crypter/NosvideoCom.py
new file mode 100644
index 000000000..e1c9e2c55
--- /dev/null
+++ b/pyload/plugins/crypter/NosvideoCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class NosvideoCom(SimpleCrypter):
+ __name__ = "NosvideoCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?nosvideo\.com/\?v=\w+'
+
+ __description__ = """Nosvideo.com decrypter plugin"""
+ __author_name__ = "igel"
+ __author_mail__ = "igelkun@myopera.com"
+
+ LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload.com/\?d=\w+)"'
+ TITLE_PATTERN = r'<[tT]itle>Watch (?P<title>.+)</[tT]itle>'
diff --git a/pyload/plugins/crypter/OneKhDe.py b/pyload/plugins/crypter/OneKhDe.py
new file mode 100644
index 000000000..4f3ab2a20
--- /dev/null
+++ b/pyload/plugins/crypter/OneKhDe.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import html_unescape
+from pyload.plugins.Crypter import Crypter
+
+
+class OneKhDe(Crypter):
+ __name__ = "OneKhDe"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?1kh.de/f/'
+
+ __description__ = """1kh.de decrypter plugin"""
+ __author_name__ = "spoob"
+ __author_mail__ = "spoob@pyload.org"
+
+
+ def __init__(self, parent):
+ Crypter.__init__(self, parent)
+ self.parent = parent
+ self.html = None
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ return True
+
+ def proceed(self, url, location):
+ url = self.parent.url
+ self.html = self.req.load(url)
+ link_ids = re.findall(r"<a id=\"DownloadLink_(\d*)\" href=\"http://1kh.de/", self.html)
+ for id in link_ids:
+ new_link = html_unescape(
+ re.search("width=\"100%\" src=\"(.*)\"></iframe>", self.req.load("http://1kh.de/l/" + id)).group(1))
+ self.urls.append(new_link)
diff --git a/pyload/plugins/crypter/OronComFolder.py b/pyload/plugins/crypter/OronComFolder.py
new file mode 100644
index 000000000..9b5fb3959
--- /dev/null
+++ b/pyload/plugins/crypter/OronComFolder.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class OronComFolder(DeadCrypter):
+ __name__ = "OronComFolder"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?oron.com/folder/\w+'
+
+ __description__ = """Oron.com folder decrypter plugin"""
+ __author_name__ = "DHMH"
+ __author_mail__ = "webmaster@pcProfil.de"
diff --git a/pyload/plugins/crypter/PastebinCom.py b/pyload/plugins/crypter/PastebinCom.py
new file mode 100644
index 000000000..8e394ac3a
--- /dev/null
+++ b/pyload/plugins/crypter/PastebinCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class PastebinCom(SimpleCrypter):
+ __name__ = "PastebinCom"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?pastebin\.com/\w+'
+
+ __description__ = """Pastebin.com decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>'
+ TITLE_PATTERN = r'<div class="paste_box_line1" title="(?P<title>[^"]+)">'
diff --git a/pyload/plugins/crypter/QuickshareCzFolder.py b/pyload/plugins/crypter/QuickshareCzFolder.py
new file mode 100644
index 000000000..5d99cbffd
--- /dev/null
+++ b/pyload/plugins/crypter/QuickshareCzFolder.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class QuickshareCzFolder(Crypter):
+ __name__ = "QuickshareCzFolder"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?quickshare.cz/slozka-\d+.*'
+
+ __description__ = """Quickshare.cz folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FOLDER_PATTERN = r'<textarea[^>]*>(.*?)</textarea>'
+ LINK_PATTERN = r'(http://www.quickshare.cz/\S+)'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
+ if m is None:
+ self.fail("Parse error (FOLDER)")
+ self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1)))
+
+ if not self.urls:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/RSLayerCom.py b/pyload/plugins/crypter/RSLayerCom.py
new file mode 100644
index 000000000..ded550a50
--- /dev/null
+++ b/pyload/plugins/crypter/RSLayerCom.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class RSLayerCom(DeadCrypter):
+ __name__ = "RSLayerCom"
+ __type__ = "crypter"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?rs-layer.com/directory-'
+
+ __description__ = """RS-Layer.com decrypter plugin"""
+ __author_name__ = "hzpz"
+ __author_mail__ = None
diff --git a/pyload/plugins/crypter/RelinkUs.py b/pyload/plugins/crypter/RelinkUs.py
new file mode 100644
index 000000000..74228d41a
--- /dev/null
+++ b/pyload/plugins/crypter/RelinkUs.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+import os
+
+from Crypto.Cipher import AES
+from pyload.plugins.Crypter import Crypter
+
+
+class RelinkUs(Crypter):
+ __name__ = "RelinkUs"
+ __type__ = "crypter"
+ __version__ = "3.0"
+
+ __pattern__ = r'http://(?:www\.)?relink.us/(f/|((view|go).php\?id=))(?P<id>.+)'
+
+ __description__ = """Relink.us decrypter plugin"""
+ __author_name__ = "fragonib"
+ __author_mail__ = "fragonib[AT]yahoo[DOT]es"
+
+ # Constants
+ PREFERRED_LINK_SOURCES = ["cnl2", "dlc", "web"]
+
+ OFFLINE_TOKEN = r'<title>Tattooside'
+ PASSWORD_TOKEN = r'container_password\.php'
+ PASSWORD_ERROR_ROKEN = r'You have entered an incorrect password'
+ PASSWORD_SUBMIT_URL = r'http://www\.relink\.us/container_password\.php'
+ CAPTCHA_TOKEN = r'container_captcha\.php'
+ CAPTCHA_ERROR_ROKEN = r'You have solved the captcha wrong'
+ CAPTCHA_IMG_URL = r'http://www\.relink\.us/core/captcha/circlecaptcha\.php'
+ CAPTCHA_SUBMIT_URL = r'http://www\.relink\.us/container_captcha\.php'
+ FILE_TITLE_REGEX = r'<th>Title</th><td><i>(.*)</i></td></tr>'
+ FILE_NOTITLE = r'No title'
+
+ CNL2_FORM_REGEX = r'<form id="cnl_form-(.*?)</form>'
+ CNL2_FORMINPUT_REGEX = r'<input.*?name="%s".*?value="(.*?)"'
+ CNL2_JK_KEY = "jk"
+ CNL2_CRYPTED_KEY = "crypted"
+ DLC_LINK_REGEX = r'<a href=".*?" class="dlc_button" target="_blank">'
+ DLC_DOWNLOAD_URL = r'http://www\.relink\.us/download\.php'
+ WEB_FORWARD_REGEX = r"getFile\('(?P<link>.+)'\)"
+ WEB_FORWARD_URL = r'http://www\.relink\.us/frame\.php'
+ WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(?P<link>.+)"></iframe>'
+
+
+ def setup(self):
+ self.fileid = None
+ self.package = None
+ self.password = None
+ self.html = None
+ self.captcha = False
+
+ def decrypt(self, pyfile):
+ # Init
+ self.initPackage(pyfile)
+
+ # Request package
+ self.requestPackage()
+
+ # Check for online
+ if not self.isOnline():
+ self.offline()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleErrors()
+
+ # Get package name and folder
+ (package_name, folder_name) = self.getPackageInfo()
+
+ # Extract package links
+ package_links = []
+ for sources in self.PREFERRED_LINK_SOURCES:
+ package_links.extend(self.handleLinkSource(sources))
+ if package_links: # use only first source which provides links
+ break
+ package_links = set(package_links)
+
+ # Pack
+ if package_links:
+ self.packages = [(package_name, package_links, folder_name)]
+ else:
+ self.fail('Could not extract any links')
+
+ def initPackage(self, pyfile):
+ self.fileid = re.match(self.__pattern__, pyfile.url).group('id')
+ self.package = pyfile.package()
+ self.password = self.getPassword()
+
+ def requestPackage(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ def isOnline(self):
+ if self.OFFLINE_TOKEN in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+ def isPasswordProtected(self):
+ if self.PASSWORD_TOKEN in self.html:
+ self.logDebug("Links are password protected")
+ return True
+
+ def isCaptchaProtected(self):
+ if self.CAPTCHA_TOKEN in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ return False
+
+ def unlockPasswordProtection(self):
+ self.logDebug("Submitting password [%s] for protected links" % self.password)
+ passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid
+ passwd_data = {'id': self.fileid, 'password': self.password, 'pw': 'submit'}
+ self.html = self.load(passwd_url, post=passwd_data, decode=True)
+
+ def unlockCaptchaProtection(self):
+ self.logDebug("Request user positional captcha resolving")
+ captcha_img_url = self.CAPTCHA_IMG_URL + "?id=%s" % self.fileid
+ coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+ captcha_post_url = self.CAPTCHA_SUBMIT_URL + "?id=%s" % self.fileid
+ captcha_post_data = {'button.x': coords[0], 'button.y': coords[1], 'captcha': 'submit'}
+ self.html = self.load(captcha_post_url, post=captcha_post_data, decode=True)
+
+ def getPackageInfo(self):
+ name = folder = None
+
+ # Try to get info from web
+ m = re.search(self.FILE_TITLE_REGEX, self.html)
+ if m is not None:
+ title = m.group(1).strip()
+ if not self.FILE_NOTITLE in title:
+ name = folder = title
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+
+ # Fallback to defaults
+ if not name or not folder:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+
+ # Return package info
+ return name, folder
+
+ def handleErrors(self):
+ if self.PASSWORD_ERROR_ROKEN in self.html:
+ msg = "Incorrect password, please set right password on 'Edit package' form and retry"
+ self.logDebug(msg)
+ self.fail(msg)
+
+ if self.captcha:
+ if self.CAPTCHA_ERROR_ROKEN in self.html:
+ self.logDebug("Invalid captcha, retrying")
+ self.invalidCaptcha()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+ def handleLinkSource(self, source):
+ if source == 'cnl2':
+ return self.handleCNL2Links()
+ elif source == 'dlc':
+ return self.handleDLCLinks()
+ elif source == 'web':
+ return self.handleWEBLinks()
+ else:
+ self.fail('Unknown source [%s] (this is probably a bug)' % source)
+
+ def handleCNL2Links(self):
+ self.logDebug("Search for CNL2 links")
+ package_links = []
+ m = re.search(self.CNL2_FORM_REGEX, self.html, re.DOTALL)
+ if m is not None:
+ cnl2_form = m.group(1)
+ try:
+ (vcrypted, vjk) = self._getCipherParams(cnl2_form)
+ for (crypted, jk) in zip(vcrypted, vjk):
+ package_links.extend(self._getLinks(crypted, jk))
+ except:
+ self.logDebug("Unable to decrypt CNL2 links")
+ return package_links
+
+ def handleDLCLinks(self):
+ self.logDebug('Search for DLC links')
+ package_links = []
+ m = re.search(self.DLC_LINK_REGEX, self.html)
+ if m is not None:
+ container_url = self.DLC_DOWNLOAD_URL + "?id=%s&dlc=1" % self.fileid
+ self.logDebug("Downloading DLC container link [%s]" % container_url)
+ try:
+ dlc = self.load(container_url)
+ dlc_filename = self.fileid + ".dlc"
+ dlc_filepath = os.path.join(self.config['general']['download_folder'], dlc_filename)
+ f = open(dlc_filepath, "wb")
+ f.write(dlc)
+ f.close()
+ package_links.append(dlc_filepath)
+ except:
+ self.logDebug("Unable to download DLC container")
+ return package_links
+
+ def handleWEBLinks(self):
+ self.logDebug("Search for WEB links")
+ package_links = []
+ fw_params = re.findall(self.WEB_FORWARD_REGEX, self.html)
+ self.logDebug("Decrypting %d Web links" % len(fw_params))
+ for index, fw_param in enumerate(fw_params):
+ try:
+ fw_url = self.WEB_FORWARD_URL + "?%s" % fw_param
+ self.logDebug("Decrypting Web link %d, %s" % (index + 1, fw_url))
+ fw_response = self.load(fw_url, decode=True)
+ dl_link = re.search(self.WEB_LINK_REGEX, fw_response).group('link')
+ package_links.append(dl_link)
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link %s, %s" % (index, detail))
+ self.setWait(4)
+ self.wait()
+ return package_links
+
+ def _getCipherParams(self, cnl2_form):
+ # Get jk
+ jk_re = self.CNL2_FORMINPUT_REGEX % self.CNL2_JK_KEY
+ vjk = re.findall(jk_re, cnl2_form, re.IGNORECASE)
+
+ # Get crypted
+ crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY
+ vcrypted = re.findall(crypted_re, cnl2_form, re.IGNORECASE)
+
+ # Log and return
+ self.logDebug("Detected %d crypted blocks" % len(vcrypted))
+ return vcrypted, vjk
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Package has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/SafelinkingNet.py b/pyload/plugins/crypter/SafelinkingNet.py
new file mode 100644
index 000000000..9c68ba915
--- /dev/null
+++ b/pyload/plugins/crypter/SafelinkingNet.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.utils import json_loads
+from pyload.plugins.Crypter import Crypter
+from pyload.plugins.internal.CaptchaService import SolveMedia
+
+
+class SafelinkingNet(Crypter):
+ __name__ = "SafelinkingNet"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://(?:www\.)?safelinking.net/([pd])/\w+'
+
+ __description__ = """Safelinking.net decrypter plugin"""
+ __author_name__ = "quareevo"
+ __author_mail__ = "quareevo@arcor.de"
+
+ SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w\.\-_]+)';"
+
+
+ def decrypt(self, pyfile):
+ url = pyfile.url
+ if re.match(self.__pattern__, url).group(1) == "d":
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+ self.load(url)
+ m = re.search("^Location: (.+)$", self.req.http.header, re.MULTILINE)
+ if m:
+ self.urls = [m.group(1)]
+ else:
+ self.fail("Couldn't find forwarded Link")
+
+ else:
+ password = ""
+ postData = {"post-protect": "1"}
+
+ self.html = self.load(url)
+
+ if "link-password" in self.html:
+ password = pyfile.package().password
+ postData['link-password'] = password
+
+ if "altcaptcha" in self.html:
+ for _ in xrange(5):
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captchaKey = m.group(1)
+ captcha = SolveMedia(self)
+ captchaProvider = "Solvemedia"
+ else:
+ self.fail("Error parsing captcha")
+
+ challenge, response = captcha.challenge(captchaKey)
+ postData['adcopy_challenge'] = challenge
+ postData['adcopy_response'] = response
+
+ self.html = self.load(url, post=postData)
+ if "The password you entered was incorrect" in self.html:
+ self.fail("Incorrect Password")
+ if not "The CAPTCHA code you entered was wrong" in self.html:
+ break
+
+ pyfile.package().password = ""
+ soup = BeautifulSoup(self.html)
+ scripts = soup.findAll("script")
+ for s in scripts:
+ if "d_links" in s.text:
+ break
+ m = re.search('d_links":(\[.*?\])', s.text)
+ if m:
+ linkDict = json_loads(m.group(1))
+ for link in linkDict:
+ if not "http://" in link['full']:
+ self.urls.append("https://safelinking.net/d/" + link['full'])
+ else:
+ self.urls.append(link['full'])
diff --git a/pyload/plugins/crypter/SecuredIn.py b/pyload/plugins/crypter/SecuredIn.py
new file mode 100644
index 000000000..fc2667586
--- /dev/null
+++ b/pyload/plugins/crypter/SecuredIn.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class SecuredIn(DeadCrypter):
+ __name__ = "SecuredIn"
+ __type__ = "crypter"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?secured\.in/download-[\d]+-[\w]{8}\.html'
+
+ __description__ = """Secured.in decrypter plugin"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
diff --git a/pyload/plugins/crypter/SerienjunkiesOrg.py b/pyload/plugins/crypter/SerienjunkiesOrg.py
new file mode 100644
index 000000000..713086cb9
--- /dev/null
+++ b/pyload/plugins/crypter/SerienjunkiesOrg.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from time import sleep
+
+from BeautifulSoup import BeautifulSoup
+
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import html_unescape
+
+
+class SerienjunkiesOrg(Crypter):
+ __name__ = "SerienjunkiesOrg"
+ __type__ = "crypter"
+ __version__ = "0.39"
+
+ __pattern__ = r'http://(?:www\.)?(serienjunkies.org|dokujunkies.org)/.*?'
+ __config__ = [("changeNameSJ", "Packagename;Show;Season;Format;Episode", "Take SJ.org name", "Show"),
+ ("changeNameDJ", "Packagename;Show;Format;Episode", "Take DJ.org name", "Show"),
+ ("randomPreferred", "bool", "Randomize Preferred-List", False),
+ ("hosterListMode", "OnlyOne;OnlyPreferred(One);OnlyPreferred(All);All",
+ "Use for hosters (if supported)", "All"),
+ ("hosterList", "str", "Preferred Hoster list (comma separated)",
+ "RapidshareCom,UploadedTo,NetloadIn,FilefactoryCom,FreakshareNet,FilebaseTo,HotfileCom,DepositfilesCom,EasyshareCom,KickloadCom"),
+ ("ignoreList", "str", "Ignored Hoster list (comma separated)", "MegauploadCom")]
+
+ __description__ = """Serienjunkies.org decrypter plugin"""
+ __author_name__ = ("mkaay", "godofdream")
+ __author_mail__ = ("mkaay@mkaay.de", "soilfiction@gmail.com")
+
+
+ def setup(self):
+ self.multiDL = False
+
+ def getSJSrc(self, url):
+ src = self.req.load(str(url))
+ if "This website is not available in your country" in src:
+ self.fail("Not available in your country")
+ if not src.find("Enter Serienjunkies") == -1:
+ sleep(1)
+ src = self.req.load(str(url))
+ return src
+
+ def handleShow(self, url):
+ src = self.getSJSrc(url)
+ soup = BeautifulSoup(src)
+ packageName = self.pyfile.package().name
+ if self.getConfig("changeNameSJ") == "Show":
+ found = html_unescape(soup.find("h2").find("a").string.split(' &#8211;')[0])
+ if found:
+ packageName = found
+
+ nav = soup.find("div", attrs={"id": "scb"})
+
+ package_links = []
+ for a in nav.findAll("a"):
+ if self.getConfig("changeNameSJ") == "Show":
+ package_links.append(a['href'])
+ else:
+ package_links.append(a['href'] + "#hasName")
+ if self.getConfig("changeNameSJ") == "Show":
+ self.packages.append((packageName, package_links, packageName))
+ else:
+ self.core.files.addLinks(package_links, self.pyfile.package().id)
+
+ def handleSeason(self, url):
+ src = self.getSJSrc(url)
+ soup = BeautifulSoup(src)
+ post = soup.find("div", attrs={"class": "post-content"})
+ ps = post.findAll("p")
+
+ seasonName = html_unescape(soup.find("a", attrs={"rel": "bookmark"}).string).replace("&#8211;", "-")
+ groups = {}
+ gid = -1
+ for p in ps:
+ if re.search("<strong>Sprache|<strong>Format", str(p)):
+ var = p.findAll("strong")
+ opts = {"Sprache": "", "Format": ""}
+ for v in var:
+ n = html_unescape(v.string).strip()
+ n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n)
+ if n.strip() not in opts:
+ continue
+ val = v.nextSibling
+ if not val:
+ continue
+ val = val.replace("|", "").strip()
+ val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val)
+ opts[n.strip()] = val.strip()
+ gid += 1
+ groups[gid] = {}
+ groups[gid]['ep'] = {}
+ groups[gid]['opts'] = opts
+ elif re.search("<strong>Download:", str(p)):
+ parts = str(p).split("<br />")
+ if re.search("<strong>", parts[0]):
+ ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace(
+ "&#8211;", "-")
+ groups[gid]['ep'][ename] = {}
+ parts.remove(parts[0])
+ for part in parts:
+ hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part)
+ if hostername:
+ hostername = hostername.group(1)
+ groups[gid]['ep'][ename][hostername] = []
+ links = re.findall('href="(.*?)"', part)
+ for link in links:
+ groups[gid]['ep'][ename][hostername].append(link + "#hasName")
+
+ links = []
+ for g in groups.values():
+ for ename in g['ep']:
+ links.extend(self.getpreferred(g['ep'][ename]))
+ if self.getConfig("changeNameSJ") == "Episode":
+ self.packages.append((ename, links, ename))
+ links = []
+ package = "%s (%s, %s)" % (seasonName, g['opts']['Format'], g['opts']['Sprache'])
+ if self.getConfig("changeNameSJ") == "Format":
+ self.packages.append((package, links, package))
+ links = []
+ if (self.getConfig("changeNameSJ") == "Packagename") or re.search("#hasName", url):
+ self.core.files.addLinks(links, self.pyfile.package().id)
+ elif (self.getConfig("changeNameSJ") == "Season") or not re.search("#hasName", url):
+ self.packages.append((seasonName, links, seasonName))
+
+ def handleEpisode(self, url):
+ src = self.getSJSrc(url)
+ if not src.find(
+ "Du hast das Download-Limit &uuml;berschritten! Bitte versuche es sp&auml;ter nocheinmal.") == -1:
+ self.fail(_("Downloadlimit reached"))
+ else:
+ soup = BeautifulSoup(src)
+ form = soup.find("form")
+ h1 = soup.find("h1")
+
+ if h1.get("class") == "wrap":
+ captchaTag = soup.find(attrs={"src": re.compile("^/secure/")})
+ if not captchaTag:
+ sleep(5)
+ self.retry()
+
+ captchaUrl = "http://download.serienjunkies.org" + captchaTag['src']
+ result = self.decryptCaptcha(str(captchaUrl), imgtype="png")
+ sinp = form.find(attrs={"name": "s"})
+
+ self.req.lastURL = str(url)
+ sj = self.load(str(url), post={'s': sinp['value'], 'c': result, 'action': "Download"})
+
+ soup = BeautifulSoup(sj)
+ rawLinks = soup.findAll(attrs={"action": re.compile("^http://download.serienjunkies.org/")})
+
+ if not len(rawLinks) > 0:
+ sleep(1)
+ self.retry()
+ return
+
+ self.correctCaptcha()
+
+ links = []
+ for link in rawLinks:
+ frameUrl = link['action'].replace("/go-", "/frame/go-")
+ links.append(self.handleFrame(frameUrl))
+ if re.search("#hasName", url) or ((self.getConfig("changeNameSJ") == "Packagename") and
+ (self.getConfig("changeNameDJ") == "Packagename")):
+ self.core.files.addLinks(links, self.pyfile.package().id)
+ else:
+ if h1.text[2] == "_":
+ eName = h1.text[3:]
+ else:
+ eName = h1.text
+ self.packages.append((eName, links, eName))
+
+ def handleOldStyleLink(self, url):
+ sj = self.req.load(str(url))
+ soup = BeautifulSoup(sj)
+ form = soup.find("form", attrs={"action": re.compile("^http://serienjunkies.org")})
+ captchaTag = form.find(attrs={"src": re.compile("^/safe/secure/")})
+ captchaUrl = "http://serienjunkies.org" + captchaTag['src']
+ result = self.decryptCaptcha(str(captchaUrl))
+ url = form['action']
+ sinp = form.find(attrs={"name": "s"})
+
+ self.req.load(str(url), post={'s': sinp['value'], 'c': result, 'dl.start': "Download"}, cookies=False,
+ just_header=True)
+ decrypted = self.req.lastEffectiveURL
+ if decrypted == str(url):
+ self.retry()
+ self.core.files.addLinks([decrypted], self.pyfile.package().id)
+
+ def handleFrame(self, url):
+ self.req.load(str(url))
+ return self.req.lastEffectiveURL
+
+ def handleShowDJ(self, url):
+ src = self.getSJSrc(url)
+ soup = BeautifulSoup(src)
+ post = soup.find("div", attrs={"id": "page_post"})
+ ps = post.findAll("p")
+ found = html_unescape(soup.find("h2").find("a").string.split(' &#8211;')[0])
+ if found:
+ seasonName = found
+
+ groups = {}
+ gid = -1
+ for p in ps:
+ if re.search("<strong>Sprache|<strong>Format", str(p)):
+ var = p.findAll("strong")
+ opts = {"Sprache": "", "Format": ""}
+ for v in var:
+ n = html_unescape(v.string).strip()
+ n = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', n)
+ if n.strip() not in opts:
+ continue
+ val = v.nextSibling
+ if not val:
+ continue
+ val = val.replace("|", "").strip()
+ val = re.sub(r"^([:]?)(.*?)([:]?)$", r'\2', val)
+ opts[n.strip()] = val.strip()
+ gid += 1
+ groups[gid] = {}
+ groups[gid]['ep'] = {}
+ groups[gid]['opts'] = opts
+ elif re.search("<strong>Download:", str(p)):
+ parts = str(p).split("<br />")
+ if re.search("<strong>", parts[0]):
+ ename = re.search('<strong>(.*?)</strong>', parts[0]).group(1).strip().decode("utf-8").replace(
+ "&#8211;", "-")
+ groups[gid]['ep'][ename] = {}
+ parts.remove(parts[0])
+ for part in parts:
+ hostername = re.search(r" \| ([-a-zA-Z0-9]+\.\w+)", part)
+ if hostername:
+ hostername = hostername.group(1)
+ groups[gid]['ep'][ename][hostername] = []
+ links = re.findall('href="(.*?)"', part)
+ for link in links:
+ groups[gid]['ep'][ename][hostername].append(link + "#hasName")
+
+ links = []
+ for g in groups.values():
+ for ename in g['ep']:
+ links.extend(self.getpreferred(g['ep'][ename]))
+ if self.getConfig("changeNameDJ") == "Episode":
+ self.packages.append((ename, links, ename))
+ links = []
+ package = "%s (%s, %s)" % (seasonName, g['opts']['Format'], g['opts']['Sprache'])
+ if self.getConfig("changeNameDJ") == "Format":
+ self.packages.append((package, links, package))
+ links = []
+ if (self.getConfig("changeNameDJ") == "Packagename") or re.search("#hasName", url):
+ self.core.files.addLinks(links, self.pyfile.package().id)
+ elif (self.getConfig("changeNameDJ") == "Show") or not re.search("#hasName", url):
+ self.packages.append((seasonName, links, seasonName))
+
+ def handleCategoryDJ(self, url):
+ package_links = []
+ src = self.getSJSrc(url)
+ soup = BeautifulSoup(src)
+ content = soup.find("div", attrs={"id": "content"})
+ for a in content.findAll("a", attrs={"rel": "bookmark"}):
+ package_links.append(a['href'])
+ self.core.files.addLinks(package_links, self.pyfile.package().id)
+
+ def decrypt(self, pyfile):
+ showPattern = re.compile("^http://serienjunkies.org/serie/(.*)/$")
+ seasonPattern = re.compile("^http://serienjunkies.org/.*?/(.*)/$")
+ episodePattern = re.compile("^http://download.serienjunkies.org/f-.*?.html(#hasName)?$")
+ oldStyleLink = re.compile("^http://serienjunkies.org/safe/(.*)$")
+ categoryPatternDJ = re.compile("^http://dokujunkies.org/.*?(.*)$")
+ showPatternDJ = re.compile(r"^http://dokujunkies.org/.*?/(.*)\.html(#hasName)?$")
+ framePattern = re.compile("^http://download.(serienjunkies.org|dokujunkies.org)/frame/go-.*?/$")
+ url = pyfile.url
+ if framePattern.match(url):
+ self.packages.append((pyfile.package().name, [self.handleFrame(url)], pyfile.package().name))
+ elif episodePattern.match(url):
+ self.handleEpisode(url)
+ elif oldStyleLink.match(url):
+ self.handleOldStyleLink(url)
+ elif showPattern.match(url):
+ self.handleShow(url)
+ elif showPatternDJ.match(url):
+ self.handleShowDJ(url)
+ elif seasonPattern.match(url):
+ self.handleSeason(url)
+ elif categoryPatternDJ.match(url):
+ self.handleCategoryDJ(url)
+
+ #selects the preferred hoster, after that selects any hoster (ignoring the one to ignore)
+ def getpreferred(self, hosterlist):
+
+ result = []
+ preferredList = self.getConfig("hosterList").strip().lower().replace(
+ '|', ',').replace('.', '').replace(';', ',').split(',')
+ if (self.getConfig("randomPreferred") is True) and (
+ self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]):
+ random.shuffle(preferredList)
+ # we don't want hosters be read two times
+ hosterlist2 = hosterlist.copy()
+
+ for preferred in preferredList:
+ for Hoster in hosterlist:
+ if preferred == Hoster.lower().replace('.', ''):
+ for Part in hosterlist[Hoster]:
+ self.logDebug("selected " + Part)
+ result.append(str(Part))
+ del (hosterlist2[Hoster])
+ if self.getConfig("hosterListMode") in ["OnlyOne", "OnlyPreferred(One)"]:
+ return result
+
+ ignorelist = self.getConfig("ignoreList").strip().lower().replace(
+ '|', ',').replace('.', '').replace(';', ',').split(',')
+ if self.getConfig('hosterListMode') in ["OnlyOne", "All"]:
+ for Hoster in hosterlist2:
+ if Hoster.strip().lower().replace('.', '') not in ignorelist:
+ for Part in hosterlist2[Hoster]:
+ self.logDebug("selected2 " + Part)
+ result.append(str(Part))
+
+ if self.getConfig('hosterListMode') == "OnlyOne":
+ return result
+ return result
diff --git a/pyload/plugins/crypter/ShareLinksBiz.py b/pyload/plugins/crypter/ShareLinksBiz.py
new file mode 100644
index 000000000..132d2160b
--- /dev/null
+++ b/pyload/plugins/crypter/ShareLinksBiz.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import binascii
+import re
+
+from Crypto.Cipher import AES
+from pyload.plugins.Crypter import Crypter
+
+
+class ShareLinksBiz(Crypter):
+ __name__ = "ShareLinksBiz"
+ __type__ = "crypter"
+ __version__ = "1.13"
+
+ __pattern__ = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)'
+
+ __description__ = """Share-Links.biz decrypter plugin"""
+ __author_name__ = "fragonib"
+ __author_mail__ = "fragonib[AT]yahoo[DOT]es"
+
+
+ def setup(self):
+ self.baseUrl = None
+ self.fileId = None
+ self.package = None
+ self.html = None
+ self.captcha = False
+
+ def decrypt(self, pyfile):
+ # Init
+ self.initFile(pyfile)
+
+ # Request package
+ url = self.baseUrl + '/' + self.fileId
+ self.html = self.load(url, decode=True)
+
+ # Unblock server (load all images)
+ self.unblockServer()
+
+ # Check for protection
+ if self.isPasswordProtected():
+ self.unlockPasswordProtection()
+ self.handleErrors()
+
+ if self.isCaptchaProtected():
+ self.captcha = True
+ self.unlockCaptchaProtection()
+ self.handleErrors()
+
+ # Extract package links
+ package_links = []
+ package_links.extend(self.handleWebLinks())
+ package_links.extend(self.handleContainers())
+ package_links.extend(self.handleCNL2())
+ package_links = set(package_links)
+
+ # Get package info
+ package_name, package_folder = self.getPackageInfo()
+
+ # Pack
+ self.packages = [(package_name, package_links, package_folder)]
+
+ def initFile(self, pyfile):
+ url = pyfile.url
+ if 's2l.biz' in url:
+ url = self.load(url, just_header=True)['location']
+ self.baseUrl = "http://www.%s.biz" % re.match(self.__pattern__, url).group(1)
+ self.fileId = re.match(self.__pattern__, url).group('ID')
+ self.package = pyfile.package()
+
+ def isOnline(self):
+ if "No usable content was found" in self.html:
+ self.logDebug("File not found")
+ return False
+ return True
+
+ def isPasswordProtected(self):
+ if re.search(r'''<form.*?id="passwordForm".*?>''', self.html):
+ self.logDebug("Links are protected")
+ return True
+ return False
+
+ def isCaptchaProtected(self):
+ if '<map id="captchamap"' in self.html:
+ self.logDebug("Links are captcha protected")
+ return True
+ return False
+
+ def unblockServer(self):
+ imgs = re.findall(r"(/template/images/.*?\.gif)", self.html)
+ for img in imgs:
+ self.load(self.baseUrl + img)
+
+ def unlockPasswordProtection(self):
+ password = self.getPassword()
+ self.logDebug("Submitting password [%s] for protected links" % password)
+ post = {"password": password, 'login': 'Submit form'}
+ url = self.baseUrl + '/' + self.fileId
+ self.html = self.load(url, post=post, decode=True)
+
+ def unlockCaptchaProtection(self):
+ # Get captcha map
+ captchaMap = self._getCaptchaMap()
+ self.logDebug("Captcha map with [%d] positions" % len(captchaMap.keys()))
+
+ # Request user for captcha coords
+ m = re.search(r'<img src="/captcha.gif\?d=(.*?)&amp;PHPSESSID=(.*?)&amp;legend=1"', self.html)
+ captchaUrl = self.baseUrl + '/captcha.gif?d=%s&PHPSESSID=%s' % (m.group(1), m.group(2))
+ self.logDebug("Waiting user for correct position")
+ coords = self.decryptCaptcha(captchaUrl, forceUser=True, imgtype="gif", result_type='positional')
+ self.logDebug("Captcha resolved, coords [%s]" % str(coords))
+
+ # Resolve captcha
+ href = self._resolveCoords(coords, captchaMap)
+ if href is None:
+ self.logDebug("Invalid captcha resolving, retrying")
+ self.invalidCaptcha()
+ self.setWait(5, False)
+ self.wait()
+ self.retry()
+ url = self.baseUrl + href
+ self.html = self.load(url, decode=True)
+
+ def _getCaptchaMap(self):
+ mapp = {}
+ for m in re.finditer(r'<area shape="rect" coords="(.*?)" href="(.*?)"', self.html):
+ rect = eval('(' + m.group(1) + ')')
+ href = m.group(2)
+ mapp[rect] = href
+ return mapp
+
+ def _resolveCoords(self, coords, captchaMap):
+ x, y = coords
+ for rect, href in captchaMap.items():
+ x1, y1, x2, y2 = rect
+ if (x >= x1 and x <= x2) and (y >= y1 and y <= y2):
+ return href
+
+ def handleErrors(self):
+ if "The inserted password was wrong" in self.html:
+ self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry")
+ self.fail("Incorrect password, please set right password on 'Edit package' form and retry")
+
+ if self.captcha:
+ if "Your choice was wrong" in self.html:
+ self.logDebug("Invalid captcha, retrying")
+ self.invalidCaptcha()
+ self.setWait(5)
+ self.wait()
+ self.retry()
+ else:
+ self.correctCaptcha()
+
+ def getPackageInfo(self):
+ name = folder = None
+
+ # Extract from web package header
+ title_re = r'<h2><img.*?/>(.*)</h2>'
+ m = re.search(title_re, self.html, re.DOTALL)
+ if m is not None:
+ title = m.group(1).strip()
+ if 'unnamed' not in title:
+ name = folder = title
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+
+ # Fallback to defaults
+ if not name or not folder:
+ name = self.package.name
+ folder = self.package.folder
+ self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+
+ # Return package info
+ return name, folder
+
+ def handleWebLinks(self):
+ package_links = []
+ self.logDebug("Handling Web links")
+
+ #@TODO: Gather paginated web links
+ pattern = r"javascript:_get\('(.*?)', \d+, ''\)"
+ ids = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Web links" % len(ids))
+ for i, ID in enumerate(ids):
+ try:
+ self.logDebug("Decrypting Web link %d, [%s]" % (i + 1, ID))
+ dwLink = self.baseUrl + "/get/lnk/" + ID
+ response = self.load(dwLink)
+ code = re.search(r'frm/(\d+)', response).group(1)
+ fwLink = self.baseUrl + "/get/frm/" + code
+ response = self.load(fwLink)
+ jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', response,
+ re.DOTALL).group(1)
+ jscode = self.js.eval("f = %s" % jscode)
+ jslauncher = "window=''; parent={frames:{Main:{location:{href:''}}},location:''}; %s; parent.frames.Main.location.href"
+ dlLink = self.js.eval(jslauncher % jscode)
+ self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink)
+ package_links.append(dlLink)
+ except Exception, detail:
+ self.logDebug("Error decrypting Web link [%s], %s" % (ID, detail))
+ return package_links
+
+ def handleContainers(self):
+ package_links = []
+ self.logDebug("Handling Container links")
+
+ pattern = r"javascript:_get\('(.*?)', 0, '(rsdf|ccf|dlc)'\)"
+ containersLinks = re.findall(pattern, self.html)
+ self.logDebug("Decrypting %d Container links" % len(containersLinks))
+ for containerLink in containersLinks:
+ link = "%s/get/%s/%s" % (self.baseUrl, containerLink[1], containerLink[0])
+ package_links.append(link)
+ return package_links
+
+ def handleCNL2(self):
+ package_links = []
+ self.logDebug("Handling CNL2 links")
+
+ if '/lib/cnl2/ClicknLoad.swf' in self.html:
+ try:
+ (crypted, jk) = self._getCipherParams()
+ package_links.extend(self._getLinks(crypted, jk))
+ except:
+ self.fail("Unable to decrypt CNL2 links")
+ return package_links
+
+ def _getCipherParams(self):
+ # Request CNL2
+ code = re.search(r'ClicknLoad.swf\?code=(.*?)"', self.html).group(1)
+ url = "%s/get/cnl2/%s" % (self.baseUrl, code)
+ response = self.load(url)
+ params = response.split(";;")
+
+ # Get jk
+ strlist = list(base64.standard_b64decode(params[1]))
+ strlist.reverse()
+ jk = ''.join(strlist)
+
+ # Get crypted
+ strlist = list(base64.standard_b64decode(params[2]))
+ strlist.reverse()
+ crypted = ''.join(strlist)
+
+ # Log and return
+ return crypted, jk
+
+ def _getLinks(self, crypted, jk):
+ # Get key
+ jreturn = self.js.eval("%s f()" % jk)
+ self.logDebug("JsEngine returns value [%s]" % jreturn)
+ key = binascii.unhexlify(jreturn)
+
+ # Decode crypted
+ crypted = base64.standard_b64decode(crypted)
+
+ # Decrypt
+ Key = key
+ IV = key
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ text = obj.decrypt(crypted)
+
+ # Extract links
+ text = text.replace("\x00", "").replace("\r", "")
+ links = text.split("\n")
+ links = filter(lambda x: x != "", links)
+
+ # Log and return
+ self.logDebug("Block has %d links" % len(links))
+ return links
diff --git a/pyload/plugins/crypter/ShareRapidComFolder.py b/pyload/plugins/crypter/ShareRapidComFolder.py
new file mode 100644
index 000000000..c8e95be1c
--- /dev/null
+++ b/pyload/plugins/crypter/ShareRapidComFolder.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class ShareRapidComFolder(SimpleCrypter):
+ __name__ = "ShareRapidComFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?((share(-?rapid\.(biz|com|cz|info|eu|net|org|pl|sk)|-(central|credit|free|net)\.cz|-ms\.net)|(s-?rapid|rapids)\.(cz|sk))|(e-stahuj|mediatack|premium-rapidshare|rapidshare-premium|qiuck)\.cz|kadzet\.com|stahuj-zdarma\.eu|strelci\.net|universal-share\.com)/(slozka/.+)'
+
+ __description__ = """Share-Rapid.com folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'<td class="soubor"[^>]*><a href="([^"]+)">'
diff --git a/pyload/plugins/crypter/SpeedLoadOrgFolder.py b/pyload/plugins/crypter/SpeedLoadOrgFolder.py
new file mode 100644
index 000000000..fff119a93
--- /dev/null
+++ b/pyload/plugins/crypter/SpeedLoadOrgFolder.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class SpeedLoadOrgFolder(DeadCrypter):
+ __name__ = "SpeedLoadOrgFolder"
+ __type__ = "crypter"
+ __version__ = "0.3"
+
+ __pattern__ = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)'
+
+ __description__ = """Speedload decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
diff --git a/pyload/plugins/crypter/StealthTo.py b/pyload/plugins/crypter/StealthTo.py
new file mode 100644
index 000000000..24489a1b3
--- /dev/null
+++ b/pyload/plugins/crypter/StealthTo.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class StealthTo(DeadCrypter):
+ __name__ = "StealthTo"
+ __type__ = "crypter"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?stealth\.to/folder/.+'
+
+ __description__ = """Stealth.to decrypter plugin"""
+ __author_name__ = "spoob"
+ __author_mail__ = "spoob@pyload.org"
diff --git a/pyload/plugins/crypter/TnyCz.py b/pyload/plugins/crypter/TnyCz.py
new file mode 100644
index 000000000..879941ba4
--- /dev/null
+++ b/pyload/plugins/crypter/TnyCz.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+import re
+
+
+class TnyCz(SimpleCrypter):
+ __name__ = "TnyCz"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?tny\.cz/\w+'
+
+ __description__ = """Tny.cz decrypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ TITLE_PATTERN = r'<title>(?P<title>.+) - .+</title>'
+
+
+ def getLinks(self):
+ m = re.search(r'<a id=\'save_paste\' href="(.+save\.php\?hash=.+)">', self.html)
+ return re.findall(".+", self.load(m.group(1), decode=True)) if m else None
diff --git a/pyload/plugins/crypter/TrailerzoneInfo.py b/pyload/plugins/crypter/TrailerzoneInfo.py
new file mode 100644
index 000000000..7be3beef0
--- /dev/null
+++ b/pyload/plugins/crypter/TrailerzoneInfo.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class TrailerzoneInfo(DeadCrypter):
+ __name__ = "TrailerzoneInfo"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?trailerzone.info/.*?'
+
+ __description__ = """TrailerZone.info decrypter plugin"""
+ __author_name__ = "godofdream"
+ __author_mail__ = "soilfiction@gmail.com"
diff --git a/pyload/plugins/crypter/TurbobitNetFolder.py b/pyload/plugins/crypter/TurbobitNetFolder.py
new file mode 100644
index 000000000..c7786b7be
--- /dev/null
+++ b/pyload/plugins/crypter/TurbobitNetFolder.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+from pyload.utils import json_loads
+
+
+class TurbobitNetFolder(SimpleCrypter):
+ __name__ = "TurbobitNetFolder"
+ __type__ = "crypter"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)'
+
+ __description__ = """Turbobit.net folder decrypter plugin"""
+ __author_name__ = ("stickell", "Walter Purcaro")
+ __author_mail__ = ("l.stickell@yahoo.it", "vuolter@gmail.com")
+
+ TITLE_PATTERN = r"src='/js/lib/grid/icon/folder.png'> <span>(?P<title>.+?)</span>"
+
+
+ def _getLinks(self, id, page=1):
+ gridFile = self.load("http://turbobit.net/downloadfolder/gridFile",
+ get={"rootId": id, "rows": 200, "page": page}, decode=True)
+ grid = json_loads(gridFile)
+
+ if grid['rows']:
+ for i in grid['rows']:
+ yield i['id']
+ for id in self._getLinks(id, page + 1):
+ yield id
+ else:
+ return
+
+ def getLinks(self):
+ id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ fixurl = lambda id: "http://turbobit.net/%s.html" % id
+ return map(fixurl, self._getLinks(id))
diff --git a/pyload/plugins/crypter/TusfilesNetFolder.py b/pyload/plugins/crypter/TusfilesNetFolder.py
new file mode 100644
index 000000000..f4f1c7723
--- /dev/null
+++ b/pyload/plugins/crypter/TusfilesNetFolder.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import math
+import re
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class TusfilesNetFolder(SimpleCrypter):
+ __name__ = "TusfilesNetFolder"
+ __type__ = "crypter"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)/?'
+
+ __description__ = """Tusfiles.net folder decrypter plugin"""
+ __author_name__ = ("Walter Purcaro", "stickell")
+ __author_mail__ = ("vuolter@gmail.com", "l.stickell@yahoo.it")
+
+ LINK_PATTERN = r'<TD align=left><a href="(.*?)">'
+ TITLE_PATTERN = r'<Title>.*?\: (?P<title>.+) folder</Title>'
+ PAGES_PATTERN = r'>\((?P<pages>\d+) \w+\)<'
+
+ URL_REPLACEMENTS = [(__pattern__, r'https://www.tusfiles.net/go/\g<ID>/')]
+
+
+ def loadPage(self, page_n):
+ return self.load(urljoin(self.pyfile.url, str(page_n)), decode=True)
+
+ def handleMultiPages(self):
+ pages = re.search(self.PAGES_PATTERN, self.html)
+ if pages:
+ pages = int(math.ceil(int(pages.group('pages')) / 25.0))
+ else:
+ return
+
+ for p in xrange(2, pages + 1):
+ self.html = self.loadPage(p)
+ self.package_links += self.getLinks()
diff --git a/pyload/plugins/crypter/UlozToFolder.py b/pyload/plugins/crypter/UlozToFolder.py
new file mode 100644
index 000000000..2cc440a5d
--- /dev/null
+++ b/pyload/plugins/crypter/UlozToFolder.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+from pyload.plugins.Crypter import Crypter
+
+
+class UlozToFolder(Crypter):
+ __name__ = "UlozToFolder"
+ __type__ = "crypter"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj.cz|zachowajto.pl)/(m|soubory)/.*'
+
+ __description__ = """Uloz.to folder decrypter plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FOLDER_PATTERN = r'<ul class="profile_files">(.*?)</ul>'
+ LINK_PATTERN = r'<br /><a href="/([^"]+)">[^<]+</a>'
+ NEXT_PAGE_PATTERN = r'<a class="next " href="/([^"]+)">&nbsp;</a>'
+
+
+ def decrypt(self, pyfile):
+ html = self.load(pyfile.url)
+
+ new_links = []
+ for i in xrange(1, 100):
+ self.logInfo("Fetching links from page %i" % i)
+ m = re.search(self.FOLDER_PATTERN, html, re.DOTALL)
+ if m is None:
+ self.fail("Parse error (FOLDER)")
+
+ new_links.extend(re.findall(self.LINK_PATTERN, m.group(1)))
+ m = re.search(self.NEXT_PAGE_PATTERN, html)
+ if m:
+ html = self.load("http://ulozto.net/" + m.group(1))
+ else:
+ break
+ else:
+ self.logInfo("Limit of 99 pages reached, aborting")
+
+ if new_links:
+ self.urls = [map(lambda s: "http://ulozto.net/%s" % s, new_links)]
+ else:
+ self.fail('Could not extract any links')
diff --git a/pyload/plugins/crypter/UploadableChFolder.py b/pyload/plugins/crypter/UploadableChFolder.py
new file mode 100644
index 000000000..3be8b0167
--- /dev/null
+++ b/pyload/plugins/crypter/UploadableChFolder.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadableChFolder(SimpleCrypter):
+ __name__ = "UploadableChFolder"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?uploadable\.ch/list/\w+'
+
+ __description__ = """ Uploadable.ch folder decrypter plugin """
+ __author_name__ = ("guidobelix", "Walter Purcaro")
+ __author_mail__ = ("guidobelix@hotmail.it", "vuolter@gmail.com")
+
+
+ LINK_PATTERN = r'"(.+?)" class="icon_zipfile">'
+ TITLE_PATTERN = r'<div class="folder"><span>&nbsp;</span>(?P<title>.+?)</div>'
+ OFFLINE_PATTERN = r'We are sorry... The URL you entered cannot be found on the server.'
+ TEMP_OFFLINE_PATTERN = r'<div class="icon_err">'
diff --git a/pyload/plugins/crypter/UploadedToFolder.py b/pyload/plugins/crypter/UploadedToFolder.py
new file mode 100644
index 000000000..31977409d
--- /dev/null
+++ b/pyload/plugins/crypter/UploadedToFolder.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleCrypter import SimpleCrypter
+
+
+class UploadedToFolder(SimpleCrypter):
+ __name__ = "UploadedToFolder"
+ __type__ = "crypter"
+ __version__ = "0.3"
+
+ __pattern__ = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<id>\w+)'
+
+ __description__ = """UploadedTo decrypter plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ PLAIN_PATTERN = r'<small class="date"><a href="(?P<plain>[\w/]+)" onclick='
+ TITLE_PATTERN = r'<title>(?P<title>[^<]+)</title>'
+
+
+ def decrypt(self, pyfile):
+ self.html = self.load(pyfile.url)
+
+ package_name, folder_name = self.getPackageNameAndFolder()
+
+ m = re.search(self.PLAIN_PATTERN, self.html)
+ if m:
+ plain_link = 'http://uploaded.net/' + m.group('plain')
+ else:
+ self.fail('Parse error - Unable to find plain url list')
+
+ self.html = self.load(plain_link)
+ package_links = self.html.split('\n')[:-1]
+ self.logDebug('Package has %d links' % len(package_links))
+
+ self.packages = [(package_name, package_links, folder_name)]
diff --git a/pyload/plugins/crypter/WiiReloadedOrg.py b/pyload/plugins/crypter/WiiReloadedOrg.py
new file mode 100644
index 000000000..7dfe574ab
--- /dev/null
+++ b/pyload/plugins/crypter/WiiReloadedOrg.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadCrypter import DeadCrypter
+
+
+class WiiReloadedOrg(DeadCrypter):
+ __name__ = "WiiReloadedOrg"
+ __type__ = "crypter"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+'
+
+ __description__ = """Wii-Reloaded.org decrypter plugin"""
+ __author_name__ = "hzpz"
+ __author_mail__ = None
diff --git a/pyload/plugins/crypter/XupPl.py b/pyload/plugins/crypter/XupPl.py
new file mode 100644
index 000000000..8d09e28a3
--- /dev/null
+++ b/pyload/plugins/crypter/XupPl.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Crypter import Crypter
+
+
+class XupPl(Crypter):
+ __name__ = "XupPl"
+ __type__ = "crypter"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?xup\.pl/.*'
+
+ __description__ = """Xup.pl decrypter plugin"""
+ __author_name__ = "z00nx"
+ __author_mail__ = "z00nx0@gmail.com"
+
+
+ def decrypt(self, pyfile):
+ header = self.load(pyfile.url, just_header=True)
+ if 'location' in header:
+ self.urls = [header['location']]
+ else:
+ self.fail('Unable to find link')
diff --git a/pyload/plugins/crypter/YoutubeBatch.py b/pyload/plugins/crypter/YoutubeBatch.py
new file mode 100644
index 000000000..bc72e04ea
--- /dev/null
+++ b/pyload/plugins/crypter/YoutubeBatch.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urljoin
+
+from pyload.utils import json_loads
+from pyload.plugins.Crypter import Crypter
+from pyload.utils import safe_join
+
+API_URL = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0"
+
+
+class YoutubeBatch(Crypter):
+ __name__ = "YoutubeBatch"
+ __type__ = "crypter"
+ __version__ = "1.00"
+
+ __pattern__ = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)'
+ __config__ = [("likes", "bool", "Grab user (channel) liked videos", False),
+ ("favorites", "bool", "Grab user (channel) favorite videos", False),
+ ("uploads", "bool", "Grab channel unplaylisted videos", True)]
+
+ __description__ = """Youtube.com channel & playlist decrypter plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+ def api_response(self, ref, req):
+ req.update({"key": API_KEY})
+ url = urljoin("https://www.googleapis.com/youtube/v3/", ref)
+ page = self.load(url, get=req)
+ return json_loads(page)
+
+ def getChannel(self, user):
+ channels = self.api_response("channels", {"part": "id,snippet,contentDetails", "forUsername": user, "maxResults": "50"})
+ if channels['items']:
+ channel = channels['items'][0]
+ return {"id": channel['id'],
+ "title": channel['snippet']['title'],
+ "relatedPlaylists": channel['contentDetails']['relatedPlaylists'],
+ "user": user} # One lone channel for user?
+
+ def getPlaylist(self, p_id):
+ playlists = self.api_response("playlists", {"part": "snippet", "id": p_id})
+ if playlists['items']:
+ playlist = playlists['items'][0]
+ return {"id": p_id,
+ "title": playlist['snippet']['title'],
+ "channelId": playlist['snippet']['channelId'],
+ "channelTitle": playlist['snippet']['channelTitle']}
+
+ def _getPlaylists(self, id, token=None):
+ req = {"part": "id", "maxResults": "50", "channelId": id}
+ if token:
+ req.update({"pageToken": token})
+
+ playlists = self.api_response("playlists", req)
+
+ for playlist in playlists['items']:
+ yield playlist['id']
+
+ if "nextPageToken" in playlists:
+ for item in self._getPlaylists(id, playlists['nextPageToken']):
+ yield item
+
+ def getPlaylists(self, ch_id):
+ return map(self.getPlaylist, self._getPlaylists(ch_id))
+
+ def _getVideosId(self, id, token=None):
+ req = {"part": "contentDetails", "maxResults": "50", "playlistId": id}
+ if token:
+ req.update({"pageToken": token})
+
+ playlist = self.api_response("playlistItems", req)
+
+ for item in playlist['items']:
+ yield item['contentDetails']['videoId']
+
+ if "nextPageToken" in playlist:
+ for item in self._getVideosId(id, playlist['nextPageToken']):
+ yield item
+
+ def getVideosId(self, p_id):
+ return list(self._getVideosId(p_id))
+
+ def decrypt(self, pyfile):
+ m = re.match(self.__pattern__, pyfile.url)
+ m_id = m.group("ID")
+ m_type = m.group("TYPE")
+
+ if m_type == "user":
+ self.logDebug("Url recognized as Channel")
+ user = m_id
+ channel = self.getChannel(user)
+
+ if channel:
+ playlists = self.getPlaylists(channel['id'])
+ self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), channel['title']))
+
+ relatedplaylist = {p_name: self.getPlaylist(p_id) for p_name, p_id in channel['relatedPlaylists'].iteritems()}
+ self.logDebug("Channel's related playlists found = %s" % relatedplaylist.keys())
+
+ relatedplaylist['uploads']['title'] = "Unplaylisted videos"
+ relatedplaylist['uploads']['checkDups'] = True #: checkDups flag
+
+ for p_name, p_data in relatedplaylist.iteritems():
+ if self.getConfig(p_name):
+ p_data['title'] += " of " + user
+ playlists.append(p_data)
+ else:
+ playlists = []
+ else:
+ self.logDebug("Url recognized as Playlist")
+ playlists = [self.getPlaylist(m_id)]
+
+ if not playlists:
+ self.fail("No playlist available")
+
+ addedvideos = []
+ urlize = lambda x: "https://www.youtube.com/watch?v=" + x
+ for p in playlists:
+ p_name = p['title']
+ p_videos = self.getVideosId(p['id'])
+ p_folder = safe_join(self.config['general']['download_folder'], p['channelTitle'], p_name)
+ self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name))
+
+ if not p_videos:
+ continue
+ elif "checkDups" in p:
+ p_urls = [urlize(v_id) for v_id in p_videos if v_id not in addedvideos]
+ self.logDebug("%s video\s available on playlist \"%s\" after duplicates cleanup" % (len(p_urls), p_name))
+ else:
+ p_urls = map(urlize, p_videos)
+
+ self.packages.append((p_name, p_urls, p_folder)) #: folder is NOT recognized by pyload 0.4.9!
+
+ addedvideos.extend(p_videos)
diff --git a/module/plugins/internal/__init__.py b/pyload/plugins/crypter/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/plugins/internal/__init__.py
+++ b/pyload/plugins/crypter/__init__.py
diff --git a/pyload/plugins/hooks/AlldebridCom.py b/pyload/plugins/hooks/AlldebridCom.py
new file mode 100644
index 000000000..8eade2941
--- /dev/null
+++ b/pyload/plugins/hooks/AlldebridCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class AlldebridCom(MultiHoster):
+ __name__ = "AlldebridCom"
+ __type__ = "hook"
+ __version__ = "0.13"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("https", "bool", "Enable HTTPS", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Alldebrid.com hook plugin"""
+ __author_name__ = "Andy Voigt"
+ __author_mail__ = "spamsales@online.de"
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://www.alldebrid.com/api.php?action=get_host").replace("\"", "").strip()
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hooks/BypassCaptcha.py b/pyload/plugins/hooks/BypassCaptcha.py
new file mode 100644
index 000000000..9558ba4c4
--- /dev/null
+++ b/pyload/plugins/hooks/BypassCaptcha.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+
+from pycurl import FORM_FILE, LOW_SPEED_TIME
+from thread import start_new_thread
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.Hook import Hook
+
+
+class BypassCaptchaException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+ def getCode(self):
+ return self.err
+
+ def __str__(self):
+ return "<BypassCaptchaException %s>" % self.err
+
+ def __repr__(self):
+ return "<BypassCaptchaException %s>" % self.err
+
+
+class BypassCaptcha(Hook):
+ __name__ = "BypassCaptcha"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("force", "bool", "Force BC even if client is connected", False),
+ ("passkey", "password", "Passkey", "")]
+
+ __description__ = """Send captchas to BypassCaptcha.com"""
+ __author_name__ = ("RaNaN", "Godofdream", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "soilfcition@gmail.com", "zoidberg@mujmail.cz")
+
+ PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32"
+
+ SUBMIT_URL = "http://bypasscaptcha.com/upload.php"
+ RESPOND_URL = "http://bypasscaptcha.com/check_value.php"
+ GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php"
+
+
+ def setup(self):
+ self.info = {}
+
+ def getCredits(self):
+ response = getURL(self.GETCREDITS_URL, post={"key": self.getConfig("passkey")})
+
+ data = dict([x.split(' ', 1) for x in response.splitlines()])
+ return int(data['Left'])
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+
+ #raise timeout threshold
+ req.c.setopt(LOW_SPEED_TIME, 80)
+
+ try:
+ response = req.load(self.SUBMIT_URL,
+ post={"vendor_key": self.PYLOAD_KEY,
+ "key": self.getConfig("passkey"),
+ "gen_task_id": "1",
+ "file": (FORM_FILE, captcha)},
+ multipart=True)
+ finally:
+ req.close()
+
+ data = dict([x.split(' ', 1) for x in response.splitlines()])
+ if not data or "Value" not in data:
+ raise BypassCaptchaException(response)
+
+ result = data['Value']
+ ticket = data['TaskId']
+ self.logDebug("result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+ def respond(self, ticket, success):
+ try:
+ response = getURL(self.RESPOND_URL, post={"task_id": ticket, "key": self.getConfig("passkey"),
+ "cv": 1 if success else 0})
+ except BadHeader, e:
+ self.logError("Could not send response.", str(e))
+
+ def newCaptchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.data['service'] = self.__name__
+ task.setWaiting(100)
+ start_new_thread(self.processCaptcha, (task,))
+
+ else:
+ self.logInfo("Your %s account has not enough credits" % self.__name__)
+
+ def captchaCorrect(self, task):
+ if task.data['service'] == self.__name__ and "ticket" in task.data:
+ self.respond(task.data['ticket'], True)
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.__name__ and "ticket" in task.data:
+ self.respond(task.data['ticket'], False)
+
+ def processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except BypassCaptchaException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugins/hooks/Captcha9kw.py b/pyload/plugins/hooks/Captcha9kw.py
new file mode 100644
index 000000000..fcb5dd7c1
--- /dev/null
+++ b/pyload/plugins/hooks/Captcha9kw.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import time
+
+from base64 import b64encode
+from thread import start_new_thread
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hook import Hook
+
+
+class Captcha9kw(Hook):
+ __name__ = "Captcha9kw"
+ __type__ = "hook"
+ __version__ = "0.09"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("force", "bool", "Force CT even if client is connected", True),
+ ("https", "bool", "Enable HTTPS", False),
+ ("confirm", "bool", "Confirm Captcha (Cost +6)", False),
+ ("captchaperhour", "int", "Captcha per hour (max. 9999)", 9999),
+ ("prio", "int", "Prio 1-10 (Cost +1-10)", 0),
+ ("selfsolve", "bool",
+ "If enabled and you have a 9kw client active only you will get your captcha to solve it (Selfsolve)",
+ False),
+ ("timeout", "int", "Timeout (max. 300)", 300),
+ ("passkey", "password", "API key", "")]
+
+ __description__ = """Send captchas to 9kw.eu"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+ API_URL = "://www.9kw.eu/index.cgi"
+
+
+ def setup(self):
+ self.API_URL = "https" + self.API_URL if self.getConfig("https") else "http" + self.API_URL
+ self.info = {}
+
+ def getCredits(self):
+ response = getURL(self.API_URL, get={"apikey": self.getConfig("passkey"), "pyload": "1", "source": "pyload",
+ "action": "usercaptchaguthaben"})
+
+ if response.isdigit():
+ self.logInfo(_("%s credits left") % response)
+ self.info['credits'] = credits = int(response)
+ return credits
+ else:
+ self.logError(response)
+ return 0
+
+ def processCaptcha(self, task):
+ result = None
+
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+ data = b64encode(data)
+ self.logDebug("%s : %s" % (task.captchaFile, data))
+ if task.isPositional():
+ mouse = 1
+ else:
+ mouse = 0
+
+ response = getURL(self.API_URL, post={
+ "apikey": self.getConfig("passkey"),
+ "prio": self.getConfig("prio"),
+ "confirm": self.getConfig("confirm"),
+ "captchaperhour": self.getConfig("captchaperhour"),
+ "maxtimeout": self.getConfig("timeout"),
+ "selfsolve": self.getConfig("selfsolve"),
+ "pyload": "1",
+ "source": "pyload",
+ "base64": "1",
+ "mouse": mouse,
+ "file-upload-01": data,
+ "action": "usercaptchaupload"})
+
+ if response.isdigit():
+ self.logInfo(_("New CaptchaID from upload: %s : %s") % (response, task.captchaFile))
+
+ for _ in xrange(1, 100, 1):
+ response2 = getURL(self.API_URL, get={"apikey": self.getConfig("passkey"), "id": response,
+ "pyload": "1", "source": "pyload",
+ "action": "usercaptchacorrectdata"})
+
+ if response2 != "":
+ break
+
+ time.sleep(3)
+
+ result = response2
+ task.data['ticket'] = response
+ self.logInfo("result %s : %s" % (response, result))
+ task.setResult(result)
+ else:
+ self.logError("Bad upload: %s" % response)
+ return False
+
+ def newCaptchaTask(self, task):
+ if not task.isTextual() and not task.isPositional():
+ return False
+
+ if not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.setWaiting(self.getConfig("timeout"))
+ start_new_thread(self.processCaptcha, (task,))
+
+ else:
+ self.logError(_("Your Captcha 9kw.eu Account has not enough credits"))
+
+ def captchaCorrect(self, task):
+ if "ticket" in task.data:
+
+ try:
+ response = getURL(self.API_URL,
+ post={"action": "usercaptchacorrectback",
+ "apikey": self.getConfig("passkey"),
+ "api_key": self.getConfig("passkey"),
+ "correct": "1",
+ "pyload": "1",
+ "source": "pyload",
+ "id": task.data['ticket']})
+ self.logInfo("Request correct: %s" % response)
+
+ except BadHeader, e:
+ self.logError("Could not send correct request.", str(e))
+ else:
+ self.logError("No CaptchaID for correct request (task %s) found." % task)
+
+ def captchaInvalid(self, task):
+ if "ticket" in task.data:
+
+ try:
+ response = getURL(self.API_URL,
+ post={"action": "usercaptchacorrectback",
+ "apikey": self.getConfig("passkey"),
+ "api_key": self.getConfig("passkey"),
+ "correct": "2",
+ "pyload": "1",
+ "source": "pyload",
+ "id": task.data['ticket']})
+ self.logInfo("Request refund: %s" % response)
+
+ except BadHeader, e:
+ self.logError("Could not send refund request.", str(e))
+ else:
+ self.logError("No CaptchaID for not correct request (task %s) found." % task)
diff --git a/pyload/plugins/hooks/CaptchaBrotherhood.py b/pyload/plugins/hooks/CaptchaBrotherhood.py
new file mode 100644
index 000000000..81325be92
--- /dev/null
+++ b/pyload/plugins/hooks/CaptchaBrotherhood.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import StringIO
+import pycurl
+
+from PIL import Image
+from thread import start_new_thread
+from time import sleep
+from urllib import urlencode
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.Hook import Hook
+
+
+class CaptchaBrotherhoodException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+ def getCode(self):
+ return self.err
+
+ def __str__(self):
+ return "<CaptchaBrotherhoodException %s>" % self.err
+
+ def __repr__(self):
+ return "<CaptchaBrotherhoodException %s>" % self.err
+
+
+class CaptchaBrotherhood(Hook):
+ __name__ = "CaptchaBrotherhood"
+ __type__ = "hook"
+ __version__ = "0.05"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("username", "str", "Username", ""),
+ ("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Password", "")]
+
+ __description__ = """Send captchas to CaptchaBrotherhood.com"""
+ __author_name__ = ("RaNaN", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
+
+ API_URL = "http://www.captchabrotherhood.com/"
+
+
+ def setup(self):
+ self.info = {}
+
+ def getCredits(self):
+ response = getURL(self.API_URL + "askCredits.aspx",
+ get={"username": self.getConfig("username"), "password": self.getConfig("passkey")})
+ if not response.startswith("OK"):
+ raise CaptchaBrotherhoodException(response)
+ else:
+ credits = int(response[3:])
+ self.logInfo(_("%d credits left") % credits)
+ self.info['credits'] = credits
+ return credits
+
+ def submit(self, captcha, captchaType="file", match=None):
+ try:
+ img = Image.open(captcha)
+ output = StringIO.StringIO()
+ self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode)
+ if img.format in ("GIF", "JPEG"):
+ img.save(output, img.format)
+ else:
+ if img.mode != "RGB":
+ img = img.convert("RGB")
+ img.save(output, "JPEG")
+ data = output.getvalue()
+ output.close()
+ except Exception, e:
+ raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e)
+
+ req = getRequest()
+
+ url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL,
+ urlencode({"username": self.getConfig("username"),
+ "password": self.getConfig("passkey"),
+ "captchaSource": "pyLoad",
+ "timeout": "80"}))
+
+ req.c.setopt(pycurl.URL, url)
+ req.c.setopt(pycurl.POST, 1)
+ req.c.setopt(pycurl.POSTFIELDS, data)
+ req.c.setopt(pycurl.HTTPHEADER, ["Content-Type: text/html"])
+
+ try:
+ req.c.perform()
+ response = req.getResponse()
+ except Exception, e:
+ raise CaptchaBrotherhoodException("Submit captcha image failed")
+
+ req.close()
+
+ if not response.startswith("OK"):
+ raise CaptchaBrotherhoodException(response[1])
+
+ ticket = response[3:]
+
+ for _ in xrange(15):
+ sleep(5)
+ response = self.get_api("askCaptchaResult", ticket)
+ if response.startswith("OK-answered"):
+ return ticket, response[12:]
+
+ raise CaptchaBrotherhoodException("No solution received in time")
+
+ def get_api(self, api, ticket):
+ response = getURL("%s%s.aspx" % (self.API_URL, api),
+ get={"username": self.getConfig("username"),
+ "password": self.getConfig("passkey"),
+ "captchaID": ticket})
+ if not response.startswith("OK"):
+ raise CaptchaBrotherhoodException("Unknown response: %s" % response)
+
+ return response
+
+ def newCaptchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig("username") or not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if self.getCredits() > 10:
+ task.handler.append(self)
+ task.data['service'] = self.__name__
+ task.setWaiting(100)
+ start_new_thread(self.processCaptcha, (task,))
+ else:
+ self.logInfo("Your CaptchaBrotherhood Account has not enough credits")
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.__name__ and "ticket" in task.data:
+ response = self.get_api("complainCaptcha", task.data['ticket'])
+
+ def processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except CaptchaBrotherhoodException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugins/hooks/Checksum.py b/pyload/plugins/hooks/Checksum.py
new file mode 100644
index 000000000..75ebcdc4c
--- /dev/null
+++ b/pyload/plugins/hooks/Checksum.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import hashlib
+import re
+import zlib
+
+from os import remove
+from os.path import getsize, isfile, splitext
+
+from pyload.plugins.Hook import Hook
+from pyload.utils import safe_join, fs_encode
+
+
+def computeChecksum(local_file, algorithm):
+ if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")):
+ h = getattr(hashlib, algorithm)()
+
+ with open(local_file, 'rb') as f:
+ for chunk in iter(lambda: f.read(128 * h.block_size), b''):
+ h.update(chunk)
+
+ return h.hexdigest()
+
+ elif algorithm in ("adler32", "crc32"):
+ hf = getattr(zlib, algorithm)
+ last = 0
+
+ with open(local_file, 'rb') as f:
+ for chunk in iter(lambda: f.read(8192), b''):
+ last = hf(chunk, last)
+
+ return "%x" % last
+
+ else:
+ return None
+
+
+class Checksum(Hook):
+ __name__ = "Checksum"
+ __type__ = "hook"
+ __version__ = "0.13"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("check_checksum", "bool", "Check checksum? (If False only size will be verified)", True),
+ ("check_action", "fail;retry;nothing", "What to do if check fails?", "retry"),
+ ("max_tries", "int", "Number of retries", 2),
+ ("retry_action", "fail;nothing", "What to do if all retries fail?", "fail"),
+ ("wait_time", "int", "Time to wait before each retry (seconds)", 1)]
+
+ __description__ = """Verify downloaded file size and checksum"""
+ __author_name__ = ("zoidberg", "Walter Purcaro", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "vuolter@gmail.com", "l.stickell@yahoo.it")
+
+ methods = {'sfv': 'crc32', 'crc': 'crc32', 'hash': 'md5'}
+ regexps = {'sfv': r'^(?P<name>[^;].+)\s+(?P<hash>[0-9A-Fa-f]{8})$',
+ 'md5': r'^(?P<name>[0-9A-Fa-f]{32}) (?P<file>.+)$',
+ 'crc': r'filename=(?P<name>.+)\nsize=(?P<size>\d+)\ncrc32=(?P<hash>[0-9A-Fa-f]{8})$',
+ 'default': r'^(?P<hash>[0-9A-Fa-f]+)\s+\*?(?P<name>.+)$'}
+
+
+ def coreReady(self):
+ if not self.getConfig("check_checksum"):
+ self.logInfo("Checksum validation is disabled in plugin configuration")
+
+ def setup(self):
+ self.algorithms = sorted(
+ getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse=True)
+ self.algorithms.extend(["crc32", "adler32"])
+ self.formats = self.algorithms + ["sfv", "crc", "hash"]
+
+ def downloadFinished(self, pyfile):
+ """
+ Compute checksum for the downloaded file and compare it with the hash provided by the hoster.
+ pyfile.plugin.check_data should be a dictionary which can contain:
+ a) if known, the exact filesize in bytes (e.g. "size": 123456789)
+ b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307")
+ """
+ if hasattr(pyfile.plugin, "check_data") and (isinstance(pyfile.plugin.check_data, dict)):
+ data = pyfile.plugin.check_data.copy()
+ elif hasattr(pyfile.plugin, "api_data") and (isinstance(pyfile.plugin.api_data, dict)):
+ data = pyfile.plugin.api_data.copy()
+ else:
+ return
+
+ self.logDebug(data)
+
+ if not pyfile.plugin.lastDownload:
+ self.checkFailed(pyfile, None, "No file downloaded")
+
+ local_file = fs_encode(pyfile.plugin.lastDownload)
+ #download_folder = self.config['general']['download_folder']
+ #local_file = fs_encode(safe_join(download_folder, pyfile.package().folder, pyfile.name))
+
+ if not isfile(local_file):
+ self.checkFailed(pyfile, None, "File does not exist")
+
+ # validate file size
+ if "size" in data:
+ api_size = int(data['size'])
+ file_size = getsize(local_file)
+ if api_size != file_size:
+ self.logWarning("File %s has incorrect size: %d B (%d expected)" % (pyfile.name, file_size, api_size))
+ self.checkFailed(pyfile, local_file, "Incorrect file size")
+ del data['size']
+
+ # validate checksum
+ if data and self.getConfig("check_checksum"):
+ if "checksum" in data:
+ data['md5'] = data['checksum']
+
+ for key in self.algorithms:
+ if key in data:
+ checksum = computeChecksum(local_file, key.replace("-", "").lower())
+ if checksum:
+ if checksum == data[key].lower():
+ self.logInfo('File integrity of "%s" verified by %s checksum (%s).' %
+ (pyfile.name, key.upper(), checksum))
+ break
+ else:
+ self.logWarning("%s checksum for file %s does not match (%s != %s)" %
+ (key.upper(), pyfile.name, checksum, data[key]))
+ self.checkFailed(pyfile, local_file, "Checksums do not match")
+ else:
+ self.logWarning("Unsupported hashing algorithm: %s" % key.upper())
+ else:
+ self.logWarning("Unable to validate checksum for file %s" % pyfile.name)
+
+ def checkFailed(self, pyfile, local_file, msg):
+ check_action = self.getConfig("check_action")
+ if check_action == "retry":
+ max_tries = self.getConfig("max_tries")
+ retry_action = self.getConfig("retry_action")
+ if pyfile.plugin.retries < max_tries:
+ if local_file:
+ remove(local_file)
+ pyfile.plugin.retry(max_tries=max_tries, wait_time=self.getConfig("wait_time"), reason=msg)
+ elif retry_action == "nothing":
+ return
+ elif check_action == "nothing":
+ return
+ pyfile.plugin.fail(reason=msg)
+
+ def packageFinished(self, pypack):
+ download_folder = safe_join(self.config['general']['download_folder'], pypack.folder, "")
+
+ for link in pypack.getChildren().itervalues():
+ file_type = splitext(link['name'])[1][1:].lower()
+ #self.logDebug(link, file_type)
+
+ if file_type not in self.formats:
+ continue
+
+ hash_file = fs_encode(safe_join(download_folder, link['name']))
+ if not isfile(hash_file):
+ self.logWarning("File not found: %s" % link['name'])
+ continue
+
+ with open(hash_file) as f:
+ text = f.read()
+
+ for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text):
+ data = m.groupdict()
+ self.logDebug(link['name'], data)
+
+ local_file = fs_encode(safe_join(download_folder, data['name']))
+ algorithm = self.methods.get(file_type, file_type)
+ checksum = computeChecksum(local_file, algorithm)
+ if checksum == data['hash']:
+ self.logInfo('File integrity of "%s" verified by %s checksum (%s).' %
+ (data['name'], algorithm, checksum))
+ else:
+ self.logWarning("%s checksum for file %s does not match (%s != %s)" %
+ (algorithm, data['name'], checksum, data['hash']))
diff --git a/pyload/plugins/hooks/ClickAndLoad.py b/pyload/plugins/hooks/ClickAndLoad.py
new file mode 100644
index 000000000..47163ceef
--- /dev/null
+++ b/pyload/plugins/hooks/ClickAndLoad.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import socket
+import thread
+
+from pyload.plugins.Hook import Hook
+
+
+class ClickAndLoad(Hook):
+ __name__ = "ClickAndLoad"
+ __type__ = "hook"
+ __version__ = "0.22"
+
+ __config__ = [("activated", "bool", "Activated", True),
+ ("extern", "bool", "Allow external link adding", False)]
+
+ __description__ = """Gives abillity to use jd's click and load. depends on webinterface"""
+ __author_name__ = ("RaNaN", "mkaay")
+ __author_mail__ = ("RaNaN@pyload.de", "mkaay@mkaay.de")
+
+
+ def coreReady(self):
+ self.port = int(self.config['webinterface']['port'])
+ if self.config['webinterface']['activated']:
+ try:
+ if self.getConfig("extern"):
+ ip = "0.0.0.0"
+ else:
+ ip = "127.0.0.1"
+
+ thread.start_new_thread(proxy, (self, ip, self.port, 9666))
+ except:
+ self.logError("ClickAndLoad port already in use.")
+
+
+def proxy(self, *settings):
+ thread.start_new_thread(server, (self,) + settings)
+ lock = thread.allocate_lock()
+ lock.acquire()
+ lock.acquire()
+
+
+def server(self, *settings):
+ try:
+ dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ dock_socket.bind((settings[0], settings[2]))
+ dock_socket.listen(5)
+ while True:
+ client_socket = dock_socket.accept()[0]
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ server_socket.connect(("127.0.0.1", settings[1]))
+ thread.start_new_thread(forward, (client_socket, server_socket))
+ thread.start_new_thread(forward, (server_socket, client_socket))
+ except socket.error, e:
+ if hasattr(e, "errno"):
+ errno = e.errno
+ else:
+ errno = e.args[0]
+
+ if errno == 98:
+ self.logWarning(_("Click'N'Load: Port 9666 already in use"))
+ return
+ thread.start_new_thread(server, (self,) + settings)
+ except:
+ thread.start_new_thread(server, (self,) + settings)
+
+
+def forward(source, destination):
+ string = ' '
+ while string:
+ string = source.recv(1024)
+ if string:
+ destination.sendall(string)
+ else:
+ #source.shutdown(socket.SHUT_RD)
+ destination.shutdown(socket.SHUT_WR)
diff --git a/pyload/plugins/hooks/DeathByCaptcha.py b/pyload/plugins/hooks/DeathByCaptcha.py
new file mode 100644
index 000000000..6db91b8c1
--- /dev/null
+++ b/pyload/plugins/hooks/DeathByCaptcha.py
@@ -0,0 +1,202 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from base64 import b64encode
+from pycurl import FORM_FILE, HTTPHEADER
+from thread import start_new_thread
+from time import sleep
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getRequest
+from pyload.plugins.Hook import Hook
+
+
+class DeathByCaptchaException(Exception):
+ DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials',
+ 'invalid-credentials': 'Access denied, check your credentials',
+ 'banned': 'Access denied, account is suspended',
+ 'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA',
+ 'invalid-captcha': 'CAPTCHA is not a valid image',
+ 'service-overload': 'CAPTCHA was rejected due to service overload, try again later',
+ 'invalid-request': 'Invalid request',
+ 'timed-out': 'No CAPTCHA solution received in time'}
+
+ def __init__(self, err):
+ self.err = err
+
+ def getCode(self):
+ return self.err
+
+ def getDesc(self):
+ if self.err in self.DBC_ERRORS.keys():
+ return self.DBC_ERRORS[self.err]
+ else:
+ return self.err
+
+ def __str__(self):
+ return "<DeathByCaptchaException %s>" % self.err
+
+ def __repr__(self):
+ return "<DeathByCaptchaException %s>" % self.err
+
+
+class DeathByCaptcha(Hook):
+ __name__ = "DeathByCaptcha"
+ __type__ = "hook"
+ __version__ = "0.03"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force DBC even if client is connected", False)]
+
+ __description__ = """Send captchas to DeathByCaptcha.com"""
+ __author_name__ = ("RaNaN", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
+
+ API_URL = "http://api.dbcapi.me/api/"
+
+
+ def setup(self):
+ self.info = {}
+
+ def call_api(self, api="captcha", post=False, multipart=False):
+ req = getRequest()
+ req.c.setopt(HTTPHEADER, ["Accept: application/json", "User-Agent: pyLoad %s" % self.core.version])
+
+ if post:
+ if not isinstance(post, dict):
+ post = {}
+ post.update({"username": self.getConfig("username"),
+ "password": self.getConfig("passkey")})
+
+ response = None
+ try:
+ json = req.load("%s%s" % (self.API_URL, api),
+ post=post,
+ multipart=multipart)
+ self.logDebug(json)
+ response = json_loads(json)
+
+ if "error" in response:
+ raise DeathByCaptchaException(response['error'])
+ elif "status" not in response:
+ raise DeathByCaptchaException(str(response))
+
+ except BadHeader, e:
+ if 403 == e.code:
+ raise DeathByCaptchaException('not-logged-in')
+ elif 413 == e.code:
+ raise DeathByCaptchaException('invalid-captcha')
+ elif 503 == e.code:
+ raise DeathByCaptchaException('service-overload')
+ elif e.code in (400, 405):
+ raise DeathByCaptchaException('invalid-request')
+ else:
+ raise
+
+ finally:
+ req.close()
+
+ return response
+
+ def getCredits(self):
+ response = self.call_api("user", True)
+
+ if 'is_banned' in response and response['is_banned']:
+ raise DeathByCaptchaException('banned')
+ elif 'balance' in response and 'rate' in response:
+ self.info.update(response)
+ else:
+ raise DeathByCaptchaException(response)
+
+ def getStatus(self):
+ response = self.call_api("status", False)
+
+ if 'is_service_overloaded' in response and response['is_service_overloaded']:
+ raise DeathByCaptchaException('service-overload')
+
+ def submit(self, captcha, captchaType="file", match=None):
+ #workaround multipart-post bug in HTTPRequest.py
+ if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")):
+ multipart = True
+ data = (FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = "base64:" + b64encode(data)
+
+ response = self.call_api("captcha", {"captchafile": data}, multipart)
+
+ if "captcha" not in response:
+ raise DeathByCaptchaException(response)
+ ticket = response['captcha']
+
+ for _ in xrange(24):
+ sleep(5)
+ response = self.call_api("captcha/%d" % ticket, False)
+ if response['text'] and response['is_correct']:
+ break
+ else:
+ raise DeathByCaptchaException('timed-out')
+
+ result = response['text']
+ self.logDebug("result %s : %s" % (ticket, result))
+
+ return ticket, result
+
+ def newCaptchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig("username") or not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ try:
+ self.getStatus()
+ self.getCredits()
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+ return False
+
+ balance, rate = self.info['balance'], self.info['rate']
+ self.logInfo("Account balance: US$%.3f (%d captchas left at %.2f cents each)" % (balance / 100,
+ balance // rate, rate))
+
+ if balance > rate:
+ task.handler.append(self)
+ task.data['service'] = self.__name__
+ task.setWaiting(180)
+ start_new_thread(self.processCaptcha, (task,))
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.__name__ and "ticket" in task.data:
+ try:
+ response = self.call_api("captcha/%d/report" % task.data['ticket'], True)
+ except DeathByCaptchaException, e:
+ self.logError(e.getDesc())
+ except Exception, e:
+ self.logError(e)
+
+ def processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except DeathByCaptchaException, e:
+ task.error = e.getCode()
+ self.logError(e.getDesc())
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugins/hooks/DebridItaliaCom.py b/pyload/plugins/hooks/DebridItaliaCom.py
new file mode 100644
index 000000000..4272b758f
--- /dev/null
+++ b/pyload/plugins/hooks/DebridItaliaCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class DebridItaliaCom(MultiHoster):
+ __name__ = "DebridItaliaCom"
+ __type__ = "hook"
+ __version__ = "0.07"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Debriditalia.com hook plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def getHoster(self):
+ return ["netload.in", "hotfile.com", "rapidshare.com", "multiupload.com",
+ "uploading.com", "megashares.com", "crocko.com", "filepost.com",
+ "bitshare.com", "share-links.biz", "putlocker.com", "uploaded.to",
+ "speedload.org", "rapidgator.net", "likeupload.net", "cyberlocker.ch",
+ "depositfiles.com", "extabit.com", "filefactory.com", "sharefiles.co",
+ "ryushare.com", "tusfiles.net", "nowvideo.co", "cloudzer.net", "letitbit.net",
+ "easybytez.com", "uptobox.com", "ddlstorage.com"]
diff --git a/pyload/plugins/hooks/DeleteFinished.py b/pyload/plugins/hooks/DeleteFinished.py
new file mode 100644
index 000000000..99aa040bf
--- /dev/null
+++ b/pyload/plugins/hooks/DeleteFinished.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+from pyload.database import style
+from pyload.plugins.Hook import Hook
+
+
+class DeleteFinished(Hook):
+ __name__ = "DeleteFinished"
+ __type__ = "hook"
+ __version__ = "1.09"
+
+ __config__ = [('activated', 'bool', 'Activated', 'False'),
+ ('interval', 'int', 'Delete every (hours)', '72'),
+ ('deloffline', 'bool', 'Delete packages with offline links', 'False')]
+
+ __description__ = """Automatically delete all finished packages from queue"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+ ## overwritten methods ##
+ def periodical(self):
+ if not self.info['sleep']:
+ deloffline = self.getConfig('deloffline')
+ mode = '0,1,4' if deloffline else '0,4'
+ msg = 'delete all finished packages in queue list (%s packages with offline links)'
+ self.logInfo(msg % ('including' if deloffline else 'excluding'))
+ self.deleteFinished(mode)
+ self.info['sleep'] = True
+ self.addEvent('packageFinished', self.wakeup)
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == 'interval' and value != self.interval:
+ self.interval = value * 3600
+ self.initPeriodical()
+
+ def unload(self):
+ self.removeEvent('packageFinished', self.wakeup)
+
+ def coreReady(self):
+ self.info = {'sleep': True}
+ interval = self.getConfig('interval')
+ self.pluginConfigChanged('DeleteFinished', 'interval', interval)
+ self.addEvent('packageFinished', self.wakeup)
+
+ ## own methods ##
+ @style.queue
+ def deleteFinished(self, mode):
+ self.c.execute('DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE package=packages.id AND status NOT IN (%s))' % mode)
+ self.c.execute('DELETE FROM links WHERE NOT EXISTS(SELECT 1 FROM packages WHERE id=links.package)')
+
+ def wakeup(self, pypack):
+ self.removeEvent('packageFinished', self.wakeup)
+ self.info['sleep'] = False
+
+ ## event managing ##
+ def addEvent(self, event, func):
+ """Adds an event listener for event name"""
+ if event in self.m.events:
+ if func in self.m.events[event]:
+ self.logDebug('Function already registered %s' % func)
+ else:
+ self.m.events[event].append(func)
+ else:
+ self.m.events[event] = [func]
+
+ def setup(self):
+ self.m = self.manager
+ self.removeEvent = self.m.removeEvent
diff --git a/pyload/plugins/hooks/DownloadScheduler.py b/pyload/plugins/hooks/DownloadScheduler.py
new file mode 100644
index 000000000..fc2e10aac
--- /dev/null
+++ b/pyload/plugins/hooks/DownloadScheduler.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import localtime
+
+from pyload.plugins.Hook import Hook
+
+
+class DownloadScheduler(Hook):
+ __name__ = "DownloadScheduler"
+ __type__ = "hook"
+ __version__ = "0.21"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("timetable", "str", "List time periods as hh:mm full or number(kB/s)",
+ "0:00 full, 7:00 250, 10:00 0, 17:00 150"),
+ ("abort", "bool", "Abort active downloads when start period with speed 0", False)]
+
+ __description__ = """Download Scheduler"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+ def setup(self):
+ self.cb = None # callback to scheduler job; will be by removed hookmanager when hook unloaded
+
+ def coreReady(self):
+ self.updateSchedule()
+
+ def updateSchedule(self, schedule=None):
+ if schedule is None:
+ schedule = self.getConfig("timetable")
+
+ schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)",
+ schedule.lower().replace("full", "-1").replace("none", "0"))
+ if not schedule:
+ self.logError("Invalid schedule")
+ return
+
+ t0 = localtime()
+ now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X")
+ schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now])
+
+ self.logDebug("Schedule", schedule)
+
+ for i, v in enumerate(schedule):
+ if v[3] == "X":
+ last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)]
+ self.logDebug("Now/Last/Next", now, last, next)
+
+ self.setDownloadSpeed(last[3])
+
+ next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400
+ self.core.scheduler.removeJob(self.cb)
+ self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False)
+
+ def setDownloadSpeed(self, speed):
+ if speed == 0:
+ abort = self.getConfig("abort")
+ self.logInfo("Stopping download server. (Running downloads will %sbe aborted.)" % ('' if abort else 'not '))
+ self.core.api.pauseServer()
+ if abort:
+ self.core.api.stopAllDownloads()
+ else:
+ self.core.api.unpauseServer()
+
+ if speed > 0:
+ self.logInfo("Setting download speed to %d kB/s" % speed)
+ self.core.api.setConfigValue("download", "limit_speed", 1)
+ self.core.api.setConfigValue("download", "max_speed", speed)
+ else:
+ self.logInfo("Setting download speed to FULL")
+ self.core.api.setConfigValue("download", "limit_speed", 0)
+ self.core.api.setConfigValue("download", "max_speed", -1)
diff --git a/pyload/plugins/hooks/EasybytezCom.py b/pyload/plugins/hooks/EasybytezCom.py
new file mode 100644
index 000000000..1ec8a98f1
--- /dev/null
+++ b/pyload/plugins/hooks/EasybytezCom.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class EasybytezCom(MultiHoster):
+ __name__ = "EasybytezCom"
+ __type__ = "hook"
+ __version__ = "0.03"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """EasyBytez.com hook plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def getHoster(self):
+ self.account = self.core.accountManager.getAccountPlugin(self.__name__)
+ user = self.account.selectAccount()[0]
+
+ try:
+ req = self.account.getAccountRequest(user)
+ page = req.load("http://www.easybytez.com")
+
+ m = re.search(r'</textarea>\s*Supported sites:(.*)', page)
+ return m.group(1).split(',')
+ except Exception, e:
+ self.logDebug(e)
+ self.logWarning("Unable to load supported hoster list, using last known")
+ return ["bitshare.com", "crocko.com", "ddlstorage.com", "depositfiles.com", "extabit.com", "hotfile.com",
+ "mediafire.com", "netload.in", "rapidgator.net", "rapidshare.com", "uploading.com", "uload.to",
+ "uploaded.to"]
diff --git a/pyload/plugins/hooks/Ev0InFetcher.py b/pyload/plugins/hooks/Ev0InFetcher.py
new file mode 100644
index 000000000..c3def8add
--- /dev/null
+++ b/pyload/plugins/hooks/Ev0InFetcher.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import feedparser
+
+from time import mktime, time
+
+from pyload.plugins.Hook import Hook
+
+
+class Ev0InFetcher(Hook):
+ __name__ = "Ev0InFetcher"
+ __type__ = "hook"
+ __version__ = "0.21"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("interval", "int", "Check interval in minutes", 10),
+ ("queue", "bool", "Move new shows directly to Queue", False),
+ ("shows", "str", "Shows to check for (comma seperated)", ""),
+ ("quality", "xvid;x264;rmvb", "Video Format", "xvid"),
+ ("hoster", "str", "Hoster to use (comma seperated)",
+ "NetloadIn,RapidshareCom,MegauploadCom,HotfileCom")]
+
+ __description__ = """Checks rss feeds for Ev0.in"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def setup(self):
+ self.interval = self.getConfig("interval") * 60
+
+ def filterLinks(self, links):
+ results = self.core.pluginManager.parseUrls(links)
+ sortedLinks = {}
+
+ for url, hoster in results:
+ if hoster not in sortedLinks:
+ sortedLinks[hoster] = []
+ sortedLinks[hoster].append(url)
+
+ for h in self.getConfig("hoster").split(","):
+ try:
+ return sortedLinks[h.strip()]
+ except:
+ continue
+ return []
+
+
+ def periodical(self):
+
+ def normalizefiletitle(filename):
+ filename = filename.replace('.', ' ')
+ filename = filename.replace('_', ' ')
+ filename = filename.lower()
+ return filename
+
+ shows = [s.strip() for s in self.getConfig("shows").split(",")]
+
+ feed = feedparser.parse("http://feeds.feedburner.com/ev0in/%s?format=xml" % self.getConfig("quality"))
+
+ showStorage = {}
+ for show in shows:
+ showStorage[show] = int(self.getStorage("show_%s_lastfound" % show, 0))
+
+ found = False
+ for item in feed['items']:
+ for show, lastfound in showStorage.iteritems():
+ if show.lower() in normalizefiletitle(item['title']) and lastfound < int(mktime(item.date_parsed)):
+ links = self.filterLinks(item['description'].split("<br />"))
+ packagename = item['title'].encode("utf-8")
+ self.logInfo("Ev0InFetcher: new episode '%s' (matched '%s')" % (packagename, show))
+ self.core.api.addPackage(packagename, links, 1 if self.getConfig("queue") else 0)
+ self.setStorage("show_%s_lastfound" % show, int(mktime(item.date_parsed)))
+ found = True
+ if not found:
+ #self.logDebug("Ev0InFetcher: no new episodes found")
+ pass
+
+ for show, lastfound in self.getStorage().iteritems():
+ if int(lastfound) > 0 and int(lastfound) + (3600 * 24 * 30) < int(time()):
+ self.delStorage("show_%s_lastfound" % show)
+ self.logDebug("Ev0InFetcher: cleaned '%s' record" % show)
diff --git a/pyload/plugins/hooks/ExpertDecoders.py b/pyload/plugins/hooks/ExpertDecoders.py
new file mode 100644
index 000000000..ef5409b76
--- /dev/null
+++ b/pyload/plugins/hooks/ExpertDecoders.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+from base64 import b64encode
+from pycurl import LOW_SPEED_TIME
+from thread import start_new_thread
+from uuid import uuid4
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.Hook import Hook
+
+
+class ExpertDecoders(Hook):
+ __name__ = "ExpertDecoders"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("force", "bool", "Force CT even if client is connected", False),
+ ("passkey", "password", "Access key", "")]
+
+ __description__ = """Send captchas to expertdecoders.com"""
+ __author_name__ = ("RaNaN", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
+
+ API_URL = "http://www.fasttypers.org/imagepost.ashx"
+
+
+ def setup(self):
+ self.info = {}
+
+ def getCredits(self):
+ response = getURL(self.API_URL, post={"key": self.getConfig("passkey"), "action": "balance"})
+
+ if response.isdigit():
+ self.logInfo(_("%s credits left") % response)
+ self.info['credits'] = credits = int(response)
+ return credits
+ else:
+ self.logError(response)
+ return 0
+
+ def processCaptcha(self, task):
+ task.data['ticket'] = ticket = uuid4()
+ result = None
+
+ with open(task.captchaFile, 'rb') as f:
+ data = f.read()
+ data = b64encode(data)
+ #self.logDebug("%s: %s : %s" % (ticket, task.captchaFile, data))
+
+ req = getRequest()
+ #raise timeout threshold
+ req.c.setopt(LOW_SPEED_TIME, 80)
+
+ try:
+ result = req.load(self.API_URL, post={"action": "upload", "key": self.getConfig("passkey"),
+ "file": data, "gen_task_id": ticket})
+ finally:
+ req.close()
+
+ self.logDebug("result %s : %s" % (ticket, result))
+ task.setResult(result)
+
+ def newCaptchaTask(self, task):
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.setWaiting(100)
+ start_new_thread(self.processCaptcha, (task,))
+
+ else:
+ self.logInfo(_("Your ExpertDecoders Account has not enough credits"))
+
+ def captchaInvalid(self, task):
+ if "ticket" in task.data:
+
+ try:
+ response = getURL(self.API_URL, post={"action": "refund", "key": self.getConfig("passkey"),
+ "gen_task_id": task.data['ticket']})
+ self.logInfo("Request refund: %s" % response)
+
+ except BadHeader, e:
+ self.logError("Could not send refund request.", str(e))
diff --git a/pyload/plugins/hooks/ExternalScripts.py b/pyload/plugins/hooks/ExternalScripts.py
new file mode 100644
index 000000000..372035e82
--- /dev/null
+++ b/pyload/plugins/hooks/ExternalScripts.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+import subprocess
+
+from os import listdir, access, X_OK, makedirs
+from os.path import join, exists, basename, abspath
+
+from pyload.plugins.Hook import Hook
+from pyload.utils import safe_join
+
+
+class ExternalScripts(Hook):
+ __name__ = "ExternalScripts"
+ __type__ = "hook"
+ __version__ = "0.23"
+
+ __config__ = [("activated", "bool", "Activated", True)]
+
+ __description__ = """Run external scripts"""
+ __author_name__ = ("mkaay", "RaNaN", "spoob")
+ __author_mail__ = ("mkaay@mkaay.de", "ranan@pyload.org", "spoob@pyload.org")
+
+ event_list = ["unrarFinished", "allDownloadsFinished", "allDownloadsProcessed"]
+
+
+ def setup(self):
+ self.scripts = {}
+
+ folders = ["download_preparing", "download_finished", "package_finished",
+ "before_reconnect", "after_reconnect", "unrar_finished",
+ "all_dls_finished", "all_dls_processed"]
+
+ for folder in folders:
+ self.scripts[folder] = []
+
+ self.initPluginType(folder, join(pypath, 'scripts', folder))
+ self.initPluginType(folder, join('scripts', folder))
+
+ for script_type, names in self.scripts.iteritems():
+ if names:
+ self.logInfo((_("Installed scripts for %s: ") % script_type) + ", ".join([basename(x) for x in names]))
+
+ def initPluginType(self, folder, path):
+ if not exists(path):
+ try:
+ makedirs(path)
+ except:
+ self.logDebug("Script folder %s not created" % folder)
+ return
+
+ for f in listdir(path):
+ if f.startswith("#") or f.startswith(".") or f.startswith("_") or f.endswith("~") or f.endswith(".swp"):
+ continue
+
+ if not access(join(path, f), X_OK):
+ self.logWarning(_("Script not executable:") + " %s/%s" % (folder, f))
+
+ self.scripts[folder].append(join(path, f))
+
+ def callScript(self, script, *args):
+ try:
+ cmd = [script] + [str(x) if not isinstance(x, basestring) else x for x in args]
+ self.logDebug("Executing %(script)s: %(cmd)s" % {"script": abspath(script), "cmd": " ".join(cmd)})
+ #output goes to pyload
+ subprocess.Popen(cmd, bufsize=-1)
+ except Exception, e:
+ self.logError(_("Error in %(script)s: %(error)s") % {"script": basename(script), "error": str(e)})
+
+ def downloadPreparing(self, pyfile):
+ for script in self.scripts['download_preparing']:
+ self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.id)
+
+ def downloadFinished(self, pyfile):
+ for script in self.scripts['download_finished']:
+ self.callScript(script, pyfile.pluginname, pyfile.url, pyfile.name,
+ safe_join(self.config['general']['download_folder'],
+ pyfile.package().folder, pyfile.name), pyfile.id)
+
+ def packageFinished(self, pypack):
+ for script in self.scripts['package_finished']:
+ folder = self.config['general']['download_folder']
+ folder = safe_join(folder, pypack.folder)
+
+ self.callScript(script, pypack.name, folder, pypack.password, pypack.id)
+
+ def beforeReconnecting(self, ip):
+ for script in self.scripts['before_reconnect']:
+ self.callScript(script, ip)
+
+ def afterReconnecting(self, ip):
+ for script in self.scripts['after_reconnect']:
+ self.callScript(script, ip)
+
+ def unrarFinished(self, folder, fname):
+ for script in self.scripts['unrar_finished']:
+ self.callScript(script, folder, fname)
+
+ def allDownloadsFinished(self):
+ for script in self.scripts['all_dls_finished']:
+ self.callScript(script)
+
+ def allDownloadsProcessed(self):
+ for script in self.scripts['all_dls_processed']:
+ self.callScript(script)
diff --git a/pyload/plugins/hooks/ExtractArchive.py b/pyload/plugins/hooks/ExtractArchive.py
new file mode 100644
index 000000000..1a2da53ad
--- /dev/null
+++ b/pyload/plugins/hooks/ExtractArchive.py
@@ -0,0 +1,320 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+from copy import copy
+from os import remove, chmod, makedirs
+from os.path import exists, basename, isfile, isdir
+from traceback import print_exc
+
+# monkey patch bug in python 2.6 and lower
+# http://bugs.python.org/issue6122 , http://bugs.python.org/issue1236 , http://bugs.python.org/issue1731717
+if sys.version_info < (2, 7) and os.name != "nt":
+ import errno
+ from subprocess import Popen
+
+ def _eintr_retry_call(func, *args):
+ while True:
+ try:
+ return func(*args)
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
+
+ # unsued timeout option for older python version
+ def wait(self, timeout=0):
+ """Wait for child process to terminate. Returns returncode
+ attribute."""
+ if self.returncode is None:
+ try:
+ pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
+ except OSError, e:
+ if e.errno != errno.ECHILD:
+ raise
+ # This happens if SIGCLD is set to be ignored or waiting
+ # for child processes has otherwise been disabled for our
+ # process. This child is dead, we can't get the status.
+ sts = 0
+ self._handle_exitstatus(sts)
+ return self.returncode
+
+ Popen.wait = wait
+
+if os.name != "nt":
+ from grp import getgrnam
+ from os import chown
+ from pwd import getpwnam
+
+from pyload.plugins.Hook import Hook, threaded, Expose
+from pyload.plugins.internal.AbstractExtractor import ArchiveError, CRCError, WrongPassword
+from pyload.utils import safe_join, fs_encode
+
+
+class ExtractArchive(Hook):
+ """
+ Provides: unrarFinished (folder, filename)
+ """
+ __name__ = "ExtractArchive"
+ __type__ = "hook"
+ __version__ = "0.16"
+
+ __config__ = [("activated", "bool", "Activated", True),
+ ("fullpath", "bool", "Extract full path", True),
+ ("overwrite", "bool", "Overwrite files", True),
+ ("passwordfile", "file", "password file", "unrar_passwords.txt"),
+ ("deletearchive", "bool", "Delete archives when done", False),
+ ("subfolder", "bool", "Create subfolder for each package", False),
+ ("destination", "folder", "Extract files to", ""),
+ ("excludefiles", "str", "Exclude files from unpacking (seperated by ;)", ""),
+ ("recursive", "bool", "Extract archives in archvies", True),
+ ("queue", "bool", "Wait for all downloads to be finished", True),
+ ("renice", "int", "CPU Priority", 0)]
+
+ __description__ = """Extract different kind of archives"""
+ __author_name__ = ("pyLoad Team", "AndroKev")
+ __author_mail__ = ("admin@pyload.org", "@pyloadforum")
+
+ event_list = ["allDownloadsProcessed"]
+
+
+ def setup(self):
+ self.plugins = []
+ self.passwords = []
+ names = []
+
+ for p in ("UnRar", "UnZip"):
+ try:
+ module = self.core.pluginManager.loadModule("internal", p)
+ klass = getattr(module, p)
+ if klass.checkDeps():
+ names.append(p)
+ self.plugins.append(klass)
+
+ except OSError, e:
+ if e.errno == 2:
+ self.logInfo(_("No %s installed") % p)
+ else:
+ self.logWarning(_("Could not activate %s") % p, str(e))
+ if self.core.debug:
+ print_exc()
+
+ except Exception, e:
+ self.logWarning(_("Could not activate %s") % p, str(e))
+ if self.core.debug:
+ print_exc()
+
+ if names:
+ self.logInfo(_("Activated") + " " + " ".join(names))
+ else:
+ self.logInfo(_("No Extract plugins activated"))
+
+ # queue with package ids
+ self.queue = []
+
+ @Expose
+ def extractPackage(self, id):
+ """ Extract package with given id"""
+ self.manager.startThread(self.extract, [id])
+
+ def packageFinished(self, pypack):
+ if self.getConfig("queue"):
+ self.logInfo(_("Package %s queued for later extracting") % pypack.name)
+ self.queue.append(pypack.id)
+ else:
+ self.manager.startThread(self.extract, [pypack.id])
+
+ @threaded
+ def allDownloadsProcessed(self, thread):
+ local = copy(self.queue)
+ del self.queue[:]
+ self.extract(local, thread)
+
+ def extract(self, ids, thread=None):
+ # reload from txt file
+ self.reloadPasswords()
+
+ # dl folder
+ dl = self.config['general']['download_folder']
+
+ extracted = []
+
+ #iterate packages -> plugins -> targets
+ for pid in ids:
+ p = self.core.files.getPackage(pid)
+ self.logInfo(_("Check package %s") % p.name)
+ if not p:
+ continue
+
+ # determine output folder
+ out = safe_join(dl, p.folder, "")
+ # force trailing slash
+
+ if self.getConfig("destination") and self.getConfig("destination").lower() != "none":
+
+ out = safe_join(dl, p.folder, self.getConfig("destination"), "")
+ #relative to package folder if destination is relative, otherwise absolute path overwrites them
+
+ if self.getConfig("subfolder"):
+ out = safe_join(out, fs_encode(p.folder))
+
+ if not exists(out):
+ makedirs(out)
+
+ files_ids = [(safe_join(dl, p.folder, x['name']), x['id']) for x in p.getChildren().itervalues()]
+ matched = False
+
+ # check as long there are unseen files
+ while files_ids:
+ new_files_ids = []
+
+ for plugin in self.plugins:
+ targets = plugin.getTargets(files_ids)
+ if targets:
+ self.logDebug("Targets for %s: %s" % (plugin.__name__, targets))
+ matched = True
+ for target, fid in targets:
+ if target in extracted:
+ self.logDebug(basename(target), "skipped")
+ continue
+ extracted.append(target) # prevent extracting same file twice
+
+ klass = plugin(self, target, out, self.getConfig("fullpath"), self.getConfig("overwrite"), self.getConfig("excludefiles"),
+ self.getConfig("renice"))
+ klass.init()
+
+ self.logInfo(basename(target), _("Extract to %s") % out)
+ new_files = self.startExtracting(klass, fid, p.password.strip().splitlines(), thread)
+ self.logDebug("Extracted: %s" % new_files)
+ self.setPermissions(new_files)
+
+ for file in new_files:
+ if not exists(file):
+ self.logDebug("new file %s does not exists" % file)
+ continue
+ if self.getConfig("recursive") and isfile(file):
+ new_files_ids.append((file, fid)) # append as new target
+
+ files_ids = new_files_ids # also check extracted files
+
+ if not matched:
+ self.logInfo(_("No files found to extract"))
+
+ def startExtracting(self, plugin, fid, passwords, thread):
+ pyfile = self.core.files.getFile(fid)
+ if not pyfile:
+ return []
+
+ pyfile.setCustomStatus(_("extracting"))
+ thread.addActive(pyfile) # keep this file until everything is done
+
+ try:
+ progress = lambda x: pyfile.setProgress(x)
+ success = False
+
+ if not plugin.checkArchive():
+ plugin.extract(progress)
+ success = True
+ else:
+ self.logInfo(basename(plugin.file), _("Password protected"))
+ self.logDebug("Passwords: %s" % str(passwords))
+
+ pwlist = copy(self.getPasswords())
+ #remove already supplied pws from list (only local)
+ for pw in passwords:
+ if pw in pwlist:
+ pwlist.remove(pw)
+
+ for pw in passwords + pwlist:
+ try:
+ self.logDebug("Try password: %s" % pw)
+ if plugin.checkPassword(pw):
+ plugin.extract(progress, pw)
+ self.addPassword(pw)
+ success = True
+ break
+ except WrongPassword:
+ self.logDebug("Password was wrong")
+
+ if not success:
+ self.logError(basename(plugin.file), _("Wrong password"))
+ return []
+
+ if self.core.debug:
+ self.logDebug("Would delete: %s" % ", ".join(plugin.getDeleteFiles()))
+
+ if self.getConfig("deletearchive"):
+ files = plugin.getDeleteFiles()
+ self.logInfo(_("Deleting %s files") % len(files))
+ for f in files:
+ if exists(f):
+ remove(f)
+ else:
+ self.logDebug("%s does not exists" % f)
+
+ self.logInfo(basename(plugin.file), _("Extracting finished"))
+ self.manager.dispatchEvent("unrarFinished", plugin.out, plugin.file)
+
+ return plugin.getExtractedFiles()
+
+ except ArchiveError, e:
+ self.logError(basename(plugin.file), _("Archive Error"), str(e))
+ except CRCError:
+ self.logError(basename(plugin.file), _("CRC Mismatch"))
+ except Exception, e:
+ if self.core.debug:
+ print_exc()
+ self.logError(basename(plugin.file), _("Unknown Error"), str(e))
+
+ return []
+
+ @Expose
+ def getPasswords(self):
+ """ List of saved passwords """
+ return self.passwords
+
+ def reloadPasswords(self):
+ pwfile = self.getConfig("passwordfile")
+ if not exists(pwfile):
+ open(pwfile, "wb").close()
+
+ passwords = []
+ f = open(pwfile, "rb")
+ for pw in f.read().splitlines():
+ passwords.append(pw)
+ f.close()
+
+ self.passwords = passwords
+
+ @Expose
+ def addPassword(self, pw):
+ """ Adds a password to saved list"""
+ pwfile = self.getConfig("passwordfile")
+
+ if pw in self.passwords:
+ self.passwords.remove(pw)
+ self.passwords.insert(0, pw)
+
+ f = open(pwfile, "wb")
+ for pw in self.passwords:
+ f.write(pw + "\n")
+ f.close()
+
+ def setPermissions(self, files):
+ for f in files:
+ if not exists(f):
+ continue
+ try:
+ if self.config['permission']['change_file']:
+ if isfile(f):
+ chmod(f, int(self.config['permission']['file'], 8))
+ elif isdir(f):
+ chmod(f, int(self.config['permission']['folder'], 8))
+
+ if self.config['permission']['change_dl'] and os.name != "nt":
+ uid = getpwnam(self.config['permission']['user'])[2]
+ gid = getgrnam(self.config['permission']['group'])[2]
+ chown(f, uid, gid)
+ except Exception, e:
+ self.logWarning(_("Setting User and Group failed"), e)
diff --git a/pyload/plugins/hooks/FastixRu.py b/pyload/plugins/hooks/FastixRu.py
new file mode 100644
index 000000000..9e2abd92a
--- /dev/null
+++ b/pyload/plugins/hooks/FastixRu.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class FastixRu(MultiHoster):
+ __name__ = "FastixRu"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Fastix.ru hook plugin"""
+ __author_name__ = "Massimo Rosamilia"
+ __author_mail__ = "max@spiritix.eu"
+
+
+ def getHoster(self):
+ page = getURL(
+ "http://fastix.ru/api_v2/?apikey=5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y&sub=allowed_sources")
+ host_list = json_loads(page)
+ host_list = host_list['allow']
+ return host_list
diff --git a/pyload/plugins/hooks/FreeWayMe.py b/pyload/plugins/hooks/FreeWayMe.py
new file mode 100644
index 000000000..35b275067
--- /dev/null
+++ b/pyload/plugins/hooks/FreeWayMe.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class FreeWayMe(MultiHoster):
+ __name__ = "FreeWayMe"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """FreeWay.me hook plugin"""
+ __author_name__ = "Nicolas Giese"
+ __author_mail__ = "james@free-way.me"
+
+
+ def getHoster(self):
+ hostis = getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip()
+ self.logDebug("hosters: %s" % hostis)
+ return [x.strip() for x in hostis.split(",") if x.strip()]
diff --git a/pyload/plugins/hooks/HotFolder.py b/pyload/plugins/hooks/HotFolder.py
new file mode 100644
index 000000000..f76e95af4
--- /dev/null
+++ b/pyload/plugins/hooks/HotFolder.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+
+import time
+
+from os import listdir, makedirs
+from os.path import exists, isfile, join
+from shutil import move
+
+from pyload.plugins.Hook import Hook
+
+
+class HotFolder(Hook):
+ __name__ = "HotFolder"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("folder", "str", "Folder to observe", "container"),
+ ("watch_file", "bool", "Observe link file", False),
+ ("keep", "bool", "Keep added containers", True),
+ ("file", "str", "Link file", "links.txt")]
+
+ __description__ = """Observe folder and file for changes and add container and links"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.de"
+
+
+ def setup(self):
+ self.interval = 10
+
+ def periodical(self):
+ if not exists(join(self.getConfig("folder"), "finished")):
+ makedirs(join(self.getConfig("folder"), "finished"))
+
+ if self.getConfig("watch_file"):
+
+ if not exists(self.getConfig("file")):
+ f = open(self.getConfig("file"), "wb")
+ f.close()
+
+ f = open(self.getConfig("file"), "rb")
+ content = f.read().strip()
+ f.close()
+ f = open(self.getConfig("file"), "wb")
+ f.close()
+ if content:
+ name = "%s_%s.txt" % (self.getConfig("file"), time.strftime("%H-%M-%S_%d%b%Y"))
+
+ f = open(join(self.getConfig("folder"), "finished", name), "wb")
+ f.write(content)
+ f.close()
+
+ self.core.api.addPackage(f.name, [f.name], 1)
+
+ for f in listdir(self.getConfig("folder")):
+ path = join(self.getConfig("folder"), f)
+
+ if not isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."):
+ continue
+
+ newpath = join(self.getConfig("folder"), "finished", f if self.getConfig("keep") else "tmp_" + f)
+ move(path, newpath)
+
+ self.logInfo(_("Added %s from HotFolder") % f)
+ self.core.api.addPackage(f, [newpath], 1)
diff --git a/pyload/plugins/hooks/IRCInterface.py b/pyload/plugins/hooks/IRCInterface.py
new file mode 100644
index 000000000..ef1fa2a09
--- /dev/null
+++ b/pyload/plugins/hooks/IRCInterface.py
@@ -0,0 +1,404 @@
+# -*- coding: utf-8 -*-
+
+import re
+import socket
+import time
+
+from pycurl import FORM_FILE
+from select import select
+from threading import Thread
+from time import sleep
+from traceback import print_exc
+
+from pyload.api import PackageDoesNotExists, FileDoesNotExists
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hook import Hook
+from pyload.utils import formatSize
+
+
+class IRCInterface(Thread, Hook):
+ __name__ = "IRCInterface"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("host", "str", "IRC-Server Address", "Enter your server here!"),
+ ("port", "int", "IRC-Server Port", 6667),
+ ("ident", "str", "Clients ident", "pyload-irc"),
+ ("realname", "str", "Realname", "pyload-irc"),
+ ("nick", "str", "Nickname the Client will take", "pyLoad-IRC"),
+ ("owner", "str", "Nickname the Client will accept commands from", "Enter your nick here!"),
+ ("info_file", "bool", "Inform about every file finished", False),
+ ("info_pack", "bool", "Inform about every package finished", True),
+ ("captcha", "bool", "Send captcha requests", True)]
+
+ __description__ = """Connect to irc and let owner perform different tasks"""
+ __author_name__ = "Jeix"
+ __author_mail__ = "Jeix@hasnomail.com"
+
+
+ def __init__(self, core, manager):
+ Thread.__init__(self)
+ Hook.__init__(self, core, manager)
+ self.setDaemon(True)
+ # self.sm = core.server_methods
+ self.api = core.api # todo, only use api
+
+ def coreReady(self):
+ self.abort = False
+ self.more = []
+ self.new_package = {}
+
+ self.start()
+
+ def packageFinished(self, pypack):
+ try:
+ if self.getConfig("info_pack"):
+ self.response(_("Package finished: %s") % pypack.name)
+ except:
+ pass
+
+ def downloadFinished(self, pyfile):
+ try:
+ if self.getConfig("info_file"):
+ self.response(
+ _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname})
+ except:
+ pass
+
+ def newCaptchaTask(self, task):
+ if self.getConfig("captcha") and task.isTextual():
+ task.handler.append(self)
+ task.setWaiting(60)
+
+ page = getURL("http://www.freeimagehosting.net/upload.php",
+ post={"attached": (FORM_FILE, task.captchaFile)}, multipart=True)
+
+ url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", page).group(1)
+ self.response(_("New Captcha Request: %s") % url)
+ self.response(_("Answer with 'c %s text on the captcha'") % task.id)
+
+ def run(self):
+ # connect to IRC etc.
+ self.sock = socket.socket()
+ host = self.getConfig("host")
+ self.sock.connect((host, self.getConfig("port")))
+ nick = self.getConfig("nick")
+ self.sock.send("NICK %s\r\n" % nick)
+ self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick))
+ for t in self.getConfig("owner").split():
+ if t.strip().startswith("#"):
+ self.sock.send("JOIN %s\r\n" % t.strip())
+ self.logInfo("pyLoad IRC: Connected to %s!" % host)
+ self.logInfo("pyLoad IRC: Switching to listening mode!")
+ try:
+ self.main_loop()
+
+ except IRCError, ex:
+ self.sock.send("QUIT :byebye\r\n")
+ print_exc()
+ self.sock.close()
+
+ def main_loop(self):
+ readbuffer = ""
+ while True:
+ sleep(1)
+ fdset = select([self.sock], [], [], 0)
+ if self.sock not in fdset[0]:
+ continue
+
+ if self.abort:
+ raise IRCError("quit")
+
+ readbuffer += self.sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ line = line.rstrip()
+ first = line.split()
+
+ if first[0] == "PING":
+ self.sock.send("PONG %s\r\n" % first[1])
+
+ if first[0] == "ERROR":
+ raise IRCError(line)
+
+ msg = line.split(None, 3)
+ if len(msg) < 4:
+ continue
+
+ msg = {
+ "origin": msg[0][1:],
+ "action": msg[1],
+ "target": msg[2],
+ "text": msg[3][1:]
+ }
+
+ self.handle_events(msg)
+
+ def handle_events(self, msg):
+ if not msg['origin'].split("!", 1)[0] in self.getConfig("owner").split():
+ return
+
+ if msg['target'].split("!", 1)[0] != self.getConfig("nick"):
+ return
+
+ if msg['action'] != "PRIVMSG":
+ return
+
+ # HANDLE CTCP ANTI FLOOD/BOT PROTECTION
+ if msg['text'] == "\x01VERSION\x01":
+ self.logDebug("Sending CTCP VERSION.")
+ self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
+ return
+ elif msg['text'] == "\x01TIME\x01":
+ self.logDebug("Sending CTCP TIME.")
+ self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
+ return
+ elif msg['text'] == "\x01LAG\x01":
+ self.logDebug("Received CTCP LAG.") # don't know how to answer
+ return
+
+ trigger = "pass"
+ args = None
+
+ try:
+ temp = msg['text'].split()
+ trigger = temp[0]
+ if len(temp) > 1:
+ args = temp[1:]
+ except:
+ pass
+
+ handler = getattr(self, "event_%s" % trigger, self.event_pass)
+ try:
+ res = handler(args)
+ for line in res:
+ self.response(line, msg['origin'])
+ except Exception, e:
+ self.logError("pyLoad IRC: " + repr(e))
+
+ def response(self, msg, origin=""):
+ if origin == "":
+ for t in self.getConfig("owner").split():
+ self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg))
+ else:
+ self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg))
+
+ #### Events
+
+ def event_pass(self, args):
+ return []
+
+ def event_status(self, args):
+ downloads = self.api.statusDownloads()
+ if not downloads:
+ return ["INFO: There are no active downloads currently."]
+
+ temp_progress = ""
+ lines = ["ID - Name - Status - Speed - ETA - Progress"]
+ for data in downloads:
+
+ if data.status == 5:
+ temp_progress = data.format_wait
+ else:
+ temp_progress = "%d%% (%s)" % (data.percent, data.format_size)
+
+ lines.append("#%d - %s - %s - %s - %s - %s" %
+ (
+ data.fid,
+ data.name,
+ data.statusmsg,
+ "%s/s" % formatSize(data.speed),
+ "%s" % data.format_eta,
+ temp_progress
+ ))
+ return lines
+
+ def event_queue(self, args):
+ ps = self.api.getQueueData()
+
+ if not ps:
+ return ["INFO: There are no packages in queue."]
+
+ lines = []
+ for pack in ps:
+ lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
+
+ return lines
+
+ def event_collector(self, args):
+ ps = self.api.getCollectorData()
+ if not ps:
+ return ["INFO: No packages in collector!"]
+
+ lines = []
+ for pack in ps:
+ lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links)))
+
+ return lines
+
+ def event_info(self, args):
+ if not args:
+ return ["ERROR: Use info like this: info <id>"]
+
+ info = None
+ try:
+ info = self.api.getFileData(int(args[0]))
+
+ except FileDoesNotExists:
+ return ["ERROR: Link doesn't exists."]
+
+ return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)]
+
+ def event_packinfo(self, args):
+ if not args:
+ return ["ERROR: Use packinfo like this: packinfo <id>"]
+
+ lines = []
+ pack = None
+ try:
+ pack = self.api.getPackageData(int(args[0]))
+
+ except PackageDoesNotExists:
+ return ["ERROR: Package doesn't exists."]
+
+ id = args[0]
+
+ self.more = []
+
+ lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links)))
+ for pyfile in pack.links:
+ self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size,
+ pyfile.statusmsg, pyfile.plugin))
+
+ if len(self.more) < 6:
+ lines.extend(self.more)
+ self.more = []
+ else:
+ lines.extend(self.more[:6])
+ self.more = self.more[6:]
+ lines.append("%d more links do display." % len(self.more))
+
+ return lines
+
+ def event_more(self, args):
+ if not self.more:
+ return ["No more information to display."]
+
+ lines = self.more[:6]
+ self.more = self.more[6:]
+ lines.append("%d more links do display." % len(self.more))
+
+ return lines
+
+ def event_start(self, args):
+ self.api.unpauseServer()
+ return ["INFO: Starting downloads."]
+
+ def event_stop(self, args):
+ self.api.pauseServer()
+ return ["INFO: No new downloads will be started."]
+
+ def event_add(self, args):
+ if len(args) < 2:
+ return ['ERROR: Add links like this: "add <packagename|id> links". ',
+ "This will add the link <link> to to the package <package> / the package with id <id>!"]
+
+ pack = args[0].strip()
+ links = [x.strip() for x in args[1:]]
+
+ count_added = 0
+ count_failed = 0
+ try:
+ id = int(pack)
+ pack = self.api.getPackageData(id)
+ if not pack:
+ return ["ERROR: Package doesn't exists."]
+
+ #TODO add links
+
+ return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack['name'], id)]
+
+ except:
+ # create new package
+ id = self.api.addPackage(pack, links, 1)
+ return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))]
+
+ def event_del(self, args):
+ if len(args) < 2:
+ return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
+
+ if args[0] == "-p":
+ ret = self.api.deletePackages(map(int, args[1:]))
+ return ["INFO: Deleted %d packages!" % len(args[1:])]
+
+ elif args[0] == "-l":
+ ret = self.api.delLinks(map(int, args[1:]))
+ return ["INFO: Deleted %d links!" % len(args[1:])]
+
+ else:
+ return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"]
+
+ def event_push(self, args):
+ if not args:
+ return ["ERROR: Push package to queue like this: push <package id>"]
+
+ id = int(args[0])
+ try:
+ info = self.api.getPackageInfo(id)
+ except PackageDoesNotExists:
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.api.pushToQueue(id)
+ return ["INFO: Pushed package #%d to queue." % id]
+
+ def event_pull(self, args):
+ if not args:
+ return ["ERROR: Pull package from queue like this: pull <package id>."]
+
+ id = int(args[0])
+ if not self.api.getPackageData(id):
+ return ["ERROR: Package #%d does not exist." % id]
+
+ self.api.pullFromQueue(id)
+ return ["INFO: Pulled package #%d from queue to collector." % id]
+
+ def event_c(self, args):
+ """ captcha answer """
+ if not args:
+ return ["ERROR: Captcha ID missing."]
+
+ task = self.core.captchaManager.getTaskByID(args[0])
+ if not task:
+ return ["ERROR: Captcha Task with ID %s does not exists." % args[0]]
+
+ task.setResult(" ".join(args[1:]))
+ return ["INFO: Result %s saved." % " ".join(args[1:])]
+
+ def event_help(self, args):
+ lines = ["The following commands are available:",
+ "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)",
+ "queue Shows all packages in the queue",
+ "collector Shows all packages in collector",
+ "del -p|-l <id> [...] Deletes all packages|links with the ids specified",
+ "info <id> Shows info of the link with id <id>",
+ "packinfo <id> Shows info of the package with id <id>",
+ "more Shows more info when the result was truncated",
+ "start Starts all downloads",
+ "stop Stops the download (but not abort active downloads)",
+ "push <id> Push package to queue",
+ "pull <id> Pull package from queue",
+ "status Show general download status",
+ "help Shows this help message"]
+ return lines
+
+
+class IRCError(Exception):
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
diff --git a/pyload/plugins/hooks/ImageTyperz.py b/pyload/plugins/hooks/ImageTyperz.py
new file mode 100644
index 000000000..2591a1c78
--- /dev/null
+++ b/pyload/plugins/hooks/ImageTyperz.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from base64 import b64encode
+from pycurl import FORM_FILE, LOW_SPEED_TIME
+from thread import start_new_thread
+
+from pyload.network.RequestFactory import getURL, getRequest
+from pyload.plugins.Hook import Hook
+
+
+class ImageTyperzException(Exception):
+
+ def __init__(self, err):
+ self.err = err
+
+ def getCode(self):
+ return self.err
+
+ def __str__(self):
+ return "<ImageTyperzException %s>" % self.err
+
+ def __repr__(self):
+ return "<ImageTyperzException %s>" % self.err
+
+
+class ImageTyperz(Hook):
+ __name__ = "ImageTyperz"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("username", "str", "Username", ""),
+ ("passkey", "password", "Password", ""),
+ ("force", "bool", "Force IT even if client is connected", False)]
+
+ __description__ = """Send captchas to ImageTyperz.com"""
+ __author_name__ = ("RaNaN", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
+
+ SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx"
+ RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx"
+ GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx"
+
+
+ def setup(self):
+ self.info = {}
+
+ def getCredits(self):
+ response = getURL(self.GETCREDITS_URL, post={"action": "REQUESTBALANCE", "username": self.getConfig("username"),
+ "password": self.getConfig("passkey")})
+
+ if response.startswith('ERROR'):
+ raise ImageTyperzException(response)
+
+ try:
+ balance = float(response)
+ except:
+ raise ImageTyperzException("invalid response")
+
+ self.logInfo("Account balance: $%s left" % response)
+ return balance
+
+ def submit(self, captcha, captchaType="file", match=None):
+ req = getRequest()
+ #raise timeout threshold
+ req.c.setopt(LOW_SPEED_TIME, 80)
+
+ try:
+ #workaround multipart-post bug in HTTPRequest.py
+ if re.match("^[A-Za-z0-9]*$", self.getConfig("passkey")):
+ multipart = True
+ data = (FORM_FILE, captcha)
+ else:
+ multipart = False
+ with open(captcha, 'rb') as f:
+ data = f.read()
+ data = b64encode(data)
+
+ response = req.load(self.SUBMIT_URL, post={"action": "UPLOADCAPTCHA",
+ "username": self.getConfig("username"),
+ "password": self.getConfig("passkey"), "file": data},
+ multipart=multipart)
+ finally:
+ req.close()
+
+ if response.startswith("ERROR"):
+ raise ImageTyperzException(response)
+ else:
+ data = response.split('|')
+ if len(data) == 2:
+ ticket, result = data
+ else:
+ raise ImageTyperzException("Unknown response %s" % response)
+
+ return ticket, result
+
+ def newCaptchaTask(self, task):
+ if "service" in task.data:
+ return False
+
+ if not task.isTextual():
+ return False
+
+ if not self.getConfig("username") or not self.getConfig("passkey"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if self.getCredits() > 0:
+ task.handler.append(self)
+ task.data['service'] = self.__name__
+ task.setWaiting(100)
+ start_new_thread(self.processCaptcha, (task,))
+
+ else:
+ self.logInfo("Your %s account has not enough credits" % self.__name__)
+
+ def captchaInvalid(self, task):
+ if task.data['service'] == self.__name__ and "ticket" in task.data:
+ response = getURL(self.RESPOND_URL, post={"action": "SETBADIMAGE", "username": self.getConfig("username"),
+ "password": self.getConfig("passkey"),
+ "imageid": task.data['ticket']})
+
+ if response == "SUCCESS":
+ self.logInfo("Bad captcha solution received, requested refund")
+ else:
+ self.logError("Bad captcha solution received, refund request failed", response)
+
+ def processCaptcha(self, task):
+ c = task.captchaFile
+ try:
+ ticket, result = self.submit(c)
+ except ImageTyperzException, e:
+ task.error = e.getCode()
+ return
+
+ task.data['ticket'] = ticket
+ task.setResult(result)
diff --git a/pyload/plugins/hooks/LinkdecrypterCom.py b/pyload/plugins/hooks/LinkdecrypterCom.py
new file mode 100644
index 000000000..34517761a
--- /dev/null
+++ b/pyload/plugins/hooks/LinkdecrypterCom.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hook import Hook
+from pyload.utils import remove_chars
+
+
+class LinkdecrypterCom(Hook):
+ __name__ = "LinkdecrypterCom"
+ __type__ = "hook"
+ __version__ = "0.19"
+
+ __config__ = [("activated", "bool", "Activated", False)]
+
+ __description__ = """Linkdecrypter.com hook plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def coreReady(self):
+ try:
+ self.loadPatterns()
+ except Exception, e:
+ self.logError(e)
+
+ def loadPatterns(self):
+ page = getURL("http://linkdecrypter.com/")
+ m = re.search(r'<b>Supported\(\d+\)</b>: <i>([^+<]*)', page)
+ if m is None:
+ self.logError(_("Crypter list not found"))
+ return
+
+ builtin = [name.lower() for name in self.core.pluginManager.crypterPlugins.keys()]
+ builtin.append("downloadserienjunkiesorg")
+
+ crypter_pattern = re.compile("(\w[\w.-]+)")
+ online = []
+ for crypter in m.group(1).split(', '):
+ m = re.match(crypter_pattern, crypter)
+ if m and remove_chars(m.group(1), "-.") not in builtin:
+ online.append(m.group(1).replace(".", "\\."))
+
+ if not online:
+ self.logError(_("Crypter list is empty"))
+ return
+
+ regexp = r"https?://([^.]+\.)*?(%s)/.*" % "|".join(online)
+
+ dict = self.core.pluginManager.crypterPlugins[self.__name__]
+ dict['pattern'] = regexp
+ dict['re'] = re.compile(regexp)
+
+ self.logDebug("REGEXP: " + regexp)
diff --git a/pyload/plugins/hooks/LinksnappyCom.py b/pyload/plugins/hooks/LinksnappyCom.py
new file mode 100644
index 000000000..9086e3c2a
--- /dev/null
+++ b/pyload/plugins/hooks/LinksnappyCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class LinksnappyCom(MultiHoster):
+ __name__ = "LinksnappyCom"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Linksnappy.com hook plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def getHoster(self):
+ json_data = getURL('http://gen.linksnappy.com/lseAPI.php?act=FILEHOSTS')
+ json_data = json_loads(json_data)
+
+ return json_data['return'].keys()
diff --git a/pyload/plugins/hooks/MegaDebridEu.py b/pyload/plugins/hooks/MegaDebridEu.py
new file mode 100644
index 000000000..8c8894c9b
--- /dev/null
+++ b/pyload/plugins/hooks/MegaDebridEu.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class MegaDebridEu(MultiHoster):
+ __name__ = "MegaDebridEu"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False)]
+
+ __description__ = """mega-debrid.eu hook plugin"""
+ __author_name__ = "D.Ducatel"
+ __author_mail__ = "dducatel@je-geek.fr"
+
+
+ def getHoster(self):
+ reponse = getURL('http://www.mega-debrid.eu/api.php?action=getHosters')
+ json_data = json_loads(reponse)
+
+ if json_data['response_code'] == "ok":
+ host_list = [element[0] for element in json_data['hosters']]
+ else:
+ self.logError("Unable to retrieve hoster list")
+ host_list = list()
+
+ return host_list
diff --git a/pyload/plugins/hooks/MergeFiles.py b/pyload/plugins/hooks/MergeFiles.py
new file mode 100644
index 000000000..5761a5990
--- /dev/null
+++ b/pyload/plugins/hooks/MergeFiles.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import traceback
+
+from pyload.plugins.Hook import Hook, threaded
+from pyload.utils import safe_join, fs_encode
+
+
+class MergeFiles(Hook):
+ __name__ = "MergeFiles"
+ __type__ = "hook"
+ __version__ = "0.12"
+
+ __config__ = [("activated", "bool", "Activated", False)]
+
+ __description__ = """Merges parts splitted with hjsplit"""
+ __author_name__ = "and9000"
+ __author_mail__ = "me@has-no-mail.com"
+
+ BUFFER_SIZE = 4096
+
+
+ def setup(self):
+ # nothing to do
+ pass
+
+ @threaded
+ def packageFinished(self, pack):
+ files = {}
+ fid_dict = {}
+ for fid, data in pack.getChildren().iteritems():
+ if re.search("\.[0-9]{3}$", data['name']):
+ if data['name'][:-4] not in files:
+ files[data['name'][:-4]] = []
+ files[data['name'][:-4]].append(data['name'])
+ files[data['name'][:-4]].sort()
+ fid_dict[data['name']] = fid
+
+ download_folder = self.config['general']['download_folder']
+
+ if self.config['general']['folder_per_package']:
+ download_folder = safe_join(download_folder, pack.folder)
+
+ for name, file_list in files.iteritems():
+ self.logInfo("Starting merging of %s" % name)
+ final_file = open(safe_join(download_folder, name), "wb")
+
+ for splitted_file in file_list:
+ self.logDebug("Merging part %s" % splitted_file)
+ pyfile = self.core.files.getFile(fid_dict[splitted_file])
+ pyfile.setStatus("processing")
+ try:
+ s_file = open(os.path.join(download_folder, splitted_file), "rb")
+ size_written = 0
+ s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file)))
+ while True:
+ f_buffer = s_file.read(self.BUFFER_SIZE)
+ if f_buffer:
+ final_file.write(f_buffer)
+ size_written += self.BUFFER_SIZE
+ pyfile.setProgress((size_written * 100) / s_file_size)
+ else:
+ break
+ s_file.close()
+ self.logDebug("Finished merging part %s" % splitted_file)
+ except Exception, e:
+ print traceback.print_exc()
+ finally:
+ pyfile.setProgress(100)
+ pyfile.setStatus("finished")
+ pyfile.release()
+
+ final_file.close()
+ self.logInfo("Finished merging of %s" % name)
diff --git a/pyload/plugins/hooks/MultiHome.py b/pyload/plugins/hooks/MultiHome.py
new file mode 100644
index 000000000..61fbdd230
--- /dev/null
+++ b/pyload/plugins/hooks/MultiHome.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+from time import time
+
+from pyload.plugins.Hook import Hook
+
+
+class MultiHome(Hook):
+ __name__ = "MultiHome"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("interfaces", "str", "Interfaces", "None")]
+
+ __description__ = """Ip address changer"""
+ __author_name__ = "mkaay"
+ __author_mail__ = "mkaay@mkaay.de"
+
+
+ def setup(self):
+ self.register = {}
+ self.interfaces = []
+ self.parseInterfaces(self.getConfig("interfaces").split(";"))
+ if not self.interfaces:
+ self.parseInterfaces([self.config['download']['interface']])
+ self.setConfig("interfaces", self.toConfig())
+
+ def toConfig(self):
+ return ";".join([i.adress for i in self.interfaces])
+
+ def parseInterfaces(self, interfaces):
+ for interface in interfaces:
+ if not interface or str(interface).lower() == "none":
+ continue
+ self.interfaces.append(Interface(interface))
+
+ def coreReady(self):
+ requestFactory = self.core.requestFactory
+ oldGetRequest = requestFactory.getRequest
+
+ def getRequest(pluginName, account=None):
+ iface = self.bestInterface(pluginName, account)
+ if iface:
+ iface.useFor(pluginName, account)
+ requestFactory.iface = lambda: iface.adress
+ self.logDebug("Multihome: using address: " + iface.adress)
+ return oldGetRequest(pluginName, account)
+
+ requestFactory.getRequest = getRequest
+
+ def bestInterface(self, pluginName, account):
+ best = None
+ for interface in self.interfaces:
+ if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account):
+ best = interface
+ return best
+
+
+class Interface(object):
+
+ def __init__(self, adress):
+ self.adress = adress
+ self.history = {}
+
+ def lastPluginAccess(self, pluginName, account):
+ if (pluginName, account) in self.history:
+ return self.history[(pluginName, account)]
+ return 0
+
+ def useFor(self, pluginName, account):
+ self.history[(pluginName, account)] = time()
+
+ def __repr__(self):
+ return "<Interface - %s>" % self.adress
diff --git a/pyload/plugins/hooks/MultishareCz.py b/pyload/plugins/hooks/MultishareCz.py
new file mode 100644
index 000000000..9e1bd50a4
--- /dev/null
+++ b/pyload/plugins/hooks/MultishareCz.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class MultishareCz(MultiHoster):
+ __name__ = "MultishareCz"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "uloz.to")]
+
+ __description__ = """MultiShare.cz hook plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="([^"]+)"></td>\s*<td class="stav">[^>]*?alt="OK"'
+
+
+ def getHoster(self):
+ page = getURL("http://www.multishare.cz/monitoring/")
+ return re.findall(self.HOSTER_PATTERN, page)
diff --git a/pyload/plugins/hooks/MyfastfileCom.py b/pyload/plugins/hooks/MyfastfileCom.py
new file mode 100644
index 000000000..311bc2212
--- /dev/null
+++ b/pyload/plugins/hooks/MyfastfileCom.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(MultiHoster):
+ __name__ = "MyfastfileCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Myfastfile.com hook plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def getHoster(self):
+ json_data = getURL('http://myfastfile.com/api.php?hosts', decode=True)
+ self.logDebug('JSON data: ' + json_data)
+ json_data = json_loads(json_data)
+
+ return json_data['hosts']
diff --git a/pyload/plugins/hooks/OverLoadMe.py b/pyload/plugins/hooks/OverLoadMe.py
new file mode 100644
index 000000000..a57c7c2b4
--- /dev/null
+++ b/pyload/plugins/hooks/OverLoadMe.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class OverLoadMe(MultiHoster):
+ __name__ = "OverLoadMe"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("https", "bool", "Enable HTTPS", True),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 12)]
+
+ __description__ = """Over-Load.me hook plugin"""
+ __author_name__ = "marley"
+ __author_mail__ = "marley@over-load.me"
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://api.over-load.me/hoster.php",
+ get={"auth": "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}
+ ).replace("\"", "").strip()
+ self.logDebug("Hosterlist: %s" % page)
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hooks/PremiumTo.py b/pyload/plugins/hooks/PremiumTo.py
new file mode 100644
index 000000000..b95b15b53
--- /dev/null
+++ b/pyload/plugins/hooks/PremiumTo.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class PremiumTo(MultiHoster):
+ __name__ = "PremiumTo"
+ __type__ = "hook"
+ __version__ = "0.04"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for downloads from supported hosters:", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Premium.to hook plugin"""
+ __author_name__ = ("RaNaN", "zoidberg", "stickell")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+ def getHoster(self):
+ page = getURL("http://premium.to/api/hosters.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ return [x.strip() for x in page.replace("\"", "").split(";")]
+
+ def coreReady(self):
+ self.account = self.core.accountManager.getAccountPlugin("PremiumTo")
+
+ user = self.account.selectAccount()[0]
+
+ if not user:
+ self.logError(_("Please add your premium.to account first and restart pyLoad"))
+ return
+
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hooks/PremiumizeMe.py b/pyload/plugins/hooks/PremiumizeMe.py
new file mode 100644
index 000000000..b6d89adb6
--- /dev/null
+++ b/pyload/plugins/hooks/PremiumizeMe.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class PremiumizeMe(MultiHoster):
+ __name__ = "PremiumizeMe"
+ __type__ = "hook"
+ __version__ = "0.12"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Premiumize.me hook plugin"""
+ __author_name__ = "Florian Franzen"
+ __author_mail__ = "FlorianFranzen@gmail.com"
+
+
+ def getHoster(self):
+ # If no accounts are available there will be no hosters available
+ if not self.account or not self.account.canUse():
+ return []
+
+ # Get account data
+ (user, data) = self.account.selectAccount()
+
+ # Get supported hosters list from premiumize.me using the
+ # json API v1 (see https://secure.premiumize.me/?show=api)
+ answer = getURL("https://api.premiumize.me/pm-api/v1.php?method=hosterlist&params[login]=%s&params[pass]=%s" % (
+ user, data['password']))
+ data = json_loads(answer)
+
+ # If account is not valid thera are no hosters available
+ if data['status'] != 200:
+ return []
+
+ # Extract hosters from json file
+ return data['result']['hosterlist']
+
+ def coreReady(self):
+ # Get account plugin and check if there is a valid account available
+ self.account = self.core.accountManager.getAccountPlugin("PremiumizeMe")
+ if not self.account.canUse():
+ self.account = None
+ self.logError(_("Please add a valid premiumize.me account first and restart pyLoad."))
+ return
+
+ # Run the overwriten core ready which actually enables the multihoster hook
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hooks/RPNetBiz.py b/pyload/plugins/hooks/RPNetBiz.py
new file mode 100644
index 000000000..b46e326e6
--- /dev/null
+++ b/pyload/plugins/hooks/RPNetBiz.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RPNetBiz(MultiHoster):
+ __name__ = "RPNetBiz"
+ __type__ = "hook"
+ __version__ = "0.1"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """RPNet.biz hook plugin"""
+ __author_name__ = "Dman"
+ __author_mail__ = "dmanugm@gmail.com"
+
+
+ def getHoster(self):
+ # No hosts supported if no account
+ if not self.account or not self.account.canUse():
+ return []
+
+ # Get account data
+ (user, data) = self.account.selectAccount()
+
+ response = getURL("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": data['password'], "action": "showHosterList"})
+ hoster_list = json_loads(response)
+
+ # If account is not valid thera are no hosters available
+ if 'error' in hoster_list:
+ return []
+
+ # Extract hosters from json file
+ return hoster_list['hosters']
+
+ def coreReady(self):
+ # Get account plugin and check if there is a valid account available
+ self.account = self.core.accountManager.getAccountPlugin("RPNetBiz")
+ if not self.account.canUse():
+ self.account = None
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
+ return
+
+ # Run the overwriten core ready which actually enables the multihoster hook
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hooks/RealdebridCom.py b/pyload/plugins/hooks/RealdebridCom.py
new file mode 100644
index 000000000..c1c519ace
--- /dev/null
+++ b/pyload/plugins/hooks/RealdebridCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RealdebridCom(MultiHoster):
+ __name__ = "RealdebridCom"
+ __type__ = "hook"
+ __version__ = "0.43"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("https", "bool", "Enable HTTPS", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported):", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Real-Debrid.com hook plugin"""
+ __author_name__ = "Devirex Hazzard"
+ __author_mail__ = "naibaf_11@yahoo.de"
+
+
+ def getHoster(self):
+ https = "https" if self.getConfig("https") else "http"
+ page = getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip()
+
+ return [x.strip() for x in page.split(",") if x.strip()]
diff --git a/pyload/plugins/hooks/RehostTo.py b/pyload/plugins/hooks/RehostTo.py
new file mode 100644
index 000000000..097ebc646
--- /dev/null
+++ b/pyload/plugins/hooks/RehostTo.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class RehostTo(MultiHoster):
+ __name__ = "RehostTo"
+ __type__ = "hook"
+ __version__ = "0.43"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to stanard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24)]
+
+ __description__ = """Rehost.to hook plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def getHoster(self):
+ page = getURL("http://rehost.to/api.php?cmd=get_supported_och_dl&long_ses=%s" % self.long_ses)
+ return [x.strip() for x in page.replace("\"", "").split(",")]
+
+ def coreReady(self):
+ self.account = self.core.accountManager.getAccountPlugin("RehostTo")
+
+ user = self.account.selectAccount()[0]
+
+ if not user:
+ self.logError("Rehost.to: " + _("Please add your rehost.to account first and restart pyLoad"))
+ return
+
+ data = self.account.getAccountInfo(user)
+ self.ses = data['ses']
+ self.long_ses = data['long_ses']
+
+ return MultiHoster.coreReady(self)
diff --git a/pyload/plugins/hooks/RestartFailed.py b/pyload/plugins/hooks/RestartFailed.py
new file mode 100644
index 000000000..a50ab60a4
--- /dev/null
+++ b/pyload/plugins/hooks/RestartFailed.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Hook import Hook
+
+
+class RestartFailed(Hook):
+ __name__ = "RestartFailed"
+ __type__ = "hook"
+ __version__ = "1.55"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("interval", "int", "Check interval in minutes", 90)]
+
+ __description__ = """Periodically restart all failed downloads in queue"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ MIN_INTERVAL = 15 * 60 #: 15m minimum check interval (value is in seconds)
+
+ event_list = ["pluginConfigChanged"]
+
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == "interval":
+ interval = value * 60
+ if self.MIN_INTERVAL <= interval != self.interval:
+ self.core.scheduler.removeJob(self.cb)
+ self.interval = interval
+ self.initPeriodical()
+ else:
+ self.logDebug("Invalid interval value, kept current")
+
+ def periodical(self):
+ self.logInfo("Restart failed downloads")
+ self.api.restartFailed()
+
+ def setup(self):
+ self.api = self.core.api
+ self.interval = self.MIN_INTERVAL
+
+ def coreReady(self):
+ self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
diff --git a/pyload/plugins/hooks/SimplyPremiumCom.py b/pyload/plugins/hooks/SimplyPremiumCom.py
new file mode 100644
index 000000000..f0df36b22
--- /dev/null
+++ b/pyload/plugins/hooks/SimplyPremiumCom.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class SimplyPremiumCom(MultiHoster):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", "False"),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", "False"),
+ ("interval", "int", "Reload interval in hours (0 to disable)", "24")]
+
+ __description__ = """Simply-Premium.com hook plugin"""
+ __author_name__ = "EvolutionClip"
+ __author_mail__ = "evolutionclip@live.de"
+
+
+ def getHoster(self):
+ json_data = getURL('http://www.simply-premium.com/api/hosts.php?format=json&online=1')
+ json_data = json_loads(json_data)
+
+ host_list = [element['regex'] for element in json_data['result']]
+
+ return host_list
diff --git a/pyload/plugins/hooks/SimplydebridCom.py b/pyload/plugins/hooks/SimplydebridCom.py
new file mode 100644
index 000000000..f7c899a48
--- /dev/null
+++ b/pyload/plugins/hooks/SimplydebridCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class SimplydebridCom(MultiHoster):
+ __name__ = "SimplydebridCom"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Simply-Debrid.com hook plugin"""
+ __author_name__ = "Kagenoshin"
+ __author_mail__ = "kagenoshin@gmx.ch"
+
+
+ def getHoster(self):
+ page = getURL("http://simply-debrid.com/api.php?list=1")
+ return [x.strip() for x in page.rstrip(';').replace("\"", "").split(";")]
diff --git a/pyload/plugins/hooks/UnSkipOnFail.py b/pyload/plugins/hooks/UnSkipOnFail.py
new file mode 100644
index 000000000..941ce4fc7
--- /dev/null
+++ b/pyload/plugins/hooks/UnSkipOnFail.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+from os.path import basename
+
+from pyload.datatypes.PyFile import PyFile
+from pyload.plugins.Hook import Hook
+from pyload.utils import fs_encode
+
+
+class UnSkipOnFail(Hook):
+ __name__ = "UnSkipOnFail"
+ __type__ = "hook"
+ __version__ = "0.01"
+
+ __config__ = [("activated", "bool", "Activated", True)]
+
+ __description__ = """When a download fails, restart skipped duplicates"""
+ __author_name__ = "hagg"
+ __author_mail__ = None
+
+
+ def downloadFailed(self, pyfile):
+ pyfile_name = basename(pyfile.name)
+ pid = pyfile.package().id
+ msg = 'look for skipped duplicates for %s (pid:%s)...'
+ self.logInfo(msg % (pyfile_name, pid))
+ dups = self.findDuplicates(pyfile)
+ for link in dups:
+ # check if link is "skipped"(=4)
+ if link.status == 4:
+ lpid = link.packageID
+ self.logInfo('restart "%s" (pid:%s)...' % (pyfile_name, lpid))
+ self.setLinkStatus(link, "queued")
+
+ def findDuplicates(self, pyfile):
+ """ Search all packages for duplicate links to "pyfile".
+ Duplicates are links that would overwrite "pyfile".
+ To test on duplicity the package-folder and link-name
+ of twolinks are compared (basename(link.name)).
+ So this method returns a list of all links with equal
+ package-folders and filenames as "pyfile", but except
+ the data for "pyfile" iotselöf.
+ It does MOT check the link's status.
+ """
+ dups = []
+ pyfile_name = fs_encode(basename(pyfile.name))
+ # get packages (w/o files, as most file data is useless here)
+ queue = self.core.api.getQueue()
+ for package in queue:
+ # check if package-folder equals pyfile's package folder
+ if fs_encode(package.folder) == fs_encode(pyfile.package().folder):
+ # now get packaged data w/ files/links
+ pdata = self.core.api.getPackageData(package.pid)
+ if pdata.links:
+ for link in pdata.links:
+ link_name = fs_encode(basename(link.name))
+ # check if link name collides with pdata's name
+ if link_name == pyfile_name:
+ # at last check if it is not pyfile itself
+ if link.fid != pyfile.id:
+ dups.append(link)
+ return dups
+
+ def setLinkStatus(self, link, new_status):
+ """ Change status of "link" to "new_status".
+ "link" has to be a valid FileData object,
+ "new_status" has to be a valid status name
+ (i.e. "queued" for this Plugin)
+ It creates a temporary PyFile object using
+ "link" data, changes its status, and tells
+ the core.files-manager to save its data.
+ """
+ pyfile = PyFile(self.core.files,
+ link.fid,
+ link.url,
+ link.name,
+ link.size,
+ link.status,
+ link.error,
+ link.plugin,
+ link.packageID,
+ link.order)
+ pyfile.setStatus(new_status)
+ self.core.files.save()
+ pyfile.release()
diff --git a/pyload/plugins/hooks/UnrestrictLi.py b/pyload/plugins/hooks/UnrestrictLi.py
new file mode 100644
index 000000000..2ca5ad907
--- /dev/null
+++ b/pyload/plugins/hooks/UnrestrictLi.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class UnrestrictLi(MultiHoster):
+ __name__ = "UnrestrictLi"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", ""),
+ ("unloadFailing", "bool", "Revert to standard download if download fails", False),
+ ("interval", "int", "Reload interval in hours (0 to disable)", 24),
+ ("history", "bool", "Delete History", False)]
+
+ __description__ = """Unrestrict.li hook plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def getHoster(self):
+ json_data = getURL('http://unrestrict.li/api/jdownloader/hosts.php?format=json')
+ json_data = json_loads(json_data)
+
+ host_list = [element['host'] for element in json_data['result']]
+
+ return host_list
diff --git a/pyload/plugins/hooks/UpdateManager.py b/pyload/plugins/hooks/UpdateManager.py
new file mode 100644
index 000000000..ece7ca610
--- /dev/null
+++ b/pyload/plugins/hooks/UpdateManager.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+
+from operator import itemgetter
+from os import path, remove, stat
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hook import Expose, Hook, threaded
+from pyload.utils import safe_join
+
+
+class UpdateManager(Hook):
+ __name__ = "UpdateManager"
+ __type__ = "hook"
+ __version__ = "0.35"
+
+ __config__ = [("activated", "bool", "Activated", True),
+ ("mode", "pyLoad + plugins;plugins only", "Check updates for", "pyLoad + plugins"),
+ ("interval", "int", "Check interval in hours", 8),
+ ("reloadplugins", "bool", "Monitor plugins for code changes (debug mode only)", True),
+ ("nodebugupdate", "bool", "Don't check for updates in debug mode", True)]
+
+ __description__ = """ Check for updates """
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+ event_list = ["pluginConfigChanged"]
+
+ SERVER_URL = "http://updatemanager.pyload.org"
+ MIN_INTERVAL = 3 * 60 * 60 #: 3h minimum check interval (value is in seconds)
+
+
+ def pluginConfigChanged(self, plugin, name, value):
+ if name == "interval":
+ interval = value * 60 * 60
+ if self.MIN_INTERVAL <= interval != self.interval:
+ self.core.scheduler.removeJob(self.cb)
+ self.interval = interval
+ self.initPeriodical()
+ else:
+ self.logDebug("Invalid interval value, kept current")
+ elif name == "reloadplugins":
+ if self.cb2:
+ self.core.scheduler.removeJob(self.cb2)
+ if value is True and self.core.debug:
+ self.periodical2()
+
+ def coreReady(self):
+ self.pluginConfigChanged(self.__name__, "interval", self.getConfig("interval"))
+ x = lambda: self.pluginConfigChanged(self.__name__, "reloadplugins", self.getConfig("reloadplugins"))
+ self.core.scheduler.addJob(10, x, threaded=False)
+
+ def unload(self):
+ self.pluginConfigChanged(self.__name__, "reloadplugins", False)
+
+ def setup(self):
+ self.cb2 = None
+ self.interval = self.MIN_INTERVAL
+ self.updating = False
+ self.info = {'pyload': False, 'version': None, 'plugins': False}
+ self.mtimes = {} #: store modification time for each plugin
+
+ def periodical2(self):
+ if not self.updating:
+ self.autoreloadPlugins()
+ self.cb2 = self.core.scheduler.addJob(4, self.periodical2, threaded=False)
+
+ @Expose
+ def autoreloadPlugins(self):
+ """ reload and reindex all modified plugins """
+ modules = filter(
+ lambda m: m and (m.__name__.startswith("pyload.plugins.") or
+ m.__name__.startswith("userplugins.")) and
+ m.__name__.count(".") >= 2, sys.modules.itervalues()
+ )
+
+ reloads = []
+
+ for m in modules:
+ root, type, name = m.__name__.rsplit(".", 2)
+ id = (type, name)
+ if type in self.core.pluginManager.plugins:
+ f = m.__file__.replace(".pyc", ".py")
+ if not path.isfile(f):
+ continue
+
+ mtime = stat(f).st_mtime
+
+ if id not in self.mtimes:
+ self.mtimes[id] = mtime
+ elif self.mtimes[id] < mtime:
+ reloads.append(id)
+ self.mtimes[id] = mtime
+
+ return True if self.core.pluginManager.reloadPlugins(reloads) else False
+
+ def periodical(self):
+ if not self.info['pyload'] and not (self.getConfig("nodebugupdate") and self.core.debug):
+ self.updateThread()
+
+ def server_request(self):
+ try:
+ return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines()
+ except:
+ self.logWarning(_("Unable to contact server to get updates"))
+
+ @threaded
+ def updateThread(self):
+ self.updating = True
+ status = self.update(onlyplugin=self.getConfig("mode") == "plugins only")
+ if status == 2:
+ self.core.api.restart()
+ else:
+ self.updating = False
+
+ @Expose
+ def updatePlugins(self):
+ """ simple wrapper for calling plugin update quickly """
+ return self.update(onlyplugin=True)
+
+ @Expose
+ def update(self, onlyplugin=False):
+ """ check for updates """
+ data = self.server_request()
+ if not data:
+ exitcode = 0
+ elif data[0] == "None":
+ self.logInfo(_("No new pyLoad version available"))
+ updates = data[1:]
+ exitcode = self._updatePlugins(updates)
+ elif onlyplugin:
+ exitcode = 0
+ else:
+ newversion = data[0]
+ self.logInfo(_("*** New pyLoad Version %s available ***") % newversion)
+ self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***"))
+ exitcode = 3
+ self.info['pyload'] = True
+ self.info['version'] = newversion
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required; 3 = No plugins updated, new pyLoad version available
+
+ def _updatePlugins(self, updates):
+ """ check for plugin updates """
+
+ if self.info['plugins']:
+ return False #: plugins were already updated
+
+ updated = []
+
+ vre = re.compile(r'__version__.*=.*("|\')([0-9.]+)')
+ url = updates[0]
+ schema = updates[1].split('|')
+ if "BLACKLIST" in updates:
+ blacklist = updates[updates.index('BLACKLIST') + 1:]
+ updates = updates[2:updates.index('BLACKLIST')]
+ else:
+ blacklist = None
+ updates = updates[2:]
+
+ upgradable = sorted(map(lambda x: dict(zip(schema, x.split('|'))), updates), key=itemgetter("type", "name"))
+ for plugin in upgradable:
+ filename = plugin['name']
+ prefix = plugin['type']
+ version = plugin['version']
+
+ if filename.endswith(".pyc"):
+ name = filename[:filename.find("_")]
+ else:
+ name = filename.replace(".py", "")
+
+ #@TODO: obsolete after 0.4.10
+ if prefix.endswith("s"):
+ type = prefix[:-1]
+ else:
+ type = prefix
+
+ plugins = getattr(self.core.pluginManager, "%sPlugins" % type)
+
+ oldver = float(plugins[name]['v']) if name in plugins else None
+ newver = float(version)
+
+ if not oldver:
+ msg = "New [%(type)s] %(name)s (v%(newver)s)"
+ elif newver > oldver:
+ msg = "New version of [%(type)s] %(name)s (v%(oldver)s -> v%(newver)s)"
+ else:
+ continue
+
+ self.logInfo(_(msg) % {
+ 'type': type,
+ 'name': name,
+ 'oldver': oldver,
+ 'newver': newver,
+ })
+
+ try:
+ content = getURL(url % plugin)
+ m = vre.search(content)
+ if m and m.group(2) == version:
+ f = open(safe_join("userplugins", prefix, filename), "wb")
+ f.write(content)
+ f.close()
+ updated.append((prefix, name))
+ else:
+ raise Exception, _("Version mismatch")
+ except Exception, e:
+ self.logError(_("Error updating plugin %s") % filename, str(e))
+
+ if blacklist:
+ blacklisted = sorted(map(lambda x: (x.split('|')[0], x.split('|')[1].rsplit('.', 1)[0]), blacklist))
+
+ # Always protect UpdateManager from self-removing
+ try:
+ blacklisted.remove(("hook", "UpdateManager"))
+ except:
+ pass
+
+ removed = self.removePlugins(blacklisted)
+ for t, n in removed:
+ self.logInfo(_("Removed blacklisted plugin [%(type)s] %(name)s") % {
+ 'type': t,
+ 'name': n,
+ })
+
+ if updated:
+ reloaded = self.core.pluginManager.reloadPlugins(updated)
+ if reloaded:
+ self.logInfo(_("Plugins updated and reloaded"))
+ exitcode = 1
+ else:
+ self.logInfo(_("*** Plugins have been updated, but need a pyLoad restart to be reloaded ***"))
+ self.info['plugins'] = True
+ exitcode = 2
+ else:
+ self.logInfo(_("No plugin updates available"))
+ exitcode = 0
+
+ return exitcode #: 0 = No plugins updated; 1 = Plugins updated; 2 = Plugins updated, but restart required
+
+ @Expose
+ def removePlugins(self, type_plugins):
+ """ delete plugins from disk """
+
+ if not type_plugins:
+ return
+
+ self.logDebug("Request deletion of plugins: %s" % type_plugins)
+
+ removed = []
+
+ for type, name in type_plugins:
+ err = False
+ file = name + ".py"
+
+ for root in ("userplugins", path.join(pypath, "pyload", "plugins")):
+
+ filename = safe_join(root, type, file)
+ try:
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting \"%s\"" % path.basename(filename), str(e))
+ err = True
+
+ filename += "c"
+ if path.isfile(filename):
+ try:
+ if type == "hook":
+ self.manager.deactivateHook(name)
+ remove(filename)
+ except Exception, e:
+ self.logDebug("Error deleting \"%s\"" % path.basename(filename), str(e))
+ err = True
+
+ if not err:
+ id = (type, name)
+ removed.append(id)
+
+ return removed #: return a list of the plugins successfully removed
diff --git a/pyload/plugins/hooks/WindowsPhoneToastNotify.py b/pyload/plugins/hooks/WindowsPhoneToastNotify.py
new file mode 100644
index 000000000..79812cefa
--- /dev/null
+++ b/pyload/plugins/hooks/WindowsPhoneToastNotify.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import httplib
+import time
+
+from pyload.plugins.Hook import Hook
+
+
+class WindowsPhoneToastNotify(Hook):
+ __name__ = "WindowsPhoneToastNotify"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("force", "bool", "Force even if client is connected", False),
+ ("pushId", "str", "pushId", ""),
+ ("pushUrl", "str", "pushUrl", ""),
+ ("pushTimeout", "int", "Timeout between notifications in seconds", 0)]
+
+ __description__ = """Send push notifications to Windows Phone"""
+ __author_name__ = "Andy Voigt"
+ __author_mail__ = "phone-support@hotmail.de"
+
+
+ def setup(self):
+ self.info = {}
+
+ def getXmlData(self):
+ myxml = ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> "
+ "<wp:Toast> <wp:Text1>Pyload Mobile</wp:Text1> <wp:Text2>Captcha waiting!</wp:Text2> "
+ "</wp:Toast> </wp:Notification>")
+ return myxml
+
+ def doRequest(self):
+ URL = self.getConfig("pushUrl")
+ request = self.getXmlData()
+ webservice = httplib.HTTP(URL)
+ webservice.putrequest("POST", self.getConfig("pushId"))
+ webservice.putheader("Host", URL)
+ webservice.putheader("Content-type", "text/xml")
+ webservice.putheader("X-NotificationClass", "2")
+ webservice.putheader("X-WindowsPhone-Target", "toast")
+ webservice.putheader("Content-length", "%d" % len(request))
+ webservice.endheaders()
+ webservice.send(request)
+ webservice.close()
+ self.setStorage("LAST_NOTIFY", time.time())
+
+ def newCaptchaTask(self, task):
+ if not self.getConfig("pushId") or not self.getConfig("pushUrl"):
+ return False
+
+ if self.core.isClientConnected() and not self.getConfig("force"):
+ return False
+
+ if (time.time() - float(self.getStorage("LAST_NOTIFY", 0))) < self.getConf("pushTimeout"):
+ return False
+
+ self.doRequest()
diff --git a/pyload/plugins/hooks/XFileSharingPro.py b/pyload/plugins/hooks/XFileSharingPro.py
new file mode 100644
index 000000000..7478034c6
--- /dev/null
+++ b/pyload/plugins/hooks/XFileSharingPro.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hook import Hook
+
+
+class XFileSharingPro(Hook):
+ __name__ = "XFileSharingPro"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", True),
+ ("loadDefault", "bool", "Include default (built-in) hoster list", True),
+ ("includeList", "str", "Include hosters (comma separated)", ""),
+ ("excludeList", "str", "Exclude hosters (comma separated)", "")]
+
+ __description__ = """XFileSharingPro hook plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def coreReady(self):
+ self.loadPattern()
+
+ def loadPattern(self):
+ hosterList = self.getConfigSet('includeList')
+ excludeList = self.getConfigSet('excludeList')
+
+ if self.getConfig('loadDefault'):
+ hosterList |= set((
+ #WORKING HOSTERS:
+ "aieshare.com", "asixfiles.com", "banashare.com", "cyberlocker.ch", "eyesfile.co", "eyesfile.com",
+ "fileband.com", "filedwon.com", "filedownloads.org", "hipfile.com", "kingsupload.com", "mlfat4arab.com",
+ "netuploaded.com", "odsiebie.pl", "q4share.com", "ravishare.com", "uptobox.com", "verzend.be",
+ "xvidstage.com", "thefile.me", "sharesix.com", "hostingbulk.com",
+ #NOT TESTED:
+ "bebasupload.com", "boosterking.com", "divxme.com", "filevelocity.com", "glumbouploads.com",
+ "grupload.com", "heftyfile.com", "host4desi.com", "laoupload.com", "linkzhost.com", "movreel.com",
+ "rockdizfile.com", "limfile.com", "share76.com", "sharebeast.com", "sharehut.com", "sharerun.com",
+ "shareswift.com", "sharingonline.com", "6ybh-upload.com", "skipfile.com", "spaadyshare.com",
+ "space4file.com", "uploadbaz.com", "uploadc.com", "uploaddot.com", "uploadfloor.com", "uploadic.com",
+ "uploadville.com", "vidbull.com", "zalaa.com", "zomgupload.com", "kupload.org", "movbay.org",
+ "multishare.org", "omegave.org", "toucansharing.org", "uflinq.org", "banicrazy.info", "flowhot.info",
+ "upbrasil.info", "shareyourfilez.biz", "bzlink.us", "cloudcache.cc", "fileserver.cc", "farshare.to",
+ "filemaze.ws", "filehost.ws", "filestock.ru", "moidisk.ru", "4up.im", "100shared.com", "sharesix.com",
+ "thefile.me", "filenuke.com", "sharerepo.com", "mightyupload.com",
+ #WRONG FILE NAME:
+ "sendmyway.com", "upchi.co.il",
+ #NOT WORKING:
+ "amonshare.com", "imageporter.com", "file4safe.com",
+ #DOWN OR BROKEN:
+ "ddlanime.com", "fileforth.com", "loombo.com", "goldfile.eu", "putshare.com"
+ ))
+
+ hosterList -= (excludeList)
+ hosterList -= set(('', u''))
+
+ if not hosterList:
+ self.unload()
+ return
+
+ regexp = r"http://(?:[^/]*\.)?(%s)/\w{12}" % ("|".join(sorted(hosterList)).replace('.', '\.'))
+ #self.logDebug(regexp)
+
+ dict = self.core.pluginManager.hosterPlugins['XFileSharingPro']
+ dict['pattern'] = regexp
+ dict['re'] = re.compile(regexp)
+ self.logDebug("Pattern loaded - handling %d hosters" % len(hosterList))
+
+ def getConfigSet(self, option):
+ s = self.getConfig(option).lower().replace('|', ',').replace(';', ',')
+ return set([x.strip() for x in s.split(',')])
+
+ def unload(self):
+ dict = self.core.pluginManager.hosterPlugins['XFileSharingPro']
+ dict['pattern'] = r'^unmatchable$'
+ dict['re'] = re.compile(r'^unmatchable$')
diff --git a/pyload/plugins/hooks/XMPPInterface.py b/pyload/plugins/hooks/XMPPInterface.py
new file mode 100644
index 000000000..881e7f5dc
--- /dev/null
+++ b/pyload/plugins/hooks/XMPPInterface.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+
+from pyxmpp import streamtls
+from pyxmpp.all import JID, Message
+from pyxmpp.interface import implements
+from pyxmpp.interfaces import *
+from pyxmpp.jabber.client import JabberClient
+
+from pyload.plugins.hooks.IRCInterface import IRCInterface
+
+
+class XMPPInterface(IRCInterface, JabberClient):
+ __name__ = "XMPPInterface"
+ __type__ = "hook"
+ __version__ = "0.11"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("jid", "str", "Jabber ID", "user@exmaple-jabber-server.org"),
+ ("pw", "str", "Password", ""),
+ ("tls", "bool", "Use TLS", False),
+ ("owners", "str", "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"),
+ ("info_file", "bool", "Inform about every file finished", False),
+ ("info_pack", "bool", "Inform about every package finished", True),
+ ("captcha", "bool", "Send captcha requests", True)]
+
+ __description__ = """Connect to jabber and let owner perform different tasks"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ implements(IMessageHandlersProvider)
+
+ def __init__(self, core, manager):
+ IRCInterface.__init__(self, core, manager)
+
+ self.jid = JID(self.getConfig("jid"))
+ password = self.getConfig("pw")
+
+ # if bare JID is provided add a resource -- it is required
+ if not self.jid.resource:
+ self.jid = JID(self.jid.node, self.jid.domain, "pyLoad")
+
+ if self.getConfig("tls"):
+ tls_settings = streamtls.TLSSettings(require=True, verify_peer=False)
+ auth = ("sasl:PLAIN", "sasl:DIGEST-MD5")
+ else:
+ tls_settings = None
+ auth = ("sasl:DIGEST-MD5", "digest")
+
+ # setup client with provided connection information
+ # and identity data
+ JabberClient.__init__(self, self.jid, password,
+ disco_name="pyLoad XMPP Client", disco_type="bot",
+ tls_settings=tls_settings, auth_methods=auth)
+
+ self.interface_providers = [
+ VersionHandler(self),
+ self,
+ ]
+
+ def coreReady(self):
+ self.new_package = {}
+
+ self.start()
+
+ def packageFinished(self, pypack):
+ try:
+ if self.getConfig("info_pack"):
+ self.announce(_("Package finished: %s") % pypack.name)
+ except:
+ pass
+
+ def downloadFinished(self, pyfile):
+ try:
+ if self.getConfig("info_file"):
+ self.announce(
+ _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname})
+ except:
+ pass
+
+ def run(self):
+ # connect to IRC etc.
+ self.connect()
+ try:
+ self.loop()
+ except Exception, ex:
+ self.logError("pyLoad XMPP: %s" % str(ex))
+
+ def stream_state_changed(self, state, arg):
+ """This one is called when the state of stream connecting the component
+ to a server changes. This will usually be used to let the user
+ know what is going on."""
+ self.logDebug("pyLoad XMPP: *** State changed: %s %r ***" % (state, arg))
+
+ def disconnected(self):
+ self.logDebug("pyLoad XMPP: Client was disconnected")
+
+ def stream_closed(self, stream):
+ self.logDebug("pyLoad XMPP: Stream was closed | %s" % stream)
+
+ def stream_error(self, err):
+ self.logDebug("pyLoad XMPP: Stream Error: %s" % err)
+
+ def get_message_handlers(self):
+ """Return list of (message_type, message_handler) tuples.
+
+ The handlers returned will be called when matching message is received
+ in a client session."""
+ return [("normal", self.message)]
+
+ def message(self, stanza):
+ """Message handler for the component."""
+ subject = stanza.get_subject()
+ body = stanza.get_body()
+ t = stanza.get_type()
+ self.logDebug(u'pyLoad XMPP: Message from %s received.' % (unicode(stanza.get_from(),)))
+ self.logDebug(u'pyLoad XMPP: Body: %s Subject: %s Type: %s' % (body, subject, t))
+
+ if t == "headline":
+ # 'headline' messages should never be replied to
+ return True
+ if subject:
+ subject = u"Re: " + subject
+
+ to_jid = stanza.get_from()
+ from_jid = stanza.get_to()
+
+ #j = JID()
+ to_name = to_jid.as_utf8()
+ from_name = from_jid.as_utf8()
+
+ names = self.getConfig("owners").split(";")
+
+ if to_name in names or to_jid.node + "@" + to_jid.domain in names:
+ messages = []
+
+ trigger = "pass"
+ args = None
+
+ try:
+ temp = body.split()
+ trigger = temp[0]
+ if len(temp) > 1:
+ args = temp[1:]
+ except:
+ pass
+
+ handler = getattr(self, "event_%s" % trigger, self.event_pass)
+ try:
+ res = handler(args)
+ for line in res:
+ m = Message(
+ to_jid=to_jid,
+ from_jid=from_jid,
+ stanza_type=stanza.get_type(),
+ subject=subject,
+ body=line)
+
+ messages.append(m)
+ except Exception, e:
+ self.logError("pyLoad XMPP: " + repr(e))
+
+ return messages
+
+ else:
+ return True
+
+ def response(self, msg, origin=""):
+ return self.announce(msg)
+
+ def announce(self, message):
+ """ send message to all owners"""
+ for user in self.getConfig("owners").split(";"):
+ self.logDebug("pyLoad XMPP: Send message to %s" % user)
+
+ to_jid = JID(user)
+
+ m = Message(from_jid=self.jid,
+ to_jid=to_jid,
+ stanza_type="chat",
+ body=message)
+
+ stream = self.get_stream()
+ if not stream:
+ self.connect()
+ stream = self.get_stream()
+
+ stream.send(m)
+
+ def beforeReconnecting(self, ip):
+ self.disconnect()
+
+ def afterReconnecting(self, ip):
+ self.connect()
+
+
+class VersionHandler(object):
+ """Provides handler for a version query.
+
+ This class will answer version query and announce 'jabber:iq:version' namespace
+ in the client's disco#info results."""
+
+ implements(IIqHandlersProvider, IFeaturesProvider)
+
+ def __init__(self, client):
+ """Just remember who created this."""
+ self.client = client
+
+ def get_features(self):
+ """Return namespace which should the client include in its reply to a
+ disco#info query."""
+ return ["jabber:iq:version"]
+
+ def get_iq_get_handlers(self):
+ """Return list of tuples (element_name, namespace, handler) describing
+ handlers of <iq type='get'/> stanzas"""
+ return [("query", "jabber:iq:version", self.get_version)]
+
+ def get_iq_set_handlers(self):
+ """Return empty list, as this class provides no <iq type='set'/> stanza handler."""
+ return []
+
+ def get_version(self, iq):
+ """Handler for jabber:iq:version queries.
+
+ jabber:iq:version queries are not supported directly by PyXMPP, so the
+ XML node is accessed directly through the libxml2 API. This should be
+ used very carefully!"""
+ iq = iq.make_result_response()
+ q = iq.new_query("jabber:iq:version")
+ q.newTextChild(q.ns(), "name", "Echo component")
+ q.newTextChild(q.ns(), "version", "1.0")
+ return iq
diff --git a/pyload/plugins/hooks/ZeveraCom.py b/pyload/plugins/hooks/ZeveraCom.py
new file mode 100644
index 000000000..155143f64
--- /dev/null
+++ b/pyload/plugins/hooks/ZeveraCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.MultiHoster import MultiHoster
+
+
+class ZeveraCom(MultiHoster):
+ __name__ = "ZeveraCom"
+ __type__ = "hook"
+ __version__ = "0.02"
+
+ __config__ = [("activated", "bool", "Activated", False),
+ ("hosterListMode", "all;listed;unlisted", "Use for hosters (if supported)", "all"),
+ ("hosterList", "str", "Hoster list (comma separated)", "")]
+
+ __description__ = """Real-Debrid.com hook plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def getHoster(self):
+ page = getURL("http://www.zevera.com/jDownloader.ashx?cmd=gethosters")
+ return [x.strip() for x in page.replace("\"", "").split(",")]
diff --git a/module/remote/thriftbackend/__init__.py b/pyload/plugins/hooks/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/remote/thriftbackend/__init__.py
+++ b/pyload/plugins/hooks/__init__.py
diff --git a/pyload/plugins/hoster/AlldebridCom.py b/pyload/plugins/hoster/AlldebridCom.py
new file mode 100644
index 000000000..bdb5b1599
--- /dev/null
+++ b/pyload/plugins/hoster/AlldebridCom.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class AlldebridCom(Hoster):
+ __name__ = "AlldebridCom"
+ __type__ = "hoster"
+ __version__ = "0.34"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?alldebrid\..*'
+
+ __description__ = """Alldebrid.com hoster plugin"""
+ __author_name__ = "Andy Voigt"
+ __author_mail__ = "spamsales@online.de"
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "AllDebrid")
+ self.fail("No AllDebrid account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ password = self.getPassword().splitlines()
+ password = "" if not password else password[0]
+
+ url = "http://www.alldebrid.com/service.php?link=%s&json=true&pw=%s" % (pyfile.url, password)
+ page = self.load(url)
+ data = json_loads(page)
+
+ self.logDebug("Json data: %s" % str(data))
+
+ if data['error']:
+ if data['error'] == "This link isn't available on the hoster website.":
+ self.offline()
+ else:
+ self.logWarning(data['error'])
+ self.tempOffline()
+ else:
+ if pyfile.name and not pyfile.name.endswith('.tmp'):
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+ new_url = data['link']
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"error": "<title>An error occured while processing your request</title>",
+ "empty": re.compile(r"^$")})
+
+ if check == "error":
+ self.retry(wait_time=60, reason="An error occured while generating link.")
+ elif check == "empty":
+ self.retry(wait_time=60, reason="Downloaded File was empty.")
diff --git a/pyload/plugins/hoster/BasePlugin.py b/pyload/plugins/hoster/BasePlugin.py
new file mode 100644
index 000000000..55cdf5b88
--- /dev/null
+++ b/pyload/plugins/hoster/BasePlugin.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+from re import match, search
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import html_unescape, remove_chars
+
+
+class BasePlugin(Hoster):
+ __name__ = "BasePlugin"
+ __type__ = "hoster"
+ __version__ = "0.20"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """Base Plugin when any other didnt fit"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ """main function"""
+
+ #debug part, for api exerciser
+ if pyfile.url.startswith("DEBUG_API"):
+ self.multiDL = False
+ return
+
+ # self.__name__ = "NetloadIn"
+ # pyfile.name = "test"
+ # self.html = self.load("http://localhost:9000/short")
+ # self.download("http://localhost:9000/short")
+ # self.api = self.load("http://localhost:9000/short")
+ # self.decryptCaptcha("http://localhost:9000/captcha")
+ #
+ # if pyfile.url == "79":
+ # self.core.api.addPackage("test", [str(i) for i in xrange(80)], 1)
+ #
+ # return
+ if pyfile.url.startswith("http"):
+
+ try:
+ self.downloadFile(pyfile)
+ except BadHeader, e:
+ if e.code in (401, 403):
+ self.logDebug("Auth required")
+
+ account = self.core.accountManager.getAccountPlugin('Http')
+ servers = [x['login'] for x in account.getAllAccounts()]
+ server = urlparse(pyfile.url).netloc
+
+ if server in servers:
+ self.logDebug("Logging on to %s" % server)
+ self.req.addAuth(account.accounts[server]['password'])
+ else:
+ for pwd in pyfile.package().password.splitlines():
+ if ":" in pwd:
+ self.req.addAuth(pwd.strip())
+ break
+ else:
+ self.fail(_("Authorization required (username:password)"))
+
+ self.downloadFile(pyfile)
+ else:
+ raise
+
+ else:
+ self.fail("No Plugin matched and not a downloadable url.")
+
+ def downloadFile(self, pyfile):
+ url = pyfile.url
+
+ for _ in xrange(5):
+ header = self.load(url, just_header=True)
+
+ # self.load does not raise a BadHeader on 404 responses, do it here
+ if 'code' in header and header['code'] == 404:
+ raise BadHeader(404)
+
+ if 'location' in header:
+ self.logDebug("Location: " + header['location'])
+ base = match(r'https?://[^/]+', url).group(0)
+ if header['location'].startswith("http"):
+ url = header['location']
+ elif header['location'].startswith("/"):
+ url = base + unquote(header['location'])
+ else:
+ url = '%s/%s' % (base, unquote(header['location']))
+ else:
+ break
+
+ name = html_unescape(unquote(urlparse(url).path.split("/")[-1]))
+
+ if 'content-disposition' in header:
+ self.logDebug("Content-Disposition: " + header['content-disposition'])
+ m = search("filename(?P<type>=|\*=(?P<enc>.+)'')(?P<name>.*)", header['content-disposition'])
+ if m:
+ disp = m.groupdict()
+ self.logDebug(disp)
+ if not disp['enc']:
+ disp['enc'] = 'utf-8'
+ name = remove_chars(disp['name'], "\"';").strip()
+ name = unicode(unquote(name), disp['enc'])
+
+ if not name:
+ name = url
+ pyfile.name = name
+ self.logDebug("Filename: %s" % pyfile.name)
+ self.download(url, disposition=True)
diff --git a/pyload/plugins/hoster/BayfilesCom.py b/pyload/plugins/hoster/BayfilesCom.py
new file mode 100644
index 000000000..5906ade75
--- /dev/null
+++ b/pyload/plugins/hoster/BayfilesCom.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BayfilesCom(SimpleHoster):
+ __name__ = "BayfilesCom"
+ __type__ = "hoster"
+ __version__ = "0.07"
+
+ __pattern__ = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>[a-zA-Z0-9]+/[a-zA-Z0-9]+/[^/]+)'
+
+ __description__ = """Bayfiles.com hoster plugin"""
+ __author_name__ = ("zoidberg", "Walter Purcaro")
+ __author_mail__ = ("zoidberg@mujmail.cz", "vuolter@gmail.com")
+
+ FILE_INFO_PATTERN = r'<p title="(?P<N>[^"]+)">[^<]*<strong>(?P<S>[0-9., ]+)(?P<U>[kKMG])i?B</strong></p>'
+ OFFLINE_PATTERN = r'(<p>The requested file could not be found.</p>|<title>404 Not Found</title>)'
+
+ WAIT_PATTERN = r'>Your IP [0-9.]* has recently downloaded a file\. Upgrade to premium or wait (\d+) minutes\.<'
+ VARS_PATTERN = r'var vfid = (\d+);\s*var delay = (\d+);'
+ FREE_LINK_PATTERN = r"javascript:window.location.href = '([^']+)';"
+ PREMIUM_LINK_PATTERN = r'(?:<a class="highlighted-btn" href="|(?=http://s\d+\.baycdn\.com/dl/))(.*?)"'
+
+
+ def handleFree(self):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(int(m.group(1)) * 60)
+ self.retry()
+
+ # Get download token
+ m = re.search(self.VARS_PATTERN, self.html)
+ if m is None:
+ self.parseError('VARS')
+ vfid, delay = m.groups()
+
+ response = json_loads(self.load('http://bayfiles.com/ajax_download', get={
+ "_": time() * 1000,
+ "action": "startTimer",
+ "vfid": vfid}, decode=True))
+
+ if not "token" in response or not response['token']:
+ self.fail('No token')
+
+ self.wait(int(delay))
+
+ self.html = self.load('http://bayfiles.com/ajax_download', get={
+ "token": response['token'],
+ "action": "getLink",
+ "vfid": vfid})
+
+ # Get final link and download
+ m = re.search(self.FREE_LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("Free link")
+ self.startDownload(m.group(1))
+
+ def handlePremium(self):
+ m = re.search(self.PREMIUM_LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("Premium link")
+ self.startDownload(m.group(1))
+
+ def startDownload(self, url):
+ self.logDebug("%s URL: %s" % ("Premium" if self.premium else "Free", url))
+ self.download(url)
+ # check download
+ check = self.checkDownload({
+ "waitforfreeslots": re.compile(r"<title>BayFiles</title>"),
+ "notfound": re.compile(r"<title>404 Not Found</title>")
+ })
+ if check == "waitforfreeslots":
+ self.retry(30, 5 * 60, "Wait for free slot")
+ elif check == "notfound":
+ self.retry(30, 5 * 60, "404 Not found")
+
+
+getInfo = create_getInfo(BayfilesCom)
diff --git a/pyload/plugins/hoster/BezvadataCz.py b/pyload/plugins/hoster/BezvadataCz.py
new file mode 100644
index 000000000..8b989da67
--- /dev/null
+++ b/pyload/plugins/hoster/BezvadataCz.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BezvadataCz(SimpleHoster):
+ __name__ = "BezvadataCz"
+ __type__ = "hoster"
+ __version__ = "0.24"
+
+ __pattern__ = r'http://(?:www\.)?bezvadata.cz/stahnout/.*'
+
+ __description__ = """BezvaData.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>'
+ FILE_SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>'
+ OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>'
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+
+ def handleFree(self):
+ #download button
+ m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html)
+ if m is None:
+ self.parseError("page1 URL")
+ url = "http://bezvadata.cz%s" % m.group(1)
+
+ #captcha form
+ self.html = self.load(url)
+ self.checkErrors()
+ for _ in xrange(5):
+ action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm')
+ if not inputs:
+ self.parseError("FreeForm")
+
+ m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html)
+ if m is None:
+ self.parseError("captcha img")
+
+ #captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url
+ self.load, proper_load = self.loadcaptcha, self.load
+ try:
+ inputs['captcha'] = self.decryptCaptcha(m.group(1), imgtype='png')
+ finally:
+ self.load = proper_load
+
+ if '<img src="data:image/png;base64' in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("No valid captcha code entered")
+
+ #download url
+ self.html = self.load("http://bezvadata.cz%s" % action, post=inputs)
+ self.checkErrors()
+ m = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html)
+ if m is None:
+ self.parseError("page2 URL")
+ url = "http://bezvadata.cz%s" % m.group(1)
+ self.logDebug("DL URL %s" % url)
+
+ #countdown
+ m = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html)
+ wait_time = (int(m.group(1)) * 60 + int(m.group(2)) + 1) if m else 120
+ self.wait(wait_time, False)
+
+ self.download(url)
+
+ def checkErrors(self):
+ if 'images/button-download-disable.png' in self.html:
+ self.longWait(5 * 60, 24) # parallel dl limit
+ elif '<div class="infobox' in self.html:
+ self.tempOffline()
+
+ def loadcaptcha(self, data, *args, **kwargs):
+ return data.decode("base64")
+
+
+getInfo = create_getInfo(BezvadataCz)
diff --git a/pyload/plugins/hoster/BillionuploadsCom.py b/pyload/plugins/hoster/BillionuploadsCom.py
new file mode 100644
index 000000000..6c14d103d
--- /dev/null
+++ b/pyload/plugins/hoster/BillionuploadsCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class BillionuploadsCom(XFileSharingPro):
+ __name__ = "BillionuploadsCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?billionuploads.com/\w{12}'
+
+ __description__ = """Billionuploads.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "billionuploads.com"
+
+ FILE_NAME_PATTERN = r'<b>Filename:</b>(?P<N>.*?)<br>'
+ FILE_SIZE_PATTERN = r'<b>Size:</b>(?P<S>.*?)<br>'
+
+
+getInfo = create_getInfo(BillionuploadsCom)
diff --git a/pyload/plugins/hoster/BitshareCom.py b/pyload/plugins/hoster/BitshareCom.py
new file mode 100644
index 000000000..897206f87
--- /dev/null
+++ b/pyload/plugins/hoster/BitshareCom.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import with_statement
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class BitshareCom(SimpleHoster):
+ __name__ = "BitshareCom"
+ __type__ = "hoster"
+ __version__ = "0.50"
+
+ __pattern__ = r'http://(?:www\.)?bitshare\.com/(files/(?P<id1>[a-zA-Z0-9]+)(/(?P<name>.*?)\.html)?|\?f=(?P<id2>[a-zA-Z0-9]+))'
+
+ __description__ = """Bitshare.com hoster plugin"""
+ __author_name__ = ("Paul King", "fragonib")
+ __author_mail__ = ("", "fragonib[AT]yahoo[DOT]es")
+
+ FILE_INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.]+) (?P<U>\w+)</h1>'
+ OFFLINE_PATTERN = r'(>We are sorry, but the requested file was not found in our database|>Error - File not available<|The file was deleted either by the uploader, inactivity or due to copyright claim)'
+
+ FILE_AJAXID_PATTERN = r'var ajaxdl = "(.*?)";'
+ CAPTCHA_KEY_PATTERN = r'http://api\.recaptcha\.net/challenge\?k=(.*?) '
+ TRAFFIC_USED_UP = r'Your Traffic is used up for today. Upgrade to premium to continue!'
+
+
+ def setup(self):
+ self.req.cj.setCookie(".bitshare.com", "language_selection", "EN")
+ self.multiDL = self.premium
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if self.premium:
+ self.account.relogin(self.user)
+
+ self.pyfile = pyfile
+
+ # File id
+ m = re.match(self.__pattern__, pyfile.url)
+ self.file_id = max(m.group('id1'), m.group('id2'))
+ self.logDebug("File id is [%s]" % self.file_id)
+
+ # Load main page
+ self.html = self.load(pyfile.url, ref=False, decode=True)
+
+ # Check offline
+ if re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ # Check Traffic used up
+ if re.search(self.TRAFFIC_USED_UP, self.html):
+ self.logInfo("Your Traffic is used up for today")
+ self.wait(30 * 60, True)
+ self.retry()
+
+ # File name
+ m = re.match(self.__pattern__, pyfile.url)
+ name1 = m.group('name') if m else None
+ m = re.search(self.FILE_INFO_PATTERN, self.html)
+ name2 = m.group('N') if m else None
+ pyfile.name = max(name1, name2)
+
+ # Ajax file id
+ self.ajaxid = re.search(self.FILE_AJAXID_PATTERN, self.html).group(1)
+ self.logDebug("File ajax id is [%s]" % self.ajaxid)
+
+ # This may either download our file or forward us to an error page
+ url = self.getDownloadUrl()
+ self.logDebug("Downloading file with url [%s]" % url)
+ self.download(url)
+
+ check = self.checkDownload({"404": ">404 Not Found<", "Error": ">Error occured<"})
+ if check == "404":
+ self.retry(3, 60, 'Error 404')
+ elif check == "error":
+ self.retry(5, 5 * 60, "Bitshare host : Error occured")
+
+ def getDownloadUrl(self):
+ # Return location if direct download is active
+ if self.premium:
+ header = self.load(self.pyfile.url, cookies=True, just_header=True)
+ if 'location' in header:
+ return header['location']
+
+ # Get download info
+ self.logDebug("Getting download info")
+ response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "generateID", "ajaxid": self.ajaxid})
+ self.handleErrors(response, ':')
+ parts = response.split(":")
+ filetype = parts[0]
+ wait = int(parts[1])
+ captcha = int(parts[2])
+ self.logDebug("Download info [type: '%s', waiting: %d, captcha: %d]" % (filetype, wait, captcha))
+
+ # Waiting
+ if wait > 0:
+ self.logDebug("Waiting %d seconds." % wait)
+ if wait < 120:
+ self.wait(wait, False)
+ else:
+ self.wait(wait - 55, True)
+ self.retry()
+
+ # Resolve captcha
+ if captcha == 1:
+ self.logDebug("File is captcha protected")
+ id = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1)
+ # Try up to 3 times
+ for i in xrange(3):
+ self.logDebug("Resolving ReCaptcha with key [%s], round %d" % (id, i + 1))
+ recaptcha = ReCaptcha(self)
+ challenge, code = recaptcha.challenge(id)
+ response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "validateCaptcha", "ajaxid": self.ajaxid,
+ "recaptcha_challenge_field": challenge, "recaptcha_response_field": code})
+ if self.handleCaptchaErrors(response):
+ break
+
+ # Get download URL
+ self.logDebug("Getting download url")
+ response = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html",
+ post={"request": "getDownloadURL", "ajaxid": self.ajaxid})
+ self.handleErrors(response, '#')
+ url = response.split("#")[-1]
+
+ return url
+
+ def handleErrors(self, response, separator):
+ self.logDebug("Checking response [%s]" % response)
+ if "ERROR:Session timed out" in response:
+ self.retry()
+ elif "ERROR" in response:
+ msg = response.split(separator)[-1]
+ self.fail(msg)
+
+ def handleCaptchaErrors(self, response):
+ self.logDebug("Result of captcha resolving [%s]" % response)
+ if "SUCCESS" in response:
+ self.correctCaptcha()
+ return True
+ elif "ERROR:SESSION ERROR" in response:
+ self.retry()
+ self.logDebug("Wrong captcha")
+ self.invalidCaptcha()
+
+
+getInfo = create_getInfo(BitshareCom)
diff --git a/pyload/plugins/hoster/BoltsharingCom.py b/pyload/plugins/hoster/BoltsharingCom.py
new file mode 100644
index 000000000..196e801e4
--- /dev/null
+++ b/pyload/plugins/hoster/BoltsharingCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class BoltsharingCom(DeadHoster):
+ __name__ = "BoltsharingCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?boltsharing.com/\w{12}'
+
+ __description__ = """Boltsharing.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(BoltsharingCom)
diff --git a/pyload/plugins/hoster/CatShareNet.py b/pyload/plugins/hoster/CatShareNet.py
new file mode 100644
index 000000000..415ec2379
--- /dev/null
+++ b/pyload/plugins/hoster/CatShareNet.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class CatShareNet(SimpleHoster):
+ __name__ = "CatShareNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?catshare.net/\w{16}.*'
+
+ __description__ = """CatShare.net hoster plugin"""
+ __author_name__ = "z00nx"
+ __author_mail__ = "z00nx0@gmail.com"
+
+ FILE_INFO_PATTERN = r'<h3 class="pull-left"[^>]+>(?P<N>.*)</h3>\s+<h3 class="pull-right"[^>]+>(?P<S>.*)</h3>'
+ OFFLINE_PATTERN = r'Podany plik zosta'
+
+ SECONDS_PATTERN = r'var\s+count\s+=\s+(\d+);'
+
+ RECAPTCHA_KEY = "6Lfln9kSAAAAANZ9JtHSOgxUPB9qfDFeLUI_QMEy"
+
+
+ def handleFree(self):
+ m = re.search(self.SECONDS_PATTERN, self.html)
+ seconds = int(m.group(1))
+ self.logDebug("Seconds found", seconds)
+ self.wait(seconds + 1)
+ recaptcha = ReCaptcha(self)
+ challenge, code = recaptcha.challenge(self.RECAPTCHA_KEY)
+ post_data = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": code}
+ self.download(self.pyfile.url, post=post_data)
+ check = self.checkDownload({"html": re.compile("\A<!DOCTYPE html PUBLIC")})
+ if check == "html":
+ self.logDebug("Wrong captcha entered")
+ self.invalidCaptcha()
+ self.retry()
+
+
+getInfo = create_getInfo(CatShareNet)
diff --git a/pyload/plugins/hoster/CloudzerNet.py b/pyload/plugins/hoster/CloudzerNet.py
new file mode 100644
index 000000000..88313acee
--- /dev/null
+++ b/pyload/plugins/hoster/CloudzerNet.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class CloudzerNet(DeadHoster):
+ __name__ = "CloudzerNet"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+'
+
+ __description__ = """Cloudzer.net hoster plugin"""
+ __author_name__ = ("gs", "z00nx", "stickell")
+ __author_mail__ = ("I-_-I-_-I@web.de", "z00nx0@gmail.com", "l.stickell@yahoo.it")
+
+
+getInfo = create_getInfo(CloudzerNet)
diff --git a/pyload/plugins/hoster/CramitIn.py b/pyload/plugins/hoster/CramitIn.py
new file mode 100644
index 000000000..6c5142d96
--- /dev/null
+++ b/pyload/plugins/hoster/CramitIn.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class CramitIn(XFileSharingPro):
+ __name__ = "CramitIn"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?cramit.in/\w{12}'
+
+ __description__ = """Cramit.in hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "cramit.in"
+
+ FILE_INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)'
+ LINK_PATTERN = r'href="(http://cramit.in/file_download/.*?)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+getInfo = create_getInfo(CramitIn)
diff --git a/pyload/plugins/hoster/CrockoCom.py b/pyload/plugins/hoster/CrockoCom.py
new file mode 100644
index 000000000..c1e941553
--- /dev/null
+++ b/pyload/plugins/hoster/CrockoCom.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class CrockoCom(SimpleHoster):
+ __name__ = "CrockoCom"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?(crocko|easy-share).com/\w+'
+
+ __description__ = """Crocko hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)'
+ FILE_SIZE_PATTERN = r'<span class="tip1"><span class="inner">(?P<S>[^<]+)</span></span>'
+ OFFLINE_PATTERN = r"<h1>Sorry,<br />the page you're looking for <br />isn't here.</h1>|File not found"
+
+ CAPTCHA_URL_PATTERN = re.compile(r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';")
+ CAPTCHA_KEY_PATTERN = re.compile(r'Recaptcha.create\("([^"]+)"')
+
+ FORM_PATTERN = r'<form method="post" action="([^"]+)">(.*?)</form>'
+ FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?[^>]*>'
+
+ FILE_NAME_REPLACEMENTS = [(r'<[^>]*>', '')]
+
+
+ def handleFree(self):
+ if "You need Premium membership to download this file." in self.html:
+ self.fail("You need Premium membership to download this file.")
+
+ for _ in xrange(5):
+ m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
+ if m:
+ url, wait_time = 'http://crocko.com' + m.group(1), m.group(2)
+ self.wait(wait_time)
+ self.html = self.load(url)
+ else:
+ break
+
+ m = re.search(self.CAPTCHA_KEY_PATTERN, self.html)
+ if m is None:
+ self.parseError('Captcha KEY')
+ captcha_key = m.group(1)
+
+ m = re.search(self.FORM_PATTERN, self.html, re.DOTALL)
+ if m is None:
+ self.parseError('ACTION')
+ action, form = m.groups()
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+
+ recaptcha = ReCaptcha(self)
+
+ for _ in xrange(5):
+ inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(captcha_key)
+ self.download(action, post=inputs)
+
+ check = self.checkDownload({
+ "captcha_err": self.CAPTCHA_KEY_PATTERN
+ })
+
+ if check == "captcha_err":
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail('No valid captcha solution received')
+
+
+getInfo = create_getInfo(CrockoCom)
diff --git a/pyload/plugins/hoster/CyberlockerCh.py b/pyload/plugins/hoster/CyberlockerCh.py
new file mode 100644
index 000000000..7c97deedb
--- /dev/null
+++ b/pyload/plugins/hoster/CyberlockerCh.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class CyberlockerCh(DeadHoster):
+ __name__ = "CyberlockerCh"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?cyberlocker\.ch/\w+'
+
+ __description__ = """Cyberlocker.ch hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+getInfo = create_getInfo(CyberlockerCh)
diff --git a/pyload/plugins/hoster/CzshareCom.py b/pyload/plugins/hoster/CzshareCom.py
new file mode 100644
index 000000000..0e6fab15a
--- /dev/null
+++ b/pyload/plugins/hoster/CzshareCom.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://czshare.com/5278880/random.bin
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from pyload.utils import parseFileSize
+
+
+class CzshareCom(SimpleHoster):
+ __name__ = "CzshareCom"
+ __type__ = "hoster"
+ __version__ = "0.94"
+
+ __pattern__ = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download.php\?).*'
+
+ __description__ = """CZshare.com hoster plugin, now Sdilej.cz"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=[^>]*>(?P<N>[^<]+)</a>'
+ FILE_SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[0-9., ]+)(?P<U>[kKMG])i?B\s*</div>'
+ OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">'
+
+ FILE_SIZE_REPLACEMENTS = [(' ', '')]
+ FILE_URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')]
+
+ SH_CHECK_TRAFFIC = True
+
+ FREE_URL_PATTERN = r'<a href="([^"]+)" class="page-download">[^>]*alt="([^"]+)" /></a>'
+ FREE_FORM_PATTERN = r'<form action="download.php" method="post">\s*<img src="captcha.php" id="captcha" />(.*?)</form>'
+ PREMIUM_FORM_PATTERN = r'<form action="/profi_down.php" method="post">(.*?)</form>'
+ FORM_INPUT_PATTERN = r'<input[^>]* name="([^"]+)" value="([^"]+)"[^>]*/>'
+ MULTIDL_PATTERN = r"<p><font color='red'>Z[^<]*PROFI.</font></p>"
+ USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([0-9., ]+)([kKMG]i?B)</strong>\s*</div><!-- .credit -->'
+
+
+ def checkTrafficLeft(self):
+ # check if user logged in
+ m = re.search(self.USER_CREDIT_PATTERN, self.html)
+ if m is None:
+ self.account.relogin(self.user)
+ self.html = self.load(self.pyfile.url, cookies=True, decode=True)
+ m = re.search(self.USER_CREDIT_PATTERN, self.html)
+ if m is None:
+ return False
+
+ # check user credit
+ try:
+ credit = parseFileSize(m.group(1).replace(' ', ''), m.group(2))
+ self.logInfo("Premium download for %i KiB of Credit" % (self.pyfile.size / 1024))
+ self.logInfo("User %s has %i KiB left" % (self.user, credit / 1024))
+ if credit < self.pyfile.size:
+ self.logInfo("Not enough credit to download file %s" % self.pyfile.name)
+ return False
+ except Exception, e:
+ # let's continue and see what happens...
+ self.logError('Parse error (CREDIT): %s' % e)
+
+ return True
+
+ def handlePremium(self):
+ # parse download link
+ try:
+ form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.DOTALL).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ except Exception, e:
+ self.logError("Parse error (FORM): %s" % e)
+ self.resetAccount()
+
+ # download the file, destination is determined by pyLoad
+ self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True)
+ self.checkDownloadedFile()
+
+ def handleFree(self):
+ # get free url
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.parseError('Free URL')
+ parsed_url = "http://sdilej.cz" + m.group(1)
+ self.logDebug("PARSED_URL:" + parsed_url)
+
+ # get download ticket and parse html
+ self.html = self.load(parsed_url, cookies=True, decode=True)
+ if re.search(self.MULTIDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+
+ try:
+ form = re.search(self.FREE_FORM_PATTERN, self.html, re.DOTALL).group(1)
+ inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form))
+ self.pyfile.size = int(inputs['size'])
+ except Exception, e:
+ self.logError(e)
+ self.parseError('Form')
+
+ # get and decrypt captcha
+ captcha_url = 'http://sdilej.cz/captcha.php'
+ for _ in xrange(5):
+ inputs['captchastring2'] = self.decryptCaptcha(captcha_url)
+ self.html = self.load(parsed_url, cookies=True, post=inputs, decode=True)
+ if u"<li>ZadanÜ ověřovací kód nesouhlasí!</li>" in self.html:
+ self.invalidCaptcha()
+ elif re.search(self.MULTIDL_PATTERN, self.html):
+ self.longWait(5 * 60, 12)
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("No valid captcha code entered")
+
+ m = re.search("countdown_number = (\d+);", self.html)
+ self.setWait(int(m.group(1)) if m else 50)
+
+ # download the file, destination is determined by pyLoad
+ self.logDebug("WAIT URL", self.req.lastEffectiveURL)
+ m = re.search("free_wait.php\?server=(.*?)&(.*)", self.req.lastEffectiveURL)
+ if m is None:
+ self.parseError('Download URL')
+
+ url = "http://%s/download.php?%s" % (m.group(1), m.group(2))
+
+ self.wait()
+ self.download(url)
+ self.checkDownloadedFile()
+
+ def checkDownloadedFile(self):
+ # check download
+ check = self.checkDownload({
+ "tempoffline": re.compile(r"^Soubor je do.*asn.* nedostupn.*$"),
+ "credit": re.compile(r"^Nem.*te dostate.*n.* kredit.$"),
+ "multi_dl": re.compile(self.MULTIDL_PATTERN),
+ "captcha_err": "<li>ZadanÜ ověřovací kód nesouhlasí!</li>"
+ })
+
+ if check == "tempoffline":
+ self.fail("File not available - try later")
+ if check == "credit":
+ self.resetAccount()
+ elif check == "multi_dl":
+ self.longWait(5 * 60, 12)
+ elif check == "captcha_err":
+ self.invalidCaptcha()
+ self.retry()
+
+
+getInfo = create_getInfo(CzshareCom)
diff --git a/pyload/plugins/hoster/DailymotionCom.py b/pyload/plugins/hoster/DailymotionCom.py
new file mode 100644
index 000000000..5692fa652
--- /dev/null
+++ b/pyload/plugins/hoster/DailymotionCom.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.datatypes.PyFile import statusMap
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+
+
+def getInfo(urls):
+ result = [] #: [ .. (name, size, status, url) .. ]
+ regex = re.compile(DailymotionCom.__pattern__)
+ apiurl = "https://api.dailymotion.com/video/"
+ request = {"fields": "access_error,status,title"}
+ for url in urls:
+ id = regex.search(url).group("ID")
+ page = getURL(apiurl + id, get=request)
+ info = json_loads(page)
+
+ if "title" in info:
+ name = info['title'] + ".mp4"
+ else:
+ name = url
+
+ if "error" in info or info['access_error']:
+ status = "offline"
+ else:
+ status = info['status']
+ if status in ("ready", "published"):
+ status = "online"
+ elif status in ("waiting", "processing"):
+ status = "temp. offline"
+ else:
+ status = "offline"
+
+ result.append((name, 0, statusMap[status], url))
+ return result
+
+
+class DailymotionCom(Hoster):
+ __name__ = "DailymotionCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'https?://(?:www\.)?dailymotion\.com/.*?video/(?P<ID>[\w^_]+)'
+ __config__ = [("quality", "Lowest;LD 144p;LD 240p;SD 384p;HQ 480p;HD 720p;HD 1080p;Highest", "Quality", "Highest")]
+
+ __description__ = """Dailymotion.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def getStreams(self):
+ streams = []
+ for result in re.finditer(r"\"(?P<URL>http:\\/\\/www.dailymotion.com\\/cdn\\/H264-(?P<QF>.*?)\\.*?)\"",
+ self.html):
+ url = result.group("URL")
+ qf = result.group("QF")
+ link = url.replace("\\", "")
+ quality = tuple(int(x) for x in qf.split("x"))
+ streams.append((quality, link))
+ return sorted(streams, key=lambda x: x[0][::-1])
+
+ def getQuality(self):
+ q = self.getConfig("quality")
+ if q == "Lowest":
+ quality = 0
+ elif q == "Highest":
+ quality = -1
+ else:
+ quality = int(q.rsplit(" ")[1][:-1])
+ return quality
+
+ def getLink(self, streams, quality):
+ if quality > 0:
+ for x, s in reversed([item for item in enumerate(streams)]):
+ qf = s[0][1]
+ if qf <= quality:
+ idx = x
+ break
+ else:
+ idx = 0
+ else:
+ idx = quality
+
+ s = streams[idx]
+ self.logInfo("Download video quality %sx%s" % s[0])
+ return s[1]
+
+ def checkInfo(self, pyfile):
+ pyfile.name, pyfile.size, pyfile.status, pyfile.url = getInfo([pyfile.url])[0]
+ if pyfile.status == 1:
+ self.offline()
+ elif pyfile.status == 6:
+ self.tempOffline()
+
+ def process(self, pyfile):
+ self.checkInfo(pyfile)
+
+ id = re.match(self.__pattern__, pyfile.url).group("ID")
+ self.html = self.load("http://www.dailymotion.com/embed/video/" + id, decode=True)
+
+ streams = self.getStreams()
+ quality = self.getQuality()
+ link = self.getLink(streams, quality)
+
+ self.download(link)
diff --git a/pyload/plugins/hoster/DataHu.py b/pyload/plugins/hoster/DataHu.py
new file mode 100644
index 000000000..68162c203
--- /dev/null
+++ b/pyload/plugins/hoster/DataHu.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://data.hu/get/6381232/random.bin
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DataHu(SimpleHoster):
+ __name__ = "DataHu"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?data.hu/get/\w+'
+
+ __description__ = """Data.hu hoster plugin"""
+ __author_name__ = ("crash", "stickell")
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>'
+ OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik'
+ LINK_PATTERN = r'<div class="download_box_button"><a href="([^"]+)">'
+
+
+ def handleFree(self):
+ self.resumeDownload = True
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ url = m.group(1)
+ self.logDebug('Direct link: ' + url)
+ else:
+ self.parseError('Unable to get direct link')
+
+ self.download(url, disposition=True)
+
+
+getInfo = create_getInfo(DataHu)
diff --git a/pyload/plugins/hoster/DataportCz.py b/pyload/plugins/hoster/DataportCz.py
new file mode 100644
index 000000000..2d87397df
--- /dev/null
+++ b/pyload/plugins/hoster/DataportCz.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DataportCz(SimpleHoster):
+ __name__ = "DataportCz"
+ __type__ = "hoster"
+ __version__ = "0.37"
+
+ __pattern__ = r'http://(?:www\.)?dataport.cz/file/(.*)'
+
+ __description__ = """Dataport.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>'
+ FILE_SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.dataport.cz/file/\1')]
+
+ CAPTCHA_URL_PATTERN = r'<section id="captcha_bg">\s*<img src="(.*?)"'
+ FREE_SLOTS_PATTERN = ur'Počet volnÜch slotů: <span class="darkblue">(\d+)</span><br />'
+
+
+ def handleFree(self):
+ captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"}
+
+ for _ in xrange(60):
+ action, inputs = self.parseHtmlForm('free_download_form')
+ self.logDebug(action, inputs)
+ if not action or not inputs:
+ self.parseError('free_download_form')
+
+ if "captchaId" in inputs and inputs['captchaId'] in captchas:
+ inputs['captchaCode'] = captchas[inputs['captchaId']]
+ else:
+ self.parseError('captcha')
+
+ self.html = self.download("http://www.dataport.cz%s" % action, post=inputs)
+
+ check = self.checkDownload({"captcha": 'alert("\u0160patn\u011b opsan\u00fd k\u00f3d z obr\u00e1zu");',
+ "slot": 'alert("Je n\u00e1m l\u00edto, ale moment\u00e1ln\u011b nejsou'})
+ if check == "captcha":
+ self.parseError('invalid captcha')
+ elif check == "slot":
+ self.logDebug("No free slots - wait 60s and retry")
+ self.wait(60, False)
+ self.html = self.load(self.pyfile.url, decode=True)
+ continue
+ else:
+ break
+
+
+create_getInfo(DataportCz)
diff --git a/pyload/plugins/hoster/DateiTo.py b/pyload/plugins/hoster/DateiTo.py
new file mode 100644
index 000000000..1e8ca3614
--- /dev/null
+++ b/pyload/plugins/hoster/DateiTo.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DateiTo(SimpleHoster):
+ __name__ = "DateiTo"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html'
+
+ __description__ = """Datei.to hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</'
+ FILE_SIZE_PATTERN = r'Dateigr&ouml;&szlig;e:</td>\s*<td colspan="2">(?P<S>.*?)</'
+ OFFLINE_PATTERN = r'>Datei wurde nicht gefunden<|>Bitte wÀhle deine Datei aus... <'
+ PARALELL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<'
+
+ WAIT_PATTERN = r'countdown\({seconds: (\d+)'
+ DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",'
+ RECAPTCHA_KEY_PATTERN = r'Recaptcha.create\("(.*?)"'
+
+
+ def handleFree(self):
+ url = 'http://datei.to/ajax/download.php'
+ data = {'P': 'I', 'ID': self.file_info['ID']}
+
+ recaptcha = ReCaptcha(self)
+
+ for _ in xrange(10):
+ self.logDebug("URL", url, "POST", data)
+ self.html = self.load(url, post=data)
+ self.checkErrors()
+
+ if url.endswith('download.php') and 'P' in data:
+ if data['P'] == 'I':
+ self.doWait()
+
+ elif data['P'] == 'IV':
+ break
+
+ m = re.search(self.DATA_PATTERN, self.html)
+ if m is None:
+ self.parseError('data')
+ url = 'http://datei.to/' + m.group(1)
+ data = dict(x.split('=') for x in m.group(2).split('&'))
+
+ if url.endswith('recaptcha.php'):
+ m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
+ recaptcha_key = m.group(1) if m else "6LdBbL8SAAAAAI0vKUo58XRwDd5Tu_Ze1DA7qTao"
+
+ data['recaptcha_challenge_field'], data['recaptcha_response_field'] = recaptcha.challenge(recaptcha_key)
+
+ else:
+ self.fail('Too bad...')
+
+ download_url = self.html
+ self.logDebug('Download URL', download_url)
+ self.download(download_url)
+
+ def checkErrors(self):
+ m = re.search(self.PARALELL_PATTERN, self.html)
+ if m:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ wait_time = int(m.group(1)) if m else 30
+ self.wait(wait_time + 1, False)
+ self.retry()
+
+ def doWait(self):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ wait_time = int(m.group(1)) if m else 30
+
+ self.load('http://datei.to/ajax/download.php', post={'P': 'Ads'})
+ self.wait(wait_time + 1, False)
+
+
+getInfo = create_getInfo(DateiTo)
diff --git a/pyload/plugins/hoster/DdlstorageCom.py b/pyload/plugins/hoster/DdlstorageCom.py
new file mode 100644
index 000000000..8b477ade6
--- /dev/null
+++ b/pyload/plugins/hoster/DdlstorageCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class DdlstorageCom(DeadHoster):
+ __name__ = "DdlstorageCom"
+ __type__ = "hoster"
+ __version__ = "1.02"
+
+ __pattern__ = r'https?://(?:www\.)?ddlstorage\.com/\w+'
+
+ __description__ = """DDLStorage.com hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+getInfo = create_getInfo(DdlstorageCom)
diff --git a/pyload/plugins/hoster/DebridItaliaCom.py b/pyload/plugins/hoster/DebridItaliaCom.py
new file mode 100644
index 000000000..74879e6e5
--- /dev/null
+++ b/pyload/plugins/hoster/DebridItaliaCom.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class DebridItaliaCom(Hoster):
+ __name__ = "DebridItaliaCom"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?debriditalia\.com'
+
+ __description__ = """Debriditalia.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "DebridItalia")
+ self.fail("No DebridItalia account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ url = "http://debriditalia.com/linkgen2.php?xjxfun=convertiLink&xjxargs[]=S<![CDATA[%s]]>" % pyfile.url
+ page = self.load(url)
+ self.logDebug("XML data: %s" % page)
+
+ if 'File not available' in page:
+ self.fail('File not available')
+ else:
+ new_url = re.search(r'<a href="(?:[^"]+)">(?P<direct>[^<]+)</a>', page).group('direct')
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"empty": re.compile(r"^$")})
+
+ if check == "empty":
+ self.retry(5, 2 * 60, "Empty file downloaded")
diff --git a/pyload/plugins/hoster/DepositfilesCom.py b/pyload/plugins/hoster/DepositfilesCom.py
new file mode 100644
index 000000000..9c0348cbd
--- /dev/null
+++ b/pyload/plugins/hoster/DepositfilesCom.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class DepositfilesCom(SimpleHoster):
+ __name__ = "DepositfilesCom"
+ __type__ = "hoster"
+ __version__ = "0.48"
+
+ __pattern__ = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)'
+
+ __description__ = """Depositfiles.com hoster plugin"""
+ __author_name__ = ("spoob", "zoidberg", "Walter Purcaro")
+ __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz", "vuolter@gmail.com")
+
+ FILE_NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\''
+ FILE_SIZE_PATTERN = r': <b>(?P<S>[0-9.]+)&nbsp;(?P<U>[kKMG])i?B</b>'
+ OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>'
+
+ FILE_NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))),
+ (r'.*<b title="(?P<N>[^"]+).*', "\g<N>")]
+ FILE_URL_REPLACEMENTS = [(__pattern__, "https://dfiles.eu/files/\g<ID>")]
+
+ SH_COOKIES = [(".dfiles.eu", "lang_current", "en")]
+
+ RECAPTCHA_PATTERN = r"Recaptcha.create\('([^']+)'"
+
+ FREE_LINK_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"'
+ PREMIUM_LINK_PATTERN = r'class="repeat"><a href="(.+?)"'
+ PREMIUM_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"'
+
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, post={"gateway_result": "1"}, cookies=True)
+
+ if re.search(r'File is checked, please try again in a minute.', self.html) is not None:
+ self.logInfo("DepositFiles.com: The file is being checked. Waiting 1 minute.")
+ self.wait(61)
+ self.retry()
+
+ wait = re.search(r'html_download_api-limit_interval\">(\d+)</span>', self.html)
+ if wait:
+ wait_time = int(wait.group(1))
+ self.logInfo("%s: Traffic used up. Waiting %d seconds." % (self.__name__, wait_time))
+ self.wait(wait_time, True)
+ self.retry()
+
+ wait = re.search(r'>Try in (\d+) minutes or use GOLD account', self.html)
+ if wait:
+ wait_time = int(wait.group(1))
+ self.logInfo("%s: All free slots occupied. Waiting %d minutes." % (self.__name__, wait_time))
+ self.setWait(wait_time * 60, False)
+
+ wait = re.search(r'Please wait (\d+) sec', self.html)
+ if wait:
+ self.setWait(int(wait.group(1)))
+
+ m = re.search(r"var fid = '(\w+)';", self.html)
+ if m is None:
+ self.retry(wait_time=5)
+ params = {'fid': m.group(1)}
+ self.logDebug("FID: %s" % params['fid'])
+
+ captcha_key = '6LdRTL8SAAAAAE9UOdWZ4d0Ky-aeA7XfSqyWDM2m'
+ m = re.search(self.RECAPTCHA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ self.logDebug("CAPTCHA_KEY: %s" % captcha_key)
+
+ self.wait()
+ recaptcha = ReCaptcha(self)
+
+ for _ in xrange(5):
+ self.html = self.load("https://dfiles.eu/get_file.php", get=params)
+
+ if '<input type=button value="Continue" onclick="check_recaptcha' in self.html:
+ if not captcha_key:
+ self.parseError('Captcha key')
+ if 'response' in params:
+ self.invalidCaptcha()
+ params['challenge'], params['response'] = recaptcha.challenge(captcha_key)
+ self.logDebug(params)
+ continue
+
+ m = re.search(self.FREE_LINK_PATTERN, self.html)
+ if m:
+ if 'response' in params:
+ self.correctCaptcha()
+ link = unquote(m.group(1))
+ self.logDebug("LINK: %s" % link)
+ break
+ else:
+ self.parseError('Download link')
+ else:
+ self.fail('No valid captcha response received')
+
+ try:
+ self.download(link, disposition=True)
+ except:
+ self.retry(wait_time=60)
+
+ def handlePremium(self):
+ self.html = self.load(self.pyfile.url, cookies=self.SH_COOKIES)
+
+ if '<span class="html_download_api-gold_traffic_limit">' in self.html:
+ self.logWarning("Download limit reached")
+ self.retry(25, 60 * 60, "Download limit reached")
+ elif 'onClick="show_gold_offer' in self.html:
+ self.account.relogin(self.user)
+ self.retry()
+ else:
+ link = re.search(self.PREMIUM_LINK_PATTERN, self.html)
+ mirror = re.search(self.PREMIUM_MIRROR_PATTERN, self.html)
+ if link:
+ dlink = link.group(1)
+ elif mirror:
+ dlink = mirror.group(1)
+ else:
+ self.parseError("No direct download link or mirror found")
+ self.download(dlink, disposition=True)
+
+
+getInfo = create_getInfo(DepositfilesCom)
diff --git a/pyload/plugins/hoster/DlFreeFr.py b/pyload/plugins/hoster/DlFreeFr.py
new file mode 100644
index 000000000..0365754bc
--- /dev/null
+++ b/pyload/plugins/hoster/DlFreeFr.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.utils import json_loads
+from pyload.network.Browser import Browser
+from pyload.network.CookieJar import CookieJar
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, replace_patterns
+
+
+class CustomBrowser(Browser):
+
+ def __init__(self, bucket=None, options={}):
+ Browser.__init__(self, bucket, options)
+
+ def load(self, *args, **kwargs):
+ post = kwargs.get("post")
+
+ if post is None and len(args) > 2:
+ post = args[2]
+
+ if post:
+ self.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
+ self.http.c.setopt(pycurl.POST, 1)
+ self.http.c.setopt(pycurl.CUSTOMREQUEST, "POST")
+ else:
+ self.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
+ self.http.c.setopt(pycurl.POST, 0)
+ self.http.c.setopt(pycurl.CUSTOMREQUEST, "GET")
+
+ return Browser.load(self, *args, **kwargs)
+
+
+class AdYouLike:
+ """
+ Class to support adyoulike captcha service
+ """
+ ADYOULIKE_INPUT_PATTERN = r'Adyoulike.create\((.*?)\);'
+ ADYOULIKE_CALLBACK = r'Adyoulike.g._jsonp_5579316662423138'
+ ADYOULIKE_CHALLENGE_PATTERN = ADYOULIKE_CALLBACK + r'\((.*?)\)'
+
+ def __init__(self, plugin, engine="adyoulike"):
+ self.plugin = plugin
+ self.engine = engine
+
+ def challenge(self, html):
+ adyoulike_data_string = None
+ m = re.search(self.ADYOULIKE_INPUT_PATTERN, html)
+ if m:
+ adyoulike_data_string = m.group(1)
+ else:
+ self.plugin.fail("Can't read AdYouLike input data")
+
+ # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"},
+ # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}}
+ ayl_data = json_loads(adyoulike_data_string)
+
+ res = self.plugin.load(
+ r'http://api-ayl.appspot.com/challenge?key=%(ayl_key)s&env=%(ayl_env)s&callback=%(callback)s' % {
+ "ayl_key": ayl_data[self.engine]['key'], "ayl_env": ayl_data['all']['env'],
+ "callback": self.ADYOULIKE_CALLBACK})
+
+ m = re.search(self.ADYOULIKE_CHALLENGE_PATTERN, res)
+ challenge_string = None
+ if m:
+ challenge_string = m.group(1)
+ else:
+ self.plugin.fail("Invalid AdYouLike challenge")
+ challenge_data = json_loads(challenge_string)
+
+ return ayl_data, challenge_data
+
+ def result(self, ayl, challenge):
+ """
+ Adyoulike.g._jsonp_5579316662423138
+ ({"translations":{"fr":{"instructions_visual":"Recopiez « Soonnight » ci-dessous :"}},
+ "site_under":true,"clickable":true,"pixels":{"VIDEO_050":[],"DISPLAY":[],"VIDEO_000":[],"VIDEO_100":[],
+ "VIDEO_025":[],"VIDEO_075":[]},"medium_type":"image/adyoulike",
+ "iframes":{"big":"<iframe src=\"http://www.soonnight.com/campagn.html\" scrolling=\"no\"
+ height=\"250\" width=\"300\" frameborder=\"0\"></iframe>"},"shares":{},"id":256,
+ "token":"e6QuI4aRSnbIZJg02IsV6cp4JQ9~MjA1","formats":{"small":{"y":300,"x":0,"w":300,"h":60},
+ "big":{"y":0,"x":0,"w":300,"h":250},"hover":{"y":440,"x":0,"w":300,"h":60}},
+ "tid":"SqwuAdxT1EZoi4B5q0T63LN2AkiCJBg5"})
+ """
+ response = None
+ try:
+ instructions_visual = challenge['translations'][ayl['all']['lang']]['instructions_visual']
+ m = re.search(u".*«(.*)».*", instructions_visual)
+ if m:
+ response = m.group(1).strip()
+ else:
+ self.plugin.fail("Can't parse instructions visual")
+ except KeyError:
+ self.plugin.fail("No instructions visual")
+
+ #TODO: Supports captcha
+
+ if not response:
+ self.plugin.fail("AdYouLike result failed")
+
+ return {"_ayl_captcha_engine": self.engine,
+ "_ayl_env": ayl['all']['env'],
+ "_ayl_tid": challenge['tid'],
+ "_ayl_token_challenge": challenge['token'],
+ "_ayl_response": response}
+
+
+class DlFreeFr(SimpleHoster):
+ __name__ = "DlFreeFr"
+ __type__ = "hoster"
+ __version__ = "0.25"
+
+ __pattern__ = r'http://(?:www\.)?dl\.free\.fr/([a-zA-Z0-9]+|getfile\.pl\?file=/[a-zA-Z0-9]+)'
+
+ __description__ = """Dl.free.fr hoster plugin"""
+ __author_name__ = ("the-razer", "zoidberg", "Toilal")
+ __author_mail__ = ("daniel_ AT gmx DOT net", "zoidberg@mujmail.cz", "toilal.dev@gmail.com")
+
+ FILE_NAME_PATTERN = r'Fichier:</td>\s*<td[^>]*>(?P<N>[^>]*)</td>'
+ FILE_SIZE_PATTERN = r'Taille:</td>\s*<td[^>]*>(?P<S>[\d.]+[KMG])o'
+ OFFLINE_PATTERN = r"Erreur 404 - Document non trouv|Fichier inexistant|Le fichier demand&eacute; n'a pas &eacute;t&eacute; trouv&eacute;"
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+ self.limitDL = 5
+ self.chunkLimit = 1
+
+ def init(self):
+ factory = self.core.requestFactory
+ self.req = CustomBrowser(factory.bucket, factory.getOptions())
+
+ def process(self, pyfile):
+ self.req.setCookieJar(None)
+
+ pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
+ valid_url = pyfile.url
+ headers = self.load(valid_url, just_header=True)
+
+ self.html = None
+ if headers.get('code') == 302:
+ valid_url = headers.get('location')
+ headers = self.load(valid_url, just_header=True)
+
+ if headers.get('code') == 200:
+ content_type = headers.get('content-type')
+ if content_type and content_type.startswith("text/html"):
+ # Undirect acces to requested file, with a web page providing it (captcha)
+ self.html = self.load(valid_url)
+ self.handleFree()
+ else:
+ # Direct access to requested file for users using free.fr as Internet Service Provider.
+ self.download(valid_url, disposition=True)
+ elif headers.get('code') == 404:
+ self.offline()
+ else:
+ self.fail("Invalid return code: " + str(headers.get('code')))
+
+ def handleFree(self):
+ action, inputs = self.parseHtmlForm('action="getfile.pl"')
+
+ adyoulike = AdYouLike(self)
+ ayl, challenge = adyoulike.challenge(self.html)
+ result = adyoulike.result(ayl, challenge)
+ inputs.update(result)
+
+ self.load("http://dl.free.fr/getfile.pl", post=inputs)
+ headers = self.getLastHeaders()
+ if headers.get("code") == 302 and "set-cookie" in headers and "location" in headers:
+ m = re.search("(.*?)=(.*?); path=(.*?); domain=(.*?)", headers.get("set-cookie"))
+ cj = CookieJar(__name__)
+ if m:
+ cj.setCookie(m.group(4), m.group(1), m.group(2), m.group(3))
+ else:
+ self.fail("Cookie error")
+ location = headers.get("location")
+ self.req.setCookieJar(cj)
+ self.download(location, disposition=True)
+ else:
+ self.fail("Invalid response")
+
+ def getLastHeaders(self):
+ #parse header
+ header = {"code": self.req.code}
+ for line in self.req.http.header.splitlines():
+ line = line.strip()
+ if not line or ":" not in line:
+ continue
+
+ key, none, value = line.partition(":")
+ key = key.lower().strip()
+ value = value.strip()
+
+ if key in header:
+ if type(header[key]) == list:
+ header[key].append(value)
+ else:
+ header[key] = [header[key], value]
+ else:
+ header[key] = value
+ return header
+
+
+getInfo = create_getInfo(DlFreeFr)
diff --git a/pyload/plugins/hoster/DuploadOrg.py b/pyload/plugins/hoster/DuploadOrg.py
new file mode 100644
index 000000000..8c2430c87
--- /dev/null
+++ b/pyload/plugins/hoster/DuploadOrg.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class DuploadOrg(XFileSharingPro):
+ __name__ = "DuploadOrg"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?dupload\.org/\w{12}'
+
+ __description__ = """Dupload.grg hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ HOSTER_NAME = "dupload.org"
+
+ FILE_INFO_PATTERN = r'<h3[^>]*>(?P<N>.+) \((?P<S>[\d.]+) (?P<U>\w+)\)</h3>'
+
+
+getInfo = create_getInfo(DuploadOrg)
diff --git a/pyload/plugins/hoster/EasybytezCom.py b/pyload/plugins/hoster/EasybytezCom.py
new file mode 100644
index 000000000..7b1d8881f
--- /dev/null
+++ b/pyload/plugins/hoster/EasybytezCom.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class EasybytezCom(XFileSharingPro):
+ __name__ = "EasybytezCom"
+ __type__ = "hoster"
+ __version__ = "0.18"
+
+ __pattern__ = r'http://(?:www\.)?easybytez.com/(\w+).*'
+
+ __description__ = """Easybytez.com hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ HOSTER_NAME = "easybytez.com"
+
+ FILE_INFO_PATTERN = r'<span class="name">(?P<N>.+)</span><br>\s*<span class="size">(?P<S>[^<]+)</span>'
+ OFFLINE_PATTERN = r'<h1>File not available</h1>'
+
+ LINK_PATTERN = r'(http://(\w+\.(easyload|easybytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/[^"<]+)'
+ OVR_LINK_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
+ ERROR_PATTERN = r'(?:class=["\']err["\'][^>]*>|<Center><b>)(.*?)</'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+getInfo = create_getInfo(EasybytezCom)
diff --git a/pyload/plugins/hoster/EdiskCz.py b/pyload/plugins/hoster/EdiskCz.py
new file mode 100644
index 000000000..4c532b33f
--- /dev/null
+++ b/pyload/plugins/hoster/EdiskCz.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class EdiskCz(SimpleHoster):
+ __name__ = "EdiskCz"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?edisk.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.*'
+
+ __description__ = """Edisk.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<span class="fl" title="(?P<N>[^"]+)">\s*.*?\((?P<S>[0-9.]*) (?P<U>[kKMG])i?B\)</h1></span>'
+ OFFLINE_PATTERN = r'<h3>This file does not exist due to one of the following:</h3><ul><li>'
+
+ ACTION_PATTERN = r'/en/download/(\d+/.*\.html)'
+ LINK_PATTERN = r'http://.*edisk.cz.*\.html'
+
+
+ def setup(self):
+ self.multiDL = False
+
+ def process(self, pyfile):
+ url = re.sub("/(stahni|sk/stahni)/", "/en/download/", pyfile.url)
+
+ self.logDebug('URL:' + url)
+
+ m = re.search(self.ACTION_PATTERN, url)
+ if m is None:
+ self.parseError("ACTION")
+ action = m.group(1)
+
+ self.html = self.load(url, decode=True)
+ self.getFileInfo()
+
+ self.html = self.load(re.sub("/en/download/", "/en/download-slow/", url))
+
+ url = self.load(re.sub("/en/download/", "/x-download/", url), post={
+ "action": action
+ })
+
+ if not re.match(self.LINK_PATTERN, url):
+ self.fail("Unexpected server response")
+
+ self.download(url)
+
+
+getInfo = create_getInfo(EdiskCz)
diff --git a/pyload/plugins/hoster/EgoFilesCom.py b/pyload/plugins/hoster/EgoFilesCom.py
new file mode 100644
index 000000000..7d59b274c
--- /dev/null
+++ b/pyload/plugins/hoster/EgoFilesCom.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://egofiles.com/mOZfMI1WLZ6HBkGG/random.bin
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class EgoFilesCom(SimpleHoster):
+ __name__ = "EgoFilesCom"
+ __type__ = "hoster"
+ __version__ = "0.15"
+
+ __pattern__ = r'https?://(?:www\.)?egofiles.com/(\w+)'
+
+ __description__ = """Egofiles.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_INFO_PATTERN = r'<div class="down-file">\s+(?P<N>[^\t]+)\s+<div class="file-properties">\s+(File size|Rozmiar): (?P<S>[\w.]+) (?P<U>\w+) \|'
+ OFFLINE_PATTERN = r'(File size|Rozmiar): 0 KB'
+ WAIT_TIME_PATTERN = r'For next free download you have to wait <strong>((?P<m>\d*)m)? ?((?P<s>\d+)s)?</strong>'
+ LINK_PATTERN = r'<a href="(?P<link>[^"]+)">Download ></a>'
+ RECAPTCHA_KEY = "6LeXatQSAAAAAHezcjXyWAni-4t302TeYe7_gfvX"
+
+
+ def setup(self):
+ # Set English language
+ self.load("https://egofiles.com/ajax/lang.php?lang=en", just_header=True)
+
+ def process(self, pyfile):
+ if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+ self.getFileInfo()
+
+ # Wait time between free downloads
+ if 'For next free download you have to wait' in self.html:
+ m = re.search(self.WAIT_TIME_PATTERN, self.html).groupdict('0')
+ waittime = int(m['m']) * 60 + int(m['s'])
+ self.wait(waittime, True)
+
+ downloadURL = r''
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ post_data = {'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response}
+ self.html = self.load(self.pyfile.url, post=post_data, decode=True)
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.logInfo('Wrong captcha')
+ self.invalidCaptcha()
+ elif hasattr(m, 'group'):
+ downloadURL = m.group('link')
+ self.correctCaptcha()
+ break
+ else:
+ self.fail('Unknown error - Plugin may be out of date')
+
+ if not downloadURL:
+ self.fail("No Download url retrieved/all captcha attempts failed")
+
+ self.download(downloadURL, disposition=True)
+
+ def handlePremium(self):
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header:
+ self.logDebug('DIRECT LINK from header: ' + header['location'])
+ self.download(header['location'])
+ else:
+ self.html = self.load(self.pyfile.url, decode=True)
+ self.getFileInfo()
+ m = re.search(r'<a href="(?P<link>[^"]+)">Download ></a>', self.html)
+ if m is None:
+ self.parseError('Unable to detect direct download url')
+ else:
+ self.logDebug('DIRECT URL from html: ' + m.group('link'))
+ self.download(m.group('link'), disposition=True)
+
+
+getInfo = create_getInfo(EgoFilesCom)
diff --git a/pyload/plugins/hoster/EpicShareNet.py b/pyload/plugins/hoster/EpicShareNet.py
new file mode 100644
index 000000000..a4a6008ae
--- /dev/null
+++ b/pyload/plugins/hoster/EpicShareNet.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://epicshare.net/fch3m2bk6ihp/BigBuckBunny_320x180.mp4.html
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class EpicShareNet(XFileSharingPro):
+ __name__ = "EpicShareNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?epicshare\.net/\w{12}'
+
+ __description__ = """EpicShare.net hoster plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ HOSTER_NAME = "epicshare.net"
+
+ OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
+ FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h2>(?P<N>[^<]+)</h2>'
+
+
+getInfo = create_getInfo(EpicShareNet)
diff --git a/pyload/plugins/hoster/EuroshareEu.py b/pyload/plugins/hoster/EuroshareEu.py
new file mode 100644
index 000000000..d7c172594
--- /dev/null
+++ b/pyload/plugins/hoster/EuroshareEu.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class EuroshareEu(SimpleHoster):
+ __name__ = "EuroshareEu"
+ __type__ = "hoster"
+ __version__ = "0.25"
+
+ __pattern__ = r'http://(?:www\.)?euroshare.(eu|sk|cz|hu|pl)/file/.*'
+
+ __description__ = """Euroshare.eu hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<span style="float: left;"><strong>(?P<N>.+?)</strong> \((?P<S>.+?)\)</span>'
+ OFFLINE_PATTERN = ur'<h2>S.bor sa nena.iel</h2>|Poşadovaná stránka neexistuje!'
+
+ FREE_URL_PATTERN = r'<a href="(/file/\d+/[^/]*/download/)"><div class="downloadButton"'
+ ERR_PARDL_PATTERN = r'<h2>Prebieha s.ahovanie</h2>|<p>Naraz je z jednej IP adresy mo.n. s.ahova. iba jeden s.bor'
+ ERR_NOT_LOGGED_IN_PATTERN = r'href="/customer-zone/login/"'
+
+ FILE_URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")]
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.req.setOption("timeout", 120)
+
+ def handlePremium(self):
+ if self.ERR_NOT_LOGGED_IN_PATTERN in self.html:
+ self.account.relogin(self.user)
+ self.retry(reason="User not logged in")
+
+ self.download(self.pyfile.url.rstrip('/') + "/download/")
+
+ check = self.checkDownload({"login": re.compile(self.ERR_NOT_LOGGED_IN_PATTERN),
+ "json": re.compile(r'\{"status":"error".*?"message":"(.*?)"')})
+ if check == "login" or (check == "json" and self.lastCheck.group(1) == "Access token expired"):
+ self.account.relogin(self.user)
+ self.retry(reason="Access token expired")
+ elif check == "json":
+ self.fail(self.lastCheck.group(1))
+
+ def handleFree(self):
+ if re.search(self.ERR_PARDL_PATTERN, self.html) is not None:
+ self.longWait(5 * 60, 12)
+
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.parseError("Parse error (URL)")
+ parsed_url = "http://euroshare.eu%s" % m.group(1)
+ self.logDebug("URL", parsed_url)
+ self.download(parsed_url, disposition=True)
+
+ check = self.checkDownload({"multi_dl": re.compile(self.ERR_PARDL_PATTERN)})
+ if check == "multi_dl":
+ self.longWait(5 * 60, 12)
+
+
+getInfo = create_getInfo(EuroshareEu)
diff --git a/pyload/plugins/hoster/ExtabitCom.py b/pyload/plugins/hoster/ExtabitCom.py
new file mode 100644
index 000000000..78172ef02
--- /dev/null
+++ b/pyload/plugins/hoster/ExtabitCom.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class ExtabitCom(SimpleHoster):
+ __name__ = "ExtabitCom"
+ __type__ = "hoster"
+ __version__ = "0.6"
+
+ __pattern__ = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)'
+
+ __description__ = """Extabit.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>[^"]+)">'
+ FILE_SIZE_PATTERN = r'<th>Size:</th>\s*<td class="col-fileinfo">(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'>File not found<'
+ TEMP_OFFLINE_PATTERN = r'>(File is temporary unavailable|No download mirror)<'
+
+ LINK_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/[a-z0-9]+/.*?)[\'"]'
+
+
+ def handleFree(self):
+ if r">Only premium users can download this file" in self.html:
+ self.fail("Only premium users can download this file")
+
+ m = re.search(r"Next free download from your ip will be available in <b>(\d+)\s*minutes", self.html)
+ if m:
+ self.wait(int(m.group(1)) * 60, True)
+ elif "The daily downloads limit from your IP is exceeded" in self.html:
+ self.logWarning("You have reached your daily downloads limit for today")
+ self.wait(secondsToMidnight(gmt=2), True)
+
+ self.logDebug("URL: " + self.req.http.lastEffectiveURL)
+ m = re.match(self.__pattern__, self.req.http.lastEffectiveURL)
+ fileID = m.group('ID') if m else self.file_info('ID')
+
+ m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html)
+ if m:
+ recaptcha = ReCaptcha(self)
+ captcha_key = m.group(1)
+
+ for _ in xrange(5):
+ get_data = {"type": "recaptcha"}
+ get_data['challenge'], get_data['capture'] = recaptcha.challenge(captcha_key)
+ response = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data))
+ if "ok" in response:
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail("Invalid captcha")
+ else:
+ self.parseError('Captcha')
+
+ if not "href" in response:
+ self.parseError('JSON')
+
+ self.html = self.load("http://extabit.com/file/%s%s" % (fileID, response['href']))
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('Download URL')
+ url = m.group(1)
+ self.logDebug("Download URL: " + url)
+ self.download(url)
+
+
+getInfo = create_getInfo(ExtabitCom)
diff --git a/pyload/plugins/hoster/FastixRu.py b/pyload/plugins/hoster/FastixRu.py
new file mode 100644
index 000000000..aa1794047
--- /dev/null
+++ b/pyload/plugins/hoster/FastixRu.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+
+
+class FastixRu(Hoster):
+ __name__ = "FastixRu"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?fastix\.(ru|it)/file/(?P<ID>[a-zA-Z0-9]{24})'
+
+ __description__ = """Fastix hoster plugin"""
+ __author_name__ = "Massimo Rosamilia"
+ __author_mail__ = "max@spiritix.eu"
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+ def setup(self):
+ self.chunkLimit = 3
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Fastix")
+ self.fail("No Fastix account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ api_key = self.account.getAccountData(self.user)
+ api_key = api_key['api']
+ url = "http://fastix.ru/api_v2/?apikey=%s&sub=getdirectlink&link=%s" % (api_key, pyfile.url)
+ page = self.load(url)
+ data = json_loads(page)
+ self.logDebug("Json data: %s" % str(data))
+ if "error\":true" in page:
+ self.offline()
+ else:
+ new_url = data['downloadlink']
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown"):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"error": "<title>An error occurred while processing your request</title>",
+ "empty": re.compile(r"^$")})
+
+ if check == "error":
+ self.retry(wait_time=60, reason="An error occurred while generating link.")
+ elif check == "empty":
+ self.retry(wait_time=60, reason="Downloaded File was empty.")
diff --git a/pyload/plugins/hoster/FastshareCz.py b/pyload/plugins/hoster/FastshareCz.py
new file mode 100644
index 000000000..3897a1c23
--- /dev/null
+++ b/pyload/plugins/hoster/FastshareCz.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.fastshare.cz/2141189/random.bin
+
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FastshareCz(SimpleHoster):
+ __name__ = "FastshareCz"
+ __type__ = "hoster"
+ __version__ = "0.22"
+
+ __pattern__ = r'http://(?:www\.)?fastshare\.cz/\d+/.+'
+
+ __description__ = """FastShare.cz hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell", "Walter Purcaro")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it", "vuolter@gmail.com")
+
+ FILE_INFO_PATTERN = r'<h1 class="dwp">(?P<N>[^<]+)</h1>\s*<div class="fileinfo">\s*Size\s*: (?P<S>\d+) (?P<U>\w+),'
+ OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)'
+
+ FILE_URL_REPLACEMENTS = [("#.*", "")]
+
+ SH_COOKIES = [(".fastshare.cz", "lang", "en")]
+
+ FREE_URL_PATTERN = r'action=(/free/.*?)>\s*<img src="([^"]*)"><br'
+ PREMIUM_URL_PATTERN = r'(http://data\d+\.fastshare\.cz/download\.php\?id=\d+&)'
+ CREDIT_PATTERN = r' credit for '
+
+
+ def handleFree(self):
+ if "> 100% of FREE slots are full" in self.html:
+ self.retry(120, 60, "No free slots")
+
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m:
+ action, captcha_src = m.groups()
+ else:
+ self.parseError("Free URL")
+
+ baseurl = "http://www.fastshare.cz"
+ captcha = self.decryptCaptcha(urljoin(baseurl, captcha_src))
+ self.download(urljoin(baseurl, action), post={"code": captcha, "btn.x": 77, "btn.y": 18})
+
+ check = self.checkDownload({
+ "paralell_dl":
+ "<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)",
+ "wrong_captcha": "Download for FREE"
+ })
+
+ if check == "paralell_dl":
+ self.retry(6, 10 * 60, "Paralell download")
+ elif check == "wrong_captcha":
+ self.retry(max_tries=5, reason="Wrong captcha")
+
+ def handlePremium(self):
+ header = self.load(self.pyfile.url, just_header=True)
+ if "location" in header:
+ url = header['location']
+ else:
+ self.html = self.load(self.pyfile.url)
+
+ self.getFileInfo() #
+
+ if self.CREDIT_PATTERN in self.html:
+ self.logWarning("Not enough traffic left")
+ self.resetAccount()
+ else:
+ m = re.search(self.PREMIUM_URL_PATTERN, self.html)
+ if m:
+ url = m.group(1)
+ else:
+ self.parseError("Premium URL")
+
+ self.logDebug("PREMIUM URL: " + url)
+ self.download(url, disposition=True)
+
+ check = self.checkDownload({"credit": re.compile(self.CREDIT_PATTERN)})
+ if check == "credit":
+ self.resetAccount()
+
+
+getInfo = create_getInfo(FastshareCz)
diff --git a/pyload/plugins/hoster/File4safeCom.py b/pyload/plugins/hoster/File4safeCom.py
new file mode 100644
index 000000000..4aa0e26a4
--- /dev/null
+++ b/pyload/plugins/hoster/File4safeCom.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class File4safeCom(XFileSharingPro):
+ __name__ = "File4safeCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?file4safe\.com/\w+'
+
+ __description__ = """File4safe.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ HOSTER_NAME = "file4safe.com"
+
+
+ def handlePremium(self):
+ self.req.http.lastURL = self.pyfile.url
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+ self.load(self.pyfile.url, post=self.getPostParameters(), decode=True)
+ self.header = self.req.http.header
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
+ if m and re.match(self.LINK_PATTERN, m.group(1)):
+ location = m.group(1).strip()
+ self.startDownload(location)
+ else:
+ self.parseError("Unable to detect premium download link")
+
+
+getInfo = create_getInfo(File4safeCom)
diff --git a/pyload/plugins/hoster/FileApeCom.py b/pyload/plugins/hoster/FileApeCom.py
new file mode 100644
index 000000000..8c6305631
--- /dev/null
+++ b/pyload/plugins/hoster/FileApeCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class FileApeCom(DeadHoster):
+ __name__ = "FileApeCom"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+'
+
+ __description__ = """FileApe.com hoster plugin"""
+ __author_name__ = "espes"
+ __author_mail__ = None
+
+
+getInfo = create_getInfo(FileApeCom)
diff --git a/pyload/plugins/hoster/FileParadoxIn.py b/pyload/plugins/hoster/FileParadoxIn.py
new file mode 100644
index 000000000..955a9726b
--- /dev/null
+++ b/pyload/plugins/hoster/FileParadoxIn.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class FileParadoxIn(XFileSharingPro):
+ __name__ = "FileParadoxIn"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?fileparadox\.in/\w+'
+
+ __description__ = """FileParadox.in hoster plugin"""
+ __author_name__ = "RazorWing"
+ __author_mail__ = "muppetuk1@hotmail.com"
+
+ HOSTER_NAME = "fileparadox.in"
+
+ FILE_SIZE_PATTERN = r'</font>\s*\(\s*(?P<S>[^)]+)\s*\)</font>'
+ LINK_PATTERN = r'(http://([^/]*?fileparadox.in|\d+\.\d+\.\d+\.\d+)(:\d+/d/|/files/\w+/\w+/)[^"\'<]+)'
+
+
+getInfo = create_getInfo(FileParadoxIn)
diff --git a/pyload/plugins/hoster/FileStoreTo.py b/pyload/plugins/hoster/FileStoreTo.py
new file mode 100644
index 000000000..6a2963ec2
--- /dev/null
+++ b/pyload/plugins/hoster/FileStoreTo.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FileStoreTo(SimpleHoster):
+ __name__ = "FileStoreTo"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)'
+
+ __description__ = """FileStore.to hoster plugin"""
+ __author_name__ = ("Walter Purcaro", "stickell")
+ __author_mail__ = ("vuolter@gmail.com", "l.stickell@yahoo.it")
+
+ FILE_INFO_PATTERN = r'File: <span[^>]*>(?P<N>.+)</span><br />Size: (?P<S>[\d,.]+) (?P<U>\w+)'
+ OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def handleFree(self):
+ self.wait(10)
+ ldc = re.search(r'wert="(\w+)"', self.html).group(1)
+ link = self.load("http://filestore.to/ajax/download.php", get={"LDC": ldc})
+ self.logDebug("Download link = " + link)
+ self.download(link)
+
+
+getInfo = create_getInfo(FileStoreTo)
diff --git a/pyload/plugins/hoster/FilebeerInfo.py b/pyload/plugins/hoster/FilebeerInfo.py
new file mode 100644
index 000000000..561660148
--- /dev/null
+++ b/pyload/plugins/hoster/FilebeerInfo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class FilebeerInfo(DeadHoster):
+ __name__ = "FilebeerInfo"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+).*'
+
+ __description__ = """Filebeer.info plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(FilebeerInfo)
diff --git a/pyload/plugins/hoster/FilecloudIo.py b/pyload/plugins/hoster/FilecloudIo.py
new file mode 100644
index 000000000..9cf9306d1
--- /dev/null
+++ b/pyload/plugins/hoster/FilecloudIo.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilecloudIo(SimpleHoster):
+ __name__ = "FilecloudIo"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+).*'
+
+ __description__ = """Filecloud.io hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ FILE_SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}'
+ FILE_NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?)&nbsp;&nbsp;<'
+ OFFLINE_PATTERN = r'l10n.(FILES__DOESNT_EXIST|REMOVED)'
+ TEMP_OFFLINE_PATTERN = r'l10n.FILES__WARNING'
+
+ UKEY_PATTERN = r"'ukey'\s*:'(\w+)',"
+ AB1_PATTERN = r"if\( __ab1 == '(\w+)' \)"
+ ERROR_MSG_PATTERN = r'var __error_msg\s*=\s*l10n\.(.*?);'
+ LINK_PATTERN = r'"(http://s\d+.filecloud.io/%s/\d+/.*?)"'
+ RECAPTCHA_KEY_PATTERN = r"var __recaptcha_public\s*=\s*'([^']+)';"
+ RECAPTCHA_KEY = "6Lf5OdISAAAAAEZObLcx5Wlv4daMaASRov1ysDB1"
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = 1
+
+ def handleFree(self):
+ data = {"ukey": self.file_info['ID']}
+
+ m = re.search(self.AB1_PATTERN, self.html)
+ if m is None:
+ self.parseError("__AB1")
+ data['__ab1'] = m.group(1)
+
+ if not self.account:
+ self.fail("User not logged in")
+ elif not self.account.logged_in:
+ recaptcha = ReCaptcha(self)
+ captcha_challenge, captcha_response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ self.account.form_data = {"recaptcha_challenge_field": captcha_challenge,
+ "recaptcha_response_field": captcha_response}
+ self.account.relogin(self.user)
+ self.retry(2)
+
+ json_url = "http://filecloud.io/download-request.json"
+ response = self.load(json_url, post=data)
+ self.logDebug(response)
+ response = json_loads(response)
+
+ if "error" in response and response['error']:
+ self.fail(response)
+
+ self.logDebug(response)
+ if response['captcha']:
+ recaptcha = ReCaptcha(self)
+ m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
+ captcha_key = m.group(1) if m else self.RECAPTCHA_KEY
+ data['ctype'] = "recaptcha"
+
+ for _ in xrange(5):
+ data['recaptcha_challenge'], data['recaptcha_response'] = recaptcha.challenge(captcha_key)
+
+ json_url = "http://filecloud.io/download-request.json"
+ response = self.load(json_url, post=data)
+ self.logDebug(response)
+ response = json_loads(response)
+
+ if "retry" in response and response['retry']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("Incorrect captcha")
+
+ if response['dl']:
+ self.html = self.load('http://filecloud.io/download.html')
+ m = re.search(self.LINK_PATTERN % self.file_info['ID'], self.html)
+ if m is None:
+ self.parseError("Download URL")
+ download_url = m.group(1)
+ self.logDebug("Download URL: %s" % download_url)
+
+ if "size" in self.file_info and self.file_info['size']:
+ self.check_data = {"size": int(self.file_info['size'])}
+ self.download(download_url)
+ else:
+ self.fail("Unexpected server response")
+
+ def handlePremium(self):
+ akey = self.account.getAccountData(self.user)['akey']
+ ukey = self.file_info['ID']
+ self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey))
+ rep = self.load("http://api.filecloud.io/api-fetch_download_url.api",
+ post={"akey": akey, "ukey": ukey})
+ self.logDebug("FetchDownloadUrl: " + rep)
+ rep = json_loads(rep)
+ if rep['status'] == 'ok':
+ self.download(rep['download_url'], disposition=True)
+ else:
+ self.fail(rep['message'])
+
+
+getInfo = create_getInfo(FilecloudIo)
diff --git a/pyload/plugins/hoster/FilefactoryCom.py b/pyload/plugins/hoster/FilefactoryCom.py
new file mode 100644
index 000000000..fafe96477
--- /dev/null
+++ b/pyload/plugins/hoster/FilefactoryCom.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ for url in urls:
+ h = getURL(url, just_header=True)
+ m = re.search(r'Location: (.+)\r\n', h)
+ if m and not re.match(m.group(1), FilefactoryCom.__pattern__): # It's a direct link! Skipping
+ yield (url, 0, 3, url)
+ else: # It's a standard html page
+ file_info = parseFileInfo(FilefactoryCom, url, getURL(url))
+ yield file_info
+
+
+class FilefactoryCom(SimpleHoster):
+ __name__ = "FilefactoryCom"
+ __type__ = "hoster"
+ __version__ = "0.50"
+
+ __pattern__ = r'https?://(?:www\.)?filefactory\.com/file/(?P<id>[a-zA-Z0-9]+)'
+
+ __description__ = """Filefactory.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_INFO_PATTERN = r'<div id="file_name"[^>]*>\s*<h2>(?P<N>[^<]+)</h2>\s*<div id="file_info">\s*(?P<S>[\d.]+) (?P<U>\w+) uploaded'
+ LINK_PATTERN = r'<a href="(https?://[^"]+)"[^>]*><i[^>]*></i> Download with FileFactory Premium</a>'
+ OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available'
+ PREMIUM_ONLY_PATTERN = r'>Premium Account Required<'
+
+ SH_COOKIES = [(".filefactory.com", "locale", "en_US.utf8")]
+
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+ if "Currently only Premium Members can download files larger than" in self.html:
+ self.fail("File too large for free download")
+ elif "All free download slots on this server are currently in use" in self.html:
+ self.retry(50, 15 * 60, "All free slots are busy")
+
+ m = re.search(r'data-href(?:-direct)?="(http://[^"]+)"', self.html)
+ if m:
+ t = re.search(r'<div id="countdown_clock" data-delay="(\d+)">', self.html)
+ if t:
+ t = t.group(1)
+ else:
+ self.logDebug("Unable to detect countdown duration. Guessing 60 seconds")
+ t = 60
+ self.wait(t)
+ direct = m.group(1)
+ else: # This section could be completely useless now
+ # Load the page that contains the direct link
+ url = re.search(r"document\.location\.host \+\s*'(.+)';", self.html)
+ if url is None:
+ self.parseError('Unable to detect free link')
+ url = 'http://www.filefactory.com' + url.group(1)
+ self.html = self.load(url, decode=True)
+
+ # Free downloads wait time
+ waittime = re.search(r'id="startWait" value="(\d+)"', self.html)
+ if not waittime:
+ self.parseError('Unable to detect wait time')
+ self.wait(int(waittime.group(1)))
+
+ # Parse the direct link and download it
+ direct = re.search(r'data-href(?:-direct)?="(.*)" class="button', self.html)
+ if not direct:
+ self.parseError('Unable to detect free direct link')
+ direct = direct.group(1)
+
+ self.logDebug('DIRECT LINK: ' + direct)
+ self.download(direct, disposition=True)
+
+ check = self.checkDownload({"multiple": "You are currently downloading too many files at once.",
+ "error": '<div id="errorMessage">'})
+
+ if check == "multiple":
+ self.logDebug("Parallel downloads detected; waiting 15 minutes")
+ self.retry(wait_time=15 * 60, reason="Parallel downloads")
+ elif check == "error":
+ self.fail("Unknown error")
+
+ def handlePremium(self):
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header:
+ url = header['location'].strip()
+ if not url.startswith("http://"):
+ url = "http://www.filefactory.com" + url
+ elif 'content-disposition' in header:
+ url = self.pyfile.url
+ else:
+ self.logInfo('You could enable "Direct Downloads" on http://filefactory.com/account/')
+ html = self.load(self.pyfile.url)
+ m = re.search(self.LINK_PATTERN, html)
+ if m:
+ url = m.group(1)
+ else:
+ self.parseError('Unable to detect premium direct link')
+
+ self.logDebug('DIRECT PREMIUM LINK: ' + url)
+ self.download(url, disposition=True)
diff --git a/pyload/plugins/hoster/FilejungleCom.py b/pyload/plugins/hoster/FilejungleCom.py
new file mode 100644
index 000000000..0bbc7502e
--- /dev/null
+++ b/pyload/plugins/hoster/FilejungleCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.FileserveCom import FileserveCom, checkFile
+from pyload.plugins.Plugin import chunks
+
+
+class FilejungleCom(FileserveCom):
+ __name__ = "FilejungleCom"
+ __type__ = "hoster"
+ __version__ = "0.51"
+
+ __pattern__ = r'http://(?:www\.)?filejungle\.com/f/(?P<id>[^/]+).*'
+
+ __description__ = """Filejungle.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ URLS = ["http://www.filejungle.com/f/", "http://www.filejungle.com/check_links.php",
+ "http://www.filejungle.com/checkReCaptcha.php"]
+ LINKCHECK_TR = r'<li>\s*(<div class="col1">.*?)</li>'
+ LINKCHECK_TD = r'<div class="(?:col )?col\d">(?:<[^>]*>|&nbsp;)*([^<]*)'
+
+ LONG_WAIT_PATTERN = r'<h1>Please wait for (\d+) (\w+)\s*to download the next file\.</h1>'
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 100):
+ yield checkFile(FilejungleCom, chunk)
diff --git a/pyload/plugins/hoster/FileomCom.py b/pyload/plugins/hoster/FileomCom.py
new file mode 100644
index 000000000..11052e289
--- /dev/null
+++ b/pyload/plugins/hoster/FileomCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://fileom.com/gycaytyzdw3g/random.bin.html
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class FileomCom(XFileSharingPro):
+ __name__ = "FileomCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?fileom\.com/\w+'
+
+ __description__ = """Fileom.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ HOSTER_NAME = "fileom.com"
+
+ FILE_URL_REPLACEMENTS = [(r'/$', "")]
+ SH_COOKIES = [(".fileom.com", "lang", "english")]
+
+ FILE_NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<'
+ FILE_SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d\.]+) (?P<U>\w+)'
+
+ ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.*?)(?:\'|</)'
+
+ LINK_PATTERN = r"var url2 = '(.+?)';"
+
+
+ def setup(self):
+ self.resumeDownload = self.premium
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+getInfo = create_getInfo(FileomCom)
diff --git a/pyload/plugins/hoster/FilepostCom.py b/pyload/plugins/hoster/FilepostCom.py
new file mode 100644
index 000000000..382971c61
--- /dev/null
+++ b/pyload/plugins/hoster/FilepostCom.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilepostCom(SimpleHoster):
+ __name__ = "FilepostCom"
+ __type__ = "hoster"
+ __version__ = "0.28"
+
+ __pattern__ = r'https?://(?:www\.)?(?:filepost\.com/files|fp.io)/([^/]+).*'
+
+ __description__ = """Filepost.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<input type="text" id="url" value=\'<a href[^>]*>(?P<N>[^>]+?) - (?P<S>[0-9\.]+ [kKMG]i?B)</a>\' class="inp_text"/>'
+ OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>|<div class="file_info file_info_deleted">'
+
+ PREMIUM_ONLY_PATTERN = r'members only. Please upgrade to premium|a premium membership is required to download this file'
+ RECAPTCHA_KEY_PATTERN = r"Captcha.init\({\s*key:\s*'([^']+)'"
+ FLP_TOKEN_PATTERN = r"set_store_options\({token: '([^']+)'"
+
+
+ def handleFree(self):
+ # Find token and captcha key
+ file_id = re.match(self.__pattern__, self.pyfile.url).group(1)
+
+ m = re.search(self.FLP_TOKEN_PATTERN, self.html)
+ if m is None:
+ self.parseError("Token")
+ flp_token = m.group(1)
+
+ m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
+ if m is None:
+ self.parseError("Captcha key")
+ captcha_key = m.group(1)
+
+ # Get wait time
+ get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time() * 10000)) + '-xml'}
+ post_dict = {'action': 'set_download', 'token': flp_token, 'code': file_id}
+ wait_time = int(self.getJsonResponse(get_dict, post_dict, 'wait_time'))
+
+ if wait_time > 0:
+ self.wait(wait_time)
+
+ post_dict = {"token": flp_token, "code": file_id, "file_pass": ''}
+
+ if 'var is_pass_exists = true;' in self.html:
+ # Solve password
+ for file_pass in self.getPassword().splitlines():
+ get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
+ post_dict['file_pass'] = file_pass
+ self.logInfo("Password protected link, trying " + file_pass)
+
+ download_url = self.getJsonResponse(get_dict, post_dict, 'link')
+ if download_url:
+ break
+
+ else:
+ self.fail("No or incorrect password")
+
+ else:
+ # Solve recaptcha
+ recaptcha = ReCaptcha(self)
+
+ for i in xrange(5):
+ get_dict['JsHttpRequest'] = str(int(time() * 10000)) + '-xml'
+ if i:
+ post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field'] = recaptcha.challenge(
+ captcha_key)
+ self.logDebug(u"RECAPTCHA: %s : %s : %s" % (
+ captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field']))
+
+ download_url = self.getJsonResponse(get_dict, post_dict, 'link')
+ if download_url:
+ if i:
+ self.correctCaptcha()
+ break
+ elif i:
+ self.invalidCaptcha()
+
+ else:
+ self.fail("Invalid captcha")
+
+ # Download
+ self.download(download_url)
+
+ def getJsonResponse(self, get_dict, post_dict, field):
+ json_response = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict))
+ self.logDebug(json_response)
+
+ if not 'js' in json_response:
+ self.parseError('JSON %s 1' % field)
+
+ # i changed js_answer to json_response['js'] since js_answer is nowhere set.
+ # i don't know the JSON-HTTP specs in detail, but the previous author
+ # accessed json_response['js']['error'] as well as js_answer['error'].
+ # see the two lines commented out with "# ~?".
+ if 'error' in json_response['js']:
+ if json_response['js']['error'] == 'download_delay':
+ self.retry(wait_time=json_response['js']['params']['next_download'])
+ # ~? self.retry(wait_time=js_answer['params']['next_download'])
+ elif 'Wrong file password' in json_response['js']['error']:
+ return None
+ elif 'You entered a wrong CAPTCHA code' in json_response['js']['error']:
+ return None
+ elif 'CAPTCHA Code nicht korrekt' in json_response['js']['error']:
+ return None
+ elif 'CAPTCHA' in json_response['js']['error']:
+ self.logDebug('error response is unknown, but mentions CAPTCHA -> return None')
+ return None
+ else:
+ self.fail(json_response['js']['error'])
+ # ~? self.fail(js_answer['error'])
+
+ if not 'answer' in json_response['js'] or not field in json_response['js']['answer']:
+ self.parseError('JSON %s 2' % field)
+
+ return json_response['js']['answer'][field]
+
+
+getInfo = create_getInfo(FilepostCom)
diff --git a/pyload/plugins/hoster/FilerNet.py b/pyload/plugins/hoster/FilerNet.py
new file mode 100644
index 000000000..5f1b6bea8
--- /dev/null
+++ b/pyload/plugins/hoster/FilerNet.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://filer.net/get/ivgf5ztw53et3ogd
+# http://filer.net/get/hgo14gzcng3scbvv
+
+import pycurl
+import re
+
+from urlparse import urljoin
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FilerNet(SimpleHoster):
+ __name__ = "FilerNet"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?filer\.net/get/(\w+)'
+
+ __description__ = """Filer.net hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_INFO_PATTERN = r'<h1 class="page-header">Free Download (?P<N>\S+) <small>(?P<S>[\w.]+) (?P<U>\w+)</small></h1>'
+ OFFLINE_PATTERN = r'Nicht gefunden'
+ RECAPTCHA_KEY = "6LcFctISAAAAAAgaeHgyqhNecGJJRnxV1m_vAz3V"
+ LINK_PATTERN = r'href="([^"]+)">Get download</a>'
+
+
+ def process(self, pyfile):
+ if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def handleFree(self):
+ self.req.setOption("timeout", 120)
+ self.html = self.load(self.pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
+
+ # Wait between downloads
+ m = re.search(r'musst du <span id="time">(\d+)</span> Sekunden warten', self.html)
+ if m:
+ waittime = int(m.group(1))
+ self.retry(3, waittime, "Wait between free downloads")
+
+ self.getFileInfo()
+
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ inputs = self.parseHtmlForm(input_names='token')[1]
+ if 'token' not in inputs:
+ self.parseError('Unable to detect token')
+ token = inputs['token']
+ self.logDebug('Token: ' + token)
+
+ self.html = self.load(self.pyfile.url, post={'token': token}, decode=True)
+
+ inputs = self.parseHtmlForm(input_names='hash')[1]
+ if 'hash' not in inputs:
+ self.parseError('Unable to detect hash')
+ hash_data = inputs['hash']
+ self.logDebug('Hash: ' + hash_data)
+
+ downloadURL = r''
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ post_data = {'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response,
+ 'hash': hash_data}
+
+ # Workaround for 0.4.9 just_header issue. In 0.5 clean the code using just_header
+ self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 0)
+ self.load(self.pyfile.url, post=post_data)
+ self.req.http.c.setopt(pycurl.FOLLOWLOCATION, 1)
+
+ if 'location' in self.req.http.header.lower():
+ location = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1)
+ downloadURL = urljoin('http://filer.net', location)
+ self.correctCaptcha()
+ break
+ else:
+ self.logInfo('Wrong captcha')
+ self.invalidCaptcha()
+
+ if not downloadURL:
+ self.fail("No Download url retrieved/all captcha attempts failed")
+
+ self.download(downloadURL, disposition=True)
+
+ def handlePremium(self):
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header: # Direct Download ON
+ dl = self.pyfile.url
+ else: # Direct Download OFF
+ html = self.load(self.pyfile.url)
+ m = re.search(self.LINK_PATTERN, html)
+ if m is None:
+ self.parseError("Unable to detect direct link, try to enable 'Direct download' in your user settings")
+ dl = 'http://filer.net' + m.group(1)
+
+ self.logDebug('Direct link: ' + dl)
+ self.download(dl, disposition=True)
+
+
+getInfo = create_getInfo(FilerNet)
diff --git a/pyload/plugins/hoster/FilerioCom.py b/pyload/plugins/hoster/FilerioCom.py
new file mode 100644
index 000000000..31d04b0ee
--- /dev/null
+++ b/pyload/plugins/hoster/FilerioCom.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class FilerioCom(XFileSharingPro):
+ __name__ = "FilerioCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}'
+
+ __description__ = """FileRio.in hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "filerio.in"
+
+ OFFLINE_PATTERN = r'<b>&quot;File Not Found&quot;</b>|File has been removed due to Copyright Claim'
+ FILE_URL_REPLACEMENTS = [(r'http://.*?/', 'http://filerio.in/')]
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+
+getInfo = create_getInfo(FilerioCom)
diff --git a/pyload/plugins/hoster/FilesMailRu.py b/pyload/plugins/hoster/FilesMailRu.py
new file mode 100644
index 000000000..01d9c256a
--- /dev/null
+++ b/pyload/plugins/hoster/FilesMailRu.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+
+
+def getInfo(urls):
+ result = []
+ for chunk in chunks(urls, 10):
+ for url in chunk:
+ src = getURL(url)
+ if r'<div class="errorMessage mb10">' in src:
+ result.append((url, 0, 1, url))
+ elif r'Page cannot be displayed' in src:
+ result.append((url, 0, 1, url))
+ else:
+ try:
+ url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
+ file_name = re.search(url_pattern, src).group(0).split(', event)">')[1].split('</a>')[0]
+ result.append((file_name, 0, 2, url))
+ except:
+ pass
+
+ # status 1=OFFLINE, 2=OK, 3=UNKNOWN
+ # result.append((#name,#size,#status,#url))
+ yield result
+
+
+class FilesMailRu(Hoster):
+ __name__ = "FilesMailRu"
+ __type__ = "hoster"
+ __version__ = "0.31"
+
+ __pattern__ = r'http://(?:www\.)?files\.mail\.ru/.*'
+
+ __description__ = """Files.mail.ru hoster plugin"""
+ __author_name__ = "oZiRiz"
+ __author_mail__ = "ich@oziriz.de"
+
+
+ def setup(self):
+ if not self.account:
+ self.multiDL = False
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url)
+ self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>'
+
+ #marks the file as "offline" when the pattern was found on the html-page'''
+ if r'<div class="errorMessage mb10">' in self.html:
+ self.offline()
+
+ elif r'Page cannot be displayed' in self.html:
+ self.offline()
+
+ #the filename that will be showed in the list (e.g. test.part1.rar)'''
+ pyfile.name = self.getFileName()
+
+ #prepare and download'''
+ if not self.account:
+ self.prepare()
+ self.download(self.getFileUrl())
+ self.myPostProcess()
+ else:
+ self.download(self.getFileUrl())
+ self.myPostProcess()
+
+ def prepare(self):
+ """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected"""
+ self.setWait(10)
+ self.wait()
+ return True
+
+ def getFileUrl(self):
+ """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html"""
+ return re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[0]
+
+ def getFileName(self):
+ """gives you the Name for each file. Also extracted from the HTML-Page"""
+ return re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0]
+
+ def myPostProcess(self):
+ # searches the file for HTMl-Code. Sometimes the Redirect
+ # doesn't work (maybe a curl Problem) and you get only a small
+ # HTML file and the Download is marked as "finished"
+ # then the download will be restarted. It's only bad for these
+ # who want download a HTML-File (it's one in a million ;-) )
+ #
+ # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB
+ # so i set it to check every download because sometimes there are downloads
+ # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file
+ # (Loading 100MB in to ram is not an option)
+ check = self.checkDownload({"html": "<meta name="}, read_size=50000)
+ if check == "html":
+ self.logInfo(_(
+ "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." %
+ self.pyfile.name))
+ self.retry()
diff --git a/pyload/plugins/hoster/FileserveCom.py b/pyload/plugins/hoster/FileserveCom.py
new file mode 100644
index 000000000..5892cd96a
--- /dev/null
+++ b/pyload/plugins/hoster/FileserveCom.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.utils import parseFileSize
+
+
+def checkFile(plugin, urls):
+ html = getURL(plugin.URLS[1], post={"urls": "\n".join(urls)}, decode=True)
+
+ file_info = []
+ for li in re.finditer(plugin.LINKCHECK_TR, html, re.DOTALL):
+ try:
+ cols = re.findall(plugin.LINKCHECK_TD, li.group(1))
+ if cols:
+ file_info.append((
+ cols[1] if cols[1] != '--' else cols[0],
+ parseFileSize(cols[2]) if cols[2] != '--' else 0,
+ 2 if cols[3].startswith('Available') else 1,
+ cols[0]))
+ except Exception, e:
+ continue
+
+ return file_info
+
+
+class FileserveCom(Hoster):
+ __name__ = "FileserveCom"
+ __type__ = "hoster"
+ __version__ = "0.52"
+
+ __pattern__ = r'http://(?:www\.)?fileserve\.com/file/(?P<id>[^/]+).*'
+
+ __description__ = """Fileserve.com hoster plugin"""
+ __author_name__ = ("jeix", "mkaay", "Paul King", "zoidberg")
+ __author_mail__ = ("jeix@hasnomail.de", "mkaay@mkaay.de", "", "zoidberg@mujmail.cz")
+
+ URLS = ["http://www.fileserve.com/file/", "http://www.fileserve.com/link-checker.php",
+ "http://www.fileserve.com/checkReCaptcha.php"]
+ LINKCHECK_TR = r'<tr>\s*(<td>http://www.fileserve\.com/file/.*?)</tr>'
+ LINKCHECK_TD = r'<td>(?:<[^>]*>|&nbsp;)*([^<]*)'
+
+ CAPTCHA_KEY_PATTERN = r"var reCAPTCHA_publickey='(?P<key>[^']+)'"
+ LONG_WAIT_PATTERN = r'<li class="title">You need to wait (\d+) (\w+) to start another download\.</li>'
+ LINK_EXPIRED_PATTERN = r'Your download link has expired'
+ DAILY_LIMIT_PATTERN = r'Your daily download limit has been reached'
+ NOT_LOGGED_IN_PATTERN = r'<form (name="loginDialogBoxForm"|id="login_form")|<li><a href="/login.php">Login</a></li>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+ self.file_id = re.match(self.__pattern__, self.pyfile.url).group('id')
+ self.url = "%s%s" % (self.URLS[0], self.file_id)
+ self.logDebug("File ID: %s URL: %s" % (self.file_id, self.url))
+
+ def process(self, pyfile):
+ pyfile.name, pyfile.size, status, self.url = checkFile(self, [self.url])[0]
+ if status != 2:
+ self.offline()
+ self.logDebug("File Name: %s Size: %d" % (pyfile.name, pyfile.size))
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def handleFree(self):
+ self.html = self.load(self.url)
+ action = self.load(self.url, post={"checkDownload": "check"}, decode=True)
+ action = json_loads(action)
+ self.logDebug(action)
+
+ if "fail" in action:
+ if action['fail'] == "timeLimit":
+ self.html = self.load(self.url, post={"checkDownload": "showError", "errorType": "timeLimit"},
+ decode=True)
+
+ self.doLongWait(re.search(self.LONG_WAIT_PATTERN, self.html))
+
+ elif action['fail'] == "parallelDownload":
+ self.logWarning(_("Parallel download error, now waiting 60s."))
+ self.retry(wait_time=60, reason="parallelDownload")
+
+ else:
+ self.fail("Download check returned %s" % action['fail'])
+
+ elif "success" in action:
+ if action['success'] == "showCaptcha":
+ self.doCaptcha()
+ self.doTimmer()
+ elif action['success'] == "showTimmer":
+ self.doTimmer()
+
+ else:
+ self.fail("Unknown server response")
+
+ # show download link
+ response = self.load(self.url, post={"downloadLink": "show"}, decode=True)
+ self.logDebug("show downloadLink response : %s" % response)
+ if "fail" in response:
+ self.fail("Couldn't retrieve download url")
+
+ # this may either download our file or forward us to an error page
+ self.download(self.url, post={"download": "normal"})
+ self.logDebug(self.req.http.lastEffectiveURL)
+
+ check = self.checkDownload({"expired": self.LINK_EXPIRED_PATTERN,
+ "wait": re.compile(self.LONG_WAIT_PATTERN),
+ "limit": self.DAILY_LIMIT_PATTERN})
+
+ if check == "expired":
+ self.logDebug("Download link was expired")
+ self.retry()
+ elif check == "wait":
+ self.doLongWait(self.lastCheck)
+ elif check == "limit":
+ self.logWarning("Download limited reached for today")
+ self.setWait(secondsToMidnight(gmt=2), True)
+ self.wait()
+ self.retry()
+
+ self.thread.m.reconnecting.wait(3) # Ease issue with later downloads appearing to be in parallel
+
+ def doTimmer(self):
+ response = self.load(self.url, post={"downloadLink": "wait"}, decode=True)
+ self.logDebug("wait response : %s" % response[:80])
+
+ if "fail" in response:
+ self.fail("Failed getting wait time")
+
+ if self.__name__ == "FilejungleCom":
+ m = re.search(r'"waitTime":(\d+)', response)
+ if m is None:
+ self.fail("Cannot get wait time")
+ wait_time = int(m.group(1))
+ else:
+ wait_time = int(response) + 3
+
+ self.setWait(wait_time)
+ self.wait()
+
+ def doCaptcha(self):
+ captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group("key")
+ recaptcha = ReCaptcha(self)
+
+ for _ in xrange(5):
+ challenge, code = recaptcha.challenge(captcha_key)
+
+ response = json_loads(self.load(self.URLS[2],
+ post={'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': code,
+ 'recaptcha_shortencode_field': self.file_id}))
+ self.logDebug("reCaptcha response : %s" % response)
+ if not response['success']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("Invalid captcha")
+
+ def doLongWait(self, m):
+ wait_time = (int(m.group(1)) * {'seconds': 1, 'minutes': 60, 'hours': 3600}[m.group(2)]) if m else 12 * 60
+ self.setWait(wait_time, True)
+ self.wait()
+ self.retry()
+
+ def handlePremium(self):
+ premium_url = None
+ if self.__name__ == "FileserveCom":
+ #try api download
+ response = self.load("http://app.fileserve.com/api/download/premium/",
+ post={"username": self.user,
+ "password": self.account.getAccountData(self.user)['password'],
+ "shorten": self.file_id},
+ decode=True)
+ if response:
+ response = json_loads(response)
+ if response['error_code'] == "302":
+ premium_url = response['next']
+ elif response['error_code'] in ["305", "500"]:
+ self.tempOffline()
+ elif response['error_code'] in ["403", "605"]:
+ self.resetAccount()
+ elif response['error_code'] in ["606", "607", "608"]:
+ self.offline()
+ else:
+ self.logError(response['error_code'], response['error_message'])
+
+ self.download(premium_url or self.pyfile.url)
+
+ if not premium_url:
+ check = self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)})
+
+ if check == "login":
+ self.account.relogin(self.user)
+ self.retry(reason=_("Not logged in."))
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 100):
+ yield checkFile(FileserveCom, chunk)
diff --git a/pyload/plugins/hoster/FileshareInUa.py b/pyload/plugins/hoster/FileshareInUa.py
new file mode 100644
index 000000000..162217de2
--- /dev/null
+++ b/pyload/plugins/hoster/FileshareInUa.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class FileshareInUa(Hoster):
+ __name__ = "FileshareInUa"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?fileshare.in.ua/[A-Za-z0-9]+'
+
+ __description__ = """Fileshare.in.ua hoster plugin"""
+ __author_name__ = "fwannmacher"
+ __author_mail__ = "felipe@warhammerproject.com"
+
+ PATTERN_FILENAME = r'<h3 class="b-filename">(.*?)</h3>'
+ PATTERN_FILESIZE = r'<b class="b-filesize">(.*?)</b>'
+ PATTERN_OFFLINE = r"This file doesn't exist, or has been removed."
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.html = self.load(pyfile.url, decode=True)
+
+ if not self._checkOnline():
+ self.offline()
+
+ pyfile.name = self._getName()
+
+ link = self._getLink()
+
+ if not link.startswith('http://'):
+ link = "http://fileshare.in.ua" + link
+
+ self.download(link)
+
+ def _checkOnline(self):
+ if re.search(self.PATTERN_OFFLINE, self.html):
+ return False
+ else:
+ return True
+
+ def _getName(self):
+ name = re.search(self.PATTERN_FILENAME, self.html)
+ if name is None:
+ self.fail("%s: Plugin broken." % self.__name__)
+
+ return name.group(1)
+
+ def _getLink(self):
+ return re.search("<a href=\"(/get/.+)\" class=\"b-button m-blue m-big\" >", self.html).group(1)
+
+
+def getInfo(urls):
+ result = []
+
+ for url in urls:
+ html = getURL(url)
+
+ if re.search(FileshareInUa.PATTERN_OFFLINE, html):
+ result.append((url, 0, 1, url))
+ else:
+ name = re.search(FileshareInUa.PATTERN_FILENAME, html)
+
+ if name is None:
+ result.append((url, 0, 1, url))
+ continue
+
+ name = name.group(1)
+ size = re.search(FileshareInUa.PATTERN_FILESIZE, html)
+ size = parseFileSize(size.group(1))
+
+ result.append((name, size, 3, url))
+
+ yield result
diff --git a/pyload/plugins/hoster/FilezyNet.py b/pyload/plugins/hoster/FilezyNet.py
new file mode 100644
index 000000000..eeba4add0
--- /dev/null
+++ b/pyload/plugins/hoster/FilezyNet.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class FilezyNet(XFileSharingPro):
+ __name__ = "FilezyNet"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?filezy.net/.*/.*.html'
+
+ __description__ = """Filezy.net hoster plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+ HOSTER_NAME = "filezy.net"
+
+ FILE_SIZE_PATTERN = r'<span class="plansize">(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</span>'
+ WAIT_PATTERN = r'<div id="countdown_str" class="seconds">\n<!--Wait--> <span id=".*?">(\d+)</span>'
+ DOWNLOAD_JS_PATTERN = r"<script type='text/javascript'>eval(.*)"
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+ def getDownloadLink(self):
+ self.logDebug("Getting download link")
+
+ data = self.getPostParameters()
+ self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
+
+ obfuscated_js = re.search(self.DOWNLOAD_JS_PATTERN, self.html)
+ dl_file_now = self.js.eval(obfuscated_js.group(1))
+ link = re.search(self.LINK_PATTERN, dl_file_now)
+ return link.group(1)
+
+
+getInfo = create_getInfo(FilezyNet)
diff --git a/pyload/plugins/hoster/FiredriveCom.py b/pyload/plugins/hoster/FiredriveCom.py
new file mode 100644
index 000000000..a9d62bb75
--- /dev/null
+++ b/pyload/plugins/hoster/FiredriveCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FiredriveCom(SimpleHoster):
+ __name__ = "FiredriveCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+
+ __description__ = """Firedrive.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ FILE_NAME_PATTERN = r'<b>Name:</b> (?P<N>.+) <br>'
+ FILE_SIZE_PATTERN = r'<b>Size:</b> (?P<S>[\d.]+) (?P<U>[a-zA-Z]+) <br>'
+ OFFLINE_PATTERN = r'class="sad_face_image"|>No such page here.<'
+ TEMP_OFFLINE_PATTERN = r'>(File Temporarily Unavailable|Server Error. Try again later)'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.firedrive.com/file/\g<ID>')]
+
+ LINK_PATTERN = r'<a href="(https?://dl\.firedrive\.com/\?key=.+?)"'
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+ self.chunkLimit = -1
+
+ def handleFree(self):
+ link = self._getLink()
+ self.logDebug("Direct link: " + link)
+ self.download(link, disposition=True)
+
+ def _getLink(self):
+ f = re.search(self.LINK_PATTERN, self.html)
+ if f:
+ return f.group(1)
+ else:
+ self.html = self.load(self.pyfile.url, post={"confirm": re.search(r'name="confirm" value="(.+?)"', self.html).group(1)})
+ f = re.search(self.LINK_PATTERN, self.html)
+ if f:
+ return f.group(1)
+ else:
+ self.parseError("Direct download link not found")
+
+
+getInfo = create_getInfo(FiredriveCom)
diff --git a/pyload/plugins/hoster/FlyFilesNet.py b/pyload/plugins/hoster/FlyFilesNet.py
new file mode 100644
index 000000000..d8d6efb7e
--- /dev/null
+++ b/pyload/plugins/hoster/FlyFilesNet.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+class FlyFilesNet(SimpleHoster):
+ __name__ = "FlyFilesNet"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?flyfiles\.net/.*'
+
+ __description__ = """FlyFiles.net hoster plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+ SESSION_PATTERN = r'flyfiles\.net/(.*)/.*'
+ FILE_NAME_PATTERN = r'flyfiles\.net/.*/(.*)'
+
+
+ def process(self, pyfile):
+ name = re.search(self.FILE_NAME_PATTERN, pyfile.url).group(1)
+ pyfile.name = unquote_plus(name)
+
+ session = re.search(self.SESSION_PATTERN, pyfile.url).group(1)
+
+ url = "http://flyfiles.net"
+
+ # get download URL
+ parsed_url = getURL(url, post={"getDownLink": session}, cookies=True)
+ self.logDebug("Parsed URL: %s" % parsed_url)
+
+ if parsed_url == '#downlink|' or parsed_url == "#downlink|#":
+ self.logWarning("Could not get the download URL. Please wait 10 minutes.")
+ self.wait(10 * 60, True)
+ self.retry()
+
+ download_url = parsed_url.replace('#downlink|', '')
+
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
diff --git a/pyload/plugins/hoster/FourSharedCom.py b/pyload/plugins/hoster/FourSharedCom.py
new file mode 100644
index 000000000..e2cb980a4
--- /dev/null
+++ b/pyload/plugins/hoster/FourSharedCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class FourSharedCom(SimpleHoster):
+ __name__ = "FourSharedCom"
+ __type__ = "hoster"
+ __version__ = "0.29"
+
+ __pattern__ = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+?/.*'
+
+ __description__ = """4Shared.com hoster plugin"""
+ __author_name__ = ("jeix", "zoidberg")
+ __author_mail__ = ("jeix@hasnomail.de", "zoidberg@mujmail.cz")
+
+ FILE_NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"'
+ FILE_SIZE_PATTERN = r'<span title="Size: (?P<S>[0-9,.]+) (?P<U>[kKMG])i?B">'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.'
+
+ FILE_NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))]
+ FILE_SIZE_REPLACEMENTS = [(",", "")]
+
+ DOWNLOAD_URL_PATTERN = r'name="d3link" value="(.*?)"'
+ DOWNLOAD_BUTTON_PATTERN = r'id="btnLink" href="(.*?)"'
+ FID_PATTERN = r'name="d3fid" value="(.*?)"'
+
+
+ def handleFree(self):
+ if not self.account:
+ self.fail("User not logged in")
+
+ m = re.search(self.DOWNLOAD_BUTTON_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ else:
+ link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', self.pyfile.url)
+
+ self.html = self.load(link)
+
+ m = re.search(self.DOWNLOAD_URL_PATTERN, self.html)
+ if m is None:
+ self.parseError('Download link')
+ link = m.group(1)
+
+ try:
+ m = re.search(self.FID_PATTERN, self.html)
+ response = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1))
+ self.logDebug(response)
+ except:
+ pass
+
+ self.wait(20)
+ self.download(link)
+
+
+getInfo = create_getInfo(FourSharedCom)
diff --git a/pyload/plugins/hoster/FreakshareCom.py b/pyload/plugins/hoster/FreakshareCom.py
new file mode 100644
index 000000000..979b3c5f2
--- /dev/null
+++ b/pyload/plugins/hoster/FreakshareCom.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+
+
+class FreakshareCom(Hoster):
+ __name__ = "FreakshareCom"
+ __type__ = "hoster"
+ __version__ = "0.39"
+
+ __pattern__ = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/'
+
+ __description__ = """Freakshare.com hoster plugin"""
+ __author_name__ = ("sitacuisses", "spoob", "mkaay", "Toilal")
+ __author_mail__ = ("sitacuisses@yahoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "toilal.dev@gmail.com")
+
+
+ def setup(self):
+ self.multiDL = False
+ self.req_opts = []
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ pyfile.url = pyfile.url.replace("freakshare.net/", "freakshare.com/")
+
+ if self.account:
+ self.html = self.load(pyfile.url, cookies=False)
+ pyfile.name = self.get_file_name()
+ self.download(pyfile.url)
+
+ else:
+ self.prepare()
+ self.get_file_url()
+
+ self.download(pyfile.url, post=self.req_opts)
+
+ check = self.checkDownload({"bad": "bad try",
+ "paralell": "> Sorry, you cant download more then 1 files at time. <",
+ "empty": "Warning: Unknown: Filename cannot be empty",
+ "wrong_captcha": "Wrong Captcha!",
+ "downloadserver": "No Downloadserver. Please try again later!"})
+
+ if check == "bad":
+ self.fail("Bad Try.")
+ elif check == "paralell":
+ self.setWait(300, True)
+ self.wait()
+ self.retry()
+ elif check == "empty":
+ self.fail("File not downloadable")
+ elif check == "wrong_captcha":
+ self.invalidCaptcha()
+ self.retry()
+ elif check == "downloadserver":
+ self.retry(5, 15 * 60, "No Download server")
+
+ def prepare(self):
+ pyfile = self.pyfile
+
+ self.wantReconnect = False
+
+ self.download_html()
+
+ if not self.file_exists():
+ self.offline()
+
+ self.setWait(self.get_waiting_time())
+
+ pyfile.name = self.get_file_name()
+ pyfile.size = self.get_file_size()
+
+ self.wait()
+
+ return True
+
+ def download_html(self):
+ self.load("http://freakshare.com/index.php", {"language": "EN"}) # Set english language in server session
+ self.html = self.load(self.pyfile.url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+ if not self.wantReconnect:
+ self.req_opts = self.get_download_options() # get the Post options for the Request
+ #file_url = self.pyfile.url
+ #return file_url
+ else:
+ self.offline()
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+ if not self.wantReconnect:
+ file_name = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html)
+ if file_name is not None:
+ file_name = file_name.group(1)
+ else:
+ file_name = self.pyfile.url
+ return file_name
+ else:
+ return self.pyfile.url
+
+ def get_file_size(self):
+ size = 0
+ if not self.html:
+ self.download_html()
+ if not self.wantReconnect:
+ file_size_check = re.search(
+ r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html)
+ if file_size_check is not None:
+ units = float(file_size_check.group(1).replace(",", ""))
+ pow = {'KB': 1, 'MB': 2, 'GB': 3}[file_size_check.group(2)]
+ size = int(units * 1024 ** pow)
+
+ return size
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ if "Your Traffic is used up for today" in self.html:
+ self.wantReconnect = True
+ return secondsToMidnight(gmt=2)
+
+ timestring = re.search('\s*var\s(?:downloadWait|time)\s=\s(\d*)[.\d]*;', self.html)
+ if timestring:
+ return int(timestring.group(1)) + 1 # add 1 sec as tenths of seconds are cut off
+ else:
+ return 60
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"This file does not exist!", self.html) is not None:
+ return False
+ else:
+ return True
+
+ def get_download_options(self):
+ re_envelope = re.search(r".*?value=\"Free\sDownload\".*?\n*?(.*?<.*?>\n*)*?\n*\s*?</form>",
+ self.html).group(0) # get the whole request
+ to_sort = re.findall(r"<input\stype=\"hidden\"\svalue=\"(.*?)\"\sname=\"(.*?)\"\s\/>", re_envelope)
+ request_options = dict((n, v) for (v, n) in to_sort)
+
+ herewego = self.load(self.pyfile.url, None, request_options) # the actual download-Page
+
+ # comment this in, when it doesnt work
+ # with open("DUMP__FS_.HTML", "w") as fp:
+ # fp.write(herewego)
+
+ to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego)
+ request_options = dict((n, v) for (v, n) in to_sort)
+
+ # comment this in, when it doesnt work as well
+ #print "\n\n%s\n\n" % ";".join(["%s=%s" % x for x in to_sort])
+
+ challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=([0-9A-Za-z]+)", herewego)
+
+ if challenge:
+ re_captcha = ReCaptcha(self)
+ (request_options['recaptcha_challenge_field'],
+ request_options['recaptcha_response_field']) = re_captcha.challenge(challenge.group(1))
+
+ return request_options
diff --git a/pyload/plugins/hoster/FreeWayMe.py b/pyload/plugins/hoster/FreeWayMe.py
new file mode 100644
index 000000000..392430791
--- /dev/null
+++ b/pyload/plugins/hoster/FreeWayMe.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Hoster import Hoster
+
+
+class FreeWayMe(Hoster):
+ __name__ = "FreeWayMe"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'https://(?:www\.)?free-way.me/.*'
+
+ __description__ = """FreeWayMe hoster plugin"""
+ __author_name__ = "Nicolas Giese"
+ __author_mail__ = "james@free-way.me"
+
+
+ def setup(self):
+ self.resumeDownload = False
+ self.chunkLimit = 1
+ self.multiDL = self.premium
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "FreeWayMe")
+ self.fail("No FreeWay account provided")
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ (user, data) = self.account.selectAccount()
+
+ self.download(
+ "https://www.free-way.me/load.php",
+ get={"multiget": 7, "url": pyfile.url, "user": user, "pw": self.account.getpw(user), "json": ""},
+ disposition=True)
diff --git a/pyload/plugins/hoster/FreevideoCz.py b/pyload/plugins/hoster/FreevideoCz.py
new file mode 100644
index 000000000..dc7dd04bd
--- /dev/null
+++ b/pyload/plugins/hoster/FreevideoCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class FreevideoCz(DeadHoster):
+ __name__ = "FreevideoCz"
+ __type__ = "hoster"
+ __version__ = "0.3"
+
+ __pattern__ = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+'
+
+ __description__ = """Freevideo.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(FreevideoCz) \ No newline at end of file
diff --git a/pyload/plugins/hoster/FshareVn.py b/pyload/plugins/hoster/FshareVn.py
new file mode 100644
index 000000000..5109d239d
--- /dev/null
+++ b/pyload/plugins/hoster/FshareVn.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import strptime, mktime, gmtime
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ for url in urls:
+ html = getURL('http://www.fshare.vn/check_link.php', post={
+ "action": "check_link",
+ "arrlinks": url
+ }, decode=True)
+
+ file_info = parseFileInfo(FshareVn, url, html)
+
+ yield file_info
+
+
+def doubleDecode(m):
+ return m.group(1).decode('raw_unicode_escape')
+
+
+class FshareVn(SimpleHoster):
+ __name__ = "FshareVn"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?fshare.vn/file/.*'
+
+ __description__ = """FshareVn hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[0-9,.]+)\s*(?P<U>[kKMG])i?B<\\/p>'
+ OFFLINE_PATTERN = r'<div class=\\"f_left file_w\\"|<\\/p>\\t\\t\\t\\t\\r\\n\\t\\t<p><\\/p>\\t\\t\\r\\n\\t\\t<p>0 KB<\\/p>'
+
+ FILE_NAME_REPLACEMENTS = [("(.*)", doubleDecode)]
+
+ LINK_PATTERN = r'action="(http://download.*?)[#"]'
+ WAIT_PATTERN = ur'Lượt tải xuống kế tiếp là:\s*(.*?)\s*<'
+
+
+ def process(self, pyfile):
+ self.html = self.load('http://www.fshare.vn/check_link.php', post={
+ "action": "check_link",
+ "arrlinks": pyfile.url
+ }, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ self.checkDownloadedFile()
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ self.checkErrors()
+
+ action, inputs = self.parseHtmlForm('frm_download')
+ self.url = self.pyfile.url + action
+
+ if not inputs:
+ self.parseError('FORM')
+ elif 'link_file_pwd_dl' in inputs:
+ for password in self.getPassword().splitlines():
+ self.logInfo('Password protected link, trying "%s"' % password)
+ inputs['link_file_pwd_dl'] = password
+ self.html = self.load(self.url, post=inputs, decode=True)
+ if not 'name="link_file_pwd_dl"' in self.html:
+ break
+ else:
+ self.fail("No or incorrect password")
+ else:
+ self.html = self.load(self.url, post=inputs, decode=True)
+
+ self.checkErrors()
+
+ m = re.search(r'var count = (\d+)', self.html)
+ self.setWait(int(m.group(1)) if m else 30)
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('FREE DL URL')
+ self.url = m.group(1)
+ self.logDebug("FREE DL URL: %s" % self.url)
+
+ self.wait()
+ self.download(self.url)
+
+ def handlePremium(self):
+ self.download(self.pyfile.url)
+
+ def checkErrors(self):
+ if '/error.php?' in self.req.lastEffectiveURL or u"Liên kết bạn chọn khÃŽng tồn" in self.html:
+ self.offline()
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logInfo("Wait until %s ICT" % m.group(1))
+ wait_until = mktime(strptime(m.group(1), "%d/%m/%Y %H:%M"))
+ self.wait(wait_until - mktime(gmtime()) - 7 * 60 * 60, True)
+ self.retry()
+ elif '<ul class="message-error">' in self.html:
+ self.logError("Unknown error occured or wait time not parsed")
+ self.retry(30, 2 * 60, "Unknown error")
+
+ def checkDownloadedFile(self):
+ # check download
+ check = self.checkDownload({
+ "not_found": "<head><title>404 Not Found</title></head>"
+ })
+
+ if check == "not_found":
+ self.fail("File not m on server")
diff --git a/pyload/plugins/hoster/Ftp.py b/pyload/plugins/hoster/Ftp.py
new file mode 100644
index 000000000..641d93276
--- /dev/null
+++ b/pyload/plugins/hoster/Ftp.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from urllib import quote, unquote
+from urlparse import urlparse
+
+from pyload.plugins.Hoster import Hoster
+
+
+class Ftp(Hoster):
+ __name__ = "Ftp"
+ __type__ = "hoster"
+ __version__ = "0.42"
+
+ __pattern__ = r'(ftps?|sftp)://(.*?:.*?@)?.*?/.*' #: ftp://user:password@ftp.server.org/path/to/file
+
+ __description__ = """Download from ftp directory"""
+ __author_name__ = ("jeix", "mkaay", "zoidberg")
+ __author_mail__ = ("jeix@hasnomail.com", "mkaay@mkaay.de", "zoidberg@mujmail.cz")
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ parsed_url = urlparse(pyfile.url)
+ netloc = parsed_url.netloc
+
+ pyfile.name = parsed_url.path.rpartition('/')[2]
+ try:
+ pyfile.name = unquote(str(pyfile.name)).decode('utf8')
+ except:
+ pass
+
+ if not "@" in netloc:
+ servers = [x['login'] for x in self.account.getAllAccounts()] if self.account else []
+
+ if netloc in servers:
+ self.logDebug("Logging on to %s" % netloc)
+ self.req.addAuth(self.account.accounts[netloc]['password'])
+ else:
+ for pwd in pyfile.package().password.splitlines():
+ if ":" in pwd:
+ self.req.addAuth(pwd.strip())
+ break
+
+ self.req.http.c.setopt(pycurl.NOBODY, 1)
+
+ try:
+ response = self.load(pyfile.url)
+ except pycurl.error, e:
+ self.fail("Error %d: %s" % e.args)
+
+ self.req.http.c.setopt(pycurl.NOBODY, 0)
+ self.logDebug(self.req.http.header)
+
+ m = re.search(r"Content-Length:\s*(\d+)", response)
+ if m:
+ pyfile.size = int(m.group(1))
+ self.download(pyfile.url)
+ else:
+ #Naive ftp directory listing
+ if re.search(r'^25\d.*?"', self.req.http.header, re.M):
+ pyfile.url = pyfile.url.rstrip('/')
+ pkgname = "/".join(pyfile.package().name, urlparse(pyfile.url).path.rpartition('/')[2])
+ pyfile.url += '/'
+ self.req.http.c.setopt(48, 1) # CURLOPT_DIRLISTONLY
+ response = self.load(pyfile.url, decode=False)
+ links = [pyfile.url + quote(x) for x in response.splitlines()]
+ self.logDebug("LINKS", links)
+ self.core.api.addPackage(pkgname, links)
+ else:
+ self.fail("Unexpected server response")
diff --git a/pyload/plugins/hoster/GamefrontCom.py b/pyload/plugins/hoster/GamefrontCom.py
new file mode 100644
index 000000000..5d88fc0db
--- /dev/null
+++ b/pyload/plugins/hoster/GamefrontCom.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class GamefrontCom(Hoster):
+ __name__ = "GamefrontCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?gamefront.com/files/[A-Za-z0-9]+'
+
+ __description__ = """Gamefront.com hoster plugin"""
+ __author_name__ = "fwannmacher"
+ __author_mail__ = "felipe@warhammerproject.com"
+
+ PATTERN_FILENAME = r'<title>(.*?) | Game Front'
+ PATTERN_FILESIZE = r'<dt>File Size:</dt>[\n\s]*<dd>(.*?)</dd>'
+ PATTERN_OFFLINE = r"This file doesn't exist, or has been removed."
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = -1
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.html = self.load(pyfile.url, decode=True)
+
+ if not self._checkOnline():
+ self.offline()
+
+ pyfile.name = self._getName()
+
+ link = self._getLink()
+
+ if not link.startswith('http://'):
+ link = "http://www.gamefront.com/" + link
+
+ self.download(link)
+
+ def _checkOnline(self):
+ if re.search(self.PATTERN_OFFLINE, self.html):
+ return False
+ else:
+ return True
+
+ def _getName(self):
+ name = re.search(self.PATTERN_FILENAME, self.html)
+ if name is None:
+ self.fail("%s: Plugin broken." % self.__name__)
+
+ return name.group(1)
+
+ def _getLink(self):
+ self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=[A-Za-z0-9]+)",
+ self.html).group(1))
+ return re.search("<a href=\"(http://media[0-9]+\.gamefront.com/.*)\">click here</a>", self.html2).group(1).replace("&amp;", "&")
+
+
+def getInfo(urls):
+ result = []
+
+ for url in urls:
+ html = getURL(url)
+
+ if re.search(GamefrontCom.PATTERN_OFFLINE, html):
+ result.append((url, 0, 1, url))
+ else:
+ name = re.search(GamefrontCom.PATTERN_FILENAME, html)
+ if name is None:
+ result.append((url, 0, 1, url))
+ else:
+ name = name.group(1)
+ size = re.search(GamefrontCom.PATTERN_FILESIZE, html)
+ size = parseFileSize(size.group(1))
+
+ result.append((name, size, 3, url))
+
+ yield result
diff --git a/pyload/plugins/hoster/GigapetaCom.py b/pyload/plugins/hoster/GigapetaCom.py
new file mode 100644
index 000000000..d09a1fb0c
--- /dev/null
+++ b/pyload/plugins/hoster/GigapetaCom.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+from random import randint
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class GigapetaCom(SimpleHoster):
+ __name__ = "GigapetaCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?gigapeta\.com/dl/\w+'
+
+ __description__ = """GigaPeta.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>'
+ FILE_SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>'
+ OFFLINE_PATTERN = r'<div id="page_error">'
+
+ SH_COOKIES = [(".gigapeta.com", "lang", "us")]
+
+
+ def handleFree(self):
+ captcha_key = str(randint(1, 100000000))
+ captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+
+ for _ in xrange(5):
+ self.checkErrors()
+
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(self.pyfile.url, post={
+ "captcha_key": captcha_key,
+ "captcha": captcha,
+ "download": "Download"})
+
+ m = re.search(r"Location\s*:\s*(.*)", self.req.http.header, re.I)
+ if m:
+ download_url = m.group(1)
+ break
+ elif "Entered figures don&#96;t coincide with the picture" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.fail("No valid captcha code entered")
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
+
+ def checkErrors(self):
+ if "All threads for IP" in self.html:
+ self.logDebug("Your IP is already downloading a file - wait and retry")
+ self.wait(5 * 60, True)
+ self.retry()
+
+
+getInfo = create_getInfo(GigapetaCom)
diff --git a/pyload/plugins/hoster/GooIm.py b/pyload/plugins/hoster/GooIm.py
new file mode 100644
index 000000000..13598a8b6
--- /dev/null
+++ b/pyload/plugins/hoster/GooIm.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class GooIm(SimpleHoster):
+ __name__ = "GooIm"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?goo\.im/.+'
+
+ __description__ = """Goo.im hoster plugin"""
+ __author_name__ = "zapp-brannigan"
+ __author_mail__ = "fuerst.reinje@web.de"
+
+ FILE_NAME_PATTERN = r'You will be redirected to .*(?P<N>[^/ ]+) in'
+ OFFLINE_PATTERN = r'The file you requested was not found'
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+
+ def handleFree(self):
+ url = self.pyfile.url
+ self.html = self.load(url, cookies=True)
+ self.wait(10)
+ self.download(url, cookies=True)
+
+
+getInfo = create_getInfo(GooIm)
diff --git a/pyload/plugins/hoster/HellshareCz.py b/pyload/plugins/hoster/HellshareCz.py
new file mode 100644
index 000000000..5f3236876
--- /dev/null
+++ b/pyload/plugins/hoster/HellshareCz.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class HellshareCz(SimpleHoster):
+ __name__ = "HellshareCz"
+ __type__ = "hoster"
+ __version__ = "0.82"
+
+ __pattern__ = r'(http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+).*'
+
+ __description__ = """Hellshare.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>'
+ FILE_SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[0-9.]*)&nbsp;(?P<U>[kKMG])i?B</strong>'
+ OFFLINE_PATTERN = r'<h1>File not found.</h1>'
+ SHOW_WINDOW_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True if self.account else False
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if not self.account:
+ self.fail("User not logged in")
+ pyfile.url = re.match(self.__pattern__, pyfile.url).group(1)
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+ if not self.checkTrafficLeft():
+ self.fail("Not enough traffic left for user %s." % self.user)
+
+ m = re.search(self.SHOW_WINDOW_PATTERN, self.html)
+ if m is None:
+ self.parseError('SHOW WINDOW')
+ self.url = "http://www.hellshare.com" + m.group(1)
+ self.logDebug("DOWNLOAD URL: " + self.url)
+
+ self.download(self.url)
+
+
+getInfo = create_getInfo(HellshareCz)
diff --git a/pyload/plugins/hoster/HellspyCz.py b/pyload/plugins/hoster/HellspyCz.py
new file mode 100644
index 000000000..68b61caf0
--- /dev/null
+++ b/pyload/plugins/hoster/HellspyCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class HellspyCz(DeadHoster):
+ __name__ = "HellspyCz"
+ __type__ = "hoster"
+ __version__ = "0.28"
+
+ __pattern__ = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj.pl)(/\S+/\d+)/?.*'
+
+ __description__ = """HellSpy.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(HellspyCz)
diff --git a/pyload/plugins/hoster/HotfileCom.py b/pyload/plugins/hoster/HotfileCom.py
new file mode 100644
index 000000000..1dd8b4f4e
--- /dev/null
+++ b/pyload/plugins/hoster/HotfileCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class HotfileCom(DeadHoster):
+ __name__ = "HotfileCom"
+ __type__ = "hoster"
+ __version__ = "0.37"
+
+ __pattern__ = r'https?://(www.)?hotfile\.com/dl/\d+/[0-9a-zA-Z]+/'
+
+ __description__ = """Hotfile.com hoster plugin"""
+ __author_name__ = ("sitacuisses", "spoob", "mkaay", "JoKoT3")
+ __author_mail__ = ("sitacuisses@yhoo.de", "spoob@pyload.org", "mkaay@mkaay.de", "jokot3@gmail.com")
+
+
+getInfo = create_getInfo(HotfileCom)
diff --git a/pyload/plugins/hoster/HugefilesNet.py b/pyload/plugins/hoster/HugefilesNet.py
new file mode 100644
index 000000000..8a960c7fa
--- /dev/null
+++ b/pyload/plugins/hoster/HugefilesNet.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://hugefiles.net/prthf9ya4w6s
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class HugefilesNet(XFileSharingPro):
+ __name__ = "HugefilesNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?hugefiles\.net/\w{12}'
+
+ __description__ = """Hugefiles.net hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ HOSTER_NAME = "hugefiles.net"
+
+ FILE_SIZE_PATTERN = r'File Size:</span>\s*<span[^>]*>(?P<S>[^<]+)</span></div>'
+
+
+getInfo = create_getInfo(HugefilesNet)
diff --git a/pyload/plugins/hoster/HundredEightyUploadCom.py b/pyload/plugins/hoster/HundredEightyUploadCom.py
new file mode 100644
index 000000000..29e152c1d
--- /dev/null
+++ b/pyload/plugins/hoster/HundredEightyUploadCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://180upload.com/js9qdm6kjnrs
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class HundredEightyUploadCom(XFileSharingPro):
+ __name__ = "HundredEightyUploadCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?180upload\.com/(\w+).*'
+
+ __description__ = """180upload.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ HOSTER_NAME = "180upload.com"
+
+ FILE_NAME_PATTERN = r'Filename:</b></td><td nowrap>(?P<N>.+)</td></tr>-->'
+ FILE_SIZE_PATTERN = r'Size:</b></td><td>(?P<S>[\d.]+) (?P<U>[A-Z]+)\s*<small>'
+
+
+getInfo = create_getInfo(HundredEightyUploadCom)
diff --git a/pyload/plugins/hoster/IFileWs.py b/pyload/plugins/hoster/IFileWs.py
new file mode 100644
index 000000000..45039f8e0
--- /dev/null
+++ b/pyload/plugins/hoster/IFileWs.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class IFileWs(XFileSharingPro):
+ __name__ = "IFileWs"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?ifile\.ws/\w+(/.+)?'
+
+ __description__ = """Ifile.ws hoster plugin"""
+ __author_name__ = "z00nx"
+ __author_mail__ = "z00nx0@gmail.com"
+
+ HOSTER_NAME = "ifile.ws"
+
+ FILE_INFO_PATTERN = r'<h1\s+style="display:inline;">(?P<N>[^<]+)</h1>\s+\[(?P<S>[^]]+)\]'
+ OFFLINE_PATTERN = r'File Not Found|The file was removed by administrator'
+
+
+getInfo = create_getInfo(IFileWs)
diff --git a/pyload/plugins/hoster/IcyFilesCom.py b/pyload/plugins/hoster/IcyFilesCom.py
new file mode 100644
index 000000000..532cd094b
--- /dev/null
+++ b/pyload/plugins/hoster/IcyFilesCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class IcyFilesCom(DeadHoster):
+ __name__ = "IcyFilesCom"
+ __type__ = "hoster"
+ __version__ = "0.06"
+
+ __pattern__ = r'http://(?:www\.)?icyfiles\.com/(.*)'
+
+ __description__ = """IcyFiles.com hoster plugin"""
+ __author_name__ = "godofdream"
+ __author_mail__ = "soilfiction@gmail.com"
+
+
+getInfo = create_getInfo(IcyFilesCom)
diff --git a/pyload/plugins/hoster/IfileIt.py b/pyload/plugins/hoster/IfileIt.py
new file mode 100644
index 000000000..fc26e2f23
--- /dev/null
+++ b/pyload/plugins/hoster/IfileIt.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class IfileIt(SimpleHoster):
+ __name__ = "IfileIt"
+ __type__ = "hoster"
+ __version__ = "0.27"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """Ifile.it"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ LINK_PATTERN = r'</span> If it doesn\'t, <a target="_blank" href="([^"]+)">'
+ RECAPTCHA_KEY_PATTERN = r"var __recaptcha_public\s*=\s*'([^']+)';"
+ FILE_INFO_PATTERN = r'<span style="cursor: default;[^>]*>\s*(?P<N>.*?)\s*&nbsp;\s*<strong>\s*(?P<S>[0-9.]+)\s*(?P<U>[kKMG])i?B\s*</strong>\s*</span>'
+ OFFLINE_PATTERN = r'<span style="cursor: default;[^>]*>\s*&nbsp;\s*<strong>\s*</strong>\s*</span>'
+ TEMP_OFFLINE_PATTERN = r'<span class="msg_red">Downloading of this file is temporarily disabled</span>'
+
+
+ def handleFree(self):
+ ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
+ json_url = 'http://ifile.it/new_download-request.json'
+ post_data = {"ukey": ukey, "ab": "0"}
+
+ json_response = json_loads(self.load(json_url, post=post_data))
+ self.logDebug(json_response)
+ if json_response['status'] == 3:
+ self.offline()
+
+ if json_response['captcha']:
+ captcha_key = re.search(self.RECAPTCHA_KEY_PATTERN, self.html).group(1)
+ recaptcha = ReCaptcha(self)
+ post_data['ctype'] = "recaptcha"
+
+ for _ in xrange(5):
+ post_data['recaptcha_challenge'], post_data['recaptcha_response'] = recaptcha.challenge(captcha_key)
+ json_response = json_loads(self.load(json_url, post=post_data))
+ self.logDebug(json_response)
+
+ if json_response['retry']:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("Incorrect captcha")
+
+ if not "ticket_url" in json_response:
+ self.parseError("Download URL")
+
+ self.download(json_response['ticket_url'])
+
+
+getInfo = create_getInfo(IfileIt)
diff --git a/pyload/plugins/hoster/IfolderRu.py b/pyload/plugins/hoster/IfolderRu.py
new file mode 100644
index 000000000..4f84e6b32
--- /dev/null
+++ b/pyload/plugins/hoster/IfolderRu.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class IfolderRu(SimpleHoster):
+ __name__ = "IfolderRu"
+ __type__ = "hoster"
+ __version__ = "0.38"
+
+ __pattern__ = r'http://(?:www\.)?(?:ifolder\.ru|rusfolder\.(?:com|net|ru))/(?:files/)?(?P<ID>\d+).*'
+
+ __description__ = """Ifolder.ru hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_SIZE_REPLACEMENTS = [(u'Кб', 'KB'), (u'Мб', 'MB'), (u'Гб', 'GB')]
+ FILE_NAME_PATTERN = ur'(?:<div><span>)?НазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>'
+ FILE_SIZE_PATTERN = ur'(?:<div><span>)?РазЌер:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>'
+ OFFLINE_PATTERN = ur'<p>Ѐайл МПЌер <b>[^<]*</b> (Ме МайЎеМ|уЎалеМ) !!!</p>'
+
+ SESSION_ID_PATTERN = r'<a href=(http://ints.(?:rusfolder.com|ifolder.ru)/ints/sponsor/\?bi=\d*&session=([^&]+)&u=[^>]+)>'
+ INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag.value = "([^"]+)";\}'
+ HIDDEN_INPUT_PATTERN = r"var v = .*?name='([^']+)' value='1'"
+ LINK_PATTERN = r'<a id="download_file_href" href="([^"]+)"'
+ WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МеверМый кПЎ,<br>ввеЎОте еще раз</font><br>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True if self.account else False
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ file_id = re.match(self.__pattern__, pyfile.url).group('ID')
+ self.html = self.load("http://rusfolder.com/%s" % file_id, cookies=True, decode=True)
+ self.getFileInfo()
+
+ url = re.search(r"location\.href = '(http://ints\..*?=)'", self.html).group(1)
+ self.html = self.load(url, cookies=True, decode=True)
+
+ url, session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups()
+ self.html = self.load(url, cookies=True, decode=True)
+
+ url = "http://ints.rusfolder.com/ints/frame/?session=%s" % session_id
+ self.html = self.load(url, cookies=True)
+
+ self.wait(31, False)
+
+ captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id
+ for _ in xrange(5):
+ self.html = self.load(url, cookies=True)
+ action, inputs = self.parseHtmlForm('ID="Form1"')
+ inputs['ints_session'] = re.search(self.INTS_SESSION_PATTERN, self.html).group(1)
+ inputs[re.search(self.HIDDEN_INPUT_PATTERN, self.html).group(1)] = '1'
+ inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True)
+ inputs['action'] = '1'
+ self.logDebug(inputs)
+
+ self.html = self.load(url, decode=True, cookies=True, post=inputs)
+ if self.WRONG_CAPTCHA_PATTERN in self.html:
+ self.invalidCaptcha()
+ else:
+ break
+ else:
+ self.fail("Invalid captcha")
+
+ download_url = re.search(self.LINK_PATTERN, self.html).group(1)
+ self.correctCaptcha()
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
+
+
+getInfo = create_getInfo(IfolderRu)
diff --git a/pyload/plugins/hoster/JumbofilesCom.py b/pyload/plugins/hoster/JumbofilesCom.py
new file mode 100644
index 000000000..d3ee9ee9b
--- /dev/null
+++ b/pyload/plugins/hoster/JumbofilesCom.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class JumbofilesCom(SimpleHoster):
+ __name__ = "JumbofilesCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?jumbofiles.com/(\w{12}).*'
+
+ __description__ = """JumboFiles.com hoster plugin"""
+ __author_name__ = "godofdream"
+ __author_mail__ = "soilfiction@gmail.com"
+
+ FILE_INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.]+)\s*(?P<U>[KMG][bB])\)</small></TD></TR>'
+ OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA'
+ LINK_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def handleFree(self):
+ ukey = re.match(self.__pattern__, self.pyfile.url).group(1)
+ post_data = {"id": ukey, "op": "download3", "rand": ""}
+ html = self.load(self.pyfile.url, post=post_data, decode=True)
+ url = re.search(self.LINK_PATTERN, html).group(1)
+ self.logDebug("Download " + url)
+ self.download(url)
+
+
+getInfo = create_getInfo(JumbofilesCom)
diff --git a/pyload/plugins/hoster/Keep2shareCC.py b/pyload/plugins/hoster/Keep2shareCC.py
new file mode 100644
index 000000000..088a1b012
--- /dev/null
+++ b/pyload/plugins/hoster/Keep2shareCC.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://k2s.cc/file/55fb73e1c00c5/random.bin
+
+import re
+
+from urlparse import urlparse, urljoin
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class Keep2shareCC(SimpleHoster):
+ __name__ = "Keep2shareCC"
+ __type__ = "hoster"
+ __version__ = "0.10"
+
+ __pattern__ = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)'
+
+ __description__ = """Keep2share.cc hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_NAME_PATTERN = r'File: <span>(?P<N>.+)</span>'
+ FILE_SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>'
+ OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404'
+
+ LINK_PATTERN = r'To download this file with slow speed, use <a href="([^"]+)">this link</a>'
+ WAIT_PATTERN = r'Please wait ([\d:]+) to download this file'
+ ALREADY_DOWNLOADING_PATTERN = r'Free account does not allow to download more than one file at the same time'
+
+ RECAPTCHA_KEY = "6LcYcN0SAAAAABtMlxKj7X0hRxOY8_2U86kI1vbb"
+
+
+ def handleFree(self):
+ self.sanitize_url()
+ self.html = self.load(self.pyfile.url)
+
+ self.fid = re.search(r'<input type="hidden" name="slow_id" value="([^"]+)">', self.html).group(1)
+ self.html = self.load(self.pyfile.url, post={'yt0': '', 'slow_id': self.fid})
+
+ m = re.search(r"function download\(\){.*window\.location\.href = '([^']+)';", self.html, re.DOTALL)
+ if m: # Direct mode
+ self.startDownload(m.group(1))
+ else:
+ self.handleCaptcha()
+
+ self.wait(30)
+
+ self.html = self.load(self.pyfile.url, post={'uniqueId': self.fid, 'free': 1})
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.logDebug('Hoster told us to wait for %s' % m.group(1))
+ # string to time convert courtesy of https://stackoverflow.com/questions/10663720
+ ftr = [3600, 60, 1]
+ wait_time = sum([a * b for a, b in zip(ftr, map(int, m.group(1).split(':')))])
+ self.wait(wait_time, reconnect=True)
+ self.retry()
+
+ m = re.search(self.ALREADY_DOWNLOADING_PATTERN, self.html)
+ if m:
+ # if someone is already downloading on our line, wait 30min and retry
+ self.logDebug('Already downloading, waiting for 30 minutes')
+ self.wait(30 * 60, reconnect=True)
+ self.retry()
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("Unable to detect direct link")
+ self.startDownload(m.group(1))
+
+ def handleCaptcha(self):
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ post_data = {'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response,
+ 'CaptchaForm%5Bcode%5D': '',
+ 'free': 1,
+ 'freeDownloadRequest': 1,
+ 'uniqueId': self.fid,
+ 'yt0': ''}
+
+ self.html = self.load(self.pyfile.url, post=post_data)
+
+ if 'recaptcha' not in self.html:
+ self.correctCaptcha()
+ break
+ else:
+ self.logInfo('Wrong captcha')
+ self.invalidCaptcha()
+ else:
+ self.fail("All captcha attempts failed")
+
+ def startDownload(self, url):
+ d = urljoin(self.base_url, url)
+ self.logDebug('Direct Link: ' + d)
+ self.download(d, disposition=True)
+
+ def sanitize_url(self):
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header:
+ self.pyfile.url = header['location']
+ p = urlparse(self.pyfile.url)
+ self.base_url = "%s://%s" % (p.scheme, p.hostname)
+
+
+getInfo = create_getInfo(Keep2shareCC)
diff --git a/pyload/plugins/hoster/LemUploadsCom.py b/pyload/plugins/hoster/LemUploadsCom.py
new file mode 100644
index 000000000..8556e3c9c
--- /dev/null
+++ b/pyload/plugins/hoster/LemUploadsCom.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://lemuploads.com/uwol0aly9dld
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class LemUploadsCom(XFileSharingPro):
+ __name__ = "LemUploadsCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?lemuploads.com/\w{12}'
+
+ __description__ = """LemUploads.com hoster plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ HOSTER_NAME = "lemuploads.com"
+
+ OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
+ FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h2>(?P<N>[^<]+)</h2>'
+
+
+getInfo = create_getInfo(LemUploadsCom)
diff --git a/pyload/plugins/hoster/LetitbitNet.py b/pyload/plugins/hoster/LetitbitNet.py
new file mode 100644
index 000000000..a28f06571
--- /dev/null
+++ b/pyload/plugins/hoster/LetitbitNet.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+#
+# API Documentation:
+# http://api.letitbit.net/reg/static/api.pdf
+#
+# Test links:
+# http://letitbit.net/download/07874.0b5709a7d3beee2408bb1f2eefce/random.bin.html
+
+import re
+
+from urllib import urlencode, urlopen
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+def api_download_info(url):
+ json_data = ["yw7XQy2v9", ["download/info", {"link": url}]]
+ post_data = urlencode({'r': json_dumps(json_data)})
+ api_rep = urlopen("http://api.letitbit.net/json", data=post_data).read()
+ return json_loads(api_rep)
+
+
+def getInfo(urls):
+ for url in urls:
+ api_rep = api_download_info(url)
+ if api_rep['status'] == 'OK':
+ info = api_rep['data'][0]
+ yield (info['name'], info['size'], 2, url)
+ else:
+ yield (url, 0, 1, url)
+
+
+class LetitbitNet(SimpleHoster):
+ __name__ = "LetitbitNet"
+ __type__ = "hoster"
+ __version__ = "0.24"
+
+ __pattern__ = r'http://(?:www\.)?(letitbit|shareflare).net/download/.*'
+
+ __description__ = """Letitbit.net hoster plugin"""
+ __author_name__ = ("zoidberg", "z00nx")
+ __author_mail__ = ("zoidberg@mujmail.cz", "z00nx0@gmail.com")
+
+ FILE_URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")]
+
+ HOSTER_NAME = "letitbit.net"
+
+ SECONDS_PATTERN = r'seconds\s*=\s*(\d+);'
+ CAPTCHA_CONTROL_FIELD = r"recaptcha_control_field\s=\s'(?P<value>[^']+)'"
+ RECAPTCHA_KEY = "6Lc9zdMSAAAAAF-7s2wuQ-036pLRbM0p8dDaQdAM"
+
+
+ def setup(self):
+ self.resumeDownload = True
+ #TODO confirm that resume works
+
+ def getFileInfo(self):
+ api_rep = api_download_info(self.pyfile.url)
+ if api_rep['status'] == 'OK':
+ self.api_data = api_rep['data'][0]
+ self.pyfile.name = self.api_data['name']
+ self.pyfile.size = self.api_data['size']
+ else:
+ self.offline()
+
+ def handleFree(self):
+ action, inputs = self.parseHtmlForm('id="ifree_form"')
+ if not action:
+ self.parseError("page 1 / ifree_form")
+
+ domain = "http://www." + self.HOSTER_NAME
+ self.pyfile.size = float(inputs['sssize'])
+ self.logDebug(action, inputs)
+ inputs['desc'] = ""
+
+ self.html = self.load(domain + action, post=inputs, cookies=True)
+
+ # action, inputs = self.parseHtmlForm('id="d3_form"')
+ # if not action:
+ # self.parseError("page 2 / d3_form")
+ # self.logDebug(action, inputs)
+ #
+ # self.html = self.load(action, post = inputs, cookies = True)
+ #
+ # try:
+ # ajax_check_url, captcha_url = re.search(self.CHECK_URL_PATTERN, self.html).groups()
+ # m = re.search(self.SECONDS_PATTERN, self.html)
+ # seconds = int(m.group(1)) if m else 60
+ # self.wait(seconds+1)
+ # except Exception, e:
+ # self.logError(e)
+ # self.parseError("page 3 / js")
+
+ m = re.search(self.SECONDS_PATTERN, self.html)
+ seconds = int(m.group(1)) if m else 60
+ self.logDebug("Seconds found", seconds)
+ m = re.search(self.CAPTCHA_CONTROL_FIELD, self.html)
+ recaptcha_control_field = m.group(1)
+ self.logDebug("ReCaptcha control field found", recaptcha_control_field)
+ self.wait(seconds + 1)
+
+ response = self.load("%s/ajax/download3.php" % domain, post=" ", cookies=True)
+ if response != '1':
+ self.parseError('Unknown response - ajax_check_url')
+ self.logDebug(response)
+
+ recaptcha = ReCaptcha(self)
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ post_data = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": response,
+ "recaptcha_control_field": recaptcha_control_field}
+ self.logDebug("Post data to send", post_data)
+ response = self.load('%s/ajax/check_recaptcha.php' % domain, post=post_data, cookies=True)
+ self.logDebug(response)
+ if not response:
+ self.invalidCaptcha()
+ if response == "error_free_download_blocked":
+ self.logWarning("Daily limit reached")
+ self.wait(secondsToMidnight(gmt=2), True)
+ if response == "error_wrong_captcha":
+ self.logError("Wrong Captcha")
+ self.invalidCaptcha()
+ self.retry()
+ elif response.startswith('['):
+ urls = json_loads(response)
+ elif response.startswith('http://'):
+ urls = [response]
+ else:
+ self.parseError("Unknown response - captcha check")
+
+ self.correctCaptcha()
+
+ for download_url in urls:
+ try:
+ self.logDebug("Download URL", download_url)
+ self.download(download_url)
+ break
+ except Exception, e:
+ self.logError(e)
+ else:
+ self.fail("Download did not finish correctly")
+
+ def handlePremium(self):
+ api_key = self.user
+ premium_key = self.account.getAccountData(self.user)['password']
+
+ json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": self.pyfile.url}]]
+ api_rep = self.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)})
+ self.logDebug('API Data: ' + api_rep)
+ api_rep = json_loads(api_rep)
+
+ if api_rep['status'] == 'FAIL':
+ self.fail(api_rep['data'])
+
+ direct_link = api_rep['data'][0][0]
+ self.logDebug('Direct Link: ' + direct_link)
+
+ self.download(direct_link, disposition=True)
diff --git a/pyload/plugins/hoster/LinksnappyCom.py b/pyload/plugins/hoster/LinksnappyCom.py
new file mode 100644
index 000000000..e7cc61391
--- /dev/null
+++ b/pyload/plugins/hoster/LinksnappyCom.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urlparse import urlsplit
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.Hoster import Hoster
+
+
+class LinksnappyCom(Hoster):
+ __name__ = "LinksnappyCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?linksnappy\.com'
+
+ __description__ = """Linksnappy.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ SINGLE_CHUNK_HOSTERS = ('easybytez.com')
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Linksnappy.com")
+ self.fail("No Linksnappy.com account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ host = self._get_host(pyfile.url)
+ json_params = json_dumps({'link': pyfile.url,
+ 'type': host,
+ 'username': self.user,
+ 'password': self.account.getAccountData(self.user)['password']})
+ r = self.load('http://gen.linksnappy.com/genAPI.php',
+ post={'genLinks': json_params})
+ self.logDebug("JSON data: " + r)
+
+ j = json_loads(r)['links'][0]
+
+ if j['error']:
+ self.logError('Error converting the link: %s' % j['error'])
+ self.fail('Error converting the link')
+
+ pyfile.name = j['filename']
+ new_url = j['generated']
+
+ if host in self.SINGLE_CHUNK_HOSTERS:
+ self.chunkLimit = 1
+ else:
+ self.setup()
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"html302": "<title>302 Found</title>"})
+ if check == "html302":
+ self.retry(wait_time=5, reason="Linksnappy returns only HTML data.")
+
+ @staticmethod
+ def _get_host(url):
+ host = urlsplit(url).netloc
+ return re.search(r'[\w-]+\.\w+$', host).group(0)
diff --git a/pyload/plugins/hoster/LoadTo.py b/pyload/plugins/hoster/LoadTo.py
new file mode 100644
index 000000000..bd931f91e
--- /dev/null
+++ b/pyload/plugins/hoster/LoadTo.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www.load.to/JWydcofUY6/random.bin
+# http://www.load.to/oeSmrfkXE/random100.bin
+
+import re
+
+from pyload.plugins.internal.CaptchaService import SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class LoadTo(SimpleHoster):
+ __name__ = "LoadTo"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?load\.to/\w+'
+
+ __description__ = """ Load.to hoster plugin """
+ __author_name__ = ("halfman", "stickell")
+ __author_mail__ = ("Pulpan3@gmail.com", "l.stickell@yahoo.it")
+
+ FILE_NAME_PATTERN = r'<h1>(?P<N>.+)</h1>'
+ FILE_SIZE_PATTERN = r'Size: (?P<S>[\d.]+) (?P<U>\w+)'
+ OFFLINE_PATTERN = r'>Can\'t find file'
+
+ LINK_PATTERN = r'<form method="post" action="(.+?)"'
+ WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"'
+ SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'
+
+ FILE_URL_REPLACEMENTS = [(r'(\w)$', r'\1/')]
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+
+ def handleFree(self):
+ # Search for Download URL
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("Unable to detect download URL")
+
+ download_url = m.group(1)
+
+ # Set Timer - may be obsolete
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ self.wait(m.group(1))
+
+ # Load.to is using solvemedia captchas since ~july 2014:
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m is None:
+ self.download(download_url)
+ else:
+ captcha_key = m.group(1)
+ solvemedia = SolveMedia(self)
+ captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
+ self.download(download_url, post={"adcopy_challenge": captcha_challenge, "adcopy_response": captcha_response})
+ check = self.checkDownload({"404": re.compile("\A<h1>404 Not Found</h1>")})
+ if check == "404":
+ self.logWarning("The captcha you entered was incorrect. Please try again.")
+ self.invalidCaptcha()
+ self.retry()
+
+
+getInfo = create_getInfo(LoadTo)
diff --git a/pyload/plugins/hoster/LomafileCom.py b/pyload/plugins/hoster/LomafileCom.py
new file mode 100644
index 000000000..942afa1f4
--- /dev/null
+++ b/pyload/plugins/hoster/LomafileCom.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class LomafileCom(SimpleHoster):
+ __name__ = "LomafileCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'https?://lomafile\.com/.+/[\w\.]+'
+
+ __description__ = """ Lomafile.com hoster plugin """
+ __author_name__ = "nath_schwarz"
+ __author_mail__ = "nathan.notwhite@gmail.com"
+
+ FILE_NAME_PATTERN = r'Filename:[^>]*>(?P<N>[\w\.]+)'
+ FILE_SIZE_PATTERN = r'\((?P<S>\d+)\s(?P<U>\w+)\)'
+ OFFLINE_PATTERN = r'Software error'
+
+
+ def handleFree(self):
+ for _ in range(3):
+ captcha_id = re.search(r'src="http://lomafile\.com/captchas/(?P<id>\w+)\.jpg"', self.html)
+ if not captcha_id:
+ self.parseError("Unable to parse captcha id.")
+ else:
+ captcha_id = captcha_id.group("id")
+
+ form_id = re.search(r'name="id" value="(?P<id>\w+)"', self.html)
+ if not form_id:
+ self.parseError("Unable to parse form id")
+ else:
+ form_id = form_id.group("id")
+
+ captcha = self.decryptCaptcha("http://lomafile.com/captchas/" + captcha_id + ".jpg")
+
+ self.wait(60)
+
+ self.html = self.load(self.pyfile.url, post={
+ "op": "download2",
+ "id": form_id,
+ "rand": captcha_id,
+ "code": captcha,
+ "down_direct": "1"})
+
+ download_url = re.search(r'http://[\d\.]+:\d+/d/\w+/[\w\.]+', self.html)
+ if download_url is None:
+ self.invalidCaptcha()
+ self.logDebug("Invalid captcha.")
+ else:
+ download_url = download_url.group(0)
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
+ else:
+ self.fail("Invalid captcha-code entered.")
+
+
+getInfo = create_getInfo(LomafileCom)
diff --git a/pyload/plugins/hoster/LuckyShareNet.py b/pyload/plugins/hoster/LuckyShareNet.py
new file mode 100644
index 000000000..5cb15d49e
--- /dev/null
+++ b/pyload/plugins/hoster/LuckyShareNet.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from bottle import json_loads
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class LuckyShareNet(SimpleHoster):
+ __name__ = "LuckyShareNet"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?luckyshare.net/(?P<ID>\d{10,})'
+
+ __description__ = """LuckyShare.net hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ FILE_INFO_PATTERN = r"<h1 class='file_name'>(?P<N>\S+)</h1>\s*<span class='file_size'>Filesize: (?P<S>[\d.]+)(?P<U>\w+)</span>"
+ OFFLINE_PATTERN = r'There is no such file available'
+ RECAPTCHA_KEY = "6LdivsgSAAAAANWh-d7rPE1mus4yVWuSQIJKIYNw"
+
+
+ def parseJson(self, rep):
+ if 'AJAX Error' in rep:
+ html = self.load(self.pyfile.url, decode=True)
+ m = re.search(r"waitingtime = (\d+);", html)
+ if m:
+ waittime = int(m.group(1))
+ self.logDebug('You have to wait %d seconds between free downloads' % waittime)
+ self.retry(wait_time=waittime)
+ else:
+ self.parseError('Unable to detect wait time between free downloads')
+ elif 'Hash expired' in rep:
+ self.retry(reason="Hash expired")
+ return json_loads(rep)
+
+ # TODO: There should be a filesize limit for free downloads
+ # TODO: Some files could not be downloaded in free mode
+ def handleFree(self):
+ file_id = re.match(self.__pattern__, self.pyfile.url).group('ID')
+ self.logDebug('File ID: ' + file_id)
+ rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + file_id, decode=True)
+ self.logDebug('JSON: ' + rep)
+ json = self.parseJson(rep)
+
+ self.wait(int(json['time']))
+
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ rep = self.load(r"http://luckyshare.net/download/verify/challenge/%s/response/%s/hash/%s" %
+ (challenge, response, json['hash']), decode=True)
+ self.logDebug('JSON: ' + rep)
+ if 'link' in rep:
+ json.update(self.parseJson(rep))
+ self.correctCaptcha()
+ break
+ elif 'Verification failed' in rep:
+ self.logInfo('Wrong captcha')
+ self.invalidCaptcha()
+ else:
+ self.parseError('Unable to get downlaod link')
+
+ if not json['link']:
+ self.fail("No Download url retrieved/all captcha attempts failed")
+
+ self.logDebug('Direct URL: ' + json['link'])
+ self.download(json['link'])
+
+
+getInfo = create_getInfo(LuckyShareNet)
diff --git a/pyload/plugins/hoster/MediafireCom.py b/pyload/plugins/hoster/MediafireCom.py
new file mode 100644
index 000000000..bbf9f06b6
--- /dev/null
+++ b/pyload/plugins/hoster/MediafireCom.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.CaptchaService import SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+from pyload.network.RequestFactory import getURL
+
+
+def replace_eval(js_expr):
+ return js_expr.replace(r'eval("', '').replace(r"\'", r"'").replace(r'\"', r'"')
+
+
+def checkHTMLHeader(url):
+ try:
+ for _ in xrange(3):
+ header = getURL(url, just_header=True)
+ for line in header.splitlines():
+ line = line.lower()
+ if 'location' in line:
+ url = line.split(':', 1)[1].strip()
+ if 'error.php?errno=320' in url:
+ return url, 1
+ if not url.startswith('http://'):
+ url = 'http://www.mediafire.com' + url
+ break
+ elif 'content-disposition' in line:
+ return url, 2
+ else:
+ break
+ except:
+ return url, 3
+
+ return url, 0
+
+
+def getInfo(urls):
+ for url in urls:
+ location, status = checkHTMLHeader(url)
+ if status:
+ file_info = (url, 0, status, url)
+ else:
+ file_info = parseFileInfo(MediafireCom, url, getURL(url, decode=True))
+ yield file_info
+
+
+class MediafireCom(SimpleHoster):
+ __name__ = "MediafireCom"
+ __type__ = "hoster"
+ __version__ = "0.79"
+
+ __pattern__ = r'http://(?:www\.)?mediafire\.com/(file/|(view/?|download.php)?\?)(\w{11}|\w{15})($|/)'
+
+ __description__ = """Mediafire.com hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ LINK_PATTERN = r'<div class="download_link"[^>]*(?:z-index:(?P<zindex>\d+))?[^>]*>\s*<a href="(?P<href>http://[^"]+)"'
+ JS_KEY_PATTERN = r"DoShow\('mfpromo1'\);[^{]*{((\w+)='';.*?)eval\(\2\);"
+ JS_ZMODULO_PATTERN = r"\('z-index'\)\) \% (\d+)\)\);"
+ SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.noscript\?k=([^"]+)'
+ PAGE1_ACTION_PATTERN = r'<link rel="canonical" href="([^"]+)"/>'
+ PASSWORD_PATTERN = r'<form name="form_password"'
+
+ FILE_NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>[^"]+)"/>'
+ FILE_INFO_PATTERN = r"oFileSharePopup\.ald\('(?P<ID>[^']*)','(?P<N>[^']*)','(?P<S>[^']*)','','(?P<sha256>[^']*)'\)"
+ OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>'
+
+
+ def setup(self):
+ self.multiDL = False
+
+ def process(self, pyfile):
+ pyfile.url = re.sub(r'/view/?\?', '/?', pyfile.url)
+
+ self.url, result = checkHTMLHeader(pyfile.url)
+ self.logDebug('Location (%d): %s' % (result, self.url))
+
+ if result == 0:
+ self.html = self.load(self.url, decode=True)
+ self.checkCaptcha()
+ self.multiDL = True
+ self.check_data = self.getFileInfo()
+
+ if self.account:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ elif result == 1:
+ self.offline()
+ else:
+ self.multiDL = True
+ self.download(self.url, disposition=True)
+
+ def handleFree(self):
+ passwords = self.getPassword().splitlines()
+ while self.PASSWORD_PATTERN in self.html:
+ if len(passwords):
+ password = passwords.pop(0)
+ self.logInfo("Password protected link, trying " + password)
+ self.html = self.load(self.url, post={"downloadp": password})
+ else:
+ self.fail("No or incorrect password")
+
+ m = re.search(r'kNO = r"(http://.*?)";', self.html)
+ if m is None:
+ self.parseError("Download URL")
+ download_url = m.group(1)
+ self.logDebug("DOWNLOAD LINK:", download_url)
+
+ self.download(download_url)
+
+ def checkCaptcha(self):
+ for _ in xrange(5):
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ solvemedia = SolveMedia(self)
+ captcha_challenge, captcha_response = solvemedia.challenge(captcha_key)
+ self.html = self.load(self.url, post={"adcopy_challenge": captcha_challenge,
+ "adcopy_response": captcha_response}, decode=True)
+ else:
+ break
+ else:
+ self.fail("No valid recaptcha solution received")
diff --git a/pyload/plugins/hoster/MegaDebridEu.py b/pyload/plugins/hoster/MegaDebridEu.py
new file mode 100644
index 000000000..ed3747aed
--- /dev/null
+++ b/pyload/plugins/hoster/MegaDebridEu.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote_plus
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+
+
+class MegaDebridEu(Hoster):
+ __name__ = "MegaDebridEu"
+ __type__ = "hoster"
+ __version__ = "0.4"
+
+ __pattern__ = r'^https?://(?:w{3}\d+\.mega-debrid.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[^/]+/.+$'
+
+ __description__ = """mega-debrid.eu hoster plugin"""
+ __author_name__ = "D.Ducatel"
+ __author_mail__ = "dducatel@je-geek.fr"
+
+ API_URL = "https://www.mega-debrid.eu/api.php"
+
+
+ def getFilename(self, url):
+ try:
+ return unquote_plus(url.rsplit("/", 1)[1])
+ except IndexError:
+ return ""
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.exitOnFail(_("Please enter your %s account or deactivate this plugin") % "Mega-debrid.eu")
+ else:
+ if not self.connectToApi():
+ self.exitOnFail(_("Unable to connect to %s") % "Mega-debrid.eu")
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+ new_url = self.debridLink(pyfile.url)
+ self.logDebug("New URL: " + new_url)
+
+ filename = self.getFilename(new_url)
+ if filename != "":
+ pyfile.name = filename
+ self.download(new_url, disposition=True)
+
+ def connectToApi(self):
+ """
+ Connexion to the mega-debrid API
+ Return True if succeed
+ """
+ user, data = self.account.selectAccount()
+ jsonResponse = self.load(self.API_URL,
+ get={'action': 'connectUser', 'login': user, 'password': data['password']})
+ response = json_loads(jsonResponse)
+
+ if response['response_code'] == "ok":
+ self.token = response['token']
+ return True
+ else:
+ return False
+
+ def debridLink(self, linkToDebrid):
+ """
+ Debrid a link
+ Return The debrided link if succeed or original link if fail
+ """
+ jsonResponse = self.load(self.API_URL, get={'action': 'getLink', 'token': self.token},
+ post={"link": linkToDebrid})
+ response = json_loads(jsonResponse)
+
+ if response['response_code'] == "ok":
+ debridedLink = response['debridLink'][1:-1]
+ return debridedLink
+ else:
+ self.exitOnFail("Unable to debrid %s" % linkToDebrid)
+
+ def exitOnFail(self, msg):
+ """
+ exit the plugin on fail case
+ And display the reason of this failure
+ """
+ if self.getConfig("unloadFailing"):
+ self.logError(msg)
+ self.resetAccount()
+ else:
+ self.fail(msg)
diff --git a/pyload/plugins/hoster/MegaFilesSe.py b/pyload/plugins/hoster/MegaFilesSe.py
new file mode 100644
index 000000000..48306cd7f
--- /dev/null
+++ b/pyload/plugins/hoster/MegaFilesSe.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class MegaFilesSe(XFileSharingPro):
+ __name__ = "MegaFilesSe"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?megafiles\.se/\w{12}'
+
+ __description__ = """MegaFiles.se hoster plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ HOSTER_NAME = "megafiles.se"
+
+ OFFLINE_PATTERN = r'<b><font[^>]*>File Not Found</font></b><br><br>'
+ FILE_NAME_PATTERN = r'<div[^>]+>\s*<b>(?P<N>[^<]+)</b>\s*</div>'
+
+
+getInfo = create_getInfo(MegaFilesSe)
diff --git a/pyload/plugins/hoster/MegaNz.py b/pyload/plugins/hoster/MegaNz.py
new file mode 100644
index 000000000..2e70b5dc6
--- /dev/null
+++ b/pyload/plugins/hoster/MegaNz.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+
+from Crypto.Cipher import AES
+from Crypto.Util import Counter
+from array import array
+from base64 import standard_b64decode
+from os import remove
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.Hoster import Hoster
+
+
+class MegaNz(Hoster):
+ __name__ = "MegaNz"
+ __type__ = "hoster"
+ __version__ = "0.14"
+
+ __pattern__ = r'https?://([a-z0-9]+\.)?mega\.co\.nz/#!([a-zA-Z0-9!_\-]+)'
+
+ __description__ = """Mega.co.nz hoster plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "ranan@pyload.org"
+
+ API_URL = "https://g.api.mega.co.nz/cs?id=%d"
+ FILE_SUFFIX = ".crypted"
+
+
+ def b64_decode(self, data):
+ data = data.replace("-", "+").replace("_", "/")
+ return standard_b64decode(data + '=' * (-len(data) % 4))
+
+ def getCipherKey(self, key):
+ """ Construct the cipher key from the given data """
+ a = array("I", key)
+ key_array = array("I", [a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7]])
+ return key_array
+
+ def callApi(self, **kwargs):
+ """ Dispatch a call to the api, see https://mega.co.nz/#developers """
+ # generate a session id, no idea where to obtain elsewhere
+ uid = random.randint(10 << 9, 10 ** 10)
+
+ resp = self.load(self.API_URL % uid, post=json_dumps([kwargs]))
+ self.logDebug("Api Response: " + resp)
+ return json_loads(resp)
+
+ def decryptAttr(self, data, key):
+
+ cbc = AES.new(self.getCipherKey(key), AES.MODE_CBC, "\0" * 16)
+ attr = cbc.decrypt(self.b64_decode(data))
+ self.logDebug("Decrypted Attr: " + attr)
+ if not attr.startswith("MEGA"):
+ self.fail(_("Decryption failed"))
+
+ # Data is padded, 0-bytes must be stripped
+ return json_loads(attr.replace("MEGA", "").rstrip("\0").strip())
+
+ def decryptFile(self, key):
+ """ Decrypts the file at lastDownload` """
+
+ # upper 64 bit of counter start
+ n = key[16:24]
+
+ # convert counter to long and shift bytes
+ ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64)
+ cipher = AES.new(self.getCipherKey(key), AES.MODE_CTR, counter=ctr)
+
+ self.pyfile.setStatus("decrypting")
+
+ file_crypted = self.lastDownload
+ file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0]
+ f = open(file_crypted, "rb")
+ df = open(file_decrypted, "wb")
+
+ # TODO: calculate CBC-MAC for checksum
+
+ size = 2 ** 15 # buffer size, 32k
+ while True:
+ buf = f.read(size)
+ if not buf:
+ break
+
+ df.write(cipher.decrypt(buf))
+
+ f.close()
+ df.close()
+ remove(file_crypted)
+
+ self.lastDownload = file_decrypted
+
+ def process(self, pyfile):
+
+ key = None
+
+ # match is guaranteed because plugin was chosen to handle url
+ node = re.match(self.__pattern__, pyfile.url).group(2)
+ if "!" in node:
+ node, key = node.split("!")
+
+ self.logDebug("File id: %s | Key: %s" % (node, key))
+
+ if not key:
+ self.fail(_("No file key provided in the URL"))
+
+ # g is for requesting a download url
+ # this is similar to the calls in the mega js app, documentation is very bad
+ dl = self.callApi(a="g", g=1, p=node, ssl=1)[0]
+
+ if "e" in dl:
+ e = dl['e']
+ # ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later
+ if e == -18:
+ self.retry()
+ else:
+ self.fail(_("Error code:") + e)
+
+ # TODO: map other error codes, e.g
+ # EACCESS (-11): Access violation (e.g., trying to write to a read-only share)
+
+ key = self.b64_decode(key)
+ attr = self.decryptAttr(dl['at'], key)
+
+ pyfile.name = attr['n'] + self.FILE_SUFFIX
+
+ self.download(dl['g'])
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = attr['n']
diff --git a/pyload/plugins/hoster/MegacrypterCom.py b/pyload/plugins/hoster/MegacrypterCom.py
new file mode 100644
index 000000000..cdc019fdf
--- /dev/null
+++ b/pyload/plugins/hoster/MegacrypterCom.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads, json_dumps
+from pyload.plugins.hoster.MegaNz import MegaNz
+
+
+class MegacrypterCom(MegaNz):
+ __name__ = "MegacrypterCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'(https?://[a-z0-9]{0,10}\.?megacrypter\.com/[a-zA-Z0-9!_\-]+)'
+
+ __description__ = """Megacrypter.com decrypter plugin"""
+ __author_name__ = "GonzaloSR"
+ __author_mail__ = "gonzalo@gonzalosr.com"
+
+ API_URL = "http://megacrypter.com/api"
+ FILE_SUFFIX = ".crypted"
+
+
+ def callApi(self, **kwargs):
+ """ Dispatch a call to the api, see megacrypter.com/api_doc """
+ self.logDebug("JSON request: " + json_dumps(kwargs))
+ resp = self.load(self.API_URL, post=json_dumps(kwargs))
+ self.logDebug("API Response: " + resp)
+ return json_loads(resp)
+
+ def process(self, pyfile):
+ # match is guaranteed because plugin was chosen to handle url
+ node = re.match(self.__pattern__, pyfile.url).group(1)
+
+ # get Mega.co.nz link info
+ info = self.callApi(link=node, m="info")
+
+ # get crypted file URL
+ dl = self.callApi(link=node, m="dl")
+
+ # TODO: map error codes, implement password protection
+ # if info['pass'] is True:
+ # crypted_file_key, md5_file_key = info['key'].split("#")
+
+ key = self.b64_decode(info['key'])
+
+ pyfile.name = info['name'] + self.FILE_SUFFIX
+
+ self.download(dl['url'])
+ self.decryptFile(key)
+
+ # Everything is finished and final name can be set
+ pyfile.name = info['name']
diff --git a/pyload/plugins/hoster/MegareleaseOrg.py b/pyload/plugins/hoster/MegareleaseOrg.py
new file mode 100644
index 000000000..6a689b6dd
--- /dev/null
+++ b/pyload/plugins/hoster/MegareleaseOrg.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class MegareleaseOrg(XFileSharingPro):
+ __name__ = "MegareleaseOrg"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?megarelease.org/\w{12}'
+
+ __description__ = """Megarelease.org hoster plugin"""
+ __author_name__ = ("derek3x", "stickell")
+ __author_mail__ = ("derek3x@vmail.me", "l.stickell@yahoo.it")
+
+ HOSTER_NAME = "megarelease.org"
+
+ FILE_INFO_PATTERN = r'<font color="red">%s/(?P<N>.+)</font> \((?P<S>[^)]+)\)</font>' % __pattern__
+
+
+getInfo = create_getInfo(MegareleaseOrg)
diff --git a/pyload/plugins/hoster/MegasharesCom.py b/pyload/plugins/hoster/MegasharesCom.py
new file mode 100644
index 000000000..36e13a531
--- /dev/null
+++ b/pyload/plugins/hoster/MegasharesCom.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class MegasharesCom(SimpleHoster):
+ __name__ = "MegasharesCom"
+ __type__ = "hoster"
+ __version__ = "0.24"
+
+ __pattern__ = r'http://(?:www\.)?megashares.com/.*'
+
+ __description__ = """Megashares.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>[^"]+)">'
+ FILE_SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[0-9.]+) (?P<U>[kKMG])i?B<br />'
+ OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted)'
+
+ LINK_PATTERN = r'<div id="show_download_button_%d"[^>]*>\s*<a href="([^"]+)">'
+ PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <[^>]*>(\w+).*\s*You have\s*<[^>]*>\s*([0-9.]+) ([kKMG]i?B)'
+ PASSPORT_RENEW_PATTERN = r'Your download passport will renew in\s*<strong>(\d+)</strong>:<strong>(\d+)</strong>:<strong>(\d+)</strong>'
+ REACTIVATE_NUM_PATTERN = r'<input[^>]*id="random_num" value="(\d+)" />'
+ REACTIVATE_PASSPORT_PATTERN = r'<input[^>]*id="passport_num" value="(\w+)" />'
+ REQUEST_URI_PATTERN = r'var request_uri = "([^"]+)";'
+ NO_SLOTS_PATTERN = r'<dd class="red">All download slots for this link are currently filled'
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.multiDL = self.premium
+
+ def handlePremium(self):
+ self.handleDownload(True)
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ if self.NO_SLOTS_PATTERN in self.html:
+ self.retry(wait_time=5 * 60)
+
+ self.getFileInfo()
+ # if self.pyfile.size > 576716800:
+ # self.fail("This file is too large for free download")
+
+ # Reactivate passport if needed
+ m = re.search(self.REACTIVATE_PASSPORT_PATTERN, self.html)
+ if m:
+ passport_num = m.group(1)
+ request_uri = re.search(self.REQUEST_URI_PATTERN, self.html).group(1)
+
+ for _ in xrange(5):
+ random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1)
+
+ verifyinput = self.decryptCaptcha(
+ "http://d01.megashares.com/index.php?secgfx=gfx&random_num=%s" % random_num)
+ self.logInfo("Reactivating passport %s: %s %s" % (passport_num, random_num, verifyinput))
+
+ url = ("http://d01.megashares.com%s&rs=check_passport_renewal" % request_uri +
+ "&rsargs[]=%s&rsargs[]=%s&rsargs[]=%s" % (verifyinput, random_num, passport_num) +
+ "&rsargs[]=replace_sec_pprenewal&rsrnd=%s" % str(int(time() * 1000)))
+ self.logDebug(url)
+ response = self.load(url)
+
+ if 'Thank you for reactivating your passport.' in response:
+ self.correctCaptcha()
+ self.retry()
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail("Failed to reactivate passport")
+
+ # Check traffic left on passport
+ m = re.search(self.PASSPORT_LEFT_PATTERN, self.html)
+ if m is None:
+ self.fail('Passport not found')
+ self.logInfo("Download passport: %s" % m.group(1))
+ data_left = float(m.group(2)) * 1024 ** {'KB': 1, 'MB': 2, 'GB': 3}[m.group(3)]
+ self.logInfo("Data left: %s %s (%d MB needed)" % (m.group(2), m.group(3), self.pyfile.size / 1048576))
+
+ if not data_left:
+ m = re.search(self.PASSPORT_RENEW_PATTERN, self.html)
+ renew = m.group(1) + m.group(2) + m.group(3) * 60 * 60 if m else 10 * 60
+ self.retry(max_tries=15, wait_time=renew, reason="Unable to get passport")
+
+ self.handleDownload(False)
+
+ def handleDownload(self, premium=False):
+ # Find download link;
+ m = re.search(self.LINK_PATTERN % (1 if premium else 2), self.html)
+ msg = '%s download URL' % ('Premium' if premium else 'Free')
+ if m is None:
+ self.parseError(msg)
+
+ download_url = m.group(1)
+ self.logDebug("%s: %s" % (msg, download_url))
+ self.download(download_url)
+
+
+getInfo = create_getInfo(MegasharesCom)
diff --git a/pyload/plugins/hoster/MovReelCom.py b/pyload/plugins/hoster/MovReelCom.py
new file mode 100644
index 000000000..3f97d3fca
--- /dev/null
+++ b/pyload/plugins/hoster/MovReelCom.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class MovReelCom(XFileSharingPro):
+ __name__ = "MovReelCom"
+ __type__ = "hoster"
+ __version__ = "1.20"
+
+ __pattern__ = r'http://(?:www\.)?movreel.com/.*'
+
+ __description__ = """MovReel.com hoster plugin"""
+ __author_name__ = "JorisV83"
+ __author_mail__ = "jorisv83-pyload@yahoo.com"
+
+ HOSTER_NAME = "movreel.com"
+
+ FILE_INFO_PATTERN = r'<h3>(?P<N>.+?) <small><sup>(?P<S>[\d.]+) (?P<U>..)</sup> </small></h3>'
+ OFFLINE_PATTERN = r'<b>File Not Found</b><br><br>'
+ LINK_PATTERN = r'<a href="(http://[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/.*)">Download Link</a>'
+
+
+getInfo = create_getInfo(MovReelCom)
diff --git a/pyload/plugins/hoster/MultishareCz.py b/pyload/plugins/hoster/MultishareCz.py
new file mode 100644
index 000000000..819478659
--- /dev/null
+++ b/pyload/plugins/hoster/MultishareCz.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class MultishareCz(SimpleHoster):
+ __name__ = "MultishareCz"
+ __type__ = "hoster"
+ __version__ = "0.34"
+
+ __pattern__ = r'http://(?:www\.)?multishare.cz/stahnout/(?P<ID>\d+).*'
+
+ __description__ = """MultiShare.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = ur'(?:<li>Název|Soubor): <strong>(?P<N>[^<]+)</strong><(?:/li><li|br)>Velikost: <strong>(?P<S>[^<]+)</strong>'
+ OFFLINE_PATTERN = ur'<h1>Stáhnout soubor</h1><p><strong>PoşadovanÜ soubor neexistuje.</strong></p>'
+ FILE_SIZE_REPLACEMENTS = [('&nbsp;', '')]
+
+
+ def process(self, pyfile):
+ msurl = re.match(self.__pattern__, pyfile.url)
+ if msurl:
+ self.fileID = msurl.group('ID')
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+ else:
+ self.handleOverriden()
+
+ def handleFree(self):
+ self.download("http://www.multishare.cz/html/download_free.php?ID=%s" % self.fileID)
+
+ def handlePremium(self):
+ if not self.checkCredit():
+ self.logWarning("Not enough credit left to download file")
+ self.resetAccount()
+
+ self.download("http://www.multishare.cz/html/download_premium.php?ID=%s" % self.fileID)
+
+ def handleOverriden(self):
+ if not self.premium:
+ self.fail("Only premium users can download from other hosters")
+
+ self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": self.pyfile.url}, decode=True)
+ self.getFileInfo()
+
+ if not self.checkCredit():
+ self.fail("Not enough credit left to download file")
+
+ url = "http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random() * 10000 * random())
+ params = {"u_ID": self.acc_info['u_ID'], "u_hash": self.acc_info['u_hash'], "link": self.pyfile.url}
+ self.logDebug(url, params)
+ self.download(url, get=params)
+
+ def checkCredit(self):
+ self.acc_info = self.account.getAccountInfo(self.user, True)
+ self.logInfo("User %s has %i MB left" % (self.user, self.acc_info['trafficleft'] / 1024))
+
+ return self.pyfile.size / 1024 <= self.acc_info['trafficleft']
+
+
+getInfo = create_getInfo(MultishareCz)
diff --git a/pyload/plugins/hoster/MyfastfileCom.py b/pyload/plugins/hoster/MyfastfileCom.py
new file mode 100644
index 000000000..604e2ab06
--- /dev/null
+++ b/pyload/plugins/hoster/MyfastfileCom.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import json_loads
+
+
+class MyfastfileCom(Hoster):
+ __name__ = "MyfastfileCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/'
+
+ __description__ = """Myfastfile.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Myfastfile.com")
+ self.fail("No Myfastfile.com account provided")
+ else:
+ self.logDebug("Original URL: %s" % pyfile.url)
+ page = self.req.load('http://myfastfile.com/api.php',
+ get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'],
+ 'link': pyfile.url})
+ self.logDebug("JSON data: " + page)
+ page = json_loads(page)
+ if page['status'] != 'ok':
+ self.fail('Unable to unrestrict link')
+ new_url = page['link']
+
+ if new_url != pyfile.url:
+ self.logDebug("Unrestricted URL: " + new_url)
+
+ self.download(new_url, disposition=True)
diff --git a/pyload/plugins/hoster/MyvideoDe.py b/pyload/plugins/hoster/MyvideoDe.py
new file mode 100644
index 000000000..06cfb9c63
--- /dev/null
+++ b/pyload/plugins/hoster/MyvideoDe.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class MyvideoDe(Hoster):
+ __name__ = "MyvideoDe"
+ __type__ = "hoster"
+ __version__ = "0.9"
+
+ __pattern__ = r'http://(?:www\.)?myvideo.de/watch/'
+
+ __description__ = """Myvideo.de hoster plugin"""
+ __author_name__ = "spoob"
+ __author_mail__ = "spoob@pyload.org"
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.download_html()
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ self.html = self.load(self.pyfile.url)
+
+ def get_file_url(self):
+ videoId = re.search(r"addVariable\('_videoid','(.*)'\);p.addParam\('quality'", self.html).group(1)
+ videoServer = re.search("rel='image_src' href='(.*)thumbs/.*' />", self.html).group(1)
+ file_url = videoServer + videoId + ".flv"
+ return file_url
+
+ def get_file_name(self):
+ file_name_pattern = r"<h1 class='globalHd'>(.*)</h1>"
+ return html_unescape(re.search(file_name_pattern, self.html).group(1).replace("/", "") + '.flv')
+
+ def file_exists(self):
+ self.download_html()
+ self.load(str(self.pyfile.url), cookies=False, just_header=True)
+ if self.req.lastEffectiveURL == "http://www.myvideo.de/":
+ return False
+ return True
diff --git a/pyload/plugins/hoster/NarodRu.py b/pyload/plugins/hoster/NarodRu.py
new file mode 100644
index 000000000..22c0ba908
--- /dev/null
+++ b/pyload/plugins/hoster/NarodRu.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import random
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class NarodRu(SimpleHoster):
+ __name__ = "NarodRu"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/[0-9]+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+'
+
+ __description__ = """Narod.ru hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>'
+ FILE_SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>'
+ OFFLINE_PATTERN = r'<title>404</title>|Ѐайл уЎалеМ с сервОса|ЗакПМчОлся срПк храМеМОя файла\.'
+
+ FILE_SIZE_REPLACEMENTS = [(u'КБ', 'KB'), (u'МБ', 'MB'), (u'ГБ', 'GB')]
+ FILE_URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"),
+ (r"/start/[0-9]+\.\w+-narod\.yandex\.ru/([0-9]{6,15})/\w+/(\w+)", r"/disk/\1/\2")]
+
+ CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>'
+ LINK_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">'
+
+
+ def handleFree(self):
+ for _ in xrange(5):
+ self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random() * 777))
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.parseError('Captcha')
+ post_data = {"action": "sendcapcha"}
+ captcha_url, post_data['key'] = m.groups()
+ post_data['rep'] = self.decryptCaptcha(captcha_url)
+
+ self.html = self.load(self.pyfile.url, post=post_data, decode=True)
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ url = 'http://narod.ru' + m.group(1)
+ self.correctCaptcha()
+ break
+ elif u'<b class="error-msg"><strong>ОшОблОсь?</strong>' in self.html:
+ self.invalidCaptcha()
+ else:
+ self.parseError('Download link')
+ else:
+ self.fail("No valid captcha code entered")
+
+ self.logDebug('Download link: ' + url)
+ self.download(url)
+
+
+getInfo = create_getInfo(NarodRu)
diff --git a/pyload/plugins/hoster/NetloadIn.py b/pyload/plugins/hoster/NetloadIn.py
new file mode 100644
index 000000000..949b1aa92
--- /dev/null
+++ b/pyload/plugins/hoster/NetloadIn.py
@@ -0,0 +1,258 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import sleep, time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+
+
+def getInfo(urls):
+ ## returns list of tupels (name, size (in bytes), status (see FileDatabase), url)
+
+ apiurl = "http://api.netload.in/info.php?auth=Zf9SnQh9WiReEsb18akjvQGqT0I830e8&bz=1&md5=1&file_id="
+ id_regex = re.compile(NetloadIn.__pattern__)
+ urls_per_query = 80
+
+ for chunk in chunks(urls, urls_per_query):
+ ids = ""
+ for url in chunk:
+ match = id_regex.search(url)
+ if match:
+ ids = ids + match.group(1) + ";"
+
+ api = getURL(apiurl + ids, decode=True)
+
+ if api is None or len(api) < 10:
+ print "Netload prefetch: failed "
+ return
+ if api.find("unknown_auth") >= 0:
+ print "Netload prefetch: Outdated auth code "
+ return
+
+ result = []
+
+ for i, r in enumerate(api.splitlines()):
+ try:
+ tmp = r.split(";")
+ try:
+ size = int(tmp[2])
+ except:
+ size = 0
+ result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i]))
+ except:
+ print "Netload prefetch: Error while processing response: "
+ print r
+
+ yield result
+
+
+class NetloadIn(Hoster):
+ __name__ = "NetloadIn"
+ __type__ = "hoster"
+ __version__ = "0.45"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?netload\.in/(?:datei(.*?)(?:\.htm|/)|index.php?id=10&file_id=)'
+
+ __description__ = """Netload.in hoster plugin"""
+ __author_name__ = ("spoob", "RaNaN", "Gregy")
+ __author_mail__ = ("spoob@pyload.org", "ranan@pyload.org", "gregy@gregy.cz")
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+
+ def process(self, pyfile):
+ self.url = pyfile.url
+ self.prepare()
+ pyfile.setStatus("downloading")
+ self.proceed(self.url)
+
+ def prepare(self):
+ self.download_api_data()
+
+ if self.api_data and self.api_data['filename']:
+ self.pyfile.name = self.api_data['filename']
+
+ if self.premium:
+ self.logDebug("Netload: Use Premium Account")
+ settings = self.load("http://www.netload.in/index.php?id=2&lang=en")
+ if '<option value="2" selected="selected">Direkter Download' in settings:
+ self.logDebug("Using direct download")
+ return True
+ else:
+ self.logDebug("Direct downloads not enabled. Parsing html for a download URL")
+
+ if self.download_html():
+ return True
+ else:
+ self.fail("Failed")
+ return False
+
+ def download_api_data(self, n=0):
+ url = self.url
+ id_regex = re.compile(self.__pattern__)
+ match = id_regex.search(url)
+
+ if match:
+ #normalize url
+ self.url = 'http://www.netload.in/datei%s.htm' % match.group(1)
+ self.logDebug("URL: %s" % self.url)
+ else:
+ self.api_data = False
+ return
+
+ apiurl = "http://api.netload.in/info.php"
+ src = self.load(apiurl, cookies=False,
+ get={"file_id": match.group(1), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1",
+ "md5": "1"}, decode=True).strip()
+ if not src and n <= 3:
+ sleep(0.2)
+ self.download_api_data(n + 1)
+ return
+
+ self.logDebug("Netload: APIDATA: " + src)
+ self.api_data = {}
+ if src and ";" in src and src not in ("unknown file_data", "unknown_server_data", "No input file specified."):
+ lines = src.split(";")
+ self.api_data['exists'] = True
+ self.api_data['fileid'] = lines[0]
+ self.api_data['filename'] = lines[1]
+ self.api_data['size'] = lines[2]
+ self.api_data['status'] = lines[3]
+ if self.api_data['status'] == "online":
+ self.api_data['checksum'] = lines[4].strip()
+ else:
+ self.api_data = False # check manually since api data is useless sometimes
+
+ if lines[0] == lines[1] and lines[2] == "0": # useless api data
+ self.api_data = False
+ else:
+ self.api_data = False
+
+ def final_wait(self, page):
+ wait_time = self.get_wait_time(page)
+ self.setWait(wait_time)
+ self.logDebug("Netload: final wait %d seconds" % wait_time)
+ self.wait()
+ self.url = self.get_file_url(page)
+
+ def download_html(self):
+ self.logDebug("Netload: Entering download_html")
+ page = self.load(self.url, decode=True)
+ t = time() + 30
+
+ if "/share/templates/download_hddcrash.tpl" in page:
+ self.logError("Netload HDD Crash")
+ self.fail(_("File temporarily not available"))
+
+ if not self.api_data:
+ self.logDebug("API Data may be useless, get details from html page")
+
+ if "* The file was deleted" in page:
+ self.offline()
+
+ name = re.search(r'class="dl_first_filename">([^<]+)', page, re.MULTILINE)
+ # the found filename is not truncated
+ if name:
+ name = name.group(1).strip()
+ if not name.endswith(".."):
+ self.pyfile.name = name
+
+ captchawaited = False
+ for i in xrange(10):
+
+ if not page:
+ page = self.load(self.url)
+ t = time() + 30
+
+ if "/share/templates/download_hddcrash.tpl" in page:
+ self.logError("Netload HDD Crash")
+ self.fail(_("File temporarily not available"))
+
+ self.logDebug("Netload: try number %d " % i)
+
+ if ">Your download is being prepared.<" in page:
+ self.logDebug("Netload: We will prepare your download")
+ self.final_wait(page)
+ return True
+ if ">An access request has been made from IP address <" in page:
+ wait = self.get_wait_time(page)
+ if not wait:
+ self.logDebug("Netload: Wait was 0 setting 30")
+ wait = 30 * 60
+ self.logInfo(_("Netload: waiting between downloads %d s." % wait))
+ self.wantReconnect = True
+ self.setWait(wait)
+ self.wait()
+
+ return self.download_html()
+
+ self.logDebug("Netload: Trying to find captcha")
+
+ try:
+ url_captcha_html = "http://netload.in/" + re.search('(index.php\?id=10&amp;.*&amp;captcha=1)',
+ page).group(1).replace("amp;", "")
+ except:
+ page = None
+ continue
+
+ try:
+ page = self.load(url_captcha_html, cookies=True)
+ captcha_url = "http://netload.in/" + re.search('(share/includes/captcha.php\?t=\d*)', page).group(1)
+ except:
+ self.logDebug("Netload: Could not find captcha, try again from beginning")
+ captchawaited = False
+ continue
+
+ file_id = re.search('<input name="file_id" type="hidden" value="(.*)" />', page).group(1)
+ if not captchawaited:
+ wait = self.get_wait_time(page)
+ if i == 0:
+ self.pyfile.waitUntil = time() # dont wait contrary to time on website
+ else:
+ self.pyfile.waitUntil = t
+ self.logInfo(_("Netload: waiting for captcha %d s.") % (self.pyfile.waitUntil - time()))
+ #self.setWait(wait)
+ self.wait()
+ captchawaited = True
+
+ captcha = self.decryptCaptcha(captcha_url)
+ page = self.load("http://netload.in/index.php?id=10", post={"file_id": file_id, "captcha_check": captcha},
+ cookies=True)
+
+ return False
+
+ def get_file_url(self, page):
+ try:
+ file_url_pattern = r"<a class=\"Orange_Link\" href=\"(http://.+)\".?>Or click here"
+ attempt = re.search(file_url_pattern, page)
+ if attempt is not None:
+ return attempt.group(1)
+ else:
+ self.logDebug("Netload: Backup try for final link")
+ file_url_pattern = r"<a href=\"(.+)\" class=\"Orange_Link\">Click here"
+ attempt = re.search(file_url_pattern, page)
+ return "http://netload.in/" + attempt.group(1)
+ except:
+ self.logDebug("Netload: Getting final link failed")
+ return None
+
+ def get_wait_time(self, page):
+ wait_seconds = int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100
+ return wait_seconds
+
+ def proceed(self, url):
+ self.logDebug("Netload: Downloading..")
+
+ self.download(url, disposition=True)
+
+ check = self.checkDownload({"empty": re.compile(r"^$"), "offline": re.compile("The file was deleted")})
+
+ if check == "empty":
+ self.logInfo(_("Downloaded File was empty"))
+ self.retry()
+ elif check == "offline":
+ self.offline()
diff --git a/pyload/plugins/hoster/NosuploadCom.py b/pyload/plugins/hoster/NosuploadCom.py
new file mode 100644
index 000000000..e4feabdd0
--- /dev/null
+++ b/pyload/plugins/hoster/NosuploadCom.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class NosuploadCom(XFileSharingPro):
+ __name__ = "NosuploadCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}'
+
+ __description__ = """Nosupload.com hoster plugin"""
+ __author_name__ = "igel"
+ __author_mail__ = "igelkun@myopera.com"
+
+ HOSTER_NAME = "nosupload.com"
+
+ FILE_SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[0-9\.]+) (?P<U>[kKMG]?B)</p>'
+ LINK_PATTERN = r'<a class="select" href="(http://.+?)">Download</a>'
+ WAIT_PATTERN = r'Please wait.*?>(\d+)</span>'
+
+
+ def getDownloadLink(self):
+ # stage1: press the "Free Download" button
+ data = self.getPostParameters()
+ self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
+
+ # stage2: wait some time and press the "Download File" button
+ data = self.getPostParameters()
+ wait_time = re.search(self.WAIT_PATTERN, self.html, re.MULTILINE | re.DOTALL).group(1)
+ self.logDebug("hoster told us to wait %s seconds" % wait_time)
+ self.wait(wait_time)
+ self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
+
+ # stage3: get the download link
+ return re.search(self.LINK_PATTERN, self.html, re.S).group(1)
+
+
+getInfo = create_getInfo(NosuploadCom)
diff --git a/pyload/plugins/hoster/NovafileCom.py b/pyload/plugins/hoster/NovafileCom.py
new file mode 100644
index 000000000..1346bbde9
--- /dev/null
+++ b/pyload/plugins/hoster/NovafileCom.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://novafile.com/vfun4z6o2cit
+# http://novafile.com/s6zrr5wemuz4
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class NovafileCom(XFileSharingPro):
+ __name__ = "NovafileCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?novafile\.com/\w{12}'
+
+ __description__ = """Novafile.com hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ HOSTER_NAME = "novafile.com"
+
+ FILE_SIZE_PATTERN = r'<div class="size">(?P<S>.+?)</div>'
+ ERROR_PATTERN = r'class="alert[^"]*alert-separate"[^>]*>\s*(?:<p>)?(.*?)\s*</'
+ LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>'
+ WAIT_PATTERN = r'<p>Please wait <span id="count"[^>]*>(\d+)</span> seconds</p>'
+
+
+ def setup(self):
+ self.multiDL = False
+
+
+getInfo = create_getInfo(NovafileCom)
diff --git a/pyload/plugins/hoster/NowDownloadEu.py b/pyload/plugins/hoster/NowDownloadEu.py
new file mode 100644
index 000000000..6e42a55bb
--- /dev/null
+++ b/pyload/plugins/hoster/NowDownloadEu.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+from pyload.utils import fixup
+
+
+class NowDownloadEu(SimpleHoster):
+ __name__ = "NowDownloadEu"
+ __type__ = "hoster"
+ __version__ = "0.05"
+
+ __pattern__ = r'http://(?:www\.)?nowdownload\.(ch|co|eu|sx)/(dl/|download\.php\?id=)(?P<ID>\w+)'
+
+ __description__ = """NowDownload.ch hoster plugin"""
+ __author_name__ = ("godofdream", "Walter Purcaro")
+ __author_mail__ = ("soilfiction@gmail.com", "vuolter@gmail.com")
+
+ FILE_INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[0-9,.]+) (?P<U>[kKMG])i?B </h4>'
+ OFFLINE_PATTERN = r'(This file does not exist!)'
+
+ TOKEN_PATTERN = r'"(/api/token\.php\?token=[a-z0-9]+)"'
+ CONTINUE_PATTERN = r'"(/dl2/[a-z0-9]+/[a-z0-9]+)"'
+ WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),'
+ LINK_PATTERN = r'"(http://f\d+\.nowdownload\.ch/dl/[a-z0-9]+/[a-z0-9]+/[^<>"]*?)"'
+
+ FILE_NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<[^>]*>', '')]
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+ self.chunkLimit = -1
+
+ def handleFree(self):
+ tokenlink = re.search(self.TOKEN_PATTERN, self.html)
+ continuelink = re.search(self.CONTINUE_PATTERN, self.html)
+ if tokenlink is None or continuelink is None:
+ self.fail('Plugin out of Date')
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait = int(m.group(1))
+ else:
+ wait = 60
+
+ baseurl = "http://www.nowdownload.ch"
+ self.html = self.load(baseurl + str(tokenlink.group(1)))
+ self.wait(wait)
+
+ self.html = self.load(baseurl + str(continuelink.group(1)))
+
+ url = re.search(self.LINK_PATTERN, self.html)
+ if url is None:
+ self.fail('Download Link not Found (Plugin out of Date?)')
+ self.logDebug('Download link: ' + str(url.group(1)))
+ self.download(str(url.group(1)))
+
+
+getInfo = create_getInfo(NowDownloadEu)
diff --git a/pyload/plugins/hoster/OboomCom.py b/pyload/plugins/hoster/OboomCom.py
new file mode 100644
index 000000000..8c0d9d760
--- /dev/null
+++ b/pyload/plugins/hoster/OboomCom.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# https://www.oboom.com/B7CYZIEB/10Mio.dat
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+
+
+class OboomCom(Hoster):
+ __name__ = "OboomCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>[A-Z0-9]{8})'
+
+ __description__ = """oboom.com hoster plugin"""
+ __author_name__ = "stanley"
+ __author_mail__ = "stanley.foerster@gmail.com"
+
+ RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX"
+
+
+ def loadUrl(self, url, get=None):
+ if get is None:
+ get = dict()
+ return json_loads(self.load(url, get, decode=True))
+
+ def getFileId(self, url):
+ self.fileId = re.match(OboomCom.__pattern__, url).group('ID')
+
+ def getSessionToken(self):
+ if self.premium:
+ accountInfo = self.account.getAccountInfo(self.user, True)
+ if "session" in accountInfo:
+ self.sessionToken = accountInfo['session']
+ else:
+ self.fail("Could not retrieve premium session")
+ else:
+ apiUrl = "https://www.oboom.com/1.0/guestsession"
+ result = self.loadUrl(apiUrl)
+ if result[0] == 200:
+ self.sessionToken = result[1]
+ else:
+ self.fail("Could not retrieve token for guest session. Error code %s" % result[0])
+
+ def solveCaptcha(self):
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge(self.RECAPTCHA_KEY)
+ apiUrl = "https://www.oboom.com/1.0/download/ticket"
+ params = {"recaptcha_challenge_field": challenge,
+ "recaptcha_response_field": response,
+ "download_id": self.fileId,
+ "token": self.sessionToken}
+ result = self.loadUrl(apiUrl, params)
+
+ if result[0] == 200:
+ self.downloadToken = result[1]
+ self.downloadAuth = result[2]
+ self.correctCaptcha()
+ self.setWait(30)
+ self.wait()
+ break
+ elif result[0] == 400:
+ if result[1] == "incorrect-captcha-sol":
+ self.invalidCaptcha()
+ elif result[1] == "captcha-timeout":
+ self.invalidCaptcha()
+ elif result[1] == "forbidden":
+ self.retry(5, 15 * 60, "Service unavailable")
+ elif result[0] == 403:
+ if result[1] == -1: # another download is running
+ self.setWait(15 * 60)
+ else:
+ self.setWait(result[1], reconnect=True)
+ self.wait()
+ self.retry(5)
+ else:
+ self.invalidCaptcha()
+ self.fail("Received invalid captcha 5 times")
+
+ def getFileInfo(self, token, fileId):
+ apiUrl = "https://api.oboom.com/1.0/info"
+ params = {"token": token, "items": fileId, "http_errors": 0}
+
+ result = self.loadUrl(apiUrl, params)
+ if result[0] == 200:
+ item = result[1][0]
+ if item['state'] == "online":
+ self.fileSize = item['size']
+ self.fileName = item['name']
+ else:
+ self.offline()
+ else:
+ self.fail("Could not retrieve file info. Error code %s: %s" % (result[0], result[1]))
+
+ def getDownloadTicket(self):
+ apiUrl = "https://api.oboom.com/1.0/dl"
+ params = {"item": self.fileId, "http_errors": 0}
+ if self.premium:
+ params['token'] = self.sessionToken
+ else:
+ params['token'] = self.downloadToken
+ params['auth'] = self.downloadAuth
+
+ result = self.loadUrl(apiUrl, params)
+ if result[0] == 200:
+ self.downloadDomain = result[1]
+ self.downloadTicket = result[2]
+ else:
+ self.fail("Could not retrieve download ticket. Error code %s" % result[0])
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.multiDL = self.premium
+
+ def process(self, pyfile):
+ self.pyfile.url.replace(".com/#id=", ".com/#")
+ self.pyfile.url.replace(".com/#/", ".com/#")
+ self.getFileId(self.pyfile.url)
+ self.getSessionToken()
+ self.getFileInfo(self.sessionToken, self.fileId)
+ self.pyfile.name = self.fileName
+ self.pyfile.size = self.fileSize
+ if not self.premium:
+ self.solveCaptcha()
+ self.getDownloadTicket()
+ self.download("https://%s/1.0/dlh" % self.downloadDomain, get={"ticket": self.downloadTicket, "http_errors": 0})
diff --git a/pyload/plugins/hoster/OneFichierCom.py b/pyload/plugins/hoster/OneFichierCom.py
new file mode 100644
index 000000000..8fdecb342
--- /dev/null
+++ b/pyload/plugins/hoster/OneFichierCom.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://5pnm24ltcw.1fichier.com/
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class OneFichierCom(SimpleHoster):
+ __name__ = "OneFichierCom"
+ __type__ = "hoster"
+ __version__ = "0.61"
+
+ __pattern__ = r'(http://(?P<id>\w+)\.(?P<host>(1fichier|d(es)?fichiers|pjointe)\.(com|fr|net|org)|(cjoint|mesfichiers|piecejointe|oi)\.(org|net)|tenvoi\.(com|org|net)|dl4free\.com|alterupload\.com|megadl.fr))/?'
+
+ __description__ = """1fichier.com hoster plugin"""
+ __author_name__ = ("fragonib", "the-razer", "zoidberg", "imclem", "stickell", "Elrick69")
+ __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "daniel_ AT gmx DOT net", "zoidberg@mujmail.cz",
+ "imclem on github", "l.stickell@yahoo.it", "elrick69[AT]rocketmail[DOT]com")
+
+ FILE_NAME_PATTERN = r'">Filename :</th>\s*<td>(?P<N>[^<]+)</td>'
+ FILE_SIZE_PATTERN = r'<th>Size :</th>\s*<td>(?P<S>[^<]+)</td>'
+ OFFLINE_PATTERN = r'The (requested)? file (could not be found|has been deleted)'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'http://\g<id>.\g<host>/en/')]
+
+ WAITING_PATTERN = r'Warning ! Without premium status, you must wait between each downloads'
+ NOT_PARALLEL = r'Warning ! Without premium status, you can download only one file at a time'
+ WAIT_TIME = 10 * 60 # Retry time between each free download
+ RETRY_TIME = 15 * 60 # Default retry time in seconds (if detected parallel download)
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ if self.WAITING_PATTERN in self.html:
+ self.logInfo('You have to wait been each free download! Retrying in %d seconds.' % self.WAIT_TIME)
+ self.waitAndRetry(self.WAIT_TIME)
+ else: # detect parallel download
+ m = re.search(self.NOT_PARALLEL, self.html)
+ if m:
+ self.waitAndRetry(self.RETRY_TIME)
+
+ url, inputs = self.parseHtmlForm('action="http://%s' % self.file_info['id'])
+ if not url:
+ self.parseError("Download link not found")
+
+ # Check for protection
+ if "pass" in inputs:
+ inputs['pass'] = self.getPassword()
+ inputs['submit'] = "Download"
+
+ self.download(url, post=inputs)
+
+ # Check download
+ self.checkDownloadedFile()
+
+ def handlePremium(self):
+ url, inputs = self.parseHtmlForm('action="http://%s' % self.file_info['id'])
+ if not url:
+ self.parseError("Download link not found")
+
+ # Check for protection
+ if "pass" in inputs:
+ inputs['pass'] = self.getPassword()
+ inputs['submit'] = "Download"
+
+ self.download(url, post=inputs)
+
+ # Check download
+ self.checkDownloadedFile()
+
+ def checkDownloadedFile(self):
+ check = self.checkDownload({"wait": self.WAITING_PATTERN})
+ if check == "wait":
+ self.waitAndRetry(int(self.lastcheck.group(1)) * 60)
+
+ def waitAndRetry(self, wait_time):
+ self.wait(wait_time, True)
+ self.retry()
+
+
+
+getInfo = create_getInfo(OneFichierCom)
diff --git a/pyload/plugins/hoster/OverLoadMe.py b/pyload/plugins/hoster/OverLoadMe.py
new file mode 100644
index 000000000..e27972fc2
--- /dev/null
+++ b/pyload/plugins/hoster/OverLoadMe.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class OverLoadMe(Hoster):
+ __name__ = "OverLoadMe"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://.*overload\.me.*'
+
+ __description__ = """Over-Load.me hoster plugin"""
+ __author_name__ = "marley"
+ __author_mail__ = "marley@over-load.me"
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if name.endswith("..."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+ def setup(self):
+ self.chunkLimit = 5
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Over-Load")
+ self.fail("No Over-Load account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ data = self.account.getAccountData(self.user)
+
+ page = self.load("https://api.over-load.me/getdownload.php",
+ get={"auth": data['password'], "link": pyfile.url})
+ data = json_loads(page)
+
+ self.logDebug("Returned Data: %s" % data)
+
+ if data['err'] == 1:
+ self.logWarning(data['msg'])
+ self.tempOffline()
+ else:
+ if pyfile.name is not None and pyfile.name.endswith('.tmp') and data['filename']:
+ pyfile.name = data['filename']
+ pyfile.size = parseFileSize(data['filesize'])
+ new_url = data['downloadlink']
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
+ # only use when name wasn't already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload(
+ {"error": "<title>An error occured while processing your request</title>"})
+
+ if check == "error":
+ # usual this download can safely be retried
+ self.retry(reason="An error occured while generating link.", wait_time=60)
diff --git a/pyload/plugins/hoster/PandaPlanet.py b/pyload/plugins/hoster/PandaPlanet.py
new file mode 100644
index 000000000..8b26202df
--- /dev/null
+++ b/pyload/plugins/hoster/PandaPlanet.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# test.bin - 214 B - http://pandapla.net/pew1cz3ot586
+# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://pandapla.net/tz0rgjfyyoh7
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class PandaPlanet(XFileSharingPro):
+ __name__ = "PandaPlanet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?pandapla\.net/\w{12}'
+
+ __description__ = """Pandapla.net hoster plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ HOSTER_NAME = "pandapla.net"
+
+ FILE_SIZE_PATTERN = r'File Size:</b>\s*</td>\s*<td[^>]*>(?P<S>[^<]+)</td>\s*</tr>'
+ FILE_NAME_PATTERN = r'File Name:</b>\s*</td>\s*<td[^>]*>(?P<N>[^<]+)</td>\s*</tr>'
+ LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<]+\/(?!video\.mp4)[^"\'<]+)' % HOSTER_NAME
+
+
+getInfo = create_getInfo(PandaPlanet)
diff --git a/pyload/plugins/hoster/PornhostCom.py b/pyload/plugins/hoster/PornhostCom.py
new file mode 100644
index 000000000..802557873
--- /dev/null
+++ b/pyload/plugins/hoster/PornhostCom.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class PornhostCom(Hoster):
+ __name__ = "PornhostCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?pornhost\.com/([0-9]+/[0-9]+\.html|[0-9]+)'
+
+ __description__ = """Pornhost.com hoster plugin"""
+ __author_name__ = "jeix"
+ __author_mail__ = "jeix@hasnomail.de"
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ # Old interface
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ url = re.search(r'download this file</label>.*?<a href="(.*?)"', self.html)
+ if url is None:
+ url = re.search(r'"(http://dl[0-9]+\.pornhost\.com/files/.*?/.*?/.*?/.*?/.*?/.*?\..*?)"', self.html)
+ if url is None:
+ url = re.search(r'width: 894px; height: 675px">.*?<img src="(.*?)"', self.html)
+ if url is None:
+ url = re.search(r'"http://file[0-9]+\.pornhost\.com/[0-9]+/.*?"',
+ self.html) # TODO: fix this one since it doesn't match
+
+ return url.group(1).strip()
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ name = re.search(r'<title>pornhost\.com - free file hosting with a twist - gallery(.*?)</title>', self.html)
+ if name is None:
+ name = re.search(r'id="url" value="http://www\.pornhost\.com/(.*?)/"', self.html)
+ if name is None:
+ name = re.search(r'<title>pornhost\.com - free file hosting with a twist -(.*?)</title>', self.html)
+ if name is None:
+ name = re.search(r'"http://file[0-9]+\.pornhost\.com/.*?/(.*?)"', self.html)
+
+ name = name.group(1).strip() + ".flv"
+
+ return name
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if (re.search(r'gallery not found', self.html) is not None or
+ re.search(r'You will be redirected to', self.html) is not None):
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/PornhubCom.py b/pyload/plugins/hoster/PornhubCom.py
new file mode 100644
index 000000000..5236fe09a
--- /dev/null
+++ b/pyload/plugins/hoster/PornhubCom.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class PornhubCom(Hoster):
+ __name__ = "PornhubCom"
+ __type__ = "hoster"
+ __version__ = "0.5"
+
+ __pattern__ = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=[\w\d]+'
+
+ __description__ = """Pornhub.com hoster plugin"""
+ __author_name__ = "jeix"
+ __author_mail__ = "jeix@hasnomail.de"
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ url = "http://www.pornhub.com//gateway.php"
+ video_id = self.pyfile.url.split('=')[-1]
+ # thanks to jD team for this one v
+ post_data = "\x00\x03\x00\x00\x00\x01\x00\x0c\x70\x6c\x61\x79\x65\x72\x43\x6f\x6e\x66\x69\x67\x00\x02\x2f\x31\x00\x00\x00\x44\x0a\x00\x00\x00\x03\x02\x00"
+ post_data += chr(len(video_id))
+ post_data += video_id
+ post_data += "\x02\x00\x02\x2d\x31\x02\x00\x20"
+ post_data += "add299463d4410c6d1b1c418868225f7"
+
+ content = self.req.load(url, post=str(post_data))
+
+ new_content = ""
+ for x in content:
+ if ord(x) < 32 or ord(x) > 176:
+ new_content += '#'
+ else:
+ new_content += x
+
+ content = new_content
+
+ return re.search(r'flv_url.*(http.*?)##post_roll', content).group(1)
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<title[^>]+>([^<]+) - ', self.html)
+ if m:
+ name = m.group(1)
+ else:
+ matches = re.findall('<h1>(.*?)</h1>', self.html)
+ if len(matches) > 1:
+ name = matches[1]
+ else:
+ name = matches[0]
+
+ return name + '.flv'
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r'This video is no longer in our database or is in conversion', self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/PotloadCom.py b/pyload/plugins/hoster/PotloadCom.py
new file mode 100644
index 000000000..6a97d0289
--- /dev/null
+++ b/pyload/plugins/hoster/PotloadCom.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class PotloadCom(XFileSharingPro):
+ __name__ = "PotloadCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?potload\.com/\w{12}'
+
+ __description__ = """Potload.com hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+ HOSTER_NAME = "potload.com"
+
+ FILE_INFO_PATTERN = r'<h[1-6]>(?P<N>.+) \((?P<S>\d+) (?P<U>\w+)\)</h'
+
+
+getInfo = create_getInfo(PotloadCom)
diff --git a/pyload/plugins/hoster/PremiumTo.py b/pyload/plugins/hoster/PremiumTo.py
new file mode 100644
index 000000000..33df2e7bc
--- /dev/null
+++ b/pyload/plugins/hoster/PremiumTo.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+from os import remove
+from os.path import exists
+from urllib import quote
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import fs_encode
+
+
+class PremiumTo(Hoster):
+ __name__ = "PremiumTo"
+ __type__ = "hoster"
+ __version__ = "0.09"
+
+ __pattern__ = r'https?://(?:www\.)?premium.to/.*'
+
+ __description__ = """Premium.to hoster plugin"""
+ __author_name__ = ("RaNaN", "zoidberg", "stickell")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+
+ def setup(self):
+ self.resumeDownload = True
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "premium.to")
+ self.fail("No premium.to account provided")
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ tra = self.getTraffic()
+
+ #raise timeout to 2min
+ self.req.setOption("timeout", 120)
+
+ self.download(
+ "http://premium.to/api/getfile.php",
+ get={"username": self.account.username, "password": self.account.password, "link": quote(pyfile.url, "")},
+ disposition=True)
+
+ check = self.checkDownload({"nopremium": "No premium account available"})
+
+ if check == "nopremium":
+ self.retry(60, 5 * 60, "No premium account available")
+
+ err = ''
+ if self.req.http.code == '420':
+ # Custom error code send - fail
+ lastDownload = fs_encode(self.lastDownload)
+
+ if exists(lastDownload):
+ f = open(lastDownload, "rb")
+ err = f.read(256).strip()
+ f.close()
+ remove(lastDownload)
+ else:
+ err = 'File does not exist'
+
+ trb = self.getTraffic()
+ self.logInfo("Filesize: %d, Traffic used %d, traffic left %d" % (pyfile.size, tra - trb, trb))
+
+ if err:
+ self.fail(err)
+
+ def getTraffic(self):
+ try:
+ api_r = self.load("http://premium.to/api/straffic.php",
+ get={'username': self.account.username, 'password': self.account.password})
+ traffic = sum(map(int, api_r.split(';')))
+ except:
+ traffic = 0
+ return traffic
diff --git a/pyload/plugins/hoster/PremiumizeMe.py b/pyload/plugins/hoster/PremiumizeMe.py
new file mode 100644
index 000000000..45c011084
--- /dev/null
+++ b/pyload/plugins/hoster/PremiumizeMe.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+
+
+class PremiumizeMe(Hoster):
+ __name__ = "PremiumizeMe"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = None #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.coreReady
+
+ __description__ = """Premiumize.me hoster plugin"""
+ __author_name__ = "Florian Franzen"
+ __author_mail__ = "FlorianFranzen@gmail.com"
+
+
+ def process(self, pyfile):
+ # Check account
+ if not self.account or not self.account.canUse():
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "premiumize.me")
+ self.fail("No valid premiumize.me account provided")
+
+ # In some cases hostsers do not supply us with a filename at download, so we
+ # are going to set a fall back filename (e.g. for freakshare or xfileshare)
+ pyfile.name = pyfile.name.split('/').pop() # Remove everthing before last slash
+
+ # Correction for automatic assigned filename: Removing html at end if needed
+ suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"]
+ temp = pyfile.name.split('.')
+ if temp.pop() in suffix_to_remove:
+ pyfile.name = ".".join(temp)
+
+ # Get account data
+ (user, data) = self.account.selectAccount()
+
+ # Get rewritten link using the premiumize.me api v1 (see https://secure.premiumize.me/?show=api)
+ answer = self.load(
+ "https://api.premiumize.me/pm-api/v1.php?method=directdownloadlink&params[login]=%s&params[pass]=%s&params[link]=%s" % (
+ user, data['password'], pyfile.url))
+ data = json_loads(answer)
+
+ # Check status and decide what to do
+ status = data['status']
+ if status == 200:
+ self.download(data['result']['location'], disposition=True)
+ elif status == 400:
+ self.fail("Invalid link")
+ elif status == 404:
+ self.offline()
+ elif status >= 500:
+ self.tempOffline()
+ else:
+ self.fail(data['statusmessage'])
diff --git a/pyload/plugins/hoster/PromptfileCom.py b/pyload/plugins/hoster/PromptfileCom.py
new file mode 100644
index 000000000..108f470d2
--- /dev/null
+++ b/pyload/plugins/hoster/PromptfileCom.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class PromptfileCom(SimpleHoster):
+ __name__ = "PromptfileCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://(?:www\.)?promptfile\.com/'
+
+ __description__ = """Promptfile.com hoster plugin"""
+ __author_name__ = "igel"
+ __author_mail__ = "igelkun@myopera.com"
+
+ FILE_INFO_PATTERN = r'<span style="[^"]*" title="[^"]*">(?P<N>.*?) \((?P<S>[\d.]+) (?P<U>\w+)\)</span>'
+ OFFLINE_PATTERN = r'<span style="[^"]*" title="File Not Found">File Not Found</span>'
+
+ CHASH_PATTERN = r'<input type="hidden" name="chash" value="([^"]*)" />'
+ LINK_PATTERN = r"clip: {\s*url: '(https?://(?:www\.)promptfile[^']*)',"
+
+
+ def handleFree(self):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.parseError("Unable to detect chash")
+ chash = m.group(1)
+ self.logDebug("read chash %s" % chash)
+ # continue to stage2
+ self.html = self.load(self.pyfile.url, decode=True, post={'chash': chash})
+
+ # STAGE 2: get the direct link
+ m = re.search(self.LINK_PATTERN, self.html, re.MULTILINE | re.DOTALL)
+ if m is None:
+ self.parseError("Unable to detect direct link")
+ direct = m.group(1)
+ self.logDebug("found direct link: " + direct)
+ self.download(direct, disposition=True)
+
+
+getInfo = create_getInfo(PromptfileCom)
diff --git a/pyload/plugins/hoster/QuickshareCz.py b/pyload/plugins/hoster/QuickshareCz.py
new file mode 100644
index 000000000..d82c64888
--- /dev/null
+++ b/pyload/plugins/hoster/QuickshareCz.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class QuickshareCz(SimpleHoster):
+ __name__ = "QuickshareCz"
+ __type__ = "hoster"
+ __version__ = "0.54"
+
+ __pattern__ = r'http://(?:[^/]*\.)?quickshare.cz/stahnout-soubor/.*'
+
+ __description__ = """Quickshare.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>'
+ FILE_SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</td>'
+ OFFLINE_PATTERN = r'<script type="text/javascript">location.href=\'/chyba\';</script>'
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ # parse js variables
+ self.jsvars = dict((x, y.strip("'")) for x, y in re.findall(r"var (\w+) = ([0-9.]+|'[^']*')", self.html))
+ self.logDebug(self.jsvars)
+ pyfile.name = self.jsvars['ID3']
+
+ # determine download type - free or premium
+ if self.premium:
+ if 'UU_prihlasen' in self.jsvars:
+ if self.jsvars['UU_prihlasen'] == '0':
+ self.logWarning('User not logged in')
+ self.relogin(self.user)
+ self.retry()
+ elif float(self.jsvars['UU_kredit']) < float(self.jsvars['kredit_odecet']):
+ self.logWarning('Not enough credit left')
+ self.premium = False
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ check = self.checkDownload({"err": re.compile(r"\AChyba!")}, max_size=100)
+ if check == "err":
+ self.fail("File not m or plugin defect")
+
+ def handleFree(self):
+ # get download url
+ download_url = '%s/download.php' % self.jsvars['server']
+ data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID3", "ID4"))
+ self.logDebug("FREE URL1:" + download_url, data)
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+ self.load(download_url, post=data)
+ self.header = self.req.http.header
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ m = re.search("Location\s*:\s*(.*)", self.header, re.I)
+ if m is None:
+ self.fail('File not found')
+ download_url = m.group(1)
+ self.logDebug("FREE URL2:" + download_url)
+
+ # check errors
+ m = re.search(r'/chyba/(\d+)', download_url)
+ if m:
+ if m.group(1) == '1':
+ self.retry(60, 2 * 60, "This IP is already downloading")
+ elif m.group(1) == '2':
+ self.retry(60, 60, "No free slots available")
+ else:
+ self.fail('Error %d' % m.group(1))
+
+ # download file
+ self.download(download_url)
+
+ def handlePremium(self):
+ download_url = '%s/download_premium.php' % self.jsvars['server']
+ data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID4", "ID5"))
+ self.logDebug("PREMIUM URL:" + download_url, data)
+ self.download(download_url, get=data)
+
+
+getInfo = create_getInfo(QuickshareCz)
diff --git a/pyload/plugins/hoster/RPNetBiz.py b/pyload/plugins/hoster/RPNetBiz.py
new file mode 100644
index 000000000..8a7bec097
--- /dev/null
+++ b/pyload/plugins/hoster/RPNetBiz.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import json_loads
+
+
+class RPNetBiz(Hoster):
+ __name__ = "RPNetBiz"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __description__ = """RPNet.biz hoster plugin"""
+
+ __pattern__ = r'https?://.*rpnet\.biz'
+ __author_name__ = "Dman"
+ __author_mail__ = "dmanugm@gmail.com"
+
+
+ def setup(self):
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ link_status = {'generated': pyfile.url}
+ elif not self.account:
+ # Check account
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rpnet")
+ self.fail("No rpnet account provided")
+ else:
+ (user, data) = self.account.selectAccount()
+
+ self.logDebug("Original URL: %s" % pyfile.url)
+ # Get the download link
+ response = self.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": data['password'],
+ "action": "generate", "links": pyfile.url})
+
+ self.logDebug("JSON data: %s" % response)
+ link_status = json_loads(response)['links'][0] # get the first link... since we only queried one
+
+ # Check if we only have an id as a HDD link
+ if 'id' in link_status:
+ self.logDebug("Need to wait at least 30 seconds before requery")
+ self.setWait(30) # wait for 30 seconds
+ self.wait()
+ # Lets query the server again asking for the status on the link,
+ # we need to keep doing this until we reach 100
+ max_tries = 30
+ my_try = 0
+ while (my_try <= max_tries):
+ self.logDebug("Try: %d ; Max Tries: %d" % (my_try, max_tries))
+ response = self.load("https://premium.rpnet.biz/client_api.php",
+ get={"username": user, "password": data['password'],
+ "action": "downloadInformation", "id": link_status['id']})
+ self.logDebug("JSON data hdd query: %s" % response)
+ download_status = json_loads(response)['download']
+
+ if download_status['status'] == '100':
+ link_status['generated'] = download_status['rpnet_link']
+ self.logDebug("Successfully downloaded to rpnet HDD: %s" % link_status['generated'])
+ break
+ else:
+ self.logDebug("At %s%% for the file download" % download_status['status'])
+
+ self.setWait(30)
+ self.wait()
+ my_try += 1
+
+ if my_try > max_tries: # We went over the limit!
+ self.fail("Waited for about 15 minutes for download to finish but failed")
+
+ if 'generated' in link_status:
+ self.download(link_status['generated'], disposition=True)
+ elif 'error' in link_status:
+ self.fail(link_status['error'])
+ else:
+ self.fail("Something went wrong, not supposed to enter here")
diff --git a/pyload/plugins/hoster/RapidgatorNet.py b/pyload/plugins/hoster/RapidgatorNet.py
new file mode 100644
index 000000000..cc13f7097
--- /dev/null
+++ b/pyload/plugins/hoster/RapidgatorNet.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.utils import json_loads
+from pyload.network.HTTPRequest import BadHeader
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.CaptchaService import AdsCaptcha, ReCaptcha, SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RapidgatorNet(SimpleHoster):
+ __name__ = "RapidgatorNet"
+ __type__ = "hoster"
+ __version__ = "0.22"
+
+ __pattern__ = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+'
+
+ __description__ = """Rapidgator.net hoster plugin"""
+ __author_name__ = ("zoidberg", "chrox", "stickell", "Walter Purcaro")
+ __author_mail__ = ("zoidberg@mujmail.cz", "", "l.stickell@yahoo.it", "vuolter@gmail.com")
+
+ API_URL = "http://rapidgator.net/api/file"
+
+ FILE_NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>'
+ FILE_SIZE_PATTERN = r'File size:\s*<strong>(?P<S>[\d\.]+) (?P<U>\w+)</strong>'
+ OFFLINE_PATTERN = r'>(File not found|Error 404)'
+
+ JSVARS_PATTERN = r"\s+var\s*(startTimerUrl|getDownloadUrl|captchaUrl|fid|secs)\s*=\s*'?(.*?)'?;"
+ PREMIUM_ONLY_ERROR_PATTERN = r'You can download files up to|This file can be downloaded by premium only<'
+ DOWNLOAD_LIMIT_ERROR_PATTERN = r'You have reached your (daily|hourly) downloads limit'
+ WAIT_PATTERN = r'(?:Delay between downloads must be not less than|Try again in)\s*(\d+)\s*(hour|min)'
+ LINK_PATTERN = r"return '(http://\w+.rapidgator.net/.*)';"
+
+ RECAPTCHA_KEY_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"'
+ ADSCAPTCHA_SRC_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']*)'
+ SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+ self.sid = None
+ self.chunkLimit = 1
+ self.req.setOption("timeout", 120)
+
+ def process(self, pyfile):
+ if self.account:
+ self.sid = self.account.getAccountData(self.user).get('SID', None)
+
+ if self.sid:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def api_response(self, cmd):
+ try:
+ json = self.load('%s/%s' % (self.API_URL, cmd),
+ get={'sid': self.sid,
+ 'url': self.pyfile.url}, decode=True)
+ self.logDebug('API:%s' % cmd, json, "SID: %s" % self.sid)
+ json = json_loads(json)
+ status = json['response_status']
+ msg = json['response_details']
+ except BadHeader, e:
+ self.logError('API:%s' % cmd, e, "SID: %s" % self.sid)
+ status = e.code
+ msg = e
+
+ if status == 200:
+ return json['response']
+ elif status == 423:
+ self.account.empty(self.user)
+ self.retry()
+ else:
+ self.account.relogin(self.user)
+ self.retry(wait_time=60)
+
+ def handlePremium(self):
+ #self.logDebug("ACCOUNT_DATA", self.account.getAccountData(self.user))
+ self.api_data = self.api_response('info')
+ self.api_data['md5'] = self.api_data['hash']
+ self.pyfile.name = self.api_data['filename']
+ self.pyfile.size = self.api_data['size']
+ url = self.api_response('download')['url']
+ self.download(url)
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ self.checkFree()
+
+ jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html))
+ self.logDebug(jsvars)
+
+ self.req.http.lastURL = self.pyfile.url
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+
+ url = "http://rapidgator.net%s?fid=%s" % (
+ jsvars.get('startTimerUrl', '/download/AjaxStartTimer'), jsvars['fid'])
+ jsvars.update(self.getJsonResponse(url))
+
+ self.wait(int(jsvars.get('secs', 45)) + 1, False)
+
+ url = "http://rapidgator.net%s?sid=%s" % (
+ jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid'])
+ jsvars.update(self.getJsonResponse(url))
+
+ self.req.http.lastURL = self.pyfile.url
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
+
+ url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha')
+ self.html = self.load(url)
+
+ for _ in xrange(5):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ self.logDebug(link)
+ self.download(link, disposition=True)
+ break
+ else:
+ captcha, captcha_key = self.getCaptcha()
+ captcha_challenge, captcha_response = captcha.challenge(captcha_key)
+
+ self.html = self.load(url, post={
+ "DownloadCaptchaForm[captcha]": "",
+ "adcopy_challenge": captcha_challenge,
+ "adcopy_response": captcha_response
+ })
+
+ if "The verification code is incorrect" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ else:
+ self.parseError("Download link")
+
+ def getCaptcha(self):
+ m = re.search(self.ADSCAPTCHA_SRC_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = AdsCaptcha(self)
+ else:
+ m = re.search(self.RECAPTCHA_KEY_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = ReCaptcha(self)
+ else:
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = SolveMedia(self)
+ else:
+ self.parseError("Captcha")
+
+ return captcha, captcha_key
+
+ def checkFree(self):
+ m = re.search(self.PREMIUM_ONLY_ERROR_PATTERN, self.html)
+ if m:
+ self.fail("Premium account needed for download")
+ else:
+ m = re.search(self.WAIT_PATTERN, self.html)
+
+ if m:
+ wait_time = int(m.group(1)) * {"hour": 60, "min": 1}[m.group(2)]
+ else:
+ m = re.search(self.DOWNLOAD_LIMIT_ERROR_PATTERN, self.html)
+ if m is None:
+ return
+ elif m.group(1) == "daily":
+ self.logWarning("You have reached your daily downloads limit for today")
+ wait_time = secondsToMidnight(gmt=2)
+ else:
+ wait_time = 1 * 60 * 60
+
+ self.logDebug("Waiting %d minutes" % wait_time / 60)
+ self.wait(wait_time, True)
+ self.retry()
+
+ def getJsonResponse(self, url):
+ response = self.load(url, decode=True)
+ if not response.startswith('{'):
+ self.retry()
+ self.logDebug(url, response)
+ return json_loads(response)
+
+
+getInfo = create_getInfo(RapidgatorNet)
diff --git a/pyload/plugins/hoster/RapidshareCom.py b/pyload/plugins/hoster/RapidshareCom.py
new file mode 100644
index 000000000..fefa06fd7
--- /dev/null
+++ b/pyload/plugins/hoster/RapidshareCom.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+
+
+def getInfo(urls):
+ ids = ""
+ names = ""
+
+ p = re.compile(RapidshareCom.__pattern__)
+
+ for url in urls:
+ r = p.search(url)
+ if r.group("name"):
+ ids += "," + r.group("id")
+ names += "," + r.group("name")
+ elif r.group("name_new"):
+ ids += "," + r.group("id_new")
+ names += "," + r.group("name_new")
+
+ url = "http://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=checkfiles&files=%s&filenames=%s" % (ids[1:], names[1:])
+
+ api = getURL(url)
+ result = []
+ i = 0
+ for res in api.split():
+ tmp = res.split(",")
+ if tmp[4] in ("0", "4", "5"):
+ status = 1
+ elif tmp[4] == "1":
+ status = 2
+ else:
+ status = 3
+
+ result.append((tmp[1], tmp[2], status, urls[i]))
+ i += 1
+
+ yield result
+
+
+class RapidshareCom(Hoster):
+ __name__ = "RapidshareCom"
+ __type__ = "hoster"
+ __version__ = "1.39"
+
+ __pattern__ = r'https?://(?:www\.)?rapidshare.com/(?:files/(?P<id>\d*?)/(?P<name>[^?]+)|#!download\|(?:\w+)\|(?P<id_new>\d+)\|(?P<name_new>[^|]+))'
+ __config__ = [("server",
+ "Cogent;Deutsche Telekom;Level(3);Level(3) #2;GlobalCrossing;Level(3) #3;Teleglobe;GlobalCrossing #2;TeliaSonera #2;Teleglobe #2;TeliaSonera #3;TeliaSonera",
+ "Preferred Server", "None")]
+
+ __description__ = """Rapidshare.com hoster plugin"""
+ __author_name__ = ("spoob", "RaNaN", "mkaay")
+ __author_mail__ = ("spoob@pyload.org", "ranan@pyload.org", "mkaay@mkaay.de")
+
+
+ def setup(self):
+ self.no_download = True
+ self.api_data = None
+ self.offset = 0
+ self.dl_dict = {}
+
+ self.id = None
+ self.name = None
+
+ self.chunkLimit = -1 if self.premium else 1
+ self.multiDL = self.resumeDownload = self.premium
+
+ def process(self, pyfile):
+ self.url = pyfile.url
+ self.prepare()
+
+ def prepare(self):
+ m = re.match(self.__pattern__, self.url)
+
+ if m.group("name"):
+ self.id = m.group("id")
+ self.name = m.group("name")
+ else:
+ self.id = m.group("id_new")
+ self.name = m.group("name_new")
+
+ self.download_api_data()
+ if self.api_data['status'] == "1":
+ self.pyfile.name = self.get_file_name()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ elif self.api_data['status'] == "2":
+ self.logInfo(_("Rapidshare: Traffic Share (direct download)"))
+ self.pyfile.name = self.get_file_name()
+
+ self.download(self.pyfile.url, get={"directstart": 1})
+
+ elif self.api_data['status'] in ("0", "4", "5"):
+ self.offline()
+ elif self.api_data['status'] == "3":
+ self.tempOffline()
+ else:
+ self.fail("Unknown response code.")
+
+ def handleFree(self):
+ while self.no_download:
+ self.dl_dict = self.freeWait()
+
+ #tmp = "#!download|%(server)s|%(id)s|%(name)s|%(size)s"
+ download = "http://%(host)s/cgi-bin/rsapi.cgi?sub=download&editparentlocation=0&bin=1&fileid=%(id)s&filename=%(name)s&dlauth=%(auth)s" % self.dl_dict
+
+ self.logDebug("RS API Request: %s" % download)
+ self.download(download, ref=False)
+
+ check = self.checkDownload({"ip": "You need RapidPro to download more files from your IP address",
+ "auth": "Download auth invalid"})
+ if check == "ip":
+ self.setWait(60)
+ self.logInfo(_("Already downloading from this ip address, waiting 60 seconds"))
+ self.wait()
+ self.handleFree()
+ elif check == "auth":
+ self.logInfo(_("Invalid Auth Code, download will be restarted"))
+ self.offset += 5
+ self.handleFree()
+
+ def handlePremium(self):
+ info = self.account.getAccountInfo(self.user, True)
+ self.logDebug("%s: Use Premium Account" % self.__name__)
+ url = self.api_data['mirror']
+ self.download(url, get={"directstart": 1})
+
+ def download_api_data(self, force=False):
+ """
+ http://images.rapidshare.com/apidoc.txt
+ """
+ if self.api_data and not force:
+ return
+ api_url_base = "http://api.rapidshare.com/cgi-bin/rsapi.cgi"
+ api_param_file = {"sub": "checkfiles", "incmd5": "1", "files": self.id, "filenames": self.name}
+ src = self.load(api_url_base, cookies=False, get=api_param_file).strip()
+ self.logDebug("RS INFO API: %s" % src)
+ if src.startswith("ERROR"):
+ return
+ fields = src.split(",")
+
+ # status codes:
+ # 0=File not found
+ # 1=File OK (Anonymous downloading)
+ # 3=Server down
+ # 4=File marked as illegal
+ # 5=Anonymous file locked, because it has more than 10 downloads already
+ # 50+n=File OK (TrafficShare direct download type "n" without any logging.)
+ # 100+n=File OK (TrafficShare direct download type "n" with logging.
+ # Read our privacy policy to see what is logged.)
+
+ self.api_data = {"fileid": fields[0], "filename": fields[1], "size": int(fields[2]), "serverid": fields[3],
+ "status": fields[4], "shorthost": fields[5], "checksum": fields[6].strip().lower()}
+
+ if int(self.api_data['status']) > 100:
+ self.api_data['status'] = str(int(self.api_data['status']) - 100)
+ elif int(self.api_data['status']) > 50:
+ self.api_data['status'] = str(int(self.api_data['status']) - 50)
+
+ self.api_data['mirror'] = "http://rs%(serverid)s%(shorthost)s.rapidshare.com/files/%(fileid)s/%(filename)s" % self.api_data
+
+ def freeWait(self):
+ """downloads html with the important information
+ """
+ self.no_download = True
+
+ id = self.id
+ name = self.name
+
+ prepare = "https://api.rapidshare.com/cgi-bin/rsapi.cgi?sub=download&fileid=%(id)s&filename=%(name)s&try=1&cbf=RSAPIDispatcher&cbid=1" % {
+ "name": name, "id": id}
+
+ self.logDebug("RS API Request: %s" % prepare)
+ result = self.load(prepare, ref=False)
+ self.logDebug("RS API Result: %s" % result)
+
+ between_wait = re.search("You need to wait (\d+) seconds", result)
+
+ if "You need RapidPro to download more files from your IP address" in result:
+ self.setWait(60)
+ self.logInfo(_("Already downloading from this ip address, waiting 60 seconds"))
+ self.wait()
+ elif ("Too many users downloading from this server right now" in result or
+ "All free download slots are full" in result):
+ self.setWait(120)
+ self.logInfo(_("RapidShareCom: No free slots"))
+ self.wait()
+ elif "This file is too big to download it for free" in result:
+ self.fail(_("You need a premium account for this file"))
+ elif "Filename invalid." in result:
+ self.fail(_("Filename reported invalid"))
+ elif between_wait:
+ self.setWait(int(between_wait.group(1)))
+ self.wantReconnect = True
+ self.wait()
+ else:
+ self.no_download = False
+
+ tmp, info = result.split(":")
+ data = info.split(",")
+
+ dl_dict = {"id": id,
+ "name": name,
+ "host": data[0],
+ "auth": data[1],
+ "server": self.api_data['serverid'],
+ "size": self.api_data['size']}
+ self.setWait(int(data[2]) + 2 + self.offset)
+ self.wait()
+
+ return dl_dict
+
+ def get_file_name(self):
+ if self.api_data['filename']:
+ return self.api_data['filename']
+ return self.url.split("/")[-1]
diff --git a/pyload/plugins/hoster/RarefileNet.py b/pyload/plugins/hoster/RarefileNet.py
new file mode 100644
index 000000000..7c6632aac
--- /dev/null
+++ b/pyload/plugins/hoster/RarefileNet.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+from pyload.utils import html_unescape
+
+
+class RarefileNet(XFileSharingPro):
+ __name__ = "RarefileNet"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?rarefile.net/\w{12}'
+
+ __description__ = """Rarefile.net hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "rarefile.net"
+
+ FILE_NAME_PATTERN = r'<td><font color="red">(?P<N>.*?)</font></td>'
+ FILE_SIZE_PATTERN = r'<td>Size : (?P<S>.+?)&nbsp;'
+ LINK_PATTERN = r'<a href="(?P<link>[^"]+)">(?P=link)</a>'
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+
+ def handleCaptcha(self, inputs):
+ captcha_div = re.search(r'<b>Enter code.*?<div.*?>(.*?)</div>', self.html, re.S).group(1)
+ self.logDebug(captcha_div)
+ numerals = re.findall('<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
+ inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
+ self.logDebug("CAPTCHA", inputs['code'], numerals)
+ return 3
+
+
+getInfo = create_getInfo(RarefileNet)
diff --git a/pyload/plugins/hoster/RealdebridCom.py b/pyload/plugins/hoster/RealdebridCom.py
new file mode 100644
index 000000000..08f7b9329
--- /dev/null
+++ b/pyload/plugins/hoster/RealdebridCom.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from random import randrange
+from urllib import quote, unquote
+from time import time
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import parseFileSize
+
+
+class RealdebridCom(Hoster):
+ __name__ = "RealdebridCom"
+ __type__ = "hoster"
+ __version__ = "0.53"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?real-debrid\..*'
+
+ __description__ = """Real-Debrid.com hoster plugin"""
+ __author_name__ = "Devirex Hazzard"
+ __author_mail__ = "naibaf_11@yahoo.de"
+
+
+ def getFilename(self, url):
+ try:
+ name = unquote(url.rsplit("/", 1)[1])
+ except IndexError:
+ name = "Unknown_Filename..."
+ if not name or name.endswith(".."): # incomplete filename, append random stuff
+ name += "%s.tmp" % randrange(100, 999)
+ return name
+
+ def setup(self):
+ self.chunkLimit = 3
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Real-debrid")
+ self.fail("No Real-debrid account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ password = self.getPassword().splitlines()
+ if not password:
+ password = ""
+ else:
+ password = password[0]
+
+ url = "https://real-debrid.com/ajax/unrestrict.php?lang=en&link=%s&password=%s&time=%s" % (
+ quote(pyfile.url, ""), password, int(time() * 1000))
+ page = self.load(url)
+ data = json_loads(page)
+
+ self.logDebug("Returned Data: %s" % data)
+
+ if data['error'] != 0:
+ if data['message'] == "Your file is unavailable on the hoster.":
+ self.offline()
+ else:
+ self.logWarning(data['message'])
+ self.tempOffline()
+ else:
+ if pyfile.name is not None and pyfile.name.endswith('.tmp') and data['file_name']:
+ pyfile.name = data['file_name']
+ pyfile.size = parseFileSize(data['file_size'])
+ new_url = data['generated_links'][0][-1]
+
+ if self.getConfig("https"):
+ new_url = new_url.replace("http://", "https://")
+ else:
+ new_url = new_url.replace("https://", "http://")
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: %s" % new_url)
+
+ if pyfile.name.startswith("http") or pyfile.name.startswith("Unknown") or pyfile.name.endswith('..'):
+ #only use when name wasnt already set
+ pyfile.name = self.getFilename(new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload(
+ {"error": "<title>An error occured while processing your request</title>"})
+
+ if check == "error":
+ #usual this download can safely be retried
+ self.retry(wait_time=60, reason="An error occured while generating link.")
diff --git a/pyload/plugins/hoster/RedtubeCom.py b/pyload/plugins/hoster/RedtubeCom.py
new file mode 100644
index 000000000..9f5d2d0d9
--- /dev/null
+++ b/pyload/plugins/hoster/RedtubeCom.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import html_unescape
+
+
+class RedtubeCom(Hoster):
+ __name__ = "RedtubeCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?redtube\.com/\d+'
+
+ __description__ = """Redtube.com hoster plugin"""
+ __author_name__ = "jeix"
+ __author_mail__ = "jeix@hasnomail.de"
+
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ file_url = html_unescape(re.search(r'hashlink=(http.*?)"', self.html).group(1))
+
+ return file_url
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search('<title>(.*?)- RedTube - Free Porn Videos</title>', self.html).group(1).strip() + ".flv"
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r'This video has been removed.', self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/RehostTo.py b/pyload/plugins/hoster/RehostTo.py
new file mode 100644
index 000000000..d3d3fcd8b
--- /dev/null
+++ b/pyload/plugins/hoster/RehostTo.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from urllib import quote, unquote
+
+from pyload.plugins.Hoster import Hoster
+
+
+class RehostTo(Hoster):
+ __name__ = "RehostTo"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'https?://.*rehost.to\..*'
+
+ __description__ = """Rehost.com hoster plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def getFilename(self, url):
+ return unquote(url.rsplit("/", 1)[1])
+
+ def setup(self):
+ self.chunkLimit = 1
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "rehost.to")
+ self.fail("No rehost.to account provided")
+
+ data = self.account.getAccountInfo(self.user)
+ long_ses = data['long_ses']
+
+ self.logDebug("Rehost.to: Old URL: %s" % pyfile.url)
+ new_url = "http://rehost.to/process_download.php?user=cookie&pass=%s&dl=%s" % (long_ses, quote(pyfile.url, ""))
+
+ #raise timeout to 2min
+ self.req.setOption("timeout", 120)
+
+ self.download(new_url, disposition=True)
diff --git a/pyload/plugins/hoster/RemixshareCom.py b/pyload/plugins/hoster/RemixshareCom.py
new file mode 100644
index 000000000..dfd7db5a0
--- /dev/null
+++ b/pyload/plugins/hoster/RemixshareCom.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://remixshare.com/download/p946u
+#
+# Note:
+# The remixshare.com website is very very slow, so
+# if your download not starts because of pycurl timeouts:
+# Adjust timeouts in /usr/share/pyload/pyload/network/HTTPRequest.py
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RemixshareCom(SimpleHoster):
+ __name__ = "RemixshareCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://remixshare\.com/(download|dl)/\w+'
+
+ __description__ = """Remixshare.com hoster plugin"""
+ __author_name__ = ("zapp-brannigan", "Walter Purcaro")
+ __author_mail__ = ("fuerst.reinje@web.de", "vuolter@gmail.com")
+
+ FILE_INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'>&nbsp;\((?P<S>\d+)&nbsp;(?P<U>\w+)\)<'
+ OFFLINE_PATTERN = r'<h1>Ooops!<'
+
+ LINK_PATTERN = r'(http://remixshare\.com/downloadfinal/.+?)"'
+ TOKEN_PATTERN = r'var acc = (\d+)'
+ WAIT_PATTERN = r'var XYZ = r"(\d+)"'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.chunkLimit = 1
+
+ def handleFree(self):
+ b = re.search(self.LINK_PATTERN, self.html)
+ if not b:
+ self.parseError("Cannot parse download url")
+ c = re.search(self.TOKEN_PATTERN, self.html)
+ if not c:
+ self.parseError("Cannot parse file token")
+ dl_url = b.group(1) + c.group(1)
+
+ #Check if we have to wait
+ seconds = re.search(self.WAIT_PATTERN, self.html)
+ if seconds:
+ self.logDebug("Wait " + seconds.group(1))
+ self.wait(seconds.group(1))
+
+ # Finally start downloading...
+ self.logDebug("Download URL = r" + dl_url)
+ self.download(dl_url, disposition=True)
+
+
+getInfo = create_getInfo(RemixshareCom)
diff --git a/pyload/plugins/hoster/RgHostNet.py b/pyload/plugins/hoster/RgHostNet.py
new file mode 100644
index 000000000..0240f3a05
--- /dev/null
+++ b/pyload/plugins/hoster/RgHostNet.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class RgHostNet(SimpleHoster):
+ __name__ = "RgHostNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?rghost\.net/\d+(?:r=\d+)?'
+
+ __description__ = """RgHost.net hoster plugin"""
+ __author_name__ = "z00nx"
+ __author_mail__ = "z00nx0@gmail.com"
+
+ FILE_INFO_PATTERN = r'<h1>\s+(<a[^>]+>)?(?P<N>[^<]+)(</a>)?\s+<small[^>]+>\s+\((?P<S>[^)]+)\)\s+</small>\s+</h1>'
+ OFFLINE_PATTERN = r'File is deleted|this page is not found'
+ LINK_PATTERN = r'''<a\s+href="([^"]+)"\s+class="btn\s+large\s+download"[^>]+>Download</a>'''
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("Unable to detect the direct link")
+ download_link = m.group(1)
+ self.download(download_link, disposition=True)
+
+
+getInfo = create_getInfo(RgHostNet)
diff --git a/pyload/plugins/hoster/RyushareCom.py b/pyload/plugins/hoster/RyushareCom.py
new file mode 100644
index 000000000..326c55e0c
--- /dev/null
+++ b/pyload/plugins/hoster/RyushareCom.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://ryushare.com/cl0jy8ric2js/random.bin
+
+import re
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+from pyload.plugins.internal.CaptchaService import SolveMedia
+
+
+class RyushareCom(XFileSharingPro):
+ __name__ = "RyushareCom"
+ __type__ = "hoster"
+ __version__ = "0.16"
+
+ __pattern__ = r'http://(?:www\.)?ryushare\.com/\w+'
+
+ __description__ = """Ryushare.com hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell", "quareevo")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it", "quareevo@arcor.de")
+
+ HOSTER_NAME = "ryushare.com"
+
+ FILE_SIZE_PATTERN = r'You have requested <font color="red">[^<]+</font> \((?P<S>[\d\.]+) (?P<U>\w+)'
+
+ WAIT_PATTERN = r'You have to wait ((?P<hour>\d+) hour[s]?, )?((?P<min>\d+) minute[s], )?(?P<sec>\d+) second[s]'
+ LINK_PATTERN = r'<a href="([^"]+)">Click here to download<'
+ SOLVEMEDIA_PATTERN = r'http:\/\/api\.solvemedia\.com\/papi\/challenge\.script\?k=(.*?)"'
+
+
+ def getDownloadLink(self):
+ retry = False
+ self.html = self.load(self.pyfile.url)
+ action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
+ if "method_premium" in inputs:
+ del inputs['method_premium']
+
+ self.html = self.load(self.pyfile.url, post=inputs)
+ action, inputs = self.parseHtmlForm('F1')
+
+ self.setWait(65)
+ # Wait 1 hour
+ if "You have reached the download-limit" in self.html:
+ self.setWait(1 * 60 * 60, True)
+ retry = True
+
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait = m.groupdict(0)
+ waittime = int(wait['hour']) * 60 * 60 + int(wait['min']) * 60 + int(wait['sec'])
+ self.setWait(waittime, True)
+ retry = True
+
+ self.wait()
+ if retry:
+ self.retry()
+
+ for _ in xrange(5):
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m is None:
+ self.parseError("Error parsing captcha")
+
+ captchaKey = m.group(1)
+ captcha = SolveMedia(self)
+ challenge, response = captcha.challenge(captchaKey)
+
+ inputs['adcopy_challenge'] = challenge
+ inputs['adcopy_response'] = response
+
+ self.html = self.load(self.pyfile.url, post=inputs)
+ if "WRONG CAPTCHA" in self.html:
+ self.invalidCaptcha()
+ self.logInfo("Invalid Captcha")
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("You have entered 5 invalid captcha codes")
+
+ if "Click here to download" in self.html:
+ return re.search(r'<a href="([^"]+)">Click here to download</a>', self.html).group(1)
+
+
+getInfo = create_getInfo(RyushareCom)
diff --git a/pyload/plugins/hoster/SecureUploadEu.py b/pyload/plugins/hoster/SecureUploadEu.py
new file mode 100644
index 000000000..befe5f0e9
--- /dev/null
+++ b/pyload/plugins/hoster/SecureUploadEu.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class SecureUploadEu(XFileSharingPro):
+ __name__ = "SecureUploadEu"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?secureupload\.eu/(\w){12}(/\w+)'
+
+ __description__ = """SecureUpload.eu hoster plugin"""
+ __author_name__ = "z00nx"
+ __author_mail__ = "z00nx0@gmail.com"
+
+ HOSTER_NAME = "secureupload.eu"
+
+ FILE_INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>'
+ OFFLINE_PATTERN = r'The file was removed|File Not Found'
+
+
+getInfo = create_getInfo(SecureUploadEu)
diff --git a/pyload/plugins/hoster/SendmywayCom.py b/pyload/plugins/hoster/SendmywayCom.py
new file mode 100644
index 000000000..87cbfcc0d
--- /dev/null
+++ b/pyload/plugins/hoster/SendmywayCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class SendmywayCom(XFileSharingPro):
+ __name__ = "SendmywayCom"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?sendmyway.com/\w{12}'
+
+ __description__ = """SendMyWay hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "sendmyway.com"
+
+ FILE_NAME_PATTERN = r'<p class="file-name" ><.*?>\s*(?P<N>.+)'
+ FILE_SIZE_PATTERN = r'<small>\((?P<S>\d+) bytes\)</small>'
+
+
+getInfo = create_getInfo(SendmywayCom)
diff --git a/pyload/plugins/hoster/SendspaceCom.py b/pyload/plugins/hoster/SendspaceCom.py
new file mode 100644
index 000000000..7a0908c8d
--- /dev/null
+++ b/pyload/plugins/hoster/SendspaceCom.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class SendspaceCom(SimpleHoster):
+ __name__ = "SendspaceCom"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'http://(?:www\.)?sendspace.com/file/.*'
+
+ __description__ = """Sendspace.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</'
+ FILE_SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[0-9.]+)(?P<U>[kKMG])i?B\s*</div>'
+ OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>'
+
+ LINK_PATTERN = r'<a id="download_button" href="([^"]+)"'
+ CAPTCHA_PATTERN = r'<td><img src="(/captchas/captcha.php?captcha=([^"]+))"></td>'
+ USER_CAPTCHA_PATTERN = r'<td><img src="/captchas/captcha.php?user=([^"]+))"></td>'
+
+
+ def handleFree(self):
+ params = {}
+ for _ in xrange(3):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.correctCaptcha()
+ download_url = m.group(1)
+ break
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m:
+ if 'captcha_hash' in params:
+ self.invalidCaptcha()
+ captcha_url1 = "http://www.sendspace.com/" + m.group(1)
+ m = re.search(self.USER_CAPTCHA_PATTERN, self.html)
+ captcha_url2 = "http://www.sendspace.com/" + m.group(1)
+ params = {'captcha_hash': m.group(2),
+ 'captcha_submit': 'Verify',
+ 'captcha_answer': self.decryptCaptcha(captcha_url1) + " " + self.decryptCaptcha(captcha_url2)}
+ else:
+ params = {'download': "Regular Download"}
+
+ self.logDebug(params)
+ self.html = self.load(self.pyfile.url, post=params)
+ else:
+ self.fail("Download link not found")
+
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
+
+
+create_getInfo(SendspaceCom)
diff --git a/pyload/plugins/hoster/Share4webCom.py b/pyload/plugins/hoster/Share4webCom.py
new file mode 100644
index 000000000..a3d92d9f4
--- /dev/null
+++ b/pyload/plugins/hoster/Share4webCom.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.UnibytesCom import UnibytesCom
+from pyload.plugins.internal.SimpleHoster import create_getInfo
+
+
+class Share4webCom(UnibytesCom):
+ __name__ = "Share4webCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?share4web\.com/get/\w+'
+
+ __description__ = """Share4web.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ HOSTER_NAME = "share4web.com"
+
+
+getInfo = create_getInfo(UnibytesCom)
diff --git a/pyload/plugins/hoster/Share76Com.py b/pyload/plugins/hoster/Share76Com.py
new file mode 100644
index 000000000..2cd736992
--- /dev/null
+++ b/pyload/plugins/hoster/Share76Com.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class Share76Com(DeadHoster):
+ __name__ = "Share76Com"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?share76.com/\w{12}'
+
+ __description__ = """Share76.com hoster plugin"""
+ __author_name__ = "me"
+ __author_mail__ = None
+
+
+getInfo = create_getInfo(Share76Com)
diff --git a/pyload/plugins/hoster/ShareFilesCo.py b/pyload/plugins/hoster/ShareFilesCo.py
new file mode 100644
index 000000000..b75eb0740
--- /dev/null
+++ b/pyload/plugins/hoster/ShareFilesCo.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class ShareFilesCo(DeadHoster):
+ __name__ = "ShareFilesCo"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?sharefiles\.co/\w{12}'
+
+ __description__ = """Sharefiles.co hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+getInfo = create_getInfo(ShareFilesCo)
diff --git a/pyload/plugins/hoster/ShareRapidCom.py b/pyload/plugins/hoster/ShareRapidCom.py
new file mode 100644
index 000000000..b474103fc
--- /dev/null
+++ b/pyload/plugins/hoster/ShareRapidCom.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.network.RequestFactory import getRequest
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, parseFileInfo
+
+
+def getInfo(urls):
+ h = getRequest()
+ h.c.setopt(HTTPHEADER,
+ ["Accept: text/html",
+ "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"])
+ for url in urls:
+ html = h.load(url, decode=True)
+ file_info = parseFileInfo(ShareRapidCom, url, html)
+ yield file_info
+
+
+class ShareRapidCom(SimpleHoster):
+ __name__ = "ShareRapidCom"
+ __type__ = "hoster"
+ __version__ = "0.54"
+
+ __pattern__ = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+'
+
+ __description__ = """MegaRapid.cz hoster plugin"""
+ __author_name__ = ("MikyWoW", "zoidberg", "stickell", "Walter Purcaro")
+ __author_mail__ = ("mikywow@seznam.cz", "zoidberg@mujmail.cz", "l.stickell@yahoo.it", "vuolter@gmail.com")
+
+ FILE_NAME_PATTERN = r'<h1[^>]*><span[^>]*>(?:<a[^>]*>)?(?P<N>[^<]+)'
+ FILE_SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</strong></td>'
+ OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán'
+
+ SH_CHECK_TRAFFIC = True
+
+ LINK_PATTERN = r'<a href="([^"]+)" title="Stahnout">([^<]+)</a>'
+ ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahování je přístupné pouze přihlášenÜm uÅŸivatelům'
+ ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahování zdarma je moÅŸné jen přes náš'
+
+
+ def setup(self):
+ self.chunkLimit = 1
+
+ def handlePremium(self):
+ try:
+ self.html = self.load(self.pyfile.url, decode=True)
+ except BadHeader, e:
+ self.account.relogin(self.user)
+ self.retry(max_tries=3, reason=str(e))
+
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ link = m.group(1)
+ self.logDebug("Premium link: %s" % link)
+ self.download(link, disposition=True)
+ else:
+ if re.search(self.ERR_LOGIN_PATTERN, self.html):
+ self.relogin(self.user)
+ self.retry(max_tries=3, reason="User login failed")
+ elif re.search(self.ERR_CREDIT_PATTERN, self.html):
+ self.fail("Not enough credit left")
+ else:
+ self.fail("Download link not found")
diff --git a/pyload/plugins/hoster/SharebeesCom.py b/pyload/plugins/hoster/SharebeesCom.py
new file mode 100644
index 000000000..287dbf59c
--- /dev/null
+++ b/pyload/plugins/hoster/SharebeesCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class SharebeesCom(DeadHoster):
+ __name__ = "SharebeesCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'http://(?:www\.)?sharebees.com/\w{12}'
+
+ __description__ = """ShareBees hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(SharebeesCom)
diff --git a/pyload/plugins/hoster/ShareonlineBiz.py b/pyload/plugins/hoster/ShareonlineBiz.py
new file mode 100644
index 000000000..b1d9ae5cb
--- /dev/null
+++ b/pyload/plugins/hoster/ShareonlineBiz.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+
+
+def getInfo(urls):
+ api_url_base = "http://api.share-online.biz/linkcheck.php"
+
+ urls = [url.replace("https://", "http://") for url in urls]
+
+ for chunk in chunks(urls, 90):
+ api_param_file = {"links": "\n".join(x.replace("http://www.share-online.biz/dl/", "").rstrip("/") for x in
+ chunk)} # api only supports old style links
+ src = getURL(api_url_base, post=api_param_file, decode=True)
+ result = []
+ for i, res in enumerate(src.split("\n")):
+ if not res:
+ continue
+ fields = res.split(";")
+
+ if fields[1] == "OK":
+ status = 2
+ elif fields[1] in ("DELETED", "NOT FOUND"):
+ status = 1
+ else:
+ status = 3
+
+ result.append((fields[2], int(fields[3]), status, chunk[i]))
+ yield result
+
+
+class ShareonlineBiz(Hoster):
+ __name__ = "ShareonlineBiz"
+ __type__ = "hoster"
+ __version__ = "0.40"
+
+ __pattern__ = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download.php\?id=|dl/)(?P<ID>\w+)'
+
+ __description__ = """Shareonline.biz hoster plugin"""
+ __author_name__ = ("spoob", "mkaay", "zoidberg", "Walter Purcaro")
+ __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de", "zoidberg@mujmail.cz", "vuolter@gmail.com")
+
+ ERROR_INFO_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>'
+
+
+ def setup(self):
+ # range request not working?
+ # api supports resume, only one chunk
+ # website isn't supporting resuming in first place
+ self.file_id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ self.pyfile.url = "http://www.share-online.biz/dl/" + self.file_id
+
+ self.resumeDownload = self.premium
+ self.multiDL = False
+ #self.chunkLimit = 1
+
+ self.check_data = None
+
+ def process(self, pyfile):
+ if self.premium:
+ self.handlePremium()
+ #web-download fallback removed - didn't work anyway
+ else:
+ self.handleFree()
+
+ # check = self.checkDownload({"failure": re.compile(self.ERROR_INFO_PATTERN)})
+ # if check == "failure":
+ # try:
+ # self.retry(reason=self.lastCheck.group(1).decode("utf8"))
+ # except:
+ # self.retry(reason="Unknown error")
+
+ if self.api_data:
+ self.check_data = {"size": int(self.api_data['size']), "md5": self.api_data['md5']}
+
+ def loadAPIData(self):
+ api_url_base = "http://api.share-online.biz/linkcheck.php?md5=1"
+ api_param_file = {"links": self.file_id} # api only supports old style links
+ src = self.load(api_url_base, cookies=False, post=api_param_file, decode=True)
+
+ fields = src.split(";")
+ self.api_data = {"fileid": fields[0],
+ "status": fields[1]}
+ if not self.api_data['status'] == "OK":
+ self.offline()
+ else:
+ self.api_data['filename'] = fields[2]
+ self.api_data['size'] = fields[3] # in bytes
+ self.api_data['md5'] = fields[4].strip().lower().replace("\n\n", "") # md5
+
+ def handleFree(self):
+ self.loadAPIData()
+ self.pyfile.name = self.api_data['filename']
+ self.pyfile.size = int(self.api_data['size'])
+
+ self.html = self.load(self.pyfile.url, cookies=True) # refer, stuff
+ self.setWait(3)
+ self.wait()
+
+ self.html = self.load("%s/free/" % self.pyfile.url, post={"dl_free": "1", "choice": "free"}, decode=True)
+ self.checkErrors()
+
+ m = re.search(r'var wait=(\d+);', self.html)
+
+ recaptcha = ReCaptcha(self)
+ for _ in xrange(5):
+ challenge, response = recaptcha.challenge("6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX")
+ self.setWait(int(m.group(1)) if m else 30)
+ response = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time() * 1000)), post={
+ 'dl_free': '1',
+ 'recaptcha_challenge_field': challenge,
+ 'recaptcha_response_field': response})
+
+ if not response == '0':
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.invalidCaptcha()
+ self.fail("No valid captcha solution received")
+
+ download_url = response.decode("base64")
+ self.logDebug(download_url)
+ if not download_url.startswith("http://"):
+ self.parseError("download url")
+
+ self.wait()
+ self.download(download_url)
+ # check download
+ check = self.checkDownload({
+ "cookie": re.compile(r'<div id="dl_failure"'),
+ "fail": re.compile(r"<title>Share-Online")
+ })
+ if check == "cookie":
+ self.invalidCaptcha()
+ self.retry(5, 60, "Cookie failure")
+ elif check == "fail":
+ self.invalidCaptcha()
+ self.retry(5, 5 * 60, "Download failed")
+ else:
+ self.correctCaptcha()
+
+ def handlePremium(self): #: should be working better loading (account) api internally
+ self.account.getAccountInfo(self.user, True)
+ src = self.load("http://api.share-online.biz/account.php",
+ {"username": self.user, "password": self.account.accounts[self.user]['password'],
+ "act": "download", "lid": self.file_id})
+
+ self.api_data = dlinfo = {}
+ for line in src.splitlines():
+ key, value = line.split(": ")
+ dlinfo[key.lower()] = value
+
+ self.logDebug(dlinfo)
+ if not dlinfo['status'] == "online":
+ self.offline()
+ else:
+ self.pyfile.name = dlinfo['name']
+ self.pyfile.size = int(dlinfo['size'])
+
+ dlLink = dlinfo['url']
+ if dlLink == "server_under_maintenance":
+ self.tempOffline()
+ else:
+ self.multiDL = True
+ self.download(dlLink)
+
+ def checkErrors(self):
+ m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL)
+ if m is None:
+ return
+
+ err = m.group(1)
+ m = re.search(self.ERROR_INFO_PATTERN, self.html)
+ msg = m.group(1) if m else ""
+ self.logError(err, msg or "Unknown error occurred")
+
+ if err == "invalid":
+ self.fail(msg or "File not available")
+ elif err in ("freelimit", "size", "proxy"):
+ self.fail(msg or "Premium account needed")
+ else:
+ if err in 'server':
+ self.setWait(600, False)
+ elif err in 'expired':
+ self.setWait(30, False)
+ else:
+ self.setWait(300, True)
+
+ self.wait()
+ self.retry(max_tries=25, reason=msg)
diff --git a/pyload/plugins/hoster/ShareplaceCom.py b/pyload/plugins/hoster/ShareplaceCom.py
new file mode 100644
index 000000000..60bb596cc
--- /dev/null
+++ b/pyload/plugins/hoster/ShareplaceCom.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.Hoster import Hoster
+
+
+class ShareplaceCom(Hoster):
+ __name__ = "ShareplaceCom"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'(http://)?(?:www\.)?shareplace\.(com|org)/\?[a-zA-Z0-9]+'
+
+ __description__ = """Shareplace.com hoster plugin"""
+ __author_name__ = "ACCakut"
+ __author_mail__ = None
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.prepare()
+ self.download(self.get_file_url())
+
+ def prepare(self):
+ if not self.file_exists():
+ self.offline()
+
+ self.pyfile.name = self.get_file_name()
+
+ wait_time = self.get_waiting_time()
+ self.setWait(wait_time)
+ self.logDebug("%s: Waiting %d seconds." % (self.__name__, wait_time))
+ self.wait()
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ #var zzipitime = 15;
+ m = re.search(r'var zzipitime = (\d+);', self.html)
+ if m:
+ sec = int(m.group(1))
+ else:
+ sec = 0
+
+ return sec
+
+ def download_html(self):
+ url = re.sub("shareplace.com\/\?", "shareplace.com//index1.php/?a=", self.pyfile.url)
+ self.html = self.load(url, decode=True)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ url = re.search(r"var beer = '(.*?)';", self.html)
+ if url:
+ url = url.group(1)
+ url = unquote(
+ url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace(
+ "teletubbies", ""))
+ self.logDebug("URL: %s" % url)
+ return url
+ else:
+ self.fail("absolute filepath could not be found. offline? ")
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search("<title>\s*(.*?)\s*</title>", self.html).group(1)
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r"HTTP Status 404", self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/ShragleCom.py b/pyload/plugins/hoster/ShragleCom.py
new file mode 100644
index 000000000..0ec93fcdc
--- /dev/null
+++ b/pyload/plugins/hoster/ShragleCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class ShragleCom(DeadHoster):
+ __name__ = "ShragleCom"
+ __type__ = "hoster"
+ __version__ = "0.22"
+
+ __pattern__ = r'http://(?:www\.)?(cloudnator|shragle).com/files/(?P<ID>.*?)/'
+
+ __description__ = """Cloudnator.com (Shragle.com) hoster plugin"""
+ __author_name__ = ("RaNaN", "zoidberg")
+ __author_mail__ = ("RaNaN@pyload.org", "zoidberg@mujmail.cz")
+
+
+getInfo = create_getInfo(ShragleCom)
diff --git a/pyload/plugins/hoster/SimplyPremiumCom.py b/pyload/plugins/hoster/SimplyPremiumCom.py
new file mode 100644
index 000000000..760b7ff1b
--- /dev/null
+++ b/pyload/plugins/hoster/SimplyPremiumCom.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from datetime import datetime, timedelta
+
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+
+
+class SimplyPremiumCom(Hoster):
+ __name__ = "SimplyPremiumCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://.*(simply-premium)\.com'
+
+ __description__ = """Simply-Premium.com hoster plugin"""
+ __author_name__ = "EvolutionClip"
+ __author_mail__ = "evolutionclip@live.de"
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = False
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Simply-Premium.com")
+ self.fail("No Simply-Premium.com account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ for i in xrange(5):
+ page = self.load('http://www.simply-premium.com/premium.php?info&link=' + pyfile.url)
+ self.logDebug("JSON data: " + page)
+ if page != '':
+ break
+ else:
+ self.logInfo("Unable to get API data, waiting 1 minute and retry")
+ self.retry(5, 60, "Unable to get API data")
+
+ if '<valid>0</valid>' in page or (
+ "You are not allowed to download from this host" in page and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+ elif "NOTFOUND" in page:
+ self.offline()
+ elif "downloadlimit" in page:
+ self.logWarning("Reached maximum connctions")
+ self.retry(5, 60, "Reached maximum connctions")
+ elif "trafficlimit" in page:
+ self.logWarning("Reached daily limit for this host")
+ self.retry(1, secondsToMidnight(gmt=2), "Daily limit for this host reached")
+ elif "hostererror" in page:
+ self.logWarning("Hoster temporarily unavailable, waiting 1 minute and retry")
+ self.retry(5, 60, "Hoster is temporarily unavailable")
+ #page = json_loads(page)
+ #new_url = page.keys()[0]
+ #self.api_data = page[new_url]
+
+ try:
+ self.pyfile.name = re.search(r'<name>([^<]+)</name>', page).group(1)
+ except AttributeError:
+ self.pyfile.name = ""
+
+ try:
+ self.pyfile.size = re.search(r'<size>(\d+)</size>', page).group(1)
+ except AttributeError:
+ self.pyfile.size = 0
+
+ try:
+ new_url = re.search(r'<download>([^<]+)</download>', page).group(1)
+ except AttributeError:
+ new_url = 'http://www.simply-premium.com/premium.php?link=' + pyfile.url
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ self.download(new_url, disposition=True)
diff --git a/pyload/plugins/hoster/SimplydebridCom.py b/pyload/plugins/hoster/SimplydebridCom.py
new file mode 100644
index 000000000..c6b03c124
--- /dev/null
+++ b/pyload/plugins/hoster/SimplydebridCom.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class SimplydebridCom(Hoster):
+ __name__ = "SimplydebridCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd.php/*'
+
+ __description__ = """Simply-debrid.com hoster plugin"""
+ __author_name__ = "Kagenoshin"
+ __author_mail__ = "kagenoshin@gmx.ch"
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "simply-debrid.com")
+ self.fail("No simply-debrid.com account provided")
+
+ self.logDebug("Old URL: %s" % pyfile.url)
+
+ #fix the links for simply-debrid.com!
+ new_url = pyfile.url
+ new_url = new_url.replace("clz.to", "cloudzer.net/file")
+ new_url = new_url.replace("http://share-online", "http://www.share-online")
+ new_url = new_url.replace("ul.to", "uploaded.net/file")
+ new_url = new_url.replace("uploaded.com", "uploaded.net")
+ new_url = new_url.replace("filerio.com", "filerio.in")
+ new_url = new_url.replace("lumfile.com", "lumfile.se")
+ if('fileparadox' in new_url):
+ new_url = new_url.replace("http://", "https://")
+
+ if re.match(self.__pattern__, new_url):
+ new_url = new_url
+
+ self.logDebug("New URL: %s" % new_url)
+
+ if not re.match(self.__pattern__, new_url):
+ page = self.load('http://simply-debrid.com/api.php', get={'dl': new_url}) # +'&u='+self.user+'&p='+self.account.getAccountData(self.user)['password'])
+ if 'tiger Link' in page or 'Invalid Link' in page or ('API' in page and 'ERROR' in page):
+ self.fail('Unable to unrestrict link')
+ new_url = page
+
+ self.setWait(5)
+ self.wait()
+ self.logDebug("Unrestricted URL: " + new_url)
+
+ self.download(new_url, disposition=True)
+
+ check = self.checkDownload({"bad1": "No address associated with hostname", "bad2": "<html"})
+
+ if check == "bad1" or check == "bad2":
+ self.retry(24, 3 * 60, "Bad file downloaded")
diff --git a/pyload/plugins/hoster/SockshareCom.py b/pyload/plugins/hoster/SockshareCom.py
new file mode 100644
index 000000000..36e03a5ae
--- /dev/null
+++ b/pyload/plugins/hoster/SockshareCom.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from os import rename
+
+from pyload.plugins.hoster.UnrestrictLi import secondsToMidnight
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class SockshareCom(SimpleHoster):
+ __name__ = "SockshareCom"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)'
+
+ __description__ = """Sockshare.com hoster plugin"""
+ __author_name__ = ("jeix", "stickell", "Walter Purcaro")
+ __author_mail__ = ("jeix@hasnomail.de", "l.stickell@yahoo.it", "vuolter@gmail.com")
+
+ FILE_INFO_PATTERN = r'site-content">\s*<h1>(?P<N>.+)<strong>\( (?P<S>[^)]+) \)</strong></h1>'
+ OFFLINE_PATTERN = r'>This file doesn\'t exist, or has been removed.<'
+ TEMP_OFFLINE_PATTERN = r'(>This content server has been temporarily disabled for upgrades|Try again soon\\. You can still download it below\\.<)'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.sockshare.com/file/\g<ID>')]
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = True
+ self.chunkLimit = -1
+
+ def handleFree(self):
+ name = self.pyfile.name
+ link = self._getLink()
+ self.logDebug("Direct link: " + link)
+ self.download(link, disposition=True)
+ self.processName(name)
+
+ def _getLink(self):
+ hash_data = re.search(r'<input type="hidden" value="([a-z0-9]+)" name="hash">', self.html)
+ if not hash_data:
+ self.parseError("Unable to detect hash")
+
+ post_data = {"hash": hash_data.group(1), "confirm": "Continue+as+Free+User"}
+ self.html = self.load(self.pyfile.url, post=post_data)
+ if ">You have exceeded the daily stream limit for your country\\. You can wait until tomorrow" in self.html:
+ self.logWarning("You have exceeded your daily stream limit for today")
+ self.wait(secondsToMidnight(gmt=2), True)
+ elif re.search(self.TEMP_OFFLINE_PATTERN, self.html):
+ self.retry(wait_time=2 * 60 * 60, reason="Server temporarily offline") # 2 hours wait
+
+ patterns = (r'(/get_file\.php\?id=[A-Z0-9]+&key=[a-zA-Z0-9=]+&original=1)',
+ r'(/get_file\.php\?download=[A-Z0-9]+&key=[a-z0-9]+)',
+ r'(/get_file\.php\?download=[A-Z0-9]+&key=[a-z0-9]+&original=1)',
+ r'<a href="/gopro\.php">Tired of ads and waiting\? Go Pro!</a>[\t\n\rn ]+</div>[\t\n\rn ]+<a href="(/.*?)"')
+ for pattern in patterns:
+ link = re.search(pattern, self.html)
+ if link:
+ break
+ else:
+ link = re.search(r"playlist: '(/get_file\.php\?stream=[a-zA-Z0-9=]+)'", self.html)
+ if link:
+ self.html = self.load("http://www.sockshare.com" + link.group(1))
+ link = re.search(r'media:content url="(http://.*?)"', self.html)
+ if link is None:
+ link = re.search(r'\"(http://media\\-b\\d+\\.sockshare\\.com/download/\\d+/.*?)\"', self.html)
+ else:
+ self.parseError('Unable to detect a download link')
+
+ link = link.group(1).replace("&amp;", "&")
+ if link.startswith("http://"):
+ return link
+ else:
+ return "http://www.sockshare.com" + link
+
+ def processName(self, name_old):
+ name = self.pyfile.name
+ if name <= name_old:
+ return
+ name_new = re.sub(r'\.[^.]+$', "", name_old) + name[len(name_old):]
+ filename = self.lastDownload
+ self.pyfile.name = name_new
+ rename(filename, filename.rsplit(name)[0] + name_new)
+ self.logInfo("%(name)s renamed to %(newname)s" % {"name": name, "newname": name_new})
+
+
+getInfo = create_getInfo(SockshareCom)
diff --git a/pyload/plugins/hoster/SoundcloudCom.py b/pyload/plugins/hoster/SoundcloudCom.py
new file mode 100644
index 000000000..afe8eaf62
--- /dev/null
+++ b/pyload/plugins/hoster/SoundcloudCom.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+import pycurl
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class SoundcloudCom(Hoster):
+ __name__ = "SoundcloudCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'https?://(?:www\.)?soundcloud\.com/(?P<UID>.*?)/(?P<SID>.*)'
+
+ __description__ = """SoundCloud.com hoster plugin"""
+ __author_name__ = "Peekayy"
+ __author_mail__ = "peekayy.dev@gmail.com"
+
+
+ def process(self, pyfile):
+ # default UserAgent of HTTPRequest fails for this hoster so we use this one
+ self.req.http.c.setopt(pycurl.USERAGENT, 'Mozilla/5.0')
+ page = self.load(pyfile.url)
+ m = re.search(r'<div class="haudio.*?large.*?" data-sc-track="(?P<ID>[0-9]*)"', page)
+ songId = clientId = ""
+ if m:
+ songId = m.group("ID")
+ if len(songId) <= 0:
+ self.logError("Could not find song id")
+ self.offline()
+ else:
+ m = re.search(r'"clientID":"(?P<CID>.*?)"', page)
+ if m:
+ clientId = m.group("CID")
+
+ if len(clientId) <= 0:
+ clientId = "b45b1aa10f1ac2941910a7f0d10f8e28"
+
+ m = re.search(r'<em itemprop="name">\s(?P<TITLE>.*?)\s</em>', page)
+ if m:
+ pyfile.name = m.group("TITLE") + ".mp3"
+ else:
+ pyfile.name = re.match(self.__pattern__, pyfile.url).group("SID") + ".mp3"
+
+ # url to retrieve the actual song url
+ page = self.load("https://api.sndcdn.com/i1/tracks/%s/streams" % songId, get={"client_id": clientId})
+ # getting streams
+ # for now we choose the first stream found in all cases
+ # it could be improved if relevant for this hoster
+ streams = [
+ (result.group("QUALITY"), result.group("URL"))
+ for result in re.finditer(r'"(?P<QUALITY>.*?)":"(?P<URL>.*?)"', page)
+ ]
+ self.logDebug("Found Streams", streams)
+ self.logDebug("Downloading", streams[0][0], streams[0][1])
+ self.download(streams[0][1])
diff --git a/pyload/plugins/hoster/SpeedLoadOrg.py b/pyload/plugins/hoster/SpeedLoadOrg.py
new file mode 100644
index 000000000..74753b029
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedLoadOrg.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class SpeedLoadOrg(DeadHoster):
+ __name__ = "SpeedLoadOrg"
+ __type__ = "hoster"
+ __version__ = "1.02"
+
+ __pattern__ = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)'
+
+ __description__ = """Speedload.org hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+getInfo = create_getInfo(SpeedLoadOrg)
diff --git a/pyload/plugins/hoster/SpeedfileCz.py b/pyload/plugins/hoster/SpeedfileCz.py
new file mode 100644
index 000000000..85df88d85
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedfileCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class SpeedfileCz(DeadHoster):
+ __name__ = "SpeedFileCz"
+ __type__ = "hoster"
+ __version__ = "0.32"
+
+ __pattern__ = r'http://(?:www\.)?speedfile.cz/.*'
+
+ __description__ = """Speedfile.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(SpeedfileCz)
diff --git a/pyload/plugins/hoster/SpeedyshareCom.py b/pyload/plugins/hoster/SpeedyshareCom.py
new file mode 100644
index 000000000..ed6fc443f
--- /dev/null
+++ b/pyload/plugins/hoster/SpeedyshareCom.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+
+# Testlink:
+# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class SpeedyshareCom(SimpleHoster):
+ __name__ = "SpeedyshareCom"
+ __type__ = "hoster"
+ __pattern__ = r"https?://(www\.)?(speedyshare.com|speedy.sh)/.*"
+ __version__ = "0.01"
+ __description__ = """speedyshare.com hoster plugin"""
+ __author_name__ = ("zapp-brannigan")
+ __author_mail__ = ("fuerst.reinje@web.de")
+
+ FILE_NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>'
+ FILE_SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>'
+ LINK_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload.png alt=\'Slow Download\' border=0'
+ FILE_OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>'
+ BASE_URL = 'www.speedyshare.com'
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ try:
+ dl_link = re.search(self.LINK_PATTERN, self.html).group(1)
+ self.logDebug("Link: " + dl_link)
+ except:
+ self.parseError("Unable to find download link")
+ self.download(self.BASE_URL + dl_link, disposition=True)
+ check = self.checkDownload({"is_html": re.compile("html")})
+ if check == "is_html":
+ self.fail("The downloaded file is html, maybe the plugin is out of date")
+
+
+getInfo = create_getInfo(SpeedyshareCom)
diff --git a/pyload/plugins/hoster/StreamCz.py b/pyload/plugins/hoster/StreamCz.py
new file mode 100644
index 000000000..7b20049be
--- /dev/null
+++ b/pyload/plugins/hoster/StreamCz.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+
+
+def getInfo(urls):
+ result = []
+
+ for url in urls:
+
+ html = getURL(url)
+ if re.search(StreamCz.OFFLINE_PATTERN, html):
+ # File offline
+ result.append((url, 0, 1, url))
+ else:
+ result.append((url, 0, 2, url))
+ yield result
+
+
+class StreamCz(Hoster):
+ __name__ = "StreamCz"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+.*'
+
+ __description__ = """Stream.cz hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<link rel="video_src" href="http://www.stream.cz/\w+/(\d+)-([^"]+)" />'
+ OFFLINE_PATTERN = r'<h1 class="commonTitle">Str.nku nebylo mo.n. nal.zt \(404\)</h1>'
+
+ CDN_PATTERN = r'<param name="flashvars" value="[^"]*&id=(?P<ID>\d+)(?:&cdnLQ=(?P<cdnLQ>\d*))?(?:&cdnHQ=(?P<cdnHQ>\d*))?(?:&cdnHD=(?P<cdnHD>\d*))?&'
+
+
+ def setup(self):
+ self.multiDL = True
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+
+ self.html = self.load(pyfile.url, decode=True)
+
+ if re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+
+ m = re.search(self.CDN_PATTERN, self.html)
+ if m is None:
+ self.fail("Parse error (CDN)")
+ cdn = m.groupdict()
+ self.logDebug(cdn)
+ for cdnkey in ("cdnHD", "cdnHQ", "cdnLQ"):
+ if cdnkey in cdn and cdn[cdnkey] > '':
+ cdnid = cdn[cdnkey]
+ break
+ else:
+ self.fail("Stream URL not found")
+
+ m = re.search(self.FILE_NAME_PATTERN, self.html)
+ if m is None:
+ self.fail("Parse error (NAME)")
+ pyfile.name = "%s-%s.%s.mp4" % (m.group(2), m.group(1), cdnkey[-2:])
+
+ download_url = "http://cdn-dispatcher.stream.cz/?id=" + cdnid
+ self.logInfo("STREAM (%s): %s" % (cdnkey[-2:], download_url))
+ self.download(download_url)
diff --git a/pyload/plugins/hoster/StreamcloudEu.py b/pyload/plugins/hoster/StreamcloudEu.py
new file mode 100644
index 000000000..0e36a047c
--- /dev/null
+++ b/pyload/plugins/hoster/StreamcloudEu.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import sleep
+
+from pyload.network.HTTPRequest import HTTPRequest
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class StreamcloudEu(XFileSharingPro):
+ __name__ = "StreamcloudEu"
+ __type__ = "hoster"
+ __version__ = "0.04"
+
+ __pattern__ = r'http://(?:www\.)?streamcloud\.eu/\S+'
+
+ __description__ = """Streamcloud.eu hoster plugin"""
+ __author_name__ = "seoester"
+ __author_mail__ = "seoester@googlemail.com"
+
+ HOSTER_NAME = "streamcloud.eu"
+
+ LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud.eu:?\d*/.*/video\.(mp4|flv))",'
+
+
+ def setup(self):
+ super(StreamcloudEu, self).setup()
+ self.multiDL = True
+
+ def getDownloadLink(self):
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ return m.group(1)
+
+ for i in xrange(5):
+ self.logDebug("Getting download link: #%d" % i)
+ data = self.getPostParameters()
+ httpRequest = HTTPRequest(options=self.req.options)
+ httpRequest.cj = self.req.cj
+ sleep(10)
+ self.html = httpRequest.load(self.pyfile.url, post=data, referer=False, cookies=True, decode=True)
+ self.header = httpRequest.header
+
+ m = re.search("Location\s*:\s*(.*)", self.header, re.I)
+ if m:
+ break
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ else:
+ if self.errmsg and 'captcha' in self.errmsg:
+ self.fail("No valid captcha code entered")
+ else:
+ self.fail("Download link not found")
+
+ return m.group(1)
+
+ def getPostParameters(self):
+ for i in xrange(3):
+ if not self.errmsg:
+ self.checkErrors()
+
+ if hasattr(self, "FORM_PATTERN"):
+ action, inputs = self.parseHtmlForm(self.FORM_PATTERN)
+ else:
+ action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
+
+ if not inputs:
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ if self.errmsg:
+ self.retry()
+ else:
+ self.parseError("Form not found")
+
+ self.logDebug(self.HOSTER_NAME, inputs)
+
+ if 'op' in inputs and inputs['op'] in ("download1", "download2", "download3"):
+ if "password" in inputs:
+ if self.passwords:
+ inputs['password'] = self.passwords.pop(0)
+ else:
+ self.fail("No or invalid passport")
+
+ if not self.premium:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1)) + 1
+ self.setWait(wait_time, False)
+ else:
+ wait_time = 0
+
+ self.captcha = self.handleCaptcha(inputs)
+
+ if wait_time:
+ self.wait()
+
+ self.errmsg = None
+ self.logDebug("getPostParameters {0}".format(i))
+ return inputs
+
+ else:
+ inputs['referer'] = self.pyfile.url
+
+ if self.premium:
+ inputs['method_premium'] = "Premium Download"
+ if 'method_free' in inputs:
+ del inputs['method_free']
+ else:
+ inputs['method_free'] = "Free Download"
+ if 'method_premium' in inputs:
+ del inputs['method_premium']
+
+ self.html = self.load(self.pyfile.url, post=inputs, ref=False)
+ self.errmsg = None
+
+ else:
+ self.parseError('FORM: %s' % (inputs['op'] if 'op' in inputs else 'UNKNOWN'))
+
+
+getInfo = create_getInfo(StreamcloudEu)
diff --git a/pyload/plugins/hoster/TurbobitNet.py b/pyload/plugins/hoster/TurbobitNet.py
new file mode 100644
index 000000000..1fbdf9e87
--- /dev/null
+++ b/pyload/plugins/hoster/TurbobitNet.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+
+import random
+import re
+import time
+
+from Crypto.Cipher import ARC4
+from binascii import hexlify, unhexlify
+from pycurl import HTTPHEADER
+from urllib import quote
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
+
+
+class TurbobitNet(SimpleHoster):
+ __name__ = "TurbobitNet"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?(turbobit.net|unextfiles.com)/(?!download/folder/)(?:download/free/)?(?P<ID>\w+).*'
+
+ __description__ = """Turbobit.net plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r"<span class='file-icon1[^>]*>(?P<N>[^<]+)</span>\s*\((?P<S>[^\)]+)\)\s*</h1>" #: long filenames are shortened
+ FILE_NAME_PATTERN = r'<meta name="keywords" content="\s+(?P<N>[^,]+)' #: full name but missing on page2
+ OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found'
+
+ FILE_URL_REPLACEMENTS = [(r"http://(?:www\.)?(turbobit.net|unextfiles.com)/(?:download/free/)?(?P<ID>\w+).*",
+ "http://turbobit.net/\g<ID>.html")]
+ SH_COOKIES = [(".turbobit.net", "user_lang", "en")]
+
+ LINK_PATTERN = r'(?P<url>/download/redirect/[^"\']+)'
+ LIMIT_WAIT_PATTERN = r'<div id="time-limit-text">\s*.*?<span id=\'timeout\'>(\d+)</span>'
+ CAPTCHA_KEY_PATTERN = r'src="http://api\.recaptcha\.net/challenge\?k=([^"]+)"'
+ CAPTCHA_SRC_PATTERN = r'<img alt="Captcha" src="(.*?)"'
+
+
+ def handleFree(self):
+ self.url = "http://turbobit.net/download/free/%s" % self.file_info['ID']
+ self.html = self.load(self.url)
+
+ rtUpdate = self.getRtUpdate()
+
+ self.solveCaptcha()
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.url = self.getDownloadUrl(rtUpdate)
+
+ self.wait()
+ self.html = self.load(self.url)
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With:"])
+ self.downloadFile()
+
+ def solveCaptcha(self):
+ for _ in xrange(5):
+ m = re.search(self.LIMIT_WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1))
+ self.wait(wait_time, wait_time > 60)
+ self.retry()
+
+ action, inputs = self.parseHtmlForm("action='#'")
+ if not inputs:
+ self.parseError("captcha form")
+ self.logDebug(inputs)
+
+ if inputs['captcha_type'] == 'recaptcha':
+ recaptcha = ReCaptcha(self)
+ m = re.search(self.CAPTCHA_KEY_PATTERN, self.html)
+ captcha_key = m.group(1) if m else '6LcTGLoSAAAAAHCWY9TTIrQfjUlxu6kZlTYP50_c'
+ inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(
+ captcha_key)
+ else:
+ m = re.search(self.CAPTCHA_SRC_PATTERN, self.html)
+ if m is None:
+ self.parseError('captcha')
+ captcha_url = m.group(1)
+ inputs['captcha_response'] = self.decryptCaptcha(captcha_url)
+
+ self.logDebug(inputs)
+ self.html = self.load(self.url, post=inputs)
+
+ if not "<div class='download-timer-header'>" in self.html:
+ self.invalidCaptcha()
+ else:
+ self.correctCaptcha()
+ break
+ else:
+ self.fail("Invalid captcha")
+
+ def getRtUpdate(self):
+ rtUpdate = self.getStorage("rtUpdate")
+ if not rtUpdate:
+ if self.getStorage("version") != self.__version__ or int(
+ self.getStorage("timestamp", 0)) + 86400000 < timestamp():
+ # that's right, we are even using jdownloader updates
+ rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js")
+ rtUpdate = self.decrypt(rtUpdate.splitlines()[1])
+ # but we still need to fix the syntax to work with other engines than rhino
+ rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{',
+ r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];', rtUpdate)
+ rtUpdate = re.sub(r"for\((\w+)=", r"for(var \1=", rtUpdate)
+
+ self.logDebug("rtUpdate")
+ self.setStorage("rtUpdate", rtUpdate)
+ self.setStorage("timestamp", timestamp())
+ self.setStorage("version", self.__version__)
+ else:
+ self.logError("Unable to download, wait for update...")
+ self.tempOffline()
+
+ return rtUpdate
+
+ def getDownloadUrl(self, rtUpdate):
+ self.req.http.lastURL = self.url
+
+ m = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html)
+ url = "http://turbobit.net%s%s" % (m.groups() if m else (
+ '/files/timeout.js?ver=', ''.join(random.choice('0123456789ABCDEF') for _ in xrange(32))))
+ fun = self.load(url)
+
+ self.setWait(65, False)
+
+ for b in [1, 3]:
+ self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % (
+ self.file_info['ID'], b, quote(fun), rtUpdate)
+
+ try:
+ out = self.js.eval(self.jscode)
+ self.logDebug("URL", self.js.engine, out)
+ if out.startswith('/download/'):
+ return "http://turbobit.net%s" % out.strip()
+ except Exception, e:
+ self.logError(e)
+ else:
+ if self.retries >= 2:
+ # retry with updated js
+ self.delStorage("rtUpdate")
+ self.retry()
+
+ def decrypt(self, data):
+ cipher = ARC4.new(hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0'))
+ return unhexlify(cipher.encrypt(unhexlify(data)))
+
+ def getLocalTimeString(self):
+ lt = time.localtime()
+ tz = time.altzone if lt.tm_isdst else time.timezone
+ return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600)
+
+ def handlePremium(self):
+ self.logDebug("Premium download as user %s" % self.user)
+ self.html = self.load(self.pyfile.url) # Useless in 0.5
+ self.downloadFile()
+
+ def downloadFile(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("download link")
+ self.url = "http://turbobit.net" + m.group('url')
+ self.logDebug(self.url)
+ self.download(self.url)
+
+
+getInfo = create_getInfo(TurbobitNet)
diff --git a/pyload/plugins/hoster/TurbouploadCom.py b/pyload/plugins/hoster/TurbouploadCom.py
new file mode 100644
index 000000000..eb5978145
--- /dev/null
+++ b/pyload/plugins/hoster/TurbouploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class TurbouploadCom(DeadHoster):
+ __name__ = "TurbouploadCom"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'http://(?:www\.)?turboupload.com/(\w+).*'
+
+ __description__ = """Turboupload.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+getInfo = create_getInfo(TurbouploadCom)
diff --git a/pyload/plugins/hoster/TusfilesNet.py b/pyload/plugins/hoster/TusfilesNet.py
new file mode 100644
index 000000000..0e01ec805
--- /dev/null
+++ b/pyload/plugins/hoster/TusfilesNet.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class TusfilesNet(XFileSharingPro):
+ __name__ = "TusfilesNet"
+ __type__ = "hoster"
+ __version__ = "0.03"
+
+ __pattern__ = r'https?://(?:www\.)?tusfiles\.net/(?P<ID>\w+)'
+
+ __description__ = """Tusfiles.net hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ HOSTER_NAME = "tusfiles.net"
+
+ FILE_INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.]+) (?P<U>\w+)\['
+ OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!'
+
+ SH_COOKIES = [(".tusfiles.net", "lang", "english")]
+
+
+ def setup(self):
+ self.multiDL = False
+ self.chunkLimit = -1
+ self.resumeDownload = True
+
+
+getInfo = create_getInfo(TusfilesNet)
diff --git a/pyload/plugins/hoster/TwoSharedCom.py b/pyload/plugins/hoster/TwoSharedCom.py
new file mode 100644
index 000000000..108d31c6f
--- /dev/null
+++ b/pyload/plugins/hoster/TwoSharedCom.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class TwoSharedCom(SimpleHoster):
+ __name__ = "TwoSharedCom"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = r'http://(?:www\.)?2shared.com/(account/)?(download|get|file|document|photo|video|audio)/.*'
+
+ __description__ = """2Shared.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<h1>(?P<N>.*)</h1>'
+ FILE_SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[0-9,.]+) (?P<U>[kKMG])i?B'
+ OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.'
+
+ LINK_PATTERN = r"window.location ='([^']+)';"
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('Download link')
+ link = m.group(1)
+ self.logDebug("Download URL %s" % link)
+
+ self.download(link)
+
+
+getInfo = create_getInfo(TwoSharedCom)
diff --git a/pyload/plugins/hoster/UlozTo.py b/pyload/plugins/hoster/UlozTo.py
new file mode 100644
index 000000000..c3957aaa0
--- /dev/null
+++ b/pyload/plugins/hoster/UlozTo.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+
+import re
+import time
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+def convertDecimalPrefix(m):
+ # decimal prefixes used in filesize and traffic
+ return ("%%.%df" % {'k': 3, 'M': 6, 'G': 9}[m.group(2)] % float(m.group(1))).replace('.', '')
+
+
+class UlozTo(SimpleHoster):
+ __name__ = "UlozTo"
+ __type__ = "hoster"
+ __version__ = "0.98"
+
+ __pattern__ = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj.cz|zachowajto.pl)/(?:live/)?(?P<id>\w+/[^/?]*)'
+
+ __description__ = """Uloz.to hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>'
+ FILE_NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz.to</title>'
+ FILE_SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[0-9.]+\s[kMG]?B)</span>'
+ OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>'
+
+ FILE_SIZE_REPLACEMENTS = [('([0-9.]+)\s([kMG])B', convertDecimalPrefix)]
+ FILE_URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "www.ulozto.net")]
+
+ ADULT_PATTERN = r'<form action="(?P<link>[^\"]*)" method="post" id="frm-askAgeForm">'
+ PASSWD_PATTERN = r'<div class="passwordProtectedFile">'
+ VIPLINK_PATTERN = r'<a href="[^"]*\?disclaimer=1" class="linkVip">'
+ FREE_URL_PATTERN = r'<div class="freeDownloadForm"><form action="([^"]+)"'
+ PREMIUM_URL_PATTERN = r'<div class="downloadForm"><form action="([^"]+)"'
+ TOKEN_PATTERN = r'<input type="hidden" name="_token_" id="[^\"]*" value="(?P<token>[^\"]*)" />'
+
+
+ def setup(self):
+ self.multiDL = self.premium
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ pyfile.url = re.sub(r"(?<=http://)([^/]+)", "www.ulozto.net", pyfile.url)
+ self.html = self.load(pyfile.url, decode=True, cookies=True)
+
+ if re.search(self.ADULT_PATTERN, self.html):
+ self.logInfo("Adult content confirmation needed. Proceeding..")
+
+ m = re.search(self.TOKEN_PATTERN, self.html)
+ if m is None:
+ self.parseError('TOKEN')
+ token = m.group(1)
+
+ self.html = self.load(pyfile.url, get={"do": "askAgeForm-submit"},
+ post={"agree": "Confirm", "_token_": token}, cookies=True)
+
+ passwords = self.getPassword().splitlines()
+ while self.PASSWD_PATTERN in self.html:
+ if passwords:
+ password = passwords.pop(0)
+ self.logInfo("Password protected link, trying " + password)
+ self.html = self.load(pyfile.url, get={"do": "passwordProtectedForm-submit"},
+ post={"password": password, "password_send": 'Send'}, cookies=True)
+ else:
+ self.fail("No or incorrect password")
+
+ if re.search(self.VIPLINK_PATTERN, self.html):
+ self.html = self.load(pyfile.url, get={"disclaimer": "1"})
+
+ self.file_info = self.getFileInfo()
+
+ if self.premium and self.checkTrafficLeft():
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ self.doCheckDownload()
+
+ def handleFree(self):
+ action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"')
+ if not action or not inputs:
+ self.parseError("free download form")
+
+ self.logDebug('inputs.keys() = ' + str(inputs.keys()))
+ # get and decrypt captcha
+ if all(key in inputs for key in ("captcha_value", "captcha_id", "captcha_key")):
+ # Old version - last seen 9.12.2013
+ self.logDebug('Using "old" version')
+
+ captcha_value = self.decryptCaptcha("http://img.uloz.to/captcha/%s.png" % inputs['captcha_id'])
+ self.logDebug('CAPTCHA ID: ' + inputs['captcha_id'] + ", CAPTCHA VALUE: " + captcha_value)
+
+ inputs.update({'captcha_id': inputs['captcha_id'], 'captcha_key': inputs['captcha_key'], 'captcha_value': captcha_value})
+
+ elif all(key in inputs for key in ("captcha_value", "timestamp", "salt", "hash")):
+ # New version - better to get new parameters (like captcha reload) because of image url - since 6.12.2013
+ self.logDebug('Using "new" version')
+
+ xapca = self.load("http://www.ulozto.net/reloadXapca.php", get={"rnd": str(int(time.time()))})
+ self.logDebug('xapca = ' + str(xapca))
+
+ data = json_loads(xapca)
+ captcha_value = self.decryptCaptcha(str(data['image']))
+ self.logDebug("CAPTCHA HASH: " + data['hash'] + ", CAPTCHA SALT: " + str(data['salt']) + ", CAPTCHA VALUE: " + captcha_value)
+
+ inputs.update({'timestamp': data['timestamp'], 'salt': data['salt'], 'hash': data['hash'], 'captcha_value': captcha_value})
+ else:
+ self.parseError("CAPTCHA form changed")
+
+ self.multiDL = True
+ self.download("http://www.ulozto.net" + action, post=inputs, cookies=True, disposition=True)
+
+ def handlePremium(self):
+ self.download(self.pyfile.url + "?do=directDownload", disposition=True)
+ #parsed_url = self.findDownloadURL(premium=True)
+ #self.download(parsed_url, post={"download": "Download"})
+
+ def findDownloadURL(self, premium=False):
+ msg = "%s link" % ("Premium" if premium else "Free")
+ m = re.search(self.PREMIUM_URL_PATTERN if premium else self.FREE_URL_PATTERN, self.html)
+ if m is None:
+ self.parseError(msg)
+ parsed_url = "http://www.ulozto.net" + m.group(1)
+ self.logDebug("%s: %s" % (msg, parsed_url))
+ return parsed_url
+
+ def doCheckDownload(self):
+ check = self.checkDownload({
+ "wrong_captcha": re.compile(r'<ul class="error">\s*<li>Error rewriting the text.</li>'),
+ "offline": re.compile(self.OFFLINE_PATTERN),
+ "passwd": self.PASSWD_PATTERN,
+ "server_error": 'src="http://img.ulozto.cz/error403/vykricnik.jpg"', # paralell dl, server overload etc.
+ "not_found": "<title>UloÅŸ.to</title>"
+ })
+
+ if check == "wrong_captcha":
+ #self.delStorage("captcha_id")
+ #self.delStorage("captcha_text")
+ self.invalidCaptcha()
+ self.retry(reason="Wrong captcha code")
+ elif check == "offline":
+ self.offline()
+ elif check == "passwd":
+ self.fail("Wrong password")
+ elif check == "server_error":
+ self.logError("Server error, try downloading later")
+ self.multiDL = False
+ self.wait(1 * 60 * 60, True)
+ self.retry()
+ elif check == "not_found":
+ self.fail("Server error - file not downloadable")
+
+
+getInfo = create_getInfo(UlozTo)
diff --git a/pyload/plugins/hoster/UloziskoSk.py b/pyload/plugins/hoster/UloziskoSk.py
new file mode 100644
index 000000000..f78a6e29a
--- /dev/null
+++ b/pyload/plugins/hoster/UloziskoSk.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UloziskoSk(SimpleHoster):
+ __name__ = "UloziskoSk"
+ __type__ = "hoster"
+ __version__ = "0.23"
+
+ __pattern__ = r'http://(?:www\.)?ulozisko.sk/.*'
+
+ __description__ = """Ulozisko.sk hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>'
+ FILE_SIZE_PATTERN = ur'Veğkosť súboru: <strong>(?P<S>[0-9.]+) (?P<U>[kKMG])i?B</strong><br />'
+ OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÎvodov:</span>'
+
+ LINK_PATTERN = r'<form name = "formular" action = "([^"]+)" method = "post">'
+ ID_PATTERN = r'<input type = "hidden" name = "id" value = "([^"]+)" />'
+ CAPTCHA_PATTERN = r'<img src="(/obrazky/obrazky.php\?fid=[^"]+)" alt="" />'
+ IMG_PATTERN = ur'<strong>PRE ZVÄČŠENIE KLIKNITE NA OBRÁZOK</strong><br /><a href = "([^"]+)">'
+
+
+ def process(self, pyfile):
+ self.html = self.load(pyfile.url, decode=True)
+ self.getFileInfo()
+
+ m = re.search(self.IMG_PATTERN, self.html)
+ if m:
+ url = "http://ulozisko.sk" + m.group(1)
+ self.download(url)
+ else:
+ self.handleFree()
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('URL')
+ parsed_url = 'http://www.ulozisko.sk' + m.group(1)
+
+ m = re.search(self.ID_PATTERN, self.html)
+ if m is None:
+ self.parseError('ID')
+ id = m.group(1)
+
+ self.logDebug('URL:' + parsed_url + ' ID:' + id)
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.parseError('CAPTCHA')
+ captcha_url = 'http://www.ulozisko.sk' + m.group(1)
+
+ captcha = self.decryptCaptcha(captcha_url, cookies=True)
+
+ self.logDebug('CAPTCHA_URL:' + captcha_url + ' CAPTCHA:' + captcha)
+
+ self.download(parsed_url, post={
+ "antispam": captcha,
+ "id": id,
+ "name": self.pyfile.name,
+ "but": "++++STIAHNI+S%DABOR++++"
+ })
+
+
+getInfo = create_getInfo(UloziskoSk)
diff --git a/pyload/plugins/hoster/UnibytesCom.py b/pyload/plugins/hoster/UnibytesCom.py
new file mode 100644
index 000000000..1541265d9
--- /dev/null
+++ b/pyload/plugins/hoster/UnibytesCom.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UnibytesCom(SimpleHoster):
+ __name__ = "UnibytesCom"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?unibytes\.com/[a-zA-Z0-9-._ ]{11}B'
+
+ __description__ = """UniBytes.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_INFO_PATTERN = r'<span[^>]*?id="fileName"[^>]*>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)'
+
+ HOSTER_NAME = "unibytes.com"
+ WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec'
+ LINK_PATTERN = r'<a href="([^"]+)">Download</a>'
+
+
+ def handleFree(self):
+ domain = "http://www." + self.HOSTER_NAME
+ action, post_data = self.parseHtmlForm('id="startForm"')
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+
+ for _ in xrange(8):
+ self.logDebug(action, post_data)
+ self.html = self.load(domain + action, post=post_data)
+
+ m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I)
+ if m:
+ url = m.group(1)
+ break
+
+ if '>Somebody else is already downloading using your IP-address<' in self.html:
+ self.wait(10 * 60, True)
+ self.retry()
+
+ if post_data['step'] == 'last':
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m:
+ url = m.group(1)
+ self.correctCaptcha()
+ break
+ else:
+ self.invalidCaptcha()
+
+ last_step = post_data['step']
+ action, post_data = self.parseHtmlForm('id="stepForm"')
+
+ if last_step == 'timer':
+ m = re.search(self.WAIT_PATTERN, self.html)
+ self.wait(int(m.group(1)) if m else 60, False)
+ elif last_step in ("captcha", "last"):
+ post_data['captcha'] = self.decryptCaptcha(domain + '/captcha.jpg')
+ else:
+ self.fail("No valid captcha code entered")
+
+ self.logDebug('Download link: ' + url)
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+ self.download(url)
+
+
+getInfo = create_getInfo(UnibytesCom)
diff --git a/pyload/plugins/hoster/UnrestrictLi.py b/pyload/plugins/hoster/UnrestrictLi.py
new file mode 100644
index 000000000..c0c1e3965
--- /dev/null
+++ b/pyload/plugins/hoster/UnrestrictLi.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from datetime import datetime, timedelta
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+
+
+def secondsToMidnight(gmt=0):
+ now = datetime.utcnow() + timedelta(hours=gmt)
+ if now.hour is 0 and now.minute < 10:
+ midnight = now
+ else:
+ midnight = now + timedelta(days=1)
+ midnight = midnight.replace(hour=0, minute=10, second=0, microsecond=0)
+ return int((midnight - now).total_seconds())
+
+
+class UnrestrictLi(Hoster):
+ __name__ = "UnrestrictLi"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?(unrestrict|unr)\.li'
+
+ __description__ = """Unrestrict.li hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def setup(self):
+ self.chunkLimit = 16
+ self.resumeDownload = True
+
+ def process(self, pyfile):
+ if re.match(self.__pattern__, pyfile.url):
+ new_url = pyfile.url
+ elif not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "Unrestrict.li")
+ self.fail("No Unrestrict.li account provided")
+ else:
+ self.logDebug("Old URL: %s" % pyfile.url)
+ for _ in xrange(5):
+ page = self.req.load('https://unrestrict.li/unrestrict.php',
+ post={'link': pyfile.url, 'domain': 'long'})
+ self.logDebug("JSON data: " + page)
+ if page != '':
+ break
+ else:
+ self.logInfo("Unable to get API data, waiting 1 minute and retry")
+ self.retry(5, 60, "Unable to get API data")
+
+ if 'Expired session' in page or ("You are not allowed to "
+ "download from this host" in page and self.premium):
+ self.account.relogin(self.user)
+ self.retry()
+ elif "File offline" in page:
+ self.offline()
+ elif "You are not allowed to download from this host" in page:
+ self.fail("You are not allowed to download from this host")
+ elif "You have reached your daily limit for this host" in page:
+ self.logWarning("Reached daily limit for this host")
+ self.retry(5, secondsToMidnight(gmt=2), "Daily limit for this host reached")
+ elif "ERROR_HOSTER_TEMPORARILY_UNAVAILABLE" in page:
+ self.logInfo("Hoster temporarily unavailable, waiting 1 minute and retry")
+ self.retry(5, 60, "Hoster is temporarily unavailable")
+ page = json_loads(page)
+ new_url = page.keys()[0]
+ self.api_data = page[new_url]
+
+ if new_url != pyfile.url:
+ self.logDebug("New URL: " + new_url)
+
+ if hasattr(self, 'api_data'):
+ self.setNameSize()
+
+ self.download(new_url, disposition=True)
+
+ if self.getConfig("history"):
+ self.load("https://unrestrict.li/history/&delete=all")
+ self.logInfo("Download history deleted")
+
+ def setNameSize(self):
+ if 'name' in self.api_data:
+ self.pyfile.name = self.api_data['name']
+ if 'size' in self.api_data:
+ self.pyfile.size = self.api_data['size']
diff --git a/pyload/plugins/hoster/UploadStationCom.py b/pyload/plugins/hoster/UploadStationCom.py
new file mode 100644
index 000000000..4671b2dc5
--- /dev/null
+++ b/pyload/plugins/hoster/UploadStationCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class UploadStationCom(DeadHoster):
+ __name__ = "UploadStationCom"
+ __type__ = "hoster"
+ __version__ = "0.52"
+
+ __pattern__ = r'http://(?:www\.)?uploadstation\.com/file/(?P<id>[A-Za-z0-9]+)'
+
+ __description__ = """UploadStation.com hoster plugin"""
+ __author_name__ = ("fragonib", "zoidberg")
+ __author_mail__ = ("fragonib[AT]yahoo[DOT]es", "zoidberg@mujmail.cz")
+
+
+getInfo = create_getInfo(UploadStationCom)
diff --git a/pyload/plugins/hoster/UploadedTo.py b/pyload/plugins/hoster/UploadedTo.py
new file mode 100644
index 000000000..db620eea6
--- /dev/null
+++ b/pyload/plugins/hoster/UploadedTo.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://ul.to/044yug9o
+# http://ul.to/gzfhd0xs
+
+import re
+
+from time import sleep
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.Plugin import chunks
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.utils import html_unescape, parseFileSize
+
+
+key = "bGhGMkllZXByd2VEZnU5Y2NXbHhYVlZ5cEE1bkEzRUw=".decode('base64')
+
+
+def getID(url):
+ """ returns id from file url"""
+ m = re.match(UploadedTo.__pattern__, url)
+ return m.group('ID')
+
+
+def getAPIData(urls):
+ post = {"apikey": key}
+
+ idMap = {}
+
+ for i, url in enumerate(urls):
+ id = getID(url)
+ post['id_%s' % i] = id
+ idMap[id] = url
+
+ for _ in xrange(5):
+ api = unicode(getURL("http://uploaded.net/api/filemultiple", post=post, decode=False), 'iso-8859-1')
+ if api != "can't find request":
+ break
+ else:
+ sleep(3)
+
+ result = {}
+
+ if api:
+ for line in api.splitlines():
+ data = line.split(",", 4)
+ if data[1] in idMap:
+ result[data[1]] = (data[0], data[2], data[4], data[3], idMap[data[1]])
+
+ return result
+
+
+def parseFileInfo(self, url='', html=''):
+ if not html and hasattr(self, "html"):
+ html = self.html
+
+ name = url
+ size = 0
+ fileid = None
+
+ if re.search(self.OFFLINE_PATTERN, html):
+ # File offline
+ status = 1
+ else:
+ m = re.search(self.FILE_INFO_PATTERN, html)
+ if m:
+ name, fileid = html_unescape(m.group('N')), m.group('ID')
+ size = parseFileSize(m.group('S'))
+ status = 2
+ else:
+ status = 3
+
+ return name, size, status, fileid
+
+
+def getInfo(urls):
+ for chunk in chunks(urls, 80):
+ result = []
+
+ api = getAPIData(chunk)
+
+ for data in api.itervalues():
+ if data[0] == "online":
+ result.append((html_unescape(data[2]), data[1], 2, data[4]))
+
+ elif data[0] == "offline":
+ result.append((data[4], 0, 1, data[4]))
+
+ yield result
+
+
+class UploadedTo(Hoster):
+ __name__ = "UploadedTo"
+ __type__ = "hoster"
+ __version__ = "0.73"
+
+ __pattern__ = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)'
+
+ __description__ = """Uploaded.net hoster plugin"""
+ __author_name__ = ("spoob", "mkaay", "zoidberg", "netpok", "stickell")
+ __author_mail__ = ("spoob@pyload.org", "mkaay@mkaay.de", "zoidberg@mujmail.cz",
+ "netpok@gmail.com", "l.stickell@yahoo.it")
+
+ FILE_INFO_PATTERN = r'<a href="file/(?P<ID>\w+)" id="filename">(?P<N>[^<]+)</a> &nbsp;\s*<small[^>]*>(?P<S>[^<]+)</small>'
+ OFFLINE_PATTERN = r'<small class="cL">Error: 404</small>'
+ DL_LIMIT_PATTERN = r'You have reached the max. number of possible free downloads for this hour'
+
+
+ def setup(self):
+ self.multiDL = self.resumeDownload = self.premium
+ self.chunkLimit = 1 # critical problems with more chunks
+
+ self.fileID = getID(self.pyfile.url)
+ self.pyfile.url = "http://uploaded.net/file/%s" % self.fileID
+
+ def process(self, pyfile):
+ self.load("http://uploaded.net/language/en", just_header=True)
+
+ api = getAPIData([pyfile.url])
+
+ # TODO: fallback to parse from site, because api sometimes delivers wrong status codes
+
+ if not api:
+ self.logWarning("No response for API call")
+
+ self.html = unicode(self.load(pyfile.url, decode=False), 'iso-8859-1')
+ name, size, status, self.fileID = parseFileInfo(self)
+ self.logDebug(name, size, status, self.fileID)
+ if status == 1:
+ self.offline()
+ elif status == 2:
+ pyfile.name, pyfile.size = name, size
+ else:
+ self.fail('Parse error - file info')
+ elif api == 'Access denied':
+ self.fail(_("API key invalid"))
+
+ else:
+ if self.fileID not in api:
+ self.offline()
+
+ self.data = api[self.fileID]
+ if self.data[0] != "online":
+ self.offline()
+
+ pyfile.name = html_unescape(self.data[2])
+
+ # pyfile.name = self.get_file_name()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def handlePremium(self):
+ info = self.account.getAccountInfo(self.user, True)
+ self.logDebug("%(name)s: Use Premium Account (%(left)sGB left)" % {"name": self.__name__,
+ "left": info['trafficleft'] / 1024 / 1024})
+ if int(self.data[1]) / 1024 > info['trafficleft']:
+ self.logInfo(_("%s: Not enough traffic left" % self.__name__))
+ self.account.empty(self.user)
+ self.resetAccount()
+ self.fail(_("Traffic exceeded"))
+
+ header = self.load("http://uploaded.net/file/%s" % self.fileID, just_header=True)
+ if "location" in header:
+ #Direct download
+ print "Direct Download: " + header['location']
+ self.download(header['location'])
+ else:
+ #Indirect download
+ self.html = self.load("http://uploaded.net/file/%s" % self.fileID)
+ m = re.search(r'<div class="tfree".*\s*<form method="post" action="(.*?)"', self.html)
+ if m is None:
+ self.fail("Download URL not m. Try to enable direct downloads.")
+ url = m.group(1)
+ print "Premium URL: " + url
+ self.download(url, post={})
+
+ def handleFree(self):
+ self.html = self.load(self.pyfile.url, decode=True)
+
+ if 'var free_enabled = false;' in self.html:
+ self.logError("Free-download capacities exhausted.")
+ self.retry(max_tries=24, wait_time=5 * 60)
+
+ m = re.search(r"Current waiting period: <span>(\d+)</span> seconds", self.html)
+ if m is None:
+ self.fail("File not downloadable for free users")
+ self.setWait(int(m.group(1)))
+
+ js = self.load("http://uploaded.net/js/download.js", decode=True)
+
+ challengeId = re.search(r'Recaptcha\.create\("([^"]+)', js)
+
+ url = "http://uploaded.net/io/ticket/captcha/%s" % self.fileID
+ downloadURL = ""
+
+ for _ in xrange(5):
+ re_captcha = ReCaptcha(self)
+ challenge, result = re_captcha.challenge(challengeId.group(1))
+ options = {"recaptcha_challenge_field": challenge, "recaptcha_response_field": result}
+ self.wait()
+
+ result = self.load(url, post=options)
+ self.logDebug("result: %s" % result)
+
+ if "limit-size" in result:
+ self.fail("File too big for free download")
+ elif "limit-slot" in result: # Temporary restriction so just wait a bit
+ self.setWait(30 * 60, True)
+ self.wait()
+ self.retry()
+ elif "limit-parallel" in result:
+ self.fail("Cannot download in parallel")
+ elif self.DL_LIMIT_PATTERN in result: # limit-dl
+ self.setWait(3 * 60 * 60, True)
+ self.wait()
+ self.retry()
+ elif '"err":"captcha"' in result:
+ self.logError("ul.net captcha is disabled")
+ self.invalidCaptcha()
+ elif "type:'download'" in result:
+ self.correctCaptcha()
+ downloadURL = re.search("url:'([^']+)", result).group(1)
+ break
+ else:
+ self.fail("Unknown error '%s'" % result)
+
+ if not downloadURL:
+ self.fail("No Download url retrieved/all captcha attempts failed")
+
+ self.download(downloadURL, disposition=True)
+ check = self.checkDownload({"limit-dl": self.DL_LIMIT_PATTERN})
+ if check == "limit-dl":
+ self.setWait(3 * 60 * 60, True)
+ self.wait()
+ self.retry()
diff --git a/pyload/plugins/hoster/UploadheroCom.py b/pyload/plugins/hoster/UploadheroCom.py
new file mode 100644
index 000000000..f1f893c30
--- /dev/null
+++ b/pyload/plugins/hoster/UploadheroCom.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://uploadhero.co/dl/wQBRAVSM
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UploadheroCom(SimpleHoster):
+ __name__ = "UploadheroCom"
+ __type__ = "hoster"
+ __version__ = "0.15"
+
+ __pattern__ = r'http://(?:www\.)?uploadhero\.com?/dl/\w+'
+
+ __description__ = """UploadHero.co plugin"""
+ __author_name__ = ("mcmyst", "zoidberg")
+ __author_mail__ = ("mcmyst@hotmail.fr", "zoidberg@mujmail.cz")
+
+ FILE_NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.*?)</div>'
+ FILE_SIZE_PATTERN = r'Taille du fichier : </span><strong>(?P<S>.*?)</strong>'
+ OFFLINE_PATTERN = r'<p class="titre_dl_2">|<div class="raison"><strong>Le lien du fichier ci-dessus n\'existe plus.'
+
+ SH_COOKIES = [(".uploadhero.co", "lang", "en")]
+
+ IP_BLOCKED_PATTERN = r'href="(/lightbox_block_download.php\?min=.*?)"'
+ IP_WAIT_PATTERN = r'<span id="minutes">(\d+)</span>.*\s*<span id="seconds">(\d+)</span>'
+
+ CAPTCHA_PATTERN = r'"(/captchadl\.php\?[a-z0-9]+)"'
+ FREE_URL_PATTERN = r'var magicomfg = \'<a href="(http://[^<>"]*?)"|"(http://storage\d+\.uploadhero\.co/\?d=[A-Za-z0-9]+/[^<>"/]+)"'
+ PREMIUM_URL_PATTERN = r'<a href="([^"]+)" id="downloadnow"'
+
+
+ def handleFree(self):
+ self.checkErrors()
+
+ m = re.search(self.CAPTCHA_PATTERN, self.html)
+ if m is None:
+ self.parseError("Captcha URL")
+ captcha_url = "http://uploadhero.co" + m.group(1)
+
+ for _ in xrange(5):
+ captcha = self.decryptCaptcha(captcha_url)
+ self.html = self.load(self.pyfile.url, get={"code": captcha})
+ m = re.search(self.FREE_URL_PATTERN, self.html)
+ if m:
+ self.correctCaptcha()
+ download_url = m.group(1) or m.group(2)
+ break
+ else:
+ self.invalidCaptcha()
+ else:
+ self.fail("No valid captcha code entered")
+
+ self.download(download_url)
+
+ def handlePremium(self):
+ self.logDebug("%s: Use Premium Account" % self.__name__)
+ self.html = self.load(self.pyfile.url)
+ link = re.search(self.PREMIUM_URL_PATTERN, self.html).group(1)
+ self.logDebug("Downloading link : '%s'" % link)
+ self.download(link)
+
+ def checkErrors(self):
+ m = re.search(self.IP_BLOCKED_PATTERN, self.html)
+ if m:
+ self.html = self.load("http://uploadhero.co%s" % m.group(1))
+
+ m = re.search(self.IP_WAIT_PATTERN, self.html)
+ wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 5 * 60
+ self.wait(wait_time, True)
+ self.retry()
+
+
+getInfo = create_getInfo(UploadheroCom)
diff --git a/pyload/plugins/hoster/UploadingCom.py b/pyload/plugins/hoster/UploadingCom.py
new file mode 100644
index 000000000..882cb863f
--- /dev/null
+++ b/pyload/plugins/hoster/UploadingCom.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, timestamp
+
+
+class UploadingCom(SimpleHoster):
+ __name__ = "UploadingCom"
+ __type__ = "hoster"
+ __version__ = "0.36"
+
+ __pattern__ = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>[\w\d]+)'
+
+ __description__ = """Uploading.com hoster plugin"""
+ __author_name__ = ("jeix", "mkaay", "zoidberg")
+ __author_mail__ = ("jeix@hasnomail.de", "mkaay@mkaay.de", "zoidberg@mujmail.cz")
+
+ FILE_NAME_PATTERN = r'id="file_title">(?P<N>.+)</'
+ FILE_SIZE_PATTERN = r'size tip_container">(?P<S>[\d.]+) (?P<U>\w+)<'
+ OFFLINE_PATTERN = r'(Page|file) not found'
+
+
+ def process(self, pyfile):
+ # set lang to english
+ self.req.cj.setCookie(".uploading.com", "lang", "1")
+ self.req.cj.setCookie(".uploading.com", "language", "1")
+ self.req.cj.setCookie(".uploading.com", "setlang", "en")
+ self.req.cj.setCookie(".uploading.com", "_lang", "en")
+
+ if not "/get/" in pyfile.url:
+ pyfile.url = pyfile.url.replace("/files", "/files/get")
+
+ self.html = self.load(pyfile.url, decode=True)
+ self.file_info = self.getFileInfo()
+
+ if self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def handlePremium(self):
+ postData = {'action': 'get_link',
+ 'code': self.file_info['ID'],
+ 'pass': 'undefined'}
+
+ self.html = self.load('http://uploading.com/files/get/?JsHttpRequest=%d-xml' % timestamp(), post=postData)
+ url = re.search(r'"link"\s*:\s*"(.*?)"', self.html)
+ if url:
+ url = url.group(1).replace("\\/", "/")
+ self.download(url)
+
+ raise Exception("Plugin defect.")
+
+ def handleFree(self):
+ m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html)
+ if m:
+ self.pyfile.error = m.group(1)
+ self.logWarning(self.pyfile.error)
+ self.retry(max_tries=6, wait_time=6 * 60 * 60 if m.group(2) else 15 * 60, reason=self.pyfile.error)
+
+ ajax_url = "http://uploading.com/files/get/?ajax"
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = self.pyfile.url
+
+ response = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.file_info['ID']}))
+ if 'answer' in response and 'wait_time' in response['answer']:
+ wait_time = int(response['answer']['wait_time'])
+ self.logInfo("%s: Waiting %d seconds." % (self.__name__, wait_time))
+ self.wait(wait_time)
+ else:
+ self.parseError("AJAX/WAIT")
+
+ response = json_loads(
+ self.load(ajax_url, post={'action': 'get_link', 'code': self.file_info['ID'], 'pass': 'false'}))
+ if 'answer' in response and 'link' in response['answer']:
+ url = response['answer']['link']
+ else:
+ self.parseError("AJAX/URL")
+
+ self.html = self.load(url)
+ m = re.search(r'<form id="file_form" action="(.*?)"', self.html)
+ if m:
+ url = m.group(1)
+ else:
+ self.parseError("URL")
+
+ self.download(url)
+
+ check = self.checkDownload({"html": re.compile("\A<!DOCTYPE html PUBLIC")})
+ if check == "html":
+ self.logWarning("Redirected to a HTML page, wait 10 minutes and retry")
+ self.wait(10 * 60, True)
+
+
+getInfo = create_getInfo(UploadingCom)
diff --git a/pyload/plugins/hoster/UpstoreNet.py b/pyload/plugins/hoster/UpstoreNet.py
new file mode 100644
index 000000000..bd084612c
--- /dev/null
+++ b/pyload/plugins/hoster/UpstoreNet.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.CaptchaService import ReCaptcha
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class UpstoreNet(SimpleHoster):
+ __name__ = "UpstoreNet"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?upstore\.net/'
+
+ __description__ = """Upstore.Net File Download Hoster"""
+ __author_name__ = "igel"
+ __author_mail__ = "igelkun@myopera.com"
+
+ FILE_INFO_PATTERN = r'<div class="comment">.*?</div>\s*\n<h2 style="margin:0">(?P<N>.*?)</h2>\s*\n<div class="comment">\s*\n\s*(?P<S>[\d.]+) (?P<U>\w+)'
+ OFFLINE_PATTERN = r'<span class="error">File not found</span>'
+
+ WAIT_PATTERN = r'var sec = (\d+)'
+ CHASH_PATTERN = r'<input type="hidden" name="hash" value="([^"]*)">'
+ LINK_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>'
+
+
+ def handleFree(self):
+ # STAGE 1: get link to continue
+ m = re.search(self.CHASH_PATTERN, self.html)
+ if m is None:
+ self.parseError("could not detect hash")
+ chash = m.group(1)
+ self.logDebug("read hash " + chash)
+ # continue to stage2
+ post_data = {'hash': chash, 'free': 'Slow download'}
+ self.html = self.load(self.pyfile.url, post=post_data, decode=True)
+
+ # STAGE 2: solv captcha and wait
+ # first get the infos we need: recaptcha key and wait time
+ recaptcha = ReCaptcha(self)
+ if not recaptcha.detect_key(self.html):
+ self.parseError("could not find recaptcha pattern")
+ self.logDebug("using captcha key " + recaptcha.recaptcha_key)
+ # try the captcha 5 times
+ for i in xrange(5):
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m is None:
+ self.parseError("could not find wait pattern")
+ wait_time = m.group(1)
+
+ # then, do the waiting
+ self.wait(wait_time)
+
+ # then, handle the captcha
+ challenge, code = recaptcha.challenge()
+ post_data['recaptcha_challenge_field'] = challenge
+ post_data['recaptcha_response_field'] = code
+
+ self.html = self.load(self.pyfile.url, post=post_data, decode=True)
+
+ # STAGE 3: get direct link
+ m = re.search(self.LINK_PATTERN, self.html, re.DOTALL)
+ if m:
+ break
+
+ if m is None:
+ self.parseError("could not detect direct link")
+
+ direct = m.group(1)
+ self.logDebug('found direct link: ' + direct)
+ self.download(direct, disposition=True)
+
+
+getInfo = create_getInfo(UpstoreNet)
diff --git a/pyload/plugins/hoster/UptoboxCom.py b/pyload/plugins/hoster/UptoboxCom.py
new file mode 100644
index 000000000..8fd5e6fa7
--- /dev/null
+++ b/pyload/plugins/hoster/UptoboxCom.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+from pyload.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
+from pyload.utils import html_unescape
+
+
+class UptoboxCom(XFileSharingPro):
+ __name__ = "UptoboxCom"
+ __type__ = "hoster"
+ __version__ = "0.09"
+
+ __pattern__ = r'https?://(?:www\.)?uptobox\.com/\w+'
+
+ __description__ = """Uptobox.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ HOSTER_NAME = "uptobox.com"
+
+ FILE_INFO_PATTERN = r'"para_title">(?P<N>.+) \((?P<S>[\d\.]+) (?P<U>\w+)\)'
+ OFFLINE_PATTERN = r'>(File not found|Access Denied|404 Not Found)'
+ TEMP_OFFLINE_PATTERN = r'>This server is in maintenance mode'
+
+ WAIT_PATTERN = r'>(\d+)</span> seconds<'
+
+ LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"'
+
+
+ def handleCaptcha(self, inputs):
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = SolveMedia(self)
+ inputs['adcopy_challenge'], inputs['adcopy_response'] = captcha.challenge(captcha_key)
+ return 4
+ else:
+ m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
+ if m:
+ captcha_url = m.group(1)
+ inputs['code'] = self.decryptCaptcha(captcha_url)
+ return 2
+ else:
+ m = re.search(self.CAPTCHA_DIV_PATTERN, self.html, re.DOTALL)
+ if m:
+ captcha_div = m.group(1)
+ self.logDebug(captcha_div)
+ numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>',
+ html_unescape(captcha_div))
+ inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
+ self.logDebug("CAPTCHA", inputs['code'], numerals)
+ return 3
+ else:
+ m = re.search(self.RECAPTCHA_URL_PATTERN, self.html)
+ if m:
+ recaptcha_key = unquote(m.group(1))
+ self.logDebug("RECAPTCHA KEY: %s" % recaptcha_key)
+ recaptcha = ReCaptcha(self)
+ inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(
+ recaptcha_key)
+ return 1
+ return 0
+
+
+getInfo = create_getInfo(UptoboxCom)
diff --git a/pyload/plugins/hoster/VeehdCom.py b/pyload/plugins/hoster/VeehdCom.py
new file mode 100644
index 000000000..4d76c3525
--- /dev/null
+++ b/pyload/plugins/hoster/VeehdCom.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class VeehdCom(Hoster):
+ __name__ = "VeehdCom"
+ __type__ = "hoster"
+ __version__ = "0.23"
+
+ __pattern__ = r'http://veehd\.com/video/\d+_\S+'
+ __config__ = [("filename_spaces", "bool", "Allow spaces in filename", False),
+ ("replacement_char", "str", "Filename replacement character", "_")]
+
+ __description__ = """Veehd.com hoster plugin"""
+ __author_name__ = "cat"
+ __author_mail__ = "cat@pyload"
+
+
+ def _debug(self, msg):
+ self.logDebug('[%s] %s' % (self.__name__, msg))
+
+ def setup(self):
+ self.multiDL = True
+ self.req.canContinue = True
+
+ def process(self, pyfile):
+ self.download_html()
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ url = self.pyfile.url
+ self._debug("Requesting page: %s" % (repr(url),))
+ self.html = self.load(url)
+
+ def file_exists(self):
+ if not self.html:
+ self.download_html()
+
+ if '<title>Veehd</title>' in self.html:
+ return False
+ return True
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<title[^>]*>([^<]+) on Veehd</title>', self.html)
+ if m is None:
+ self.fail("video title not found")
+
+ name = m.group(1)
+
+ # replace unwanted characters in filename
+ if self.getConfig('filename_spaces'):
+ pattern = '[^0-9A-Za-z\.\ ]+'
+ else:
+ pattern = '[^0-9A-Za-z\.]+'
+
+ return re.sub(pattern, self.getConfig('replacement_char'), name) + '.avi'
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ m = re.search(r'<embed type="video/divx" src="(http://([^/]*\.)?veehd\.com/dl/[^"]+)"',
+ self.html)
+ if m is None:
+ self.fail("embedded video url not found")
+
+ return m.group(1)
diff --git a/pyload/plugins/hoster/VeohCom.py b/pyload/plugins/hoster/VeohCom.py
new file mode 100644
index 000000000..31b21420a
--- /dev/null
+++ b/pyload/plugins/hoster/VeohCom.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class VeohCom(SimpleHoster):
+ __name__ = "VeohCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)'
+ __config__ = [("quality", "Low;High;Auto", "Quality", "Auto")]
+
+ __description__ = """Veoh.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ FILE_NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"'
+ OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'http://www.veoh.com/watch/\g<ID>')]
+
+ SH_COOKIES = [(".veoh.com", "lassieLocale", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = -1
+
+ def handleFree(self):
+ quality = self.getConfig("quality")
+ if quality == "Auto":
+ quality = ("High", "Low")
+ for q in quality:
+ pattern = r'"fullPreviewHash%sPath":"(.+?)"' % q
+ m = re.search(pattern, self.html)
+ if m:
+ self.pyfile.name += ".mp4"
+ link = m.group(1).replace("\\", "")
+ self.logDebug("Download link: " + link)
+ self.download(link)
+ return
+ else:
+ self.logInfo("No %s quality video found" % q.upper())
+ else:
+ self.fail("No video found!")
+
+
+getInfo = create_getInfo(VeohCom)
diff --git a/pyload/plugins/hoster/VidPlayNet.py b/pyload/plugins/hoster/VidPlayNet.py
new file mode 100644
index 000000000..82afde07d
--- /dev/null
+++ b/pyload/plugins/hoster/VidPlayNet.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# BigBuckBunny_320x180.mp4 - 61.7 Mb - http://vidplay.net/38lkev0h3jv0
+
+from pyload.plugins.hoster.XFileSharingPro import XFileSharingPro, create_getInfo
+
+
+class VidPlayNet(XFileSharingPro):
+ __name__ = "VidPlayNet"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'https?://(?:www\.)?vidplay\.net/\w{12}'
+
+ __description__ = """VidPlay.net hoster plugin"""
+ __author_name__ = "t4skforce"
+ __author_mail__ = "t4skforce1337[AT]gmail[DOT]com"
+
+ HOSTER_NAME = "vidplay.net"
+
+ OFFLINE_PATTERN = r'<b>File Not Found</b><br>\s*<br>'
+ FILE_NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>'
+ LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<&]+)' % HOSTER_NAME
+
+
+getInfo = create_getInfo(VidPlayNet)
diff --git a/pyload/plugins/hoster/VimeoCom.py b/pyload/plugins/hoster/VimeoCom.py
new file mode 100644
index 000000000..aebf1c344
--- /dev/null
+++ b/pyload/plugins/hoster/VimeoCom.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class VimeoCom(SimpleHoster):
+ __name__ = "VimeoCom"
+ __type__ = "hoster"
+ __version__ = "0.02"
+
+ __pattern__ = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)'
+ __config__ = [("quality", "Lowest;Mobile;SD;HD;Highest", "Quality", "Highest"),
+ ("original", "bool", "Try to download the original file first", True)]
+
+ __description__ = """Vimeo.com hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+ FILE_NAME_PATTERN = r'<title>(?P<N>.+) on Vimeo<'
+ OFFLINE_PATTERN = r'class="exception_header"'
+ TEMP_OFFLINE_PATTERN = r'Please try again in a few minutes.<'
+
+ FILE_URL_REPLACEMENTS = [(__pattern__, r'https://www.vimeo.com/\g<ID>')]
+
+ SH_COOKIES = [(".vimeo.com", "language", "en")]
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = -1
+
+ def handleFree(self):
+ password = self.getPassword()
+
+ if self.js and 'class="btn iconify_down_b"' in self.html:
+ html = self.js.eval(self.load(self.pyfile.url, get={'action': "download", 'password': password}, decode=True))
+ pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) '
+ else:
+ id = re.match(self.__pattern__, self.pyfile.url).group("ID")
+ html = self.load("https://player.vimeo.com/video/" + id, get={'password': password})
+ pattern = r'"(?P<QL>\w+)":{"profile".*?"(?P<URL>http://pdl\.vimeocdn\.com.+?)"'
+
+ link = dict([(l.group('QL').lower(), l.group('URL')) for l in re.finditer(pattern, html)])
+
+ if self.getConfig("original"):
+ if "original" in link:
+ self.download(link[q])
+ return
+ else:
+ self.logInfo("Original file not downloadable")
+
+ quality = self.getConfig("quality")
+ if quality == "Highest":
+ qlevel = ("hd", "sd", "mobile")
+ elif quality == "Lowest":
+ qlevel = ("mobile", "sd", "hd")
+ else:
+ qlevel = quality.lower()
+
+ for q in qlevel:
+ if q in link:
+ self.download(link[q])
+ return
+ else:
+ self.logInfo("No %s quality video found" % q.upper())
+ else:
+ self.fail("No video found!")
+
+
+getInfo = create_getInfo(VimeoCom)
diff --git a/pyload/plugins/hoster/Vipleech4uCom.py b/pyload/plugins/hoster/Vipleech4uCom.py
new file mode 100644
index 000000000..436b7d484
--- /dev/null
+++ b/pyload/plugins/hoster/Vipleech4uCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class Vipleech4uCom(DeadHoster):
+ __name__ = "Vipleech4uCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?vipleech4u\.com/manager\.php'
+
+ __description__ = """Vipleech4u.com hoster plugin"""
+ __author_name__ = "Kagenoshin"
+ __author_mail__ = "kagenoshin@gmx.ch"
+
+
+getInfo = create_getInfo(Vipleech4uCom)
diff --git a/pyload/plugins/hoster/WarserverCz.py b/pyload/plugins/hoster/WarserverCz.py
new file mode 100644
index 000000000..365f0f0fa
--- /dev/null
+++ b/pyload/plugins/hoster/WarserverCz.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class WarserverCz(DeadHoster):
+ __name__ = "WarserverCz"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'http://(?:www\.)?warserver\.cz/stahnout/\d+'
+
+ __description__ = """Warserver.cz hoster plugin"""
+ __author_name__ = "Walter Purcaro"
+ __author_mail__ = "vuolter@gmail.com"
+
+
+getInfo = create_getInfo(WarserverCz)
diff --git a/pyload/plugins/hoster/WebshareCz.py b/pyload/plugins/hoster/WebshareCz.py
new file mode 100644
index 000000000..6ca8d8882
--- /dev/null
+++ b/pyload/plugins/hoster/WebshareCz.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.network.RequestFactory import getRequest
+from pyload.plugins.internal.SimpleHoster import SimpleHoster
+
+
+def getInfo(urls):
+ h = getRequest()
+ for url in urls:
+ h.load(url)
+ fid = re.search(WebshareCz.__pattern__, url).group('ID')
+ api_data = h.load('https://webshare.cz/api/file_info/', post={'ident': fid})
+ if 'File not found' in api_data:
+ file_info = (url, 0, 1, url)
+ else:
+ name = re.search('<name>(.+)</name>', api_data).group(1)
+ size = re.search('<size>(.+)</size>', api_data).group(1)
+ file_info = (name, size, 2, url)
+ yield file_info
+
+
+class WebshareCz(SimpleHoster):
+ __name__ = "WebshareCz"
+ __type__ = "hoster"
+ __version__ = "0.13"
+
+ __pattern__ = r'https?://(?:www\.)?webshare.cz/(?:#/)?file/(?P<ID>\w+)'
+
+ __description__ = """WebShare.cz hoster plugin"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def handleFree(self):
+ api_data = self.load('https://webshare.cz/api/file_link/', post={'ident': self.fid})
+ self.logDebug("API data: " + api_data)
+ m = re.search('<link>(.+)</link>', api_data)
+ if m is None:
+ self.parseError('Unable to detect direct link')
+ direct = m.group(1)
+ self.logDebug("Direct link: " + direct)
+ self.download(direct, disposition=True)
+
+ def getFileInfo(self):
+ self.logDebug("URL: %s" % self.pyfile.url)
+
+ self.fid = re.match(self.__pattern__, self.pyfile.url).group('ID')
+
+ self.load(self.pyfile.url)
+ api_data = self.load('https://webshare.cz/api/file_info/', post={'ident': self.fid})
+
+ if 'File not found' in api_data:
+ self.offline()
+ else:
+ self.pyfile.name = re.search('<name>(.+)</name>', api_data).group(1)
+ self.pyfile.size = re.search('<size>(.+)</size>', api_data).group(1)
+
+ self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size))
diff --git a/pyload/plugins/hoster/WrzucTo.py b/pyload/plugins/hoster/WrzucTo.py
new file mode 100644
index 000000000..b766ea785
--- /dev/null
+++ b/pyload/plugins/hoster/WrzucTo.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import HTTPHEADER
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class WrzucTo(SimpleHoster):
+ __name__ = "WrzucTo"
+ __type__ = "hoster"
+ __version__ = "0.01"
+
+ __pattern__ = r'http://(?:www\.)?wrzuc\.to/([a-zA-Z0-9]+(\.wt|\.html)|(\w+/?linki/[a-zA-Z0-9]+))'
+
+ __description__ = """Wrzuc.to hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>'
+ FILE_SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>'
+
+ SH_COOKIES = [(".wrzuc.to", "language", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+
+ def handleFree(self):
+ data = dict(re.findall(r'(md5|file): "(.*?)"', self.html))
+ if len(data) != 2:
+ self.parseError('File ID')
+
+ self.req.http.c.setopt(HTTPHEADER, ["X-Requested-With: XMLHttpRequest"])
+ self.req.http.lastURL = self.pyfile.url
+ self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']})
+
+ self.req.http.lastURL = self.pyfile.url
+ self.html = self.load("http://www.wrzuc.to/ajax/server/download_link", post={"file": data['file']})
+
+ data.update(re.findall(r'"(download_link|server_id)":"(.*?)"', self.html))
+ if len(data) != 4:
+ self.parseError('Download URL')
+
+ download_url = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link'])
+ self.logDebug("Download URL: %s" % download_url)
+ self.download(download_url)
+
+
+getInfo = create_getInfo(WrzucTo)
diff --git a/pyload/plugins/hoster/WuploadCom.py b/pyload/plugins/hoster/WuploadCom.py
new file mode 100644
index 000000000..5bc933ae5
--- /dev/null
+++ b/pyload/plugins/hoster/WuploadCom.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class WuploadCom(DeadHoster):
+ __name__ = "WuploadCom"
+ __type__ = "hoster"
+ __version__ = "0.23"
+
+ __pattern__ = r'http://(?:www\.)?wupload\..*?/file/(([a-z][0-9]+/)?[0-9]+)(/.*)?'
+
+ __description__ = """Wupload.com hoster plugin"""
+ __author_name__ = ("jeix", "Paul King")
+ __author_mail__ = ("jeix@hasnomail.de", "")
+
+
+getInfo = create_getInfo(WuploadCom)
diff --git a/pyload/plugins/hoster/X7To.py b/pyload/plugins/hoster/X7To.py
new file mode 100644
index 000000000..8df1d0ab3
--- /dev/null
+++ b/pyload/plugins/hoster/X7To.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.internal.DeadHoster import DeadHoster, create_getInfo
+
+
+class X7To(DeadHoster):
+ __name__ = "X7To"
+ __type__ = "hoster"
+ __version__ = "0.41"
+
+ __pattern__ = r'http://(?:www\.)?x7.to/'
+
+ __description__ = """X7.to hoster plugin"""
+ __author_name__ = "ernieb"
+ __author_mail__ = "ernieb"
+
+
+getInfo = create_getInfo(X7To)
diff --git a/pyload/plugins/hoster/XFileSharingPro.py b/pyload/plugins/hoster/XFileSharingPro.py
new file mode 100644
index 000000000..c7733600b
--- /dev/null
+++ b/pyload/plugins/hoster/XFileSharingPro.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pycurl import FOLLOWLOCATION, LOW_SPEED_TIME
+from random import random
+from urllib import unquote
+from urlparse import urlparse
+
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.internal.CaptchaService import ReCaptcha, SolveMedia
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo, PluginParseError, replace_patterns
+from pyload.utils import html_unescape
+
+
+class XFileSharingPro(SimpleHoster):
+ """
+ Common base for XFileSharingPro hosters like EasybytezCom, CramitIn, FiledinoCom...
+ Some hosters may work straight away when added to __pattern__
+ However, most of them will NOT work because they are either down or running a customized version
+ """
+ __name__ = "XFileSharingPro"
+ __type__ = "hoster"
+ __version__ = "0.32"
+
+ __pattern__ = r'^unmatchable$'
+
+ __description__ = """XFileSharingPro base hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ FILE_INFO_PATTERN = r'<tr><td align=right><b>Filename:</b></td><td nowrap>(?P<N>[^<]+)</td></tr>\s*.*?<small>\((?P<S>[^<]+)\)</small>'
+ FILE_NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>[^"]+)"'
+ FILE_SIZE_PATTERN = r'You have requested .*\((?P<S>[\d\.\,]+) ?(?P<U>\w+)?\)</font>'
+ OFFLINE_PATTERN = r'>\w+ (Not Found|file (was|has been) removed)'
+
+ WAIT_PATTERN = r'<span id="countdown_str">.*?>(\d+)</span>'
+
+ OVR_LINK_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)'
+
+ CAPTCHA_URL_PATTERN = r'(http://[^"\']+?/captchas?/[^"\']+)'
+ RECAPTCHA_URL_PATTERN = r'http://[^"\']+?recaptcha[^"\']+?\?k=([^"\']+)"'
+ CAPTCHA_DIV_PATTERN = r'>Enter code.*?<div.*?>(.*?)</div>'
+ SOLVEMEDIA_PATTERN = r'http:\/\/api\.solvemedia\.com\/papi\/challenge\.script\?k=(.*?)"'
+
+ ERROR_PATTERN = r'class=["\']err["\'][^>]*>(.*?)</'
+
+
+ def setup(self):
+ if self.__name__ == "XFileSharingPro":
+ self.__pattern__ = self.core.pluginManager.hosterPlugins[self.__name__]['pattern']
+ self.multiDL = True
+ else:
+ self.resumeDownload = self.multiDL = self.premium
+
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ self.prepare()
+
+ pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
+
+ if not re.match(self.__pattern__, pyfile.url):
+ if self.premium:
+ self.handleOverriden()
+ else:
+ self.fail("Only premium users can download from other hosters with %s" % self.HOSTER_NAME)
+ else:
+ try:
+ # Due to a 0.4.9 core bug self.load would use cookies even if
+ # cookies=False. Workaround using getURL to avoid cookies.
+ # Can be reverted in 0.5 as the cookies bug has been fixed.
+ self.html = getURL(pyfile.url, decode=True)
+ self.file_info = self.getFileInfo()
+ except PluginParseError:
+ self.file_info = None
+
+ self.location = self.getDirectDownloadLink()
+
+ if not self.file_info:
+ pyfile.name = html_unescape(unquote(urlparse(
+ self.location if self.location else pyfile.url).path.split("/")[-1]))
+
+ if self.location:
+ self.startDownload(self.location)
+ elif self.premium:
+ self.handlePremium()
+ else:
+ self.handleFree()
+
+ def prepare(self):
+ """ Initialize important variables """
+ if not hasattr(self, "HOSTER_NAME"):
+ self.HOSTER_NAME = re.match(self.__pattern__, self.pyfile.url).group(1)
+ if not hasattr(self, "LINK_PATTERN"):
+ self.LINK_PATTERN = r'(http://([^/]*?%s|\d+\.\d+\.\d+\.\d+)(:\d+)?(/d/|(?:/files)?/\d+/\w+/)[^"\'<]+)' % self.HOSTER_NAME
+
+ self.captcha = self.errmsg = None
+ self.passwords = self.getPassword().splitlines()
+
+ def getDirectDownloadLink(self):
+ """ Get download link for premium users with direct download enabled """
+ self.req.http.lastURL = self.pyfile.url
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+ self.html = self.load(self.pyfile.url, cookies=True, decode=True)
+ self.header = self.req.http.header
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ location = None
+ m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
+ if m and re.match(self.LINK_PATTERN, m.group(1)):
+ location = m.group(1).strip()
+
+ return location
+
+ def handleFree(self):
+ url = self.getDownloadLink()
+ self.logDebug("Download URL: %s" % url)
+ self.startDownload(url)
+
+ def getDownloadLink(self):
+ for i in xrange(5):
+ self.logDebug("Getting download link: #%d" % i)
+ data = self.getPostParameters()
+
+ self.req.http.c.setopt(FOLLOWLOCATION, 0)
+ self.html = self.load(self.pyfile.url, post=data, ref=True, decode=True)
+ self.header = self.req.http.header
+ self.req.http.c.setopt(FOLLOWLOCATION, 1)
+
+ m = re.search(r"Location\s*:\s*(.*)", self.header, re.I)
+ if m:
+ break
+
+ m = re.search(self.LINK_PATTERN, self.html, re.S)
+ if m:
+ break
+
+ else:
+ if self.errmsg and 'captcha' in self.errmsg:
+ self.fail("No valid captcha code entered")
+ else:
+ self.fail("Download link not found")
+
+ return m.group(1)
+
+ def handlePremium(self):
+ self.html = self.load(self.pyfile.url, post=self.getPostParameters())
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('DIRECT LINK')
+ self.startDownload(m.group(1))
+
+ def handleOverriden(self):
+ #only tested with easybytez.com
+ self.html = self.load("http://www.%s/" % self.HOSTER_NAME)
+ action, inputs = self.parseHtmlForm('')
+ upload_id = "%012d" % int(random() * 10 ** 12)
+ action += upload_id + "&js_on=1&utype=prem&upload_type=url"
+ inputs['tos'] = '1'
+ inputs['url_mass'] = self.pyfile.url
+ inputs['up1oad_type'] = 'url'
+
+ self.logDebug(self.HOSTER_NAME, action, inputs)
+ #wait for file to upload to easybytez.com
+ self.req.http.c.setopt(LOW_SPEED_TIME, 600)
+ self.html = self.load(action, post=inputs)
+
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ self.parseError('TEXTAREA')
+ self.logDebug(self.HOSTER_NAME, inputs)
+ if inputs['st'] == 'OK':
+ self.html = self.load(action, post=inputs)
+ elif inputs['st'] == 'Can not leech file':
+ self.retry(max_tries=20, wait_time=3 * 60, reason=inputs['st'])
+ else:
+ self.fail(inputs['st'])
+
+ #get easybytez.com link for uploaded file
+ m = re.search(self.OVR_LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError('DIRECT LINK (OVR)')
+ self.pyfile.url = m.group(1)
+ header = self.load(self.pyfile.url, just_header=True)
+ if 'location' in header: # Direct link
+ self.startDownload(self.pyfile.url)
+ else:
+ self.retry()
+
+ def startDownload(self, link):
+ link = link.strip()
+ if self.captcha:
+ self.correctCaptcha()
+ self.logDebug('DIRECT LINK: %s' % link)
+ self.download(link, disposition=True)
+
+ def checkErrors(self):
+ m = re.search(self.ERROR_PATTERN, self.html)
+ if m:
+ self.errmsg = m.group(1)
+ self.logWarning(re.sub(r"<.*?>", " ", self.errmsg))
+
+ if 'wait' in self.errmsg:
+ wait_time = sum([int(v) * {"hour": 3600, "minute": 60, "second": 1}[u] for v, u in
+ re.findall(r'(\d+)\s*(hour|minute|second)', self.errmsg)])
+ self.wait(wait_time, True)
+ elif 'captcha' in self.errmsg:
+ self.invalidCaptcha()
+ elif 'premium' in self.errmsg and 'require' in self.errmsg:
+ self.fail("File can be downloaded by premium users only")
+ elif 'limit' in self.errmsg:
+ self.wait(1 * 60 * 60, True)
+ self.retry(25)
+ elif 'countdown' in self.errmsg or 'Expired' in self.errmsg:
+ self.retry()
+ elif 'maintenance' in self.errmsg:
+ self.tempOffline()
+ elif 'download files up to' in self.errmsg:
+ self.fail("File too large for free download")
+ else:
+ self.fail(self.errmsg)
+
+ else:
+ self.errmsg = None
+
+ return self.errmsg
+
+ def getPostParameters(self):
+ for _ in xrange(3):
+ if not self.errmsg:
+ self.checkErrors()
+
+ if hasattr(self, "FORM_PATTERN"):
+ action, inputs = self.parseHtmlForm(self.FORM_PATTERN)
+ else:
+ action, inputs = self.parseHtmlForm(input_names={"op": re.compile("^download")})
+
+ if not inputs:
+ action, inputs = self.parseHtmlForm('F1')
+ if not inputs:
+ if self.errmsg:
+ self.retry()
+ else:
+ self.parseError("Form not found")
+
+ self.logDebug(self.HOSTER_NAME, inputs)
+
+ if 'op' in inputs and inputs['op'] in ("download2", "download3"):
+ if "password" in inputs:
+ if self.passwords:
+ inputs['password'] = self.passwords.pop(0)
+ else:
+ self.fail("No or invalid passport")
+
+ if not self.premium:
+ m = re.search(self.WAIT_PATTERN, self.html)
+ if m:
+ wait_time = int(m.group(1)) + 1
+ self.setWait(wait_time, False)
+ else:
+ wait_time = 0
+
+ self.captcha = self.handleCaptcha(inputs)
+
+ if wait_time:
+ self.wait()
+
+ self.errmsg = None
+ return inputs
+
+ else:
+ inputs['referer'] = self.pyfile.url
+
+ if self.premium:
+ inputs['method_premium'] = "Premium Download"
+ if 'method_free' in inputs:
+ del inputs['method_free']
+ else:
+ inputs['method_free'] = "Free Download"
+ if 'method_premium' in inputs:
+ del inputs['method_premium']
+
+ self.html = self.load(self.pyfile.url, post=inputs, ref=True)
+ self.errmsg = None
+
+ else:
+ self.parseError('FORM: %s' % (inputs['op'] if 'op' in inputs else 'UNKNOWN'))
+
+ def handleCaptcha(self, inputs):
+ m = re.search(self.RECAPTCHA_URL_PATTERN, self.html)
+ if m:
+ recaptcha_key = unquote(m.group(1))
+ self.logDebug("RECAPTCHA KEY: %s" % recaptcha_key)
+ recaptcha = ReCaptcha(self)
+ inputs['recaptcha_challenge_field'], inputs['recaptcha_response_field'] = recaptcha.challenge(recaptcha_key)
+ return 1
+ else:
+ m = re.search(self.CAPTCHA_URL_PATTERN, self.html)
+ if m:
+ captcha_url = m.group(1)
+ inputs['code'] = self.decryptCaptcha(captcha_url)
+ return 2
+ else:
+ m = re.search(self.CAPTCHA_DIV_PATTERN, self.html, re.DOTALL)
+ if m:
+ captcha_div = m.group(1)
+ self.logDebug(captcha_div)
+ numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div))
+ inputs['code'] = "".join([a[1] for a in sorted(numerals, key=lambda num: int(num[0]))])
+ self.logDebug("CAPTCHA", inputs['code'], numerals)
+ return 3
+ else:
+ m = re.search(self.SOLVEMEDIA_PATTERN, self.html)
+ if m:
+ captcha_key = m.group(1)
+ captcha = SolveMedia(self)
+ inputs['adcopy_challenge'], inputs['adcopy_response'] = captcha.challenge(captcha_key)
+ return 4
+ return 0
+
+
+getInfo = create_getInfo(XFileSharingPro)
diff --git a/pyload/plugins/hoster/XHamsterCom.py b/pyload/plugins/hoster/XHamsterCom.py
new file mode 100644
index 000000000..aa406cc45
--- /dev/null
+++ b/pyload/plugins/hoster/XHamsterCom.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.utils import json_loads
+from pyload.plugins.Hoster import Hoster
+
+
+def clean_json(json_expr):
+ json_expr = re.sub('[\n\r]', '', json_expr)
+ json_expr = re.sub(' +', '', json_expr)
+ json_expr = re.sub('\'', '"', json_expr)
+
+ return json_expr
+
+
+class XHamsterCom(Hoster):
+ __name__ = "XHamsterCom"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'http://(?:www\.)?xhamster\.com/movies/.+'
+ __config__ = [("type", ".mp4;.flv", "Preferred type", ".mp4")]
+
+ __description__ = """XHamster.com hoster plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ if not self.file_exists():
+ self.offline()
+
+ if self.getConfig("type"):
+ self.desired_fmt = self.getConfig("type")
+
+ pyfile.name = self.get_file_name() + self.desired_fmt
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ flashvar_pattern = re.compile('flashvars = ({.*?});', re.DOTALL)
+ json_flashvar = flashvar_pattern.search(self.html)
+
+ if not json_flashvar:
+ self.fail("Parse error (flashvars)")
+
+ j = clean_json(json_flashvar.group(1))
+ flashvars = json_loads(j)
+
+ if flashvars['srv']:
+ srv_url = flashvars['srv'] + '/'
+ else:
+ self.fail("Parse error (srv_url)")
+
+ if flashvars['url_mode']:
+ url_mode = flashvars['url_mode']
+ else:
+ self.fail("Parse error (url_mode)")
+
+ if self.desired_fmt == ".mp4":
+ file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html)
+ if file_url is None:
+ self.fail("Parse error (file_url)")
+ file_url = file_url.group(1)
+ long_url = srv_url + file_url
+ self.logDebug("long_url: %s" % long_url)
+ else:
+ if flashvars['file']:
+ file_url = unquote(flashvars['file'])
+ else:
+ self.fail("Parse error (file_url)")
+
+ if url_mode == '3':
+ long_url = file_url
+ self.logDebug("long_url: %s" % long_url)
+ else:
+ long_url = srv_url + "key=" + file_url
+ self.logDebug("long_url: %s" % long_url)
+
+ return long_url
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ pattern = r"<title>(.*?) - xHamster\.com</title>"
+ name = re.search(pattern, self.html)
+ if name is None:
+ pattern = r"<h1 >(.*)</h1>"
+ name = re.search(pattern, self.html)
+ if name is None:
+ pattern = r"http://[www.]+xhamster\.com/movies/.*/(.*?)\.html?"
+ name = re.match(file_name_pattern, self.pyfile.url)
+ if name is None:
+ pattern = r"<div id=\"element_str_id\" style=\"display:none;\">(.*)</div>"
+ name = re.search(pattern, self.html)
+ if name is None:
+ return "Unknown"
+
+ return name.group(1)
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"(.*Video not found.*)", self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/XVideosCom.py b/pyload/plugins/hoster/XVideosCom.py
new file mode 100644
index 000000000..75162955a
--- /dev/null
+++ b/pyload/plugins/hoster/XVideosCom.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.Hoster import Hoster
+
+
+class XVideosCom(Hoster):
+ __name__ = "XVideos.com"
+ __type__ = "hoster"
+ __version__ = "0.1"
+
+ __pattern__ = r'http://(?:www\.)?xvideos\.com/video([0-9]+)/.*'
+
+ __description__ = """XVideos.com hoster plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+
+ def process(self, pyfile):
+ site = self.load(pyfile.url)
+ pyfile.name = "%s (%s).flv" % (
+ re.search(r"<h2>([^<]+)<span", site).group(1),
+ re.match(self.__pattern__, pyfile.url).group(1),
+ )
+ self.download(unquote(re.search(r"flv_url=([^&]+)&", site).group(1)))
diff --git a/pyload/plugins/hoster/Xdcc.py b/pyload/plugins/hoster/Xdcc.py
new file mode 100644
index 000000000..8b427cfdc
--- /dev/null
+++ b/pyload/plugins/hoster/Xdcc.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+
+import re
+import socket
+import struct
+import sys
+import time
+
+from os import makedirs
+from os.path import exists, join
+from select import select
+
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import safe_join
+
+
+class Xdcc(Hoster):
+ __name__ = "Xdcc"
+ __type__ = "hoster"
+ __version__ = "0.32"
+
+ __config__ = [("nick", "str", "Nickname", "pyload"),
+ ("ident", "str", "Ident", "pyloadident"),
+ ("realname", "str", "Realname", "pyloadreal")]
+
+ __description__ = """Download from IRC XDCC bot"""
+ __author_name__ = "jeix"
+ __author_mail__ = "jeix@hasnomail.com"
+
+
+ def setup(self):
+ self.debug = 0 # 0,1,2
+ self.timeout = 30
+ self.multiDL = False
+
+ def process(self, pyfile):
+ # change request type
+ self.req = pyfile.m.core.requestFactory.getRequest(self.__name__, type="XDCC")
+
+ self.pyfile = pyfile
+ for _ in xrange(0, 3):
+ try:
+ nmn = self.doDownload(pyfile.url)
+ self.logDebug("%s: Download of %s finished." % (self.__name__, nmn))
+ return
+ except socket.error, e:
+ if hasattr(e, "errno"):
+ errno = e.errno
+ else:
+ errno = e.args[0]
+
+ if errno == 10054:
+ self.logDebug("XDCC: Server blocked our ip, retry in 5 min")
+ self.setWait(300)
+ self.wait()
+ continue
+
+ self.fail("Failed due to socket errors. Code: %d" % errno)
+
+ self.fail("Server blocked our ip, retry again later manually")
+
+ def doDownload(self, url):
+ self.pyfile.setStatus("waiting") # real link
+
+ m = re.match(r'xdcc://(.*?)/#?(.*?)/(.*?)/#?(\d+)/?', url)
+ server = m.group(1)
+ chan = m.group(2)
+ bot = m.group(3)
+ pack = m.group(4)
+ nick = self.getConfig('nick')
+ ident = self.getConfig('ident')
+ real = self.getConfig('realname')
+
+ temp = server.split(':')
+ ln = len(temp)
+ if ln == 2:
+ host, port = temp
+ elif ln == 1:
+ host, port = temp[0], 6667
+ else:
+ self.fail("Invalid hostname for IRC Server (%s)" % server)
+
+ #######################
+ # CONNECT TO IRC AND IDLE FOR REAL LINK
+ dl_time = time.time()
+
+ sock = socket.socket()
+ sock.connect((host, int(port)))
+ if nick == "pyload":
+ nick = "pyload-%d" % (time.time() % 1000) # last 3 digits
+ sock.send("NICK %s\r\n" % nick)
+ sock.send("USER %s %s bla :%s\r\n" % (ident, host, real))
+ time.sleep(3)
+ sock.send("JOIN #%s\r\n" % chan)
+ sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
+
+ # IRC recv loop
+ readbuffer = ""
+ done = False
+ retry = None
+ m = None
+ while True:
+
+ # done is set if we got our real link
+ if done:
+ break
+
+ if retry:
+ if time.time() > retry:
+ retry = None
+ dl_time = time.time()
+ sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
+
+ else:
+ if (dl_time + self.timeout) < time.time(): # todo: add in config
+ sock.send("QUIT :byebye\r\n")
+ sock.close()
+ self.fail("XDCC Bot did not answer")
+
+ fdset = select([sock], [], [], 0)
+ if sock not in fdset[0]:
+ continue
+
+ readbuffer += sock.recv(1024)
+ temp = readbuffer.split("\n")
+ readbuffer = temp.pop()
+
+ for line in temp:
+ if self.debug is 2:
+ print "*> " + unicode(line, errors='ignore')
+ line = line.rstrip()
+ first = line.split()
+
+ if first[0] == "PING":
+ sock.send("PONG %s\r\n" % first[1])
+
+ if first[0] == "ERROR":
+ self.fail("IRC-Error: %s" % line)
+
+ msg = line.split(None, 3)
+ if len(msg) != 4:
+ continue
+
+ msg = {
+ "origin": msg[0][1:],
+ "action": msg[1],
+ "target": msg[2],
+ "text": msg[3][1:]
+ }
+
+ if nick == msg['target'][0:len(nick)] and "PRIVMSG" == msg['action']:
+ if msg['text'] == "\x01VERSION\x01":
+ self.logDebug("XDCC: Sending CTCP VERSION.")
+ sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface"))
+ elif msg['text'] == "\x01TIME\x01":
+ self.logDebug("Sending CTCP TIME.")
+ sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time()))
+ elif msg['text'] == "\x01LAG\x01":
+ pass # don't know how to answer
+
+ if not (bot == msg['origin'][0:len(bot)]
+ and nick == msg['target'][0:len(nick)]
+ and msg['action'] in ("PRIVMSG", "NOTICE")):
+ continue
+
+ if self.debug is 1:
+ print "%s: %s" % (msg['origin'], msg['text'])
+
+ if "You already requested that pack" in msg['text']:
+ retry = time.time() + 300
+
+ if "you must be on a known channel to request a pack" in msg['text']:
+ self.fail("Wrong channel")
+
+ m = re.match('\x01DCC SEND (.*?) (\d+) (\d+)(?: (\d+))?\x01', msg['text'])
+ if m:
+ done = True
+
+ # get connection data
+ ip = socket.inet_ntoa(struct.pack('L', socket.ntohl(int(m.group(2)))))
+ port = int(m.group(3))
+ packname = m.group(1)
+
+ if len(m.groups()) > 3:
+ self.req.filesize = int(m.group(4))
+
+ self.pyfile.name = packname
+
+ download_folder = self.config['general']['download_folder']
+ filename = safe_join(download_folder, packname)
+
+ self.logInfo("XDCC: Downloading %s from %s:%d" % (packname, ip, port))
+
+ self.pyfile.setStatus("downloading")
+ newname = self.req.download(ip, port, filename, sock, self.pyfile.setProgress)
+ if newname and newname != filename:
+ self.logInfo("%(name)s saved as %(newname)s" % {"name": self.pyfile.name, "newname": newname})
+ filename = newname
+
+ # kill IRC socket
+ # sock.send("QUIT :byebye\r\n")
+ sock.close()
+
+ self.lastDownload = filename
+ return self.lastDownload
diff --git a/pyload/plugins/hoster/YibaishiwuCom.py b/pyload/plugins/hoster/YibaishiwuCom.py
new file mode 100644
index 000000000..24cd0e9fc
--- /dev/null
+++ b/pyload/plugins/hoster/YibaishiwuCom.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.utils import json_loads
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class YibaishiwuCom(SimpleHoster):
+ __name__ = "YibaishiwuCom"
+ __type__ = "hoster"
+ __version__ = "0.12"
+
+ __pattern__ = r'http://(?:www\.)?(?:u\.)?115.com/file/(?P<ID>\w+)'
+
+ __description__ = """115.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ FILE_NAME_PATTERN = r"file_name: '(?P<N>[^']+)'"
+ FILE_SIZE_PATTERN = r"file_size: '(?P<S>[^']+)'"
+ OFFLINE_PATTERN = ur'<h3><i style="color:red;">哎呀提取码䞍存圚䞍劚搜搜看吧</i></h3>'
+
+ LINK_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)'
+
+
+ def handleFree(self):
+ m = re.search(self.LINK_PATTERN, self.html)
+ if m is None:
+ self.parseError("AJAX URL")
+ url = m.group(1)
+ self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url)
+
+ response = json_loads(self.load("http://115.com" + url, decode=False))
+ if "urls" in response:
+ mirrors = response['urls']
+ elif "data" in response:
+ mirrors = response['data']
+ else:
+ mirrors = None
+
+ for mr in mirrors:
+ try:
+ url = mr['url'].replace("\\", "")
+ self.logDebug("Trying URL: " + url)
+ self.download(url)
+ break
+ except:
+ continue
+ else:
+ self.fail('No working link found')
+
+
+getInfo = create_getInfo(YibaishiwuCom)
diff --git a/pyload/plugins/hoster/YoupornCom.py b/pyload/plugins/hoster/YoupornCom.py
new file mode 100644
index 000000000..de23780c3
--- /dev/null
+++ b/pyload/plugins/hoster/YoupornCom.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hoster import Hoster
+
+
+class YoupornCom(Hoster):
+ __name__ = "YoupornCom"
+ __type__ = "hoster"
+ __version__ = "0.2"
+
+ __pattern__ = r'http://(?:www\.)?youporn\.com/watch/.+'
+
+ __description__ = """Youporn.com hoster plugin"""
+ __author_name__ = "willnix"
+ __author_mail__ = "willnix@pyload.org"
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+
+ if not self.file_exists():
+ self.offline()
+
+ pyfile.name = self.get_file_name()
+ self.download(self.get_file_url())
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url, post={"user_choice": "Enter"}, cookies=False)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ if not self.html:
+ self.download_html()
+
+ return re.search(r'(http://download\.youporn\.com/download/\d+\?save=1)">', self.html).group(1)
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ file_name_pattern = r"<title>(.*) - Free Porn Videos - YouPorn</title>"
+ return re.search(file_name_pattern, self.html).group(1).replace("&amp;", "&").replace("/", "") + '.flv'
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+ if re.search(r"(.*invalid video_id.*)", self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/YourfilesTo.py b/pyload/plugins/hoster/YourfilesTo.py
new file mode 100644
index 000000000..2de636b4b
--- /dev/null
+++ b/pyload/plugins/hoster/YourfilesTo.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from urllib import unquote
+
+from pyload.plugins.Hoster import Hoster
+
+
+class YourfilesTo(Hoster):
+ __name__ = "YourfilesTo"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'(http://)?(?:www\.)?yourfiles\.(to|biz)/\?d=[a-zA-Z0-9]+'
+
+ __description__ = """Youfiles.to hoster plugin"""
+ __author_name__ = ("jeix", "skydancer")
+ __author_mail__ = ("jeix@hasnomail.de", "skydancer@hasnomail.de")
+
+
+ def process(self, pyfile):
+ self.pyfile = pyfile
+ self.prepare()
+ self.download(self.get_file_url())
+
+ def prepare(self):
+ if not self.file_exists():
+ self.offline()
+
+ self.pyfile.name = self.get_file_name()
+
+ wait_time = self.get_waiting_time()
+ self.setWait(wait_time)
+ self.logDebug("%s: Waiting %d seconds." % (self.__name__, wait_time))
+ self.wait()
+
+ def get_waiting_time(self):
+ if not self.html:
+ self.download_html()
+
+ #var zzipitime = 15;
+ m = re.search(r'var zzipitime = (\d+);', self.html)
+ if m:
+ sec = int(m.group(1))
+ else:
+ sec = 0
+
+ return sec
+
+ def download_html(self):
+ url = self.pyfile.url
+ self.html = self.load(url)
+
+ def get_file_url(self):
+ """ returns the absolute downloadable filepath
+ """
+ url = re.search(r"var bla = '(.*?)';", self.html)
+ if url:
+ url = url.group(1)
+ url = unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", ""))
+ return url
+ else:
+ self.fail("absolute filepath could not be found. offline? ")
+
+ def get_file_name(self):
+ if not self.html:
+ self.download_html()
+
+ return re.search("<title>(.*)</title>", self.html).group(1)
+
+ def file_exists(self):
+ """ returns True or False
+ """
+ if not self.html:
+ self.download_html()
+
+ if re.search(r"HTTP Status 404", self.html) is not None:
+ return False
+ else:
+ return True
diff --git a/pyload/plugins/hoster/YoutubeCom.py b/pyload/plugins/hoster/YoutubeCom.py
new file mode 100644
index 000000000..6869d8b86
--- /dev/null
+++ b/pyload/plugins/hoster/YoutubeCom.py
@@ -0,0 +1,180 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+import subprocess
+
+from urllib import unquote
+
+from pyload.plugins.Hoster import Hoster
+from pyload.plugins.internal.SimpleHoster import replace_patterns
+from pyload.utils import html_unescape
+
+
+def which(program):
+ """Works exactly like the unix command which
+
+ Courtesy of http://stackoverflow.com/a/377028/675646"""
+
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ['PATH'].split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+
+ return None
+
+
+class YoutubeCom(Hoster):
+ __name__ = "YoutubeCom"
+ __type__ = "hoster"
+ __version__ = "0.40"
+
+ __pattern__ = r'https?://(?:[^/]*\.)?(?:youtube\.com|youtu\.be)/watch.*?[?&]v=.*'
+ __config__ = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting", "hd"),
+ ("fmt", "int", "FMT/ITAG Number (5-102, 0 for auto)", 0),
+ (".mp4", "bool", "Allow .mp4", True),
+ (".flv", "bool", "Allow .flv", True),
+ (".webm", "bool", "Allow .webm", False),
+ (".3gp", "bool", "Allow .3gp", False),
+ ("3d", "bool", "Prefer 3D", False)]
+
+ __description__ = """Youtube.com hoster plugin"""
+ __author_name__ = ("spoob", "zoidberg")
+ __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz")
+
+ FILE_URL_REPLACEMENTS = [(r'youtu\.be/', 'youtube.com/')]
+
+ # Invalid characters that must be removed from the file name
+ invalidChars = u'\u2605:?><"|\\'
+
+ # name, width, height, quality ranking, 3D
+ formats = {5: (".flv", 400, 240, 1, False),
+ 6: (".flv", 640, 400, 4, False),
+ 17: (".3gp", 176, 144, 0, False),
+ 18: (".mp4", 480, 360, 2, False),
+ 22: (".mp4", 1280, 720, 8, False),
+ 43: (".webm", 640, 360, 3, False),
+ 34: (".flv", 640, 360, 4, False),
+ 35: (".flv", 854, 480, 6, False),
+ 36: (".3gp", 400, 240, 1, False),
+ 37: (".mp4", 1920, 1080, 9, False),
+ 38: (".mp4", 4096, 3072, 10, False),
+ 44: (".webm", 854, 480, 5, False),
+ 45: (".webm", 1280, 720, 7, False),
+ 46: (".webm", 1920, 1080, 9, False),
+ 82: (".mp4", 640, 360, 3, True),
+ 83: (".mp4", 400, 240, 1, True),
+ 84: (".mp4", 1280, 720, 8, True),
+ 85: (".mp4", 1920, 1080, 9, True),
+ 100: (".webm", 640, 360, 3, True),
+ 101: (".webm", 640, 360, 4, True),
+ 102: (".webm", 1280, 720, 8, True)}
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
+ html = self.load(pyfile.url, decode=True)
+
+ if re.search(r'<div id="player-unavailable" class="\s*player-width player-height\s*">', html):
+ self.offline()
+
+ if "We have been receiving a large volume of requests from your network." in html:
+ self.tempOffline()
+
+ #get config
+ use3d = self.getConfig("3d")
+ if use3d:
+ quality = {"sd": 82, "hd": 84, "fullhd": 85, "240p": 83, "360p": 82,
+ "480p": 82, "720p": 84, "1080p": 85, "3072p": 85}
+ else:
+ quality = {"sd": 18, "hd": 22, "fullhd": 37, "240p": 5, "360p": 18,
+ "480p": 35, "720p": 22, "1080p": 37, "3072p": 38}
+ desired_fmt = self.getConfig("fmt")
+ if desired_fmt and desired_fmt not in self.formats:
+ self.logWarning("FMT %d unknown - using default." % desired_fmt)
+ desired_fmt = 0
+ if not desired_fmt:
+ desired_fmt = quality.get(self.getConfig("quality"), 18)
+
+ #parse available streams
+ streams = re.search(r'"url_encoded_fmt_stream_map": "(.*?)",', html).group(1)
+ streams = [x.split('\u0026') for x in streams.split(',')]
+ streams = [dict((y.split('=', 1)) for y in x) for x in streams]
+ streams = [(int(x['itag']), unquote(x['url'])) for x in streams]
+ #self.logDebug("Found links: %s" % streams)
+ self.logDebug("AVAILABLE STREAMS: %s" % [x[0] for x in streams])
+
+ #build dictionary of supported itags (3D/2D)
+ allowed = lambda x: self.getConfig(self.formats[x][0])
+ streams = [x for x in streams if x[0] in self.formats and allowed(x[0])]
+ if not streams:
+ self.fail("No available stream meets your preferences")
+ fmt_dict = dict([x for x in streams if self.formats[x[0]][4] == use3d] or streams)
+
+ self.logDebug("DESIRED STREAM: ITAG:%d (%s) %sfound, %sallowed" %
+ (desired_fmt, "%s %dx%d Q:%d 3D:%s" % self.formats[desired_fmt],
+ "" if desired_fmt in fmt_dict else "NOT ", "" if allowed(desired_fmt) else "NOT "))
+
+ #return fmt nearest to quality index
+ if desired_fmt in fmt_dict and allowed(desired_fmt):
+ fmt = desired_fmt
+ else:
+ sel = lambda x: self.formats[x][3] # select quality index
+ comp = lambda x, y: abs(sel(x) - sel(y))
+
+ self.logDebug("Choosing nearest fmt: %s" % [(x, allowed(x), comp(x, desired_fmt)) for x in fmt_dict.keys()])
+ fmt = reduce(lambda x, y: x if comp(x, desired_fmt) <= comp(y, desired_fmt) and
+ sel(x) > sel(y) else y, fmt_dict.keys())
+
+ self.logDebug("Chosen fmt: %s" % fmt)
+ url = fmt_dict[fmt]
+ self.logDebug("URL: %s" % url)
+
+ #set file name
+ file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv"
+ file_name_pattern = '<meta name="title" content="(.+?)">'
+ name = re.search(file_name_pattern, html).group(1).replace("/", "")
+
+ # Cleaning invalid characters from the file name
+ name = name.encode('ascii', 'replace')
+ for c in self.invalidChars:
+ name = name.replace(c, '_')
+
+ pyfile.name = html_unescape(name)
+
+ time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url)
+ ffmpeg = which("ffmpeg")
+ if ffmpeg and time:
+ m, s = time.groups()[1:]
+ if m is None:
+ m = "0"
+
+ pyfile.name += " (starting at %s:%s)" % (m, s)
+ pyfile.name += file_suffix
+
+ filename = self.download(url)
+
+ if ffmpeg and time:
+ inputfile = filename + "_"
+ os.rename(filename, inputfile)
+
+ subprocess.call([
+ ffmpeg,
+ "-ss", "00:%s:%s" % (m, s),
+ "-i", inputfile,
+ "-vcodec", "copy",
+ "-acodec", "copy",
+ filename])
+ os.remove(inputfile)
diff --git a/pyload/plugins/hoster/ZDF.py b/pyload/plugins/hoster/ZDF.py
new file mode 100644
index 000000000..d7bd5469a
--- /dev/null
+++ b/pyload/plugins/hoster/ZDF.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from xml.etree.ElementTree import fromstring
+
+from pyload.plugins.Hoster import Hoster
+
+
+# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/)
+class ZDF(Hoster):
+ __name__ = "ZDF Mediathek"
+ __type__ = "hoster"
+ __version__ = "0.8"
+
+ __pattern__ = r'http://(?:www\.)?zdf\.de/ZDFmediathek/[^0-9]*([0-9]+)[^0-9]*'
+
+ __description__ = """ZDF.de hoster plugin"""
+ __author_name__ = None
+ __author_mail__ = None
+
+ XML_API = "http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?id=%i"
+
+
+ @staticmethod
+ def video_key(video):
+ return (
+ int(video.findtext("videoBitrate", "0")),
+ any(f.text == "progressive" for f in video.iter("facet")),
+ )
+
+ @staticmethod
+ def video_valid(video):
+ return video.findtext("url").startswith("http") and video.findtext("url").endswith(".mp4") and \
+ video.findtext("facets/facet").startswith("progressive")
+
+ @staticmethod
+ def get_id(url):
+ return int(re.search(r"[^0-9]*([0-9]{4,})[^0-9]*", url).group(1))
+
+ def process(self, pyfile):
+ xml = fromstring(self.load(self.XML_API % self.get_id(pyfile.url)))
+
+ status = xml.findtext("./status/statuscode")
+ if status != "ok":
+ self.fail("Error retrieving manifest.")
+
+ video = xml.find("video")
+ title = video.findtext("information/title")
+
+ pyfile.name = title
+
+ target_url = sorted((v for v in video.iter("formitaet") if self.video_valid(v)),
+ key=self.video_key)[-1].findtext("url")
+
+ self.download(target_url)
diff --git a/pyload/plugins/hoster/ZeveraCom.py b/pyload/plugins/hoster/ZeveraCom.py
new file mode 100644
index 000000000..f76290ea5
--- /dev/null
+++ b/pyload/plugins/hoster/ZeveraCom.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Hoster import Hoster
+
+
+class ZeveraCom(Hoster):
+ __name__ = "ZeveraCom"
+ __type__ = "hoster"
+ __version__ = "0.21"
+
+ __pattern__ = r'http://(?:www\.)?zevera.com/.*'
+
+ __description__ = """Zevera.com hoster plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = True
+ self.chunkLimit = 1
+
+ def process(self, pyfile):
+ if not self.account:
+ self.logError(_("Please enter your %s account or deactivate this plugin") % "zevera.com")
+ self.fail("No zevera.com account provided")
+
+ self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
+
+ if self.account.getAPIData(self.req, cmd="checklink", olink=pyfile.url) != "Alive":
+ self.fail("Offline or not downloadable - contact Zevera support")
+
+ header = self.account.getAPIData(self.req, just_header=True, cmd="generatedownloaddirect", olink=pyfile.url)
+ if not "location" in header:
+ self.fail("Unable to initialize download - contact Zevera support")
+
+ self.download(header['location'], disposition=True)
+
+ check = self.checkDownload({"error": 'action="ErrorDownload.aspx'})
+ if check == "error":
+ self.fail("Error response received - contact Zevera support")
+
+ # BitAPI not used - defunct, probably abandoned by Zevera
+ #
+ # api_url = "http://zevera.com/API.ashx"
+ #
+ # def process(self, pyfile):
+ # if not self.account:
+ # self.logError(_("Please enter your zevera.com account or deactivate this plugin"))
+ # self.fail("No zevera.com account provided")
+ #
+ # self.logDebug("zevera.com: Old URL: %s" % pyfile.url)
+ #
+ # last_size = retries = 0
+ # olink = pyfile.url #quote(pyfile.url.encode('utf_8'))
+ #
+ # for _ in xrange(100):
+ # self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_request', olink = olink)
+ # self.checkAPIErrors(self.retData)
+ #
+ # if self.retData['FileInfo']['StatusID'] == 100:
+ # break
+ # elif self.retData['FileInfo']['StatusID'] == 99:
+ # self.fail('Failed to initialize download (99)')
+ # else:
+ # if self.retData['FileInfo']['Progress']['BytesReceived'] <= last_size:
+ # if retries >= 6:
+ # self.fail('Failed to initialize download (%d)' % self.retData['FileInfo']['StatusID'] )
+ # retries += 1
+ # else:
+ # retries = 0
+ #
+ # last_size = self.retData['FileInfo']['Progress']['BytesReceived']
+ #
+ # self.setWait(self.retData['Update_Wait'])
+ # self.wait()
+ #
+ # pyfile.name = self.retData['FileInfo']['RealFileName']
+ # pyfile.size = self.retData['FileInfo']['FileSizeInBytes']
+ #
+ # self.retData = self.account.loadAPIRequest(self.req, cmd = 'download_start',
+ # FileID = self.retData['FileInfo']['FileID'])
+ # self.checkAPIErrors(self.retData)
+ #
+ # self.download(self.api_url, get = {
+ # 'cmd': "open_stream",
+ # 'login': self.account.loginname,
+ # 'pass': self.account.password,
+ # 'FileID': self.retData['FileInfo']['FileID'],
+ # 'startBytes': 0
+ # }
+ # )
+ #
+ # def checkAPIErrors(self, retData):
+ # if not retData:
+ # self.fail('Unknown API response')
+ #
+ # if retData['ErrorCode']:
+ # self.logError(retData['ErrorCode'], retData['ErrorMessage'])
+ # #self.fail('ERROR: ' + retData['ErrorMessage'])
+ #
+ # if pyfile.size / 1024000 > retData['AccountInfo']['AvailableTODAYTrafficForUseInMBytes']:
+ # self.logWarning("Not enough data left to download the file")
+ #
+ # def crazyDecode(self, ustring):
+ # # accepts decoded ie. unicode string - API response is double-quoted, double-utf8-encoded
+ # # no idea what the proper order of calling these functions would be :-/
+ # return html_unescape(unquote(unquote(ustring.replace(
+ # '@DELIMITER@','#'))).encode('raw_unicode_escape').decode('utf-8'))
diff --git a/pyload/plugins/hoster/ZippyshareCom.py b/pyload/plugins/hoster/ZippyshareCom.py
new file mode 100644
index 000000000..d6b7375e2
--- /dev/null
+++ b/pyload/plugins/hoster/ZippyshareCom.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+#
+# Test links:
+# http://www13.zippyshare.com/v/18665333/file.html
+
+import re
+
+from pyload.plugins.internal.SimpleHoster import SimpleHoster, create_getInfo
+
+
+class ZippyshareCom(SimpleHoster):
+ __name__ = "ZippyshareCom"
+ __type__ = "hoster"
+ __version__ = "0.49"
+
+ __pattern__ = r'(?P<HOST>http://www\d{0,2}\.zippyshare.com)/v(?:/|iew.jsp.*key=)(?P<KEY>\d+)'
+
+ __description__ = """Zippyshare.com hoster plugin"""
+ __author_name__ = ("spoob", "zoidberg", "stickell", "skylab")
+ __author_mail__ = ("spoob@pyload.org", "zoidberg@mujmail.cz", "l.stickell@yahoo.it", "development@sky-lab.de")
+
+ FILE_NAME_PATTERN = r'<title>Zippyshare\.com - (?P<N>[^<]+)</title>'
+ FILE_SIZE_PATTERN = r'>Size:</font>\s*<font [^>]*>(?P<S>[0-9.,]+) (?P<U>[kKMG]+)i?B</font><br />'
+ FILE_INFO_PATTERN = r'document\.getElementById\(\'dlbutton\'\)\.href = "[^;]*/(?P<N>[^"]+)";'
+ OFFLINE_PATTERN = r'>File does not exist on this server</div>'
+
+ SH_COOKIES = [(".zippyshare.com", "ziplocale", "en")]
+
+
+ def setup(self):
+ self.multiDL = True
+
+ def handleFree(self):
+ url = self.get_file_url()
+ if not url:
+ self.fail("Download URL not found.")
+ self.logDebug("Download URL: %s" % url)
+ self.download(url)
+
+ def get_file_url(self):
+ """returns the absolute downloadable filepath"""
+ url_parts = re.search(r'(addthis:url="(http://www(\d+).zippyshare.com/v/(\d*)/file.html))', self.html)
+ number = url_parts.group(4)
+ check = re.search(r'<script type="text/javascript">([^<]*?)(var a = (\d*);)', self.html)
+ if check:
+ a = int(re.search(r'<script type="text/javascript">([^<]*?)(var a = (\d*);)', self.html).group(3))
+ k = int(re.search(r'<script type="text/javascript">([^<]*?)(\d*%(\d*))', self.html).group(3))
+ checksum = ((a + 3) % k) * ((a + 3) % 3) + 18
+ else:
+ # This might work but is insecure
+ # checksum = eval(re.search("((\d*)\s\%\s(\d*)\s\+\s(\d*)\s\%\s(\d*))", self.html).group(0))
+
+ m = re.search(r"((?P<a>\d*)\s%\s(?P<b>\d*)\s\+\s(?P<c>\d*)\s%\s(?P<k>\d*))", self.html)
+ if m is None:
+ self.parseError("Unable to detect values to calculate direct link")
+ a = int(m.group("a"))
+ b = int(m.group("b"))
+ c = int(m.group("c"))
+ k = int(m.group("k"))
+ if a == c:
+ checksum = ((a % b) + (a % k))
+ else:
+ checksum = ((a % b) + (c % k))
+
+ self.logInfo('Checksum: %s' % checksum)
+
+ filename = re.search(r'>Name:</font>\s*<font [^>]*>(?P<N>[^<]+)</font><br />', self.html).group('N')
+
+ url = "/d/%s/%s/%s" % (number, checksum, filename)
+ self.logInfo(self.file_info['HOST'] + url)
+ return self.file_info['HOST'] + url
+
+
+getInfo = create_getInfo(ZippyshareCom)
diff --git a/module/remote/thriftbackend/thriftgen/__init__.py b/pyload/plugins/hoster/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/remote/thriftbackend/thriftgen/__init__.py
+++ b/pyload/plugins/hoster/__init__.py
diff --git a/module/plugins/internal/AbstractExtractor.py b/pyload/plugins/internal/AbstractExtractor.py
index d1d1a09cb..d1d1a09cb 100644
--- a/module/plugins/internal/AbstractExtractor.py
+++ b/pyload/plugins/internal/AbstractExtractor.py
diff --git a/module/plugins/internal/CaptchaService.py b/pyload/plugins/internal/CaptchaService.py
index b247ba654..b247ba654 100644
--- a/module/plugins/internal/CaptchaService.py
+++ b/pyload/plugins/internal/CaptchaService.py
diff --git a/pyload/plugins/internal/DeadCrypter.py b/pyload/plugins/internal/DeadCrypter.py
new file mode 100644
index 000000000..ea9c414cb
--- /dev/null
+++ b/pyload/plugins/internal/DeadCrypter.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Crypter import Crypter as _Crypter
+
+
+class DeadCrypter(_Crypter):
+ __name__ = "DeadCrypter"
+ __type__ = "crypter"
+ __version__ = "0.01"
+
+ __pattern__ = None
+
+ __description__ = """Crypter is no longer available"""
+ __author_name__ = "stickell"
+ __author_mail__ = "l.stickell@yahoo.it"
+
+
+ def setup(self):
+ self.fail("Crypter is no longer available")
diff --git a/pyload/plugins/internal/DeadHoster.py b/pyload/plugins/internal/DeadHoster.py
new file mode 100644
index 000000000..0b2398020
--- /dev/null
+++ b/pyload/plugins/internal/DeadHoster.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.Hoster import Hoster as _Hoster
+
+
+def create_getInfo(plugin):
+
+ def getInfo(urls):
+ yield [('#N/A: ' + url, 0, 1, url) for url in urls]
+
+ return getInfo
+
+
+class DeadHoster(_Hoster):
+ __name__ = "DeadHoster"
+ __type__ = "hoster"
+ __version__ = "0.11"
+
+ __pattern__ = None
+
+ __description__ = """Hoster is no longer available"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+
+ def setup(self):
+ self.fail("Hoster is no longer available")
diff --git a/pyload/plugins/internal/MultiHoster.py b/pyload/plugins/internal/MultiHoster.py
new file mode 100644
index 000000000..d99ae6ff9
--- /dev/null
+++ b/pyload/plugins/internal/MultiHoster.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Hook import Hook
+from pyload.utils import remove_chars
+
+
+class MultiHoster(Hook):
+ __name__ = "MultiHoster"
+ __type__ = "hook"
+ __version__ = "0.20"
+
+ __description__ = """Generic MultiHoster plugin"""
+ __author_name__ = "pyLoad Team"
+ __author_mail__ = "admin@pyload.org"
+
+ replacements = [("2shared.com", "twoshared.com"), ("4shared.com", "fourshared.com"), ("cloudnator.com", "shragle.com"),
+ ("ifile.it", "filecloud.io"), ("easy-share.com", "crocko.com"), ("freakshare.net", "freakshare.com"),
+ ("hellshare.com", "hellshare.cz"), ("share-rapid.cz", "sharerapid.com"), ("sharerapid.cz", "sharerapid.com"),
+ ("ul.to", "uploaded.to"), ("uploaded.net", "uploaded.to"), ("1fichier.com", "onefichier.com")]
+ ignored = []
+ interval = 24 * 60 * 60 #: reload hosters daily
+
+
+ def setup(self):
+ self.hosters = []
+ self.supported = []
+ self.new_supported = []
+
+ def getConfig(self, option, default=''):
+ """getConfig with default value - subclass may not implements all config options"""
+ try:
+ # Fixed loop due to getConf deprecation in 0.4.10
+ return super(MultiHoster, self).getConfig(option)
+ except KeyError:
+ return default
+
+ def getHosterCached(self):
+ if not self.hosters:
+ try:
+ hosterSet = self.toHosterSet(self.getHoster()) - set(self.ignored)
+ except Exception, e:
+ self.logError("%s" % str(e))
+ return []
+
+ try:
+ configMode = self.getConfig('hosterListMode', 'all')
+ if configMode in ("listed", "unlisted"):
+ configSet = self.toHosterSet(self.getConfig('hosterList', '').replace('|', ',').replace(';', ',').split(','))
+
+ if configMode == "listed":
+ hosterSet &= configSet
+ else:
+ hosterSet -= configSet
+
+ except Exception, e:
+ self.logError("%s" % str(e))
+
+ self.hosters = list(hosterSet)
+
+ return self.hosters
+
+ def toHosterSet(self, hosters):
+ hosters = set((str(x).strip().lower() for x in hosters))
+
+ for rep in self.replacements:
+ if rep[0] in hosters:
+ hosters.remove(rep[0])
+ hosters.add(rep[1])
+
+ hosters.discard('')
+ return hosters
+
+ def getHoster(self):
+ """Load list of supported hoster
+
+ :return: List of domain names
+ """
+ raise NotImplementedError
+
+ def coreReady(self):
+ if self.cb:
+ self.core.scheduler.removeJob(self.cb)
+
+ self.setConfig("activated", True) #: config not in sync after plugin reload
+
+ cfg_interval = self.getConfig("interval", None) #: reload interval in hours
+ if cfg_interval is not None:
+ self.interval = cfg_interval * 60 * 60
+
+ if self.interval:
+ self._periodical()
+ else:
+ self.periodical()
+
+ def initPeriodical(self):
+ pass
+
+ def periodical(self):
+ """reload hoster list periodically"""
+ self.logInfo("Reloading supported hoster list")
+
+ old_supported = self.supported
+ self.supported, self.new_supported, self.hosters = [], [], []
+
+ self.overridePlugins()
+
+ old_supported = [hoster for hoster in old_supported if hoster not in self.supported]
+ if old_supported:
+ self.logDebug("UNLOAD: %s" % ", ".join(old_supported))
+ for hoster in old_supported:
+ self.unloadHoster(hoster)
+
+ def overridePlugins(self):
+ pluginMap = {}
+ for name in self.core.pluginManager.hosterPlugins.keys():
+ pluginMap[name.lower()] = name
+
+ accountList = [name.lower() for name, data in self.core.accountManager.accounts.items() if data]
+ excludedList = []
+
+ for hoster in self.getHosterCached():
+ name = remove_chars(hoster.lower(), "-.")
+
+ if name in accountList:
+ excludedList.append(hoster)
+ else:
+ if name in pluginMap:
+ self.supported.append(pluginMap[name])
+ else:
+ self.new_supported.append(hoster)
+
+ if not self.supported and not self.new_supported:
+ self.logError(_("No Hoster loaded"))
+ return
+
+ module = self.core.pluginManager.getPlugin(self.__name__)
+ klass = getattr(module, self.__name__)
+
+ # inject plugin plugin
+ self.logDebug("Overwritten Hosters: %s" % ", ".join(sorted(self.supported)))
+ for hoster in self.supported:
+ dict = self.core.pluginManager.hosterPlugins[hoster]
+ dict['new_module'] = module
+ dict['new_name'] = self.__name__
+
+ if excludedList:
+ self.logInfo("The following hosters were not overwritten - account exists: %s" % ", ".join(sorted(excludedList)))
+
+ if self.new_supported:
+ self.logDebug("New Hosters: %s" % ", ".join(sorted(self.new_supported)))
+
+ # create new regexp
+ regexp = r".*(%s).*" % "|".join([x.replace(".", "\\.") for x in self.new_supported])
+ if hasattr(klass, "__pattern__") and isinstance(klass.__pattern__, basestring) and '://' in klass.__pattern__:
+ regexp = r"%s|%s" % (klass.__pattern__, regexp)
+
+ self.logDebug("Regexp: %s" % regexp)
+
+ dict = self.core.pluginManager.hosterPlugins[self.__name__]
+ dict['pattern'] = regexp
+ dict['re'] = re.compile(regexp)
+
+ def unloadHoster(self, hoster):
+ dict = self.core.pluginManager.hosterPlugins[hoster]
+ if "module" in dict:
+ del dict['module']
+
+ if "new_module" in dict:
+ del dict['new_module']
+ del dict['new_name']
+
+ def unload(self):
+ """Remove override for all hosters. Scheduler job is removed by hookmanager"""
+ for hoster in self.supported:
+ self.unloadHoster(hoster)
+
+ # reset pattern
+ klass = getattr(self.core.pluginManager.getPlugin(self.__name__), self.__name__)
+ dict = self.core.pluginManager.hosterPlugins[self.__name__]
+ dict['pattern'] = getattr(klass, "__pattern__", r'^unmatchable$')
+ dict['re'] = re.compile(dict['pattern'])
+
+ def downloadFailed(self, pyfile):
+ """remove plugin override if download fails but not if file is offline/temp.offline"""
+ if pyfile.hasStatus("failed") and self.getConfig("unloadFailing", True):
+ hdict = self.core.pluginManager.hosterPlugins[pyfile.pluginname]
+ if "new_name" in hdict and hdict['new_name'] == self.__name__:
+ self.logDebug("Unload MultiHoster", pyfile.pluginname, hdict)
+ self.unloadHoster(pyfile.pluginname)
+ pyfile.setStatus("queued")
diff --git a/pyload/plugins/internal/SimpleCrypter.py b/pyload/plugins/internal/SimpleCrypter.py
new file mode 100644
index 000000000..6e639c946
--- /dev/null
+++ b/pyload/plugins/internal/SimpleCrypter.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from pyload.plugins.Crypter import Crypter
+from pyload.plugins.internal.SimpleHoster import PluginParseError, replace_patterns, set_cookies
+from pyload.utils import html_unescape
+
+
+class SimpleCrypter(Crypter):
+ __name__ = "SimpleCrypter"
+ __type__ = "crypter"
+ __version__ = "0.10"
+
+ __pattern__ = None
+
+ __description__ = """Simple decrypter plugin"""
+ __author_name__ = ("stickell", "zoidberg", "Walter Purcaro")
+ __author_mail__ = ("l.stickell@yahoo.it", "zoidberg@mujmail.cz", "vuolter@gmail.com")
+
+ """
+ Following patterns should be defined by each crypter:
+
+ LINK_PATTERN: group(1) must be a download link or a regex to catch more links
+ example: LINK_PATTERN = r'<div class="link"><a href="(http://speedload.org/\w+)'
+
+ TITLE_PATTERN: (optional) The group defined by 'title' should be the title
+ example: TITLE_PATTERN = r'<title>Files of: (?P<title>[^<]+) folder</title>'
+
+ OFFLINE_PATTERN: (optional) Checks if the file is yet available online
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: (optional) Checks if the file is temporarily offline
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+
+ If it's impossible to extract the links using the LINK_PATTERN only you can override the getLinks method.
+
+ If the links are disposed on multiple pages you need to define a pattern:
+
+ PAGES_PATTERN: The group defined by 'pages' must be the total number of pages
+ example: PAGES_PATTERN = r'Pages: (?P<pages>\d+)'
+
+ and a function:
+
+ loadPage(self, page_n):
+ return the html of the page number 'page_n'
+ """
+
+ URL_REPLACEMENTS = []
+
+ SH_COOKIES = True # or False or list of tuples [(domain, name, value)]
+
+
+ def setup(self):
+ if isinstance(self.SH_COOKIES, list):
+ set_cookies(self.req.cj, self.SH_COOKIES)
+
+ def decrypt(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS)
+
+ self.html = self.load(pyfile.url, decode=True)
+
+ self.checkOnline()
+
+ package_name, folder_name = self.getPackageNameAndFolder()
+
+ self.package_links = self.getLinks()
+
+ if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'):
+ self.handleMultiPages()
+
+ self.logDebug('Package has %d links' % len(self.package_links))
+
+ if self.package_links:
+ self.packages = [(package_name, self.package_links, folder_name)]
+ else:
+ self.fail('Could not extract any links')
+
+ def getLinks(self):
+ """
+ Returns the links extracted from self.html
+ You should override this only if it's impossible to extract links using only the LINK_PATTERN.
+ """
+ return re.findall(self.LINK_PATTERN, self.html)
+
+ def checkOnline(self):
+ if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html):
+ self.offline()
+ elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html):
+ self.tempOffline()
+
+ def getPackageNameAndFolder(self):
+ if hasattr(self, 'TITLE_PATTERN'):
+ m = re.search(self.TITLE_PATTERN, self.html)
+ if m:
+ name = folder = html_unescape(m.group('title').strip())
+ self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder))
+ return name, folder
+
+ name = self.pyfile.package().name
+ folder = self.pyfile.package().folder
+ self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder))
+ return name, folder
+
+ def handleMultiPages(self):
+ pages = re.search(self.PAGES_PATTERN, self.html)
+ if pages:
+ pages = int(pages.group('pages'))
+ else:
+ pages = 1
+
+ for p in xrange(2, pages + 1):
+ self.html = self.loadPage(p)
+ self.package_links += self.getLinks()
+
+ def parseError(self, msg):
+ raise PluginParseError(msg)
diff --git a/pyload/plugins/internal/SimpleHoster.py b/pyload/plugins/internal/SimpleHoster.py
new file mode 100644
index 000000000..ca320732f
--- /dev/null
+++ b/pyload/plugins/internal/SimpleHoster.py
@@ -0,0 +1,292 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import time
+from urlparse import urlparse
+
+from pyload.network.CookieJar import CookieJar
+from pyload.network.RequestFactory import getURL
+from pyload.plugins.Hoster import Hoster
+from pyload.utils import fixup, html_unescape, parseFileSize
+
+
+def replace_patterns(string, ruleslist):
+ for r in ruleslist:
+ rf, rt = r
+ string = re.sub(rf, rt, string)
+ #self.logDebug(rf, rt, string)
+ return string
+
+
+def set_cookies(cj, cookies):
+ for cookie in cookies:
+ if isinstance(cookie, tuple) and len(cookie) == 3:
+ domain, name, value = cookie
+ cj.setCookie(domain, name, value)
+
+
+def parseHtmlTagAttrValue(attr_name, tag):
+ m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I)
+ return m.group(2) if m else None
+
+
+def parseHtmlForm(attr_str, html, input_names=None):
+ for form in re.finditer(r"(?P<tag><form[^>]*%s[^>]*>)(?P<content>.*?)</?(form|body|html)[^>]*>" % attr_str,
+ html, re.S | re.I):
+ inputs = {}
+ action = parseHtmlTagAttrValue("action", form.group('tag'))
+ for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('content'), re.S | re.I):
+ name = parseHtmlTagAttrValue("name", inputtag.group(1))
+ if name:
+ value = parseHtmlTagAttrValue("value", inputtag.group(1))
+ if not value:
+ inputs[name] = inputtag.group(3) or ''
+ else:
+ inputs[name] = value
+
+ if isinstance(input_names, dict):
+ # check input attributes
+ for key, val in input_names.items():
+ if key in inputs:
+ if isinstance(val, basestring) and inputs[key] == val:
+ continue
+ elif isinstance(val, tuple) and inputs[key] in val:
+ continue
+ elif hasattr(val, "search") and re.match(val, inputs[key]):
+ continue
+ break # attibute value does not match
+ else:
+ break # attibute name does not match
+ else:
+ return action, inputs # passed attribute check
+ else:
+ # no attribute check
+ return action, inputs
+
+ return {}, None # no matching form found
+
+
+def parseFileInfo(self, url='', html=''):
+ info = {"name": url, "size": 0, "status": 3}
+
+ if hasattr(self, "pyfile"):
+ url = self.pyfile.url
+
+ if hasattr(self, "req") and self.req.http.code == '404':
+ info['status'] = 1
+ else:
+ if not html and hasattr(self, "html"):
+ html = self.html
+ if isinstance(self.SH_BROKEN_ENCODING, (str, unicode)):
+ html = unicode(html, self.SH_BROKEN_ENCODING)
+ if hasattr(self, "html"):
+ self.html = html
+
+ if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, html):
+ info['status'] = 1
+ elif hasattr(self, "FILE_OFFLINE_PATTERN") and re.search(self.FILE_OFFLINE_PATTERN, html): #@TODO: Remove in 0.4.10
+ info['status'] = 1
+ elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, html):
+ info['status'] = 6
+ else:
+ online = False
+ try:
+ info.update(re.match(self.__pattern__, url).groupdict())
+ except:
+ pass
+
+ for pattern in ("FILE_INFO_PATTERN", "FILE_NAME_PATTERN", "FILE_SIZE_PATTERN"):
+ try:
+ info.update(re.search(getattr(self, pattern), html).groupdict())
+ online = True
+ except AttributeError:
+ continue
+
+ if online:
+ # File online, return name and size
+ info['status'] = 2
+ if 'N' in info:
+ info['name'] = replace_patterns(info['N'], self.FILE_NAME_REPLACEMENTS)
+ if 'S' in info:
+ size = replace_patterns(info['S'] + info['U'] if 'U' in info else info['S'],
+ self.FILE_SIZE_REPLACEMENTS)
+ info['size'] = parseFileSize(size)
+ elif isinstance(info['size'], (str, unicode)):
+ if 'units' in info:
+ info['size'] += info['units']
+ info['size'] = parseFileSize(info['size'])
+
+ if hasattr(self, "file_info"):
+ self.file_info = info
+
+ return info['name'], info['size'], info['status'], url
+
+
+def create_getInfo(plugin):
+
+ def getInfo(urls):
+ for url in urls:
+ cj = CookieJar(plugin.__name__)
+ if isinstance(plugin.SH_COOKIES, list):
+ set_cookies(cj, plugin.SH_COOKIES)
+ file_info = parseFileInfo(plugin, url, getURL(replace_patterns(url, plugin.FILE_URL_REPLACEMENTS),
+ decode=not plugin.SH_BROKEN_ENCODING, cookies=cj))
+ yield file_info
+
+ return getInfo
+
+
+def timestamp():
+ return int(time() * 1000)
+
+
+class PluginParseError(Exception):
+
+ def __init__(self, msg):
+ Exception.__init__(self)
+ self.value = 'Parse error (%s) - plugin may be out of date' % msg
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class SimpleHoster(Hoster):
+ __name__ = "SimpleHoster"
+ __type__ = "hoster"
+ __version__ = "0.35"
+
+ __pattern__ = None
+
+ __description__ = """Simple hoster plugin"""
+ __author_name__ = ("zoidberg", "stickell")
+ __author_mail__ = ("zoidberg@mujmail.cz", "l.stickell@yahoo.it")
+
+ """
+ Following patterns should be defined by each hoster:
+
+ FILE_INFO_PATTERN: Name and Size of the file
+ example: FILE_INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)'
+ or
+ FILE_NAME_PATTERN: Name that will be set for the file
+ example: FILE_NAME_PATTERN = r'(?P<N>file_name)'
+ FILE_SIZE_PATTERN: Size that will be checked for the file
+ example: FILE_SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)'
+
+ OFFLINE_PATTERN: Checks if the file is yet available online
+ example: OFFLINE_PATTERN = r'File (deleted|not found)'
+
+ TEMP_OFFLINE_PATTERN: Checks if the file is temporarily offline
+ example: TEMP_OFFLINE_PATTERN = r'Server maintainance'
+
+ PREMIUM_ONLY_PATTERN: (optional) Checks if the file can be downloaded only with a premium account
+ example: PREMIUM_ONLY_PATTERN = r'Premium account required'
+ """
+
+ FILE_NAME_REPLACEMENTS = [("&#?\w+;", fixup)]
+ FILE_SIZE_REPLACEMENTS = []
+ FILE_URL_REPLACEMENTS = []
+
+ SH_BROKEN_ENCODING = False # Set to True or encoding name if encoding in http header is not correct
+ SH_COOKIES = True # or False or list of tuples [(domain, name, value)]
+ SH_CHECK_TRAFFIC = False # True = force check traffic left for a premium account
+
+
+ def init(self):
+ self.file_info = {}
+
+ def setup(self):
+ self.resumeDownload = self.multiDL = self.premium
+ if isinstance(self.SH_COOKIES, list):
+ set_cookies(self.req.cj, self.SH_COOKIES)
+
+ def process(self, pyfile):
+ pyfile.url = replace_patterns(pyfile.url, self.FILE_URL_REPLACEMENTS)
+ self.req.setOption("timeout", 120)
+ # Due to a 0.4.9 core bug self.load would keep previous cookies even if overridden by cookies parameter.
+ # Workaround using getURL. Can be reverted in 0.5 as the cookies bug has been fixed.
+ self.html = getURL(pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
+ premium_only = hasattr(self, 'PREMIUM_ONLY_PATTERN') and re.search(self.PREMIUM_ONLY_PATTERN, self.html)
+ if not premium_only: # Usually premium only pages doesn't show the file information
+ self.getFileInfo()
+
+ if self.premium and (not self.SH_CHECK_TRAFFIC or self.checkTrafficLeft()):
+ self.handlePremium()
+ elif premium_only:
+ self.fail("This link require a premium account")
+ else:
+ # This line is required due to the getURL workaround. Can be removed in 0.5
+ self.html = self.load(pyfile.url, decode=not self.SH_BROKEN_ENCODING, cookies=self.SH_COOKIES)
+ self.handleFree()
+
+ def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False):
+ if type(url) == unicode:
+ url = url.encode('utf8')
+ return Hoster.load(self, url=url, get=get, post=post, ref=ref, cookies=cookies,
+ just_header=just_header, decode=decode)
+
+ def getFileInfo(self):
+ self.logDebug("URL: %s" % self.pyfile.url)
+
+ name, size, status = parseFileInfo(self)[:3]
+
+ if status == 1:
+ self.offline()
+ elif status == 6:
+ self.tempOffline()
+ elif status != 2:
+ self.logDebug(self.file_info)
+ self.parseError('File info')
+
+ if name:
+ self.pyfile.name = name
+ else:
+ self.pyfile.name = html_unescape(urlparse(self.pyfile.url).path.split("/")[-1])
+
+ if size:
+ self.pyfile.size = size
+ else:
+ self.logError("File size not parsed")
+
+ self.logDebug("FILE NAME: %s FILE SIZE: %s" % (self.pyfile.name, self.pyfile.size))
+ return self.file_info
+
+ def handleFree(self):
+ self.fail("Free download not implemented")
+
+ def handlePremium(self):
+ self.fail("Premium download not implemented")
+
+ def parseError(self, msg):
+ raise PluginParseError(msg)
+
+ def longWait(self, wait_time=None, max_tries=3):
+ if wait_time and isinstance(wait_time, (int, long, float)):
+ time_str = "%dh %dm" % divmod(wait_time / 60, 60)
+ else:
+ wait_time = 900
+ time_str = "(unknown time)"
+ max_tries = 100
+
+ self.logInfo("Download limit reached, reconnect or wait %s" % time_str)
+
+ self.setWait(wait_time, True)
+ self.wait()
+ self.retry(max_tries=max_tries, reason="Download limit reached")
+
+ def parseHtmlForm(self, attr_str='', input_names=None):
+ return parseHtmlForm(attr_str, self.html, input_names)
+
+ def checkTrafficLeft(self):
+ traffic = self.account.getAccountInfo(self.user, True)['trafficleft']
+ if traffic == -1:
+ return True
+ size = self.pyfile.size / 1024
+ self.logInfo("Filesize: %i KiB, Traffic left for user %s: %i KiB" % (size, self.user, traffic))
+ return size <= traffic
+
+ # TODO: Remove in 0.5
+ def wait(self, seconds=False, reconnect=False):
+ if seconds:
+ self.setWait(seconds, reconnect)
+ super(SimpleHoster, self).wait()
diff --git a/pyload/plugins/internal/UnRar.py b/pyload/plugins/internal/UnRar.py
new file mode 100644
index 000000000..ed8478a3a
--- /dev/null
+++ b/pyload/plugins/internal/UnRar.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+
+import os
+import re
+
+from glob import glob
+from os.path import join
+from string import digits
+from subprocess import Popen, PIPE
+
+from pyload.plugins.internal.AbstractExtractor import AbtractExtractor, WrongPassword, ArchiveError, CRCError
+from pyload.utils import safe_join, decode
+
+
+class UnRar(AbtractExtractor):
+ __name__ = "UnRar"
+ __version__ = "0.16"
+
+ __description__ = """Rar extractor plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+ CMD = "unrar"
+
+ # there are some more uncovered rar formats
+ re_version = re.compile(r"(UNRAR 5[\.\d]+(.*?)freeware)")
+ re_splitfile = re.compile(r"(.*)\.part(\d+)\.rar$", re.I)
+ re_partfiles = re.compile(r".*\.(rar|r[0-9]+)", re.I)
+ re_filelist = re.compile(r"(.+)\s+(\d+)\s+(\d+)\s+")
+ re_filelist5 = re.compile(r"(.+)\s+(\d+)\s+\d\d-\d\d-\d\d\s+\d\d:\d\d\s+(.+)")
+ re_wrongpwd = re.compile("(Corrupt file or wrong password|password incorrect)", re.I)
+
+
+ @staticmethod
+ def checkDeps():
+ if os.name == "nt":
+ UnRar.CMD = join(pypath, "UnRAR.exe")
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+ else:
+ try:
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+ except OSError:
+
+ # fallback to rar
+ UnRar.CMD = "rar"
+ p = Popen([UnRar.CMD], stdout=PIPE, stderr=PIPE)
+ p.communicate()
+
+ return True
+
+ @staticmethod
+ def getTargets(files_ids):
+ result = []
+
+ for file, id in files_ids:
+ if not file.endswith(".rar"):
+ continue
+
+ match = UnRar.re_splitfile.findall(file)
+ if match:
+ # only add first parts
+ if int(match[0][1]) == 1:
+ result.append((file, id))
+ else:
+ result.append((file, id))
+
+ return result
+
+ def init(self):
+ self.passwordProtected = False
+ self.headerProtected = False #: list files will not work without password
+ self.smallestFile = None #: small file to test passwords
+ self.password = "" #: save the correct password
+
+ def checkArchive(self):
+ p = self.call_unrar("l", "-v", self.file)
+ out, err = p.communicate()
+ if self.re_wrongpwd.search(err):
+ self.passwordProtected = True
+ self.headerProtected = True
+ return True
+
+ # output only used to check if passworded files are present
+ if self.re_version.search(out):
+ for attr, size, name in self.re_filelist5.findall(out):
+ if attr.startswith("*"):
+ self.passwordProtected = True
+ return True
+ else:
+ for name, size, packed in self.re_filelist.findall(out):
+ if name.startswith("*"):
+ self.passwordProtected = True
+ return True
+
+ self.listContent()
+ if not self.files:
+ raise ArchiveError("Empty Archive")
+
+ return False
+
+ def checkPassword(self, password):
+ # at this point we can only verify header protected files
+ if self.headerProtected:
+ p = self.call_unrar("l", "-v", self.file, password=password)
+ out, err = p.communicate()
+ if self.re_wrongpwd.search(err):
+ return False
+
+ return True
+
+ def extract(self, progress, password=None):
+ command = "x" if self.fullpath else "e"
+
+ p = self.call_unrar(command, self.file, self.out, password=password)
+ renice(p.pid, self.renice)
+
+ progress(0)
+ progressstring = ""
+ while True:
+ c = p.stdout.read(1)
+ # quit loop on eof
+ if not c:
+ break
+ # reading a percentage sign -> set progress and restart
+ if c == '%':
+ progress(int(progressstring))
+ progressstring = ""
+ # not reading a digit -> therefore restart
+ elif c not in digits:
+ progressstring = ""
+ # add digit to progressstring
+ else:
+ progressstring = progressstring + c
+ progress(100)
+
+ # retrieve stderr
+ err = p.stderr.read()
+
+ if "CRC failed" in err and not password and not self.passwordProtected:
+ raise CRCError
+ elif "CRC failed" in err:
+ raise WrongPassword
+ if err.strip(): #: raise error if anything is on stderr
+ raise ArchiveError(err.strip())
+ if p.returncode:
+ raise ArchiveError("Process terminated")
+
+ if not self.files:
+ self.password = password
+ self.listContent()
+
+ def getDeleteFiles(self):
+ if ".part" in self.file:
+ return glob(re.sub("(?<=\.part)([01]+)", "*", self.file, re.IGNORECASE))
+ # get files which matches .r* and filter unsuited files out
+ parts = glob(re.sub(r"(?<=\.r)ar$", "*", self.file, re.IGNORECASE))
+ return filter(lambda x: self.re_partfiles.match(x), parts)
+
+ def listContent(self):
+ command = "vb" if self.fullpath else "lb"
+ p = self.call_unrar(command, "-v", self.file, password=self.password)
+ out, err = p.communicate()
+
+ if "Cannot open" in err:
+ raise ArchiveError("Cannot open file")
+
+ if err.strip(): #: only log error at this point
+ self.m.logError(err.strip())
+
+ result = set()
+
+ for f in decode(out).splitlines():
+ f = f.strip()
+ result.add(safe_join(self.out, f))
+
+ self.files = result
+
+ def call_unrar(self, command, *xargs, **kwargs):
+ args = []
+ # overwrite flag
+ args.append("-o+") if self.overwrite else args.append("-o-")
+
+ if self.excludefiles:
+ for word in self.excludefiles.split(';'):
+ args.append("-x%s" % word)
+
+ # assume yes on all queries
+ args.append("-y")
+
+ # set a password
+ if "password" in kwargs and kwargs['password']:
+ args.append("-p%s" % kwargs['password'])
+ else:
+ args.append("-p-")
+
+ # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
+ call = [self.CMD, command] + args + list(xargs)
+ self.m.logDebug(" ".join(call))
+
+ p = Popen(call, stdout=PIPE, stderr=PIPE)
+
+ return p
+
+
+def renice(pid, value):
+ if os.name != "nt" and value:
+ try:
+ Popen(["renice", str(value), str(pid)], stdout=PIPE, stderr=PIPE, bufsize=-1)
+ except:
+ print "Renice failed"
diff --git a/pyload/plugins/internal/UnZip.py b/pyload/plugins/internal/UnZip.py
new file mode 100644
index 000000000..65a5a82bb
--- /dev/null
+++ b/pyload/plugins/internal/UnZip.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import zipfile
+
+from pyload.plugins.internal.AbstractExtractor import AbtractExtractor
+
+
+class UnZip(AbtractExtractor):
+ __name__ = "UnZip"
+ __version__ = "0.1"
+
+ __description__ = """Zip extractor plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ @staticmethod
+ def checkDeps():
+ return sys.version_info[:2] >= (2, 6)
+
+ @staticmethod
+ def getTargets(files_ids):
+ result = []
+
+ for file, id in files_ids:
+ if file.endswith(".zip"):
+ result.append((file, id))
+
+ return result
+
+ def extract(self, progress, password=None):
+ z = zipfile.ZipFile(self.file)
+ self.files = z.namelist()
+ z.extractall(self.out)
+
+ def getDeleteFiles(self):
+ return [self.file]
diff --git a/pyload/plugins/internal/XFSPAccount.py b/pyload/plugins/internal/XFSPAccount.py
new file mode 100644
index 000000000..aec9b7dbc
--- /dev/null
+++ b/pyload/plugins/internal/XFSPAccount.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import re
+
+from time import mktime, strptime
+
+from pyload.plugins.Account import Account
+from pyload.plugins.internal.SimpleHoster import parseHtmlForm
+from pyload.utils import parseFileSize
+
+
+class XFSPAccount(Account):
+ __name__ = "XFSPAccount"
+ __type__ = "account"
+ __version__ = "0.06"
+
+ __description__ = """XFileSharingPro base account plugin"""
+ __author_name__ = "zoidberg"
+ __author_mail__ = "zoidberg@mujmail.cz"
+
+ MAIN_PAGE = None
+
+ VALID_UNTIL_PATTERN = r'>Premium.[Aa]ccount expire:</TD><TD><b>([^<]+)</b>'
+ TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><b>([^<]+)</b>'
+ LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|>Error<'
+ PREMIUM_PATTERN = r'>Renew premium<'
+
+
+ def loadAccountInfo(self, user, req):
+ html = req.load(self.MAIN_PAGE + "?op=my_account", decode=True)
+
+ validuntil = trafficleft = None
+ premium = True if re.search(self.PREMIUM_PATTERN, html) else False
+
+ m = re.search(self.VALID_UNTIL_PATTERN, html)
+ if m:
+ premium = True
+ trafficleft = -1
+ try:
+ self.logDebug(m.group(1))
+ validuntil = mktime(strptime(m.group(1), "%d %B %Y"))
+ except Exception, e:
+ self.logError(e)
+ else:
+ m = re.search(self.TRAFFIC_LEFT_PATTERN, html)
+ if m:
+ trafficleft = m.group(1)
+ if "Unlimited" in trafficleft:
+ premium = True
+ else:
+ trafficleft = parseFileSize(trafficleft) / 1024
+
+ return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}
+
+ def login(self, user, data, req):
+ html = req.load('%slogin.html' % self.MAIN_PAGE, decode=True)
+
+ action, inputs = parseHtmlForm('name="FL"', html)
+ if not inputs:
+ inputs = {"op": "login",
+ "redirect": self.MAIN_PAGE}
+
+ inputs.update({"login": user,
+ "password": data['password']})
+
+ html = req.load(self.MAIN_PAGE, post=inputs, decode=True)
+
+ if re.search(self.LOGIN_FAIL_PATTERN, html):
+ self.wrongPassword()
diff --git a/module/web/__init__.py b/pyload/plugins/internal/__init__.py
index e69de29bb..e69de29bb 100644
--- a/module/web/__init__.py
+++ b/pyload/plugins/internal/__init__.py
diff --git a/pyload/plugins/ocr/GigasizeCom.py b/pyload/plugins/ocr/GigasizeCom.py
new file mode 100644
index 000000000..b139c304e
--- /dev/null
+++ b/pyload/plugins/ocr/GigasizeCom.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.OCR import OCR
+
+
+class GigasizeCom(OCR):
+ __name__ = "GigasizeCom"
+ __type__ = "ocr"
+ __version__ = "0.1"
+
+ __description__ = """Gigasize.com ocr plugin"""
+ __author_name__ = "pyLoad Team"
+ __author_mail__ = "admin@pyload.org"
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.threshold(2.8)
+ self.run_tesser(True, False, False, True)
+ return self.result_captcha
diff --git a/pyload/plugins/ocr/LinksaveIn.py b/pyload/plugins/ocr/LinksaveIn.py
new file mode 100644
index 000000000..1eb8bd796
--- /dev/null
+++ b/pyload/plugins/ocr/LinksaveIn.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+
+from glob import glob
+from PIL import Image
+from os import sep
+from os.path import abspath, dirname
+
+from pyload.plugins.OCR import OCR
+
+
+class LinksaveIn(OCR):
+ __name__ = "LinksaveIn"
+ __type__ = "ocr"
+ __version__ = "0.1"
+
+ __description__ = """Linksave.in ocr plugin"""
+ __author_name__ = "pyLoad Team"
+ __author_mail__ = "admin@pyload.org"
+
+
+ def __init__(self):
+ OCR.__init__(self)
+ self.data_dir = dirname(abspath(__file__)) + sep + "LinksaveIn" + sep
+
+ def load_image(self, image):
+ im = Image.open(image)
+ frame_nr = 0
+
+ lut = im.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ new = Image.new("RGB", im.size)
+ npix = new.load()
+ while True:
+ try:
+ im.seek(frame_nr)
+ except EOFError:
+ break
+ frame = im.copy()
+ pix = frame.load()
+ for x in xrange(frame.size[0]):
+ for y in xrange(frame.size[1]):
+ if lut[pix[x, y]] != (0,0,0):
+ npix[x, y] = lut[pix[x, y]]
+ frame_nr += 1
+ new.save(self.data_dir+"unblacked.png")
+ self.image = new.copy()
+ self.pixels = self.image.load()
+ self.result_captcha = ''
+
+ def get_bg(self):
+ stat = {}
+ cstat = {}
+ img = self.image.convert("P")
+ for bgpath in glob(self.data_dir+"bg/*.gif"):
+ stat[bgpath] = 0
+ bg = Image.open(bgpath)
+
+ bglut = bg.resize((256, 1))
+ bglut.putdata(range(256))
+ bglut = list(bglut.convert("RGB").getdata())
+
+ lut = img.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ bgpix = bg.load()
+ pix = img.load()
+ for x in xrange(bg.size[0]):
+ for y in xrange(bg.size[1]):
+ rgb_bg = bglut[bgpix[x, y]]
+ rgb_c = lut[pix[x, y]]
+ try:
+ cstat[rgb_c] += 1
+ except:
+ cstat[rgb_c] = 1
+ if rgb_bg == rgb_c:
+ stat[bgpath] += 1
+ max_p = 0
+ bg = ""
+ for bgpath, value in stat.items():
+ if max_p < value:
+ bg = bgpath
+ max_p = value
+ return bg
+
+ def substract_bg(self, bgpath):
+ bg = Image.open(bgpath)
+ img = self.image.convert("P")
+
+ bglut = bg.resize((256, 1))
+ bglut.putdata(range(256))
+ bglut = list(bglut.convert("RGB").getdata())
+
+ lut = img.resize((256, 1))
+ lut.putdata(range(256))
+ lut = list(lut.convert("RGB").getdata())
+
+ bgpix = bg.load()
+ pix = img.load()
+ orgpix = self.image.load()
+ for x in xrange(bg.size[0]):
+ for y in xrange(bg.size[1]):
+ rgb_bg = bglut[bgpix[x, y]]
+ rgb_c = lut[pix[x, y]]
+ if rgb_c == rgb_bg:
+ orgpix[x, y] = (255,255,255)
+
+ def eval_black_white(self):
+ new = Image.new("RGB", (140, 75))
+ pix = new.load()
+ orgpix = self.image.load()
+ thresh = 4
+ for x in xrange(new.size[0]):
+ for y in xrange(new.size[1]):
+ rgb = orgpix[x, y]
+ r, g, b = rgb
+ pix[x, y] = (255,255,255)
+ if r > max(b, g)+thresh:
+ pix[x, y] = (0,0,0)
+ if g < min(r, b):
+ pix[x, y] = (0,0,0)
+ if g > max(r, b)+thresh:
+ pix[x, y] = (0,0,0)
+ if b > max(r, g)+thresh:
+ pix[x, y] = (0,0,0)
+ self.image = new
+ self.pixels = self.image.load()
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ bg = self.get_bg()
+ self.substract_bg(bg)
+ self.eval_black_white()
+ self.to_greyscale()
+ self.image.save(self.data_dir+"cleaned_pass1.png")
+ self.clean(4)
+ self.clean(4)
+ self.image.save(self.data_dir+"cleaned_pass2.png")
+ letters = self.split_captcha_letters()
+ final = ""
+ for n, letter in enumerate(letters):
+ self.image = letter
+ self.image.save(ocr.data_dir+"letter%d.png" % n)
+ self.run_tesser(True, True, False, False)
+ final += self.result_captcha
+
+ return final
diff --git a/pyload/plugins/ocr/NetloadIn.py b/pyload/plugins/ocr/NetloadIn.py
new file mode 100644
index 000000000..d31c30989
--- /dev/null
+++ b/pyload/plugins/ocr/NetloadIn.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.OCR import OCR
+
+class NetloadIn(OCR):
+ __name__ = "NetloadIn"
+ __type__ = "ocr"
+ __version__ = "0.1"
+
+ __description__ = """Netload.in ocr plugin"""
+ __author_name__ = "pyLoad Team"
+ __author_mail__ = "admin@pyload.org"
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.to_greyscale()
+ self.clean(3)
+ self.clean(3)
+ self.run_tesser(True, True, False, False)
+
+ self.result_captcha = self.result_captcha.replace(" ", "")[:4] # cut to 4 numbers
+
+ return self.result_captcha
diff --git a/pyload/plugins/ocr/ShareonlineBiz.py b/pyload/plugins/ocr/ShareonlineBiz.py
new file mode 100644
index 000000000..3cee0348e
--- /dev/null
+++ b/pyload/plugins/ocr/ShareonlineBiz.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from pyload.plugins.OCR import OCR
+
+
+class ShareonlineBiz(OCR):
+ __name__ = "ShareonlineBiz"
+ __type__ = "ocr"
+ __version__ = "0.1"
+
+ __description__ = """Shareonline.biz ocr plugin"""
+ __author_name__ = "RaNaN"
+ __author_mail__ = "RaNaN@pyload.org"
+
+
+ def __init__(self):
+ OCR.__init__(self)
+
+ def get_captcha(self, image):
+ self.load_image(image)
+ self.to_greyscale()
+ self.image = self.image.resize((160, 50))
+ self.pixels = self.image.load()
+ self.threshold(1.85)
+ #self.eval_black_white(240)
+ #self.derotate_by_average()
+
+ letters = self.split_captcha_letters()
+
+ final = ""
+ for letter in letters:
+ self.image = letter
+ self.run_tesser(True, True, False, False)
+ final += self.result_captcha
+
+ return final
+
+ #tesseract at 60%
diff --git a/pyload/plugins/ocr/__init__.py b/pyload/plugins/ocr/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/plugins/ocr/__init__.py
diff --git a/pyload/remote/ClickAndLoadBackend.py b/pyload/remote/ClickAndLoadBackend.py
new file mode 100644
index 000000000..365364a3b
--- /dev/null
+++ b/pyload/remote/ClickAndLoadBackend.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+import re
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+from cgi import FieldStorage
+from urllib import unquote
+from base64 import standard_b64decode
+from binascii import unhexlify
+
+try:
+ from Crypto.Cipher import AES
+except:
+ pass
+
+from pyload.manager.RemoteManager import BackendBase
+
+core = None
+js = None
+
+class ClickAndLoadBackend(BackendBase):
+ def setup(self, host, port):
+ self.httpd = HTTPServer((host, port), CNLHandler)
+ global core, js
+ core = self.m.core
+ js = core.js
+
+ def serve(self):
+ while self.enabled:
+ self.httpd.handle_request()
+
+class CNLHandler(BaseHTTPRequestHandler):
+
+ def add_package(self, name, urls, queue=0):
+ print "name", name
+ print "urls", urls
+ print "queue", queue
+
+ def get_post(self, name, default=""):
+ if name in self.post:
+ return self.post[name]
+ else:
+ return default
+
+ def start_response(self, string):
+
+ self.send_response(200)
+
+ self.send_header("Content-Length", len(string))
+ self.send_header("Content-Language", "de")
+ self.send_header("Vary", "Accept-Language, Cookie")
+ self.send_header("Cache-Control", "no-cache, must-revalidate")
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ def do_GET(self):
+ path = self.path.strip("/").lower()
+ #self.wfile.write(path+"\n")
+
+ self.map = [ (r"add$", self.add),
+ (r"addcrypted$", self.addcrypted),
+ (r"addcrypted2$", self.addcrypted2),
+ (r"flashgot", self.flashgot),
+ (r"crossdomain\.xml", self.crossdomain),
+ (r"checkSupportForUrl", self.checksupport),
+ (r"jdcheck.js", self.jdcheck),
+ (r"", self.flash) ]
+
+ func = None
+ for r, f in self.map:
+ if re.match(r"(flash(got)?/?)?"+r, path):
+ func = f
+ break
+
+ if func:
+ try:
+ resp = func()
+ if not resp: resp = "success"
+ resp += "\r\n"
+ self.start_response(resp)
+ self.wfile.write(resp)
+ except Exception, e:
+ self.send_error(500, str(e))
+ else:
+ self.send_error(404, "Not Found")
+
+ def do_POST(self):
+ form = FieldStorage(
+ fp=self.rfile,
+ headers=self.headers,
+ environ={'REQUEST_METHOD':'POST',
+ 'CONTENT_TYPE':self.headers['Content-Type'],
+ })
+
+ self.post = {}
+ for name in form.keys():
+ self.post[name] = form[name].value
+
+ return self.do_GET()
+
+ def flash(self):
+ return "JDownloader"
+
+ def add(self):
+ package = self.get_post('referer', 'ClickAndLoad Package')
+ urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
+
+ self.add_package(package, urls, 0)
+
+ def addcrypted(self):
+ package = self.get_post('referer', 'ClickAndLoad Package')
+ dlc = self.get_post('crypted').replace(" ", "+")
+
+ core.upload_container(package, dlc)
+
+ def addcrypted2(self):
+ package = self.get_post("source", "ClickAndLoad Package")
+ crypted = self.get_post("crypted")
+ jk = self.get_post("jk")
+
+ crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
+ jk = "%s f()" % jk
+ jk = js.eval(jk)
+ Key = unhexlify(jk)
+ IV = Key
+
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n")
+
+ result = filter(lambda x: x != "", result)
+
+ self.add_package(package, result, 0)
+
+
+ def flashgot(self):
+ autostart = int(self.get_post('autostart', 0))
+ package = self.get_post('package', "FlashGot")
+ urls = filter(lambda x: x != "", self.get_post('urls').split("\n"))
+
+ self.add_package(package, urls, autostart)
+
+ def crossdomain(self):
+ rep = "<?xml version=\"1.0\"?>\n"
+ rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
+ rep += "<cross-domain-policy>\n"
+ rep += "<allow-access-from domain=\"*\" />\n"
+ rep += "</cross-domain-policy>"
+ return rep
+
+ def checksupport(self):
+ pass
+
+ def jdcheck(self):
+ rep = "jdownloader=true;\n"
+ rep += "var version='10629';\n"
+ return rep
diff --git a/pyload/remote/SocketBackend.py b/pyload/remote/SocketBackend.py
new file mode 100644
index 000000000..c85e59f42
--- /dev/null
+++ b/pyload/remote/SocketBackend.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+import SocketServer
+
+from pyload.manager.RemoteManager import BackendBase
+
+class RequestHandler(SocketServer.BaseRequestHandler):
+
+ def setup(self):
+ pass
+
+ def handle(self):
+
+ print self.request.recv(1024)
+
+
+
+class SocketBackend(BackendBase):
+
+ def setup(self, host, port):
+ #local only
+ self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler)
+
+ def serve(self):
+ self.server.serve_forever()
diff --git a/pyload/remote/ThriftBackend.py b/pyload/remote/ThriftBackend.py
new file mode 100644
index 000000000..609a3c158
--- /dev/null
+++ b/pyload/remote/ThriftBackend.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: mkaay, RaNaN
+"""
+from os.path import exists
+
+from pyload.manager.RemoteManager import BackendBase
+
+from thriftbackend.Processor import Processor
+from thriftbackend.Protocol import ProtocolFactory
+from thriftbackend.Socket import ServerSocket
+from thriftbackend.Transport import TransportFactory
+#from thriftbackend.Transport import TransportFactoryCompressed
+
+from thrift.server import TServer
+
+class ThriftBackend(BackendBase):
+ def setup(self, host, port):
+ processor = Processor(self.core.api)
+
+ key = None
+ cert = None
+
+ if self.core.config['ssl']['activated']:
+ if exists(self.core.config['ssl']['cert']) and exists(self.core.config['ssl']['key']):
+ self.core.log.info(_("Using SSL ThriftBackend"))
+ key = self.core.config['ssl']['key']
+ cert = self.core.config['ssl']['cert']
+
+ transport = ServerSocket(port, host, key, cert)
+
+
+# tfactory = TransportFactoryCompressed()
+ tfactory = TransportFactory()
+ pfactory = ProtocolFactory()
+
+ self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory)
+ #self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory)
+
+ #server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory)
+
+ def serve(self):
+ self.server.serve()
diff --git a/pyload/remote/__init__.py b/pyload/remote/__init__.py
new file mode 100644
index 000000000..60dfa77c7
--- /dev/null
+++ b/pyload/remote/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+activated = True
diff --git a/pyload/remote/socketbackend/__init__.py b/pyload/remote/socketbackend/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/remote/socketbackend/__init__.py
diff --git a/pyload/remote/socketbackend/create_ttypes.py b/pyload/remote/socketbackend/create_ttypes.py
new file mode 100644
index 000000000..64398c3e7
--- /dev/null
+++ b/pyload/remote/socketbackend/create_ttypes.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+import inspect
+import sys
+from os.path import abspath, dirname, join
+
+path = dirname(abspath(__file__))
+module = join(path, "..", "..")
+
+sys.path.append(join(module, "lib"))
+sys.path.append(join(module, "remote"))
+
+from thriftbackend.thriftgen.pyload import ttypes
+from thriftbackend.thriftgen.pyload.Pyload import Iface
+
+
+def main():
+
+ enums = []
+ classes = []
+
+ print "generating lightweight ttypes.py"
+
+ for name in dir(ttypes):
+ klass = getattr(ttypes, name)
+
+ if name in ("TBase", "TExceptionBase") or name.startswith("_") or not (issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)):
+ continue
+
+ if hasattr(klass, "thrift_spec"):
+ classes.append(klass)
+ else:
+ enums.append(klass)
+
+
+ f = open(join(path, "ttypes.py"), "wb")
+
+ f.write(
+ """# -*- coding: utf-8 -*-
+# Autogenerated by pyload
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+
+class BaseObject(object):
+\t__slots__ = []
+
+""")
+
+ ## generate enums
+ for enum in enums:
+ name = enum.__name__
+ f.write("class %s:\n" % name)
+
+ for attr in dir(enum):
+ if attr.startswith("_") or attr in ("read", "write"): continue
+
+ f.write("\t%s = %s\n" % (attr, getattr(enum, attr)))
+
+ f.write("\n")
+
+ for klass in classes:
+ name = klass.__name__
+ base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject"
+ f.write("class %s(%s):\n" % (name, base))
+ f.write("\t__slots__ = %s\n\n" % klass.__slots__)
+
+ #create init
+ args = ["self"] + ["%s=None" % x for x in klass.__slots__]
+
+ f.write("\tdef __init__(%s):\n" % ", ".join(args))
+ for attr in klass.__slots__:
+ f.write("\t\tself.%s = %s\n" % (attr, attr))
+
+ f.write("\n")
+
+ f.write("class Iface:\n")
+
+ for name in dir(Iface):
+ if name.startswith("_"): continue
+
+ func = inspect.getargspec(getattr(Iface, name))
+
+ f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args)))
+
+ f.write("\n")
+
+ f.close()
diff --git a/pyload/remote/socketbackend/ttypes.py b/pyload/remote/socketbackend/ttypes.py
new file mode 100644
index 000000000..3fd02fac8
--- /dev/null
+++ b/pyload/remote/socketbackend/ttypes.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+# Autogenerated by pyload
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+
+class BaseObject(object):
+ __slots__ = []
+
+class Destination:
+ Collector = 0
+ Queue = 1
+
+class DownloadStatus:
+ Aborted = 9
+ Custom = 11
+ Decrypting = 10
+ Downloading = 12
+ Failed = 8
+ Finished = 0
+ Offline = 1
+ Online = 2
+ Processing = 13
+ Queued = 3
+ Skipped = 4
+ Starting = 7
+ TempOffline = 6
+ Unknown = 14
+ Waiting = 5
+
+class ElementType:
+ File = 1
+ Package = 0
+
+class Input:
+ BOOL = 4
+ CHOICE = 6
+ CLICK = 5
+ LIST = 8
+ MULTIPLE = 7
+ NONE = 0
+ PASSWORD = 3
+ TABLE = 9
+ TEXT = 1
+ TEXTBOX = 2
+
+class Output:
+ CAPTCHA = 1
+ NOTIFICATION = 4
+ QUESTION = 2
+
+class AccountInfo(BaseObject):
+ __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type']
+
+ def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None):
+ self.validuntil = validuntil
+ self.login = login
+ self.options = options
+ self.valid = valid
+ self.trafficleft = trafficleft
+ self.maxtraffic = maxtraffic
+ self.premium = premium
+ self.type = type
+
+class CaptchaTask(BaseObject):
+ __slots__ = ['tid', 'data', 'type', 'resultType']
+
+ def __init__(self, tid=None, data=None, type=None, resultType=None):
+ self.tid = tid
+ self.data = data
+ self.type = type
+ self.resultType = resultType
+
+class ConfigItem(BaseObject):
+ __slots__ = ['name', 'description', 'value', 'type']
+
+ def __init__(self, name=None, description=None, value=None, type=None):
+ self.name = name
+ self.description = description
+ self.value = value
+ self.type = type
+
+class ConfigSection(BaseObject):
+ __slots__ = ['name', 'description', 'items', 'outline']
+
+ def __init__(self, name=None, description=None, items=None, outline=None):
+ self.name = name
+ self.description = description
+ self.items = items
+ self.outline = outline
+
+class DownloadInfo(BaseObject):
+ __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin']
+
+ def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None):
+ self.fid = fid
+ self.name = name
+ self.speed = speed
+ self.eta = eta
+ self.format_eta = format_eta
+ self.bleft = bleft
+ self.size = size
+ self.format_size = format_size
+ self.percent = percent
+ self.status = status
+ self.statusmsg = statusmsg
+ self.format_wait = format_wait
+ self.wait_until = wait_until
+ self.packageID = packageID
+ self.packageName = packageName
+ self.plugin = plugin
+
+class EventInfo(BaseObject):
+ __slots__ = ['eventname', 'id', 'type', 'destination']
+
+ def __init__(self, eventname=None, id=None, type=None, destination=None):
+ self.eventname = eventname
+ self.id = id
+ self.type = type
+ self.destination = destination
+
+class FileData(BaseObject):
+ __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order']
+
+ def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None):
+ self.fid = fid
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.format_size = format_size
+ self.status = status
+ self.statusmsg = statusmsg
+ self.packageID = packageID
+ self.error = error
+ self.order = order
+
+class FileDoesNotExists(Exception):
+ __slots__ = ['fid']
+
+ def __init__(self, fid=None):
+ self.fid = fid
+
+class InteractionTask(BaseObject):
+ __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin']
+
+ def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None):
+ self.iid = iid
+ self.input = input
+ self.structure = structure
+ self.preset = preset
+ self.output = output
+ self.data = data
+ self.title = title
+ self.description = description
+ self.plugin = plugin
+
+class OnlineCheck(BaseObject):
+ __slots__ = ['rid', 'data']
+
+ def __init__(self, rid=None, data=None):
+ self.rid = rid
+ self.data = data
+
+class OnlineStatus(BaseObject):
+ __slots__ = ['name', 'plugin', 'packagename', 'status', 'size']
+
+ def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None):
+ self.name = name
+ self.plugin = plugin
+ self.packagename = packagename
+ self.status = status
+ self.size = size
+
+class PackageData(BaseObject):
+ __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids']
+
+ def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None):
+ self.pid = pid
+ self.name = name
+ self.folder = folder
+ self.site = site
+ self.password = password
+ self.dest = dest
+ self.order = order
+ self.linksdone = linksdone
+ self.sizedone = sizedone
+ self.sizetotal = sizetotal
+ self.linkstotal = linkstotal
+ self.links = links
+ self.fids = fids
+
+class PackageDoesNotExists(Exception):
+ __slots__ = ['pid']
+
+ def __init__(self, pid=None):
+ self.pid = pid
+
+class ServerStatus(BaseObject):
+ __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect']
+
+ def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None):
+ self.pause = pause
+ self.active = active
+ self.queue = queue
+ self.total = total
+ self.speed = speed
+ self.download = download
+ self.reconnect = reconnect
+
+class ServiceCall(BaseObject):
+ __slots__ = ['plugin', 'func', 'arguments', 'parseArguments']
+
+ def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None):
+ self.plugin = plugin
+ self.func = func
+ self.arguments = arguments
+ self.parseArguments = parseArguments
+
+class ServiceDoesNotExists(Exception):
+ __slots__ = ['plugin', 'func']
+
+ def __init__(self, plugin=None, func=None):
+ self.plugin = plugin
+ self.func = func
+
+class ServiceException(Exception):
+ __slots__ = ['msg']
+
+ def __init__(self, msg=None):
+ self.msg = msg
+
+class UserData(BaseObject):
+ __slots__ = ['name', 'email', 'role', 'permission', 'templateName']
+
+ def __init__(self, name=None, email=None, role=None, permission=None, templateName=None):
+ self.name = name
+ self.email = email
+ self.role = role
+ self.permission = permission
+ self.templateName = templateName
+
+class Iface:
+ def addFiles(self, pid, links):
+ pass
+ def addPackage(self, name, links, dest):
+ pass
+ def call(self, info):
+ pass
+ def checkOnlineStatus(self, urls):
+ pass
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ pass
+ def checkURLs(self, urls):
+ pass
+ def deleteFiles(self, fids):
+ pass
+ def deleteFinished(self):
+ pass
+ def deletePackages(self, pids):
+ pass
+ def freeSpace(self):
+ pass
+ def generateAndAddPackages(self, links, dest):
+ pass
+ def generatePackages(self, links):
+ pass
+ def getAccountTypes(self):
+ pass
+ def getAccounts(self, refresh):
+ pass
+ def getAllInfo(self):
+ pass
+ def getAllUserData(self):
+ pass
+ def getCaptchaTask(self, exclusive):
+ pass
+ def getCaptchaTaskStatus(self, tid):
+ pass
+ def getCollector(self):
+ pass
+ def getCollectorData(self):
+ pass
+ def getConfig(self):
+ pass
+ def getConfigValue(self, category, option, section):
+ pass
+ def getEvents(self, uuid):
+ pass
+ def getFileData(self, fid):
+ pass
+ def getFileOrder(self, pid):
+ pass
+ def getInfoByPlugin(self, plugin):
+ pass
+ def getLog(self, offset):
+ pass
+ def getPackageData(self, pid):
+ pass
+ def getPackageInfo(self, pid):
+ pass
+ def getPackageOrder(self, destination):
+ pass
+ def getPluginConfig(self):
+ pass
+ def getQueue(self):
+ pass
+ def getQueueData(self):
+ pass
+ def getServerVersion(self):
+ pass
+ def getServices(self):
+ pass
+ def getUserData(self, username, password):
+ pass
+ def hasService(self, plugin, func):
+ pass
+ def isCaptchaWaiting(self):
+ pass
+ def isTimeDownload(self):
+ pass
+ def isTimeReconnect(self):
+ pass
+ def kill(self):
+ pass
+ def login(self, username, password):
+ pass
+ def moveFiles(self, fids, pid):
+ pass
+ def movePackage(self, destination, pid):
+ pass
+ def orderFile(self, fid, position):
+ pass
+ def orderPackage(self, pid, position):
+ pass
+ def parseURLs(self, html, url):
+ pass
+ def pauseServer(self):
+ pass
+ def pollResults(self, rid):
+ pass
+ def pullFromQueue(self, pid):
+ pass
+ def pushToQueue(self, pid):
+ pass
+ def recheckPackage(self, pid):
+ pass
+ def removeAccount(self, plugin, account):
+ pass
+ def restart(self):
+ pass
+ def restartFailed(self):
+ pass
+ def restartFile(self, fid):
+ pass
+ def restartPackage(self, pid):
+ pass
+ def setCaptchaResult(self, tid, result):
+ pass
+ def setConfigValue(self, category, option, value, section):
+ pass
+ def setPackageData(self, pid, data):
+ pass
+ def setPackageName(self, pid, name):
+ pass
+ def statusDownloads(self):
+ pass
+ def statusServer(self):
+ pass
+ def stopAllDownloads(self):
+ pass
+ def stopDownloads(self, fids):
+ pass
+ def togglePause(self):
+ pass
+ def toggleReconnect(self):
+ pass
+ def unpauseServer(self):
+ pass
+ def updateAccount(self, plugin, account, password, options):
+ pass
+ def uploadContainer(self, filename, data):
+ pass
diff --git a/module/remote/thriftbackend/Processor.py b/pyload/remote/thriftbackend/Processor.py
index a8b87c82c..a8b87c82c 100644
--- a/module/remote/thriftbackend/Processor.py
+++ b/pyload/remote/thriftbackend/Processor.py
diff --git a/pyload/remote/thriftbackend/Protocol.py b/pyload/remote/thriftbackend/Protocol.py
new file mode 100644
index 000000000..a5822df18
--- /dev/null
+++ b/pyload/remote/thriftbackend/Protocol.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from thrift.protocol import TBinaryProtocol
+
+class Protocol(TBinaryProtocol.TBinaryProtocol):
+ def writeString(self, str):
+ try:
+ str = str.encode("utf8", "ignore")
+ except Exception, e:
+ pass
+
+ self.writeI32(len(str))
+ self.trans.write(str)
+
+ def readString(self):
+ len = self.readI32()
+ str = self.trans.readAll(len)
+ try:
+ str = str.decode("utf8", "ignore")
+ except:
+ pass
+
+ return str
+
+
+class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory):
+
+ def getProtocol(self, trans):
+ prot = Protocol(trans, self.strictRead, self.strictWrite)
+ return prot
diff --git a/pyload/remote/thriftbackend/Socket.py b/pyload/remote/thriftbackend/Socket.py
new file mode 100644
index 000000000..b9fa7edbf
--- /dev/null
+++ b/pyload/remote/thriftbackend/Socket.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+
+import sys
+import socket
+import errno
+
+from time import sleep
+
+from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException
+
+WantReadError = Exception #overwritten when ssl is used
+
+class SecureSocketConnection:
+ def __init__(self, connection):
+ self.__dict__["connection"] = connection
+
+ def __getattr__(self, name):
+ return getattr(self.__dict__["connection"], name)
+
+ def __setattr__(self, name, value):
+ setattr(self.__dict__["connection"], name, value)
+
+ def shutdown(self, how=1):
+ self.__dict__["connection"].shutdown()
+
+ def accept(self):
+ connection, address = self.__dict__["connection"].accept()
+ return SecureSocketConnection(connection), address
+
+ def send(self, buff):
+ try:
+ return self.__dict__["connection"].send(buff)
+ except WantReadError:
+ sleep(0.1)
+ return self.send(buff)
+
+ def recv(self, buff):
+ try:
+ return self.__dict__["connection"].recv(buff)
+ except WantReadError:
+ sleep(0.1)
+ return self.recv(buff)
+
+class Socket(TSocket):
+ def __init__(self, host='localhost', port=7228, ssl=False):
+ TSocket.__init__(self, host, port)
+ self.ssl = ssl
+
+ def open(self):
+ if self.ssl:
+ SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
+ WantReadError = SSL.WantReadError
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ c.set_connect_state()
+ self.handle = SecureSocketConnection(c)
+ else:
+ self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ #errno 104 connection reset
+
+ self.handle.settimeout(self._timeout)
+ self.handle.connect((self.host, self.port))
+
+ def read(self, sz):
+ try:
+ buff = self.handle.recv(sz)
+ except socket.error, e:
+ if (e.args[0] == errno.ECONNRESET and
+ (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))):
+ # freebsd and Mach don't follow POSIX semantic of recv
+ # and fail with ECONNRESET if peer performed shutdown.
+ # See corresponding comment and code in TSocket::read()
+ # in lib/cpp/src/transport/TSocket.cpp.
+ self.close()
+ # Trigger the check to raise the END_OF_FILE exception below.
+ buff = ''
+ else:
+ raise
+ except Exception, e:
+ # SSL connection was closed
+ if e.args == (-1, 'Unexpected EOF'):
+ buff = ''
+ elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],):
+ #a socket not using ssl tried to connect
+ buff = ''
+ else:
+ raise
+
+ if not len(buff):
+ raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes')
+ return buff
+
+
+class ServerSocket(TServerSocket, Socket):
+ def __init__(self, port=7228, host="0.0.0.0", key="", cert=""):
+ self.host = host
+ self.port = port
+ self.key = key
+ self.cert = cert
+ self.handle = None
+
+ def listen(self):
+ if self.cert and self.key:
+ SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL
+ WantReadError = SSL.WantReadError
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_privatekey_file(self.key)
+ ctx.use_certificate_file(self.cert)
+
+ tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
+ tmpConnection.set_accept_state()
+ self.handle = SecureSocketConnection(tmpConnection)
+
+ else:
+ self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+
+ self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ if hasattr(self.handle, 'set_timeout'):
+ self.handle.set_timeout(None)
+ self.handle.bind((self.host, self.port))
+ self.handle.listen(128)
+
+ def accept(self):
+ client, addr = self.handle.accept()
+ result = Socket()
+ result.setHandle(client)
+ return result
diff --git a/pyload/remote/thriftbackend/ThriftClient.py b/pyload/remote/thriftbackend/ThriftClient.py
new file mode 100644
index 000000000..913719ed9
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftClient.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+import sys
+from socket import error
+from os.path import dirname, abspath, join
+from traceback import print_exc
+
+try:
+ import thrift
+except ImportError:
+ sys.path.append(abspath(join(dirname(abspath(__file__)), "..", "..", "lib")))
+
+from thrift.transport import TTransport
+#from thrift.transport.TZlibTransport import TZlibTransport
+from Socket import Socket
+from Protocol import Protocol
+
+# modules should import ttypes from here, when want to avoid importing API
+
+from thriftgen.pyload import Pyload
+from thriftgen.pyload.ttypes import *
+
+ConnectionClosed = TTransport.TTransportException
+
+class WrongLogin(Exception):
+ pass
+
+class NoConnection(Exception):
+ pass
+
+class NoSSL(Exception):
+ pass
+
+class ThriftClient:
+ def __init__(self, host="localhost", port=7227, user="", password=""):
+
+ self.createConnection(host, port)
+ try:
+ self.transport.open()
+ except error, e:
+ if e.args and e.args[0] in (111, 10061):
+ raise NoConnection
+ else:
+ print_exc()
+ raise NoConnection
+
+ try:
+ correct = self.client.login(user, password)
+ except error, e:
+ if e.args and e.args[0] == 104:
+ #connection reset by peer, probably wants ssl
+ try:
+ self.createConnection(host, port, True)
+ #set timeout or a ssl socket will block when querying none ssl server
+ self.socket.setTimeout(10)
+
+ except ImportError:
+ #@TODO untested
+ raise NoSSL
+ try:
+ self.transport.open()
+ correct = self.client.login(user, password)
+ finally:
+ self.socket.setTimeout(None)
+ elif e.args and e.args[0] == 32:
+ raise NoConnection
+ else:
+ print_exc()
+ raise NoConnection
+
+ if not correct:
+ self.transport.close()
+ raise WrongLogin
+
+ def createConnection(self, host, port, ssl=False):
+ self.socket = Socket(host, port, ssl)
+ self.transport = TTransport.TBufferedTransport(self.socket)
+# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket))
+
+ protocol = Protocol(self.transport)
+ self.client = Pyload.Client(protocol)
+
+ def close(self):
+ self.transport.close()
+
+ def __getattr__(self, item):
+ return getattr(self.client, item)
diff --git a/pyload/remote/thriftbackend/ThriftTest.py b/pyload/remote/thriftbackend/ThriftTest.py
new file mode 100644
index 000000000..aec20fa33
--- /dev/null
+++ b/pyload/remote/thriftbackend/ThriftTest.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+
+import sys
+from os.path import join, abspath, dirname
+
+path = join((abspath(dirname(__file__))), "..", "..", "lib")
+sys.path.append(path)
+
+from thriftgen.pyload import Pyload
+from thriftgen.pyload.ttypes import *
+from Socket import Socket
+
+from thrift import Thrift
+from thrift.transport import TTransport
+
+from Protocol import Protocol
+
+from time import time
+
+import xmlrpclib
+
+def bench(f, *args, **kwargs):
+ s = time()
+ ret = [f(*args, **kwargs) for i in range(0, 100)]
+ e = time()
+ try:
+ print "%s: %f s" % (f._Method__name, e-s)
+ except:
+ print "%s: %f s" % (f.__name__, e-s)
+ return ret
+
+from getpass import getpass
+user = raw_input("user ")
+passwd = getpass("password ")
+
+server_url = "http%s://%s:%s@%s:%s/" % (
+ "",
+ user,
+ passwd,
+ "127.0.0.1",
+ 7227
+)
+proxy = xmlrpclib.ServerProxy(server_url, allow_none=True)
+
+bench(proxy.get_server_version)
+bench(proxy.status_server)
+bench(proxy.status_downloads)
+#bench(proxy.get_queue)
+#bench(proxy.get_collector)
+print
+try:
+
+ # Make socket
+ transport = Socket('localhost', 7228, False)
+
+ # Buffering is critical. Raw sockets are very slow
+ transport = TTransport.TBufferedTransport(transport)
+
+ # Wrap in a protocol
+ protocol = Protocol(transport)
+
+ # Create a client to use the protocol encoder
+ client = Pyload.Client(protocol)
+
+ # Connect!
+ transport.open()
+
+ print "Login", client.login(user, passwd)
+
+ bench(client.getServerVersion)
+ bench(client.statusServer)
+ bench(client.statusDownloads)
+ #bench(client.getQueue)
+ #bench(client.getCollector)
+
+ print
+ print client.getServerVersion()
+ print client.statusServer()
+ print client.statusDownloads()
+ q = client.getQueue()
+
+ for p in q:
+ data = client.getPackageData(p.pid)
+ print data
+ print "Package Name: ", data.name
+
+ # Close!
+ transport.close()
+
+except Thrift.TException, tx:
+ print 'ThriftExpection: %s' % tx.message
diff --git a/pyload/remote/thriftbackend/Transport.py b/pyload/remote/thriftbackend/Transport.py
new file mode 100644
index 000000000..b5b6c8104
--- /dev/null
+++ b/pyload/remote/thriftbackend/Transport.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+
+from thrift.transport.TTransport import TBufferedTransport
+from thrift.transport.TZlibTransport import TZlibTransport
+
+class Transport(TBufferedTransport):
+ DEFAULT_BUFFER = 4096
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ TBufferedTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+class TransportCompressed(TZlibTransport):
+ DEFAULT_BUFFER = 4096
+
+ def __init__(self, trans, rbuf_size = DEFAULT_BUFFER):
+ TZlibTransport.__init__(self, trans, rbuf_size)
+ self.handle = trans.handle
+ self.remoteaddr = trans.handle.getpeername()
+
+class TransportFactory:
+ def getTransport(self, trans):
+ buffered = Transport(trans)
+ return buffered
+
+class TransportFactoryCompressed:
+ _last_trans = None
+ _last_z = None
+
+ def getTransport(self, trans, compresslevel=9):
+ if trans == self._last_trans:
+ return self._last_z
+ ztrans = TransportCompressed(Transport(trans), compresslevel)
+ self._last_trans = trans
+ self._last_z = ztrans
+ return ztrans
diff --git a/pyload/remote/thriftbackend/__init__.py b/pyload/remote/thriftbackend/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/remote/thriftbackend/__init__.py
diff --git a/pyload/remote/thriftbackend/pyload.thrift b/pyload/remote/thriftbackend/pyload.thrift
new file mode 100644
index 000000000..3aef7af00
--- /dev/null
+++ b/pyload/remote/thriftbackend/pyload.thrift
@@ -0,0 +1,337 @@
+namespace java org.pyload.thrift
+
+typedef i32 FileID
+typedef i32 PackageID
+typedef i32 TaskID
+typedef i32 ResultID
+typedef i32 InteractionID
+typedef list<string> LinkList
+typedef string PluginName
+typedef byte Progress
+typedef byte Priority
+
+
+enum DownloadStatus {
+ Finished
+ Offline,
+ Online,
+ Queued,
+ Skipped,
+ Waiting,
+ TempOffline,
+ Starting,
+ Failed,
+ Aborted,
+ Decrypting,
+ Custom,
+ Downloading,
+ Processing,
+ Unknown
+}
+
+enum Destination {
+ Collector,
+ Queue
+}
+
+enum ElementType {
+ Package,
+ File
+}
+
+// types for user interaction
+// some may only be place holder currently not supported
+// also all input - output combination are not reasonable, see InteractionManager for further info
+enum Input {
+ NONE,
+ TEXT,
+ TEXTBOX,
+ PASSWORD,
+ BOOL, // confirm like, yes or no dialog
+ CLICK, // for positional captchas
+ CHOICE, // choice from list
+ MULTIPLE, // multiple choice from list of elements
+ LIST, // arbitary list of elements
+ TABLE // table like data structure
+}
+// more can be implemented by need
+
+// this describes the type of the outgoing interaction
+// ensure they can be logcial or'ed
+enum Output {
+ CAPTCHA = 1,
+ QUESTION = 2,
+ NOTIFICATION = 4,
+}
+
+struct DownloadInfo {
+ 1: FileID fid,
+ 2: string name,
+ 3: i64 speed,
+ 4: i32 eta,
+ 5: string format_eta,
+ 6: i64 bleft,
+ 7: i64 size,
+ 8: string format_size,
+ 9: Progress percent,
+ 10: DownloadStatus status,
+ 11: string statusmsg,
+ 12: string format_wait,
+ 13: i64 wait_until,
+ 14: PackageID packageID,
+ 15: string packageName,
+ 16: PluginName plugin,
+}
+
+struct ServerStatus {
+ 1: bool pause,
+ 2: i16 active,
+ 3: i16 queue,
+ 4: i16 total,
+ 5: i64 speed,
+ 6: bool download,
+ 7: bool reconnect
+}
+
+struct ConfigItem {
+ 1: string name,
+ 2: string description,
+ 3: string value,
+ 4: string type,
+}
+
+struct ConfigSection {
+ 1: string name,
+ 2: string description,
+ 3: list<ConfigItem> items,
+ 4: optional string outline
+}
+
+struct FileData {
+ 1: FileID fid,
+ 2: string url,
+ 3: string name,
+ 4: PluginName plugin,
+ 5: i64 size,
+ 6: string format_size,
+ 7: DownloadStatus status,
+ 8: string statusmsg,
+ 9: PackageID packageID,
+ 10: string error,
+ 11: i16 order
+}
+
+struct PackageData {
+ 1: PackageID pid,
+ 2: string name,
+ 3: string folder,
+ 4: string site,
+ 5: string password,
+ 6: Destination dest,
+ 7: i16 order,
+ 8: optional i16 linksdone,
+ 9: optional i64 sizedone,
+ 10: optional i64 sizetotal,
+ 11: optional i16 linkstotal,
+ 12: optional list<FileData> links,
+ 13: optional list<FileID> fids
+}
+
+struct InteractionTask {
+ 1: InteractionID iid,
+ 2: Input input,
+ 3: list<string> structure,
+ 4: list<string> preset,
+ 5: Output output,
+ 6: list<string> data,
+ 7: string title,
+ 8: string description,
+ 9: string plugin,
+}
+
+struct CaptchaTask {
+ 1: i16 tid,
+ 2: binary data,
+ 3: string type,
+ 4: string resultType
+}
+
+struct EventInfo {
+ 1: string eventname,
+ 2: optional i32 id,
+ 3: optional ElementType type,
+ 4: optional Destination destination
+}
+
+struct UserData {
+ 1: string name,
+ 2: string email,
+ 3: i32 role,
+ 4: i32 permission,
+ 5: string templateName
+}
+
+struct AccountInfo {
+ 1: i64 validuntil,
+ 2: string login,
+ 3: map<string, list<string>> options,
+ 4: bool valid,
+ 5: i64 trafficleft,
+ 6: i64 maxtraffic,
+ 7: bool premium,
+ 8: string type,
+}
+
+struct ServiceCall {
+ 1: PluginName plugin,
+ 2: string func,
+ 3: optional list<string> arguments,
+ 4: optional bool parseArguments, //default False
+}
+
+struct OnlineStatus {
+ 1: string name,
+ 2: PluginName plugin,
+ 3: string packagename,
+ 4: DownloadStatus status,
+ 5: i64 size, // size <= 0: unknown
+}
+
+struct OnlineCheck {
+ 1: ResultID rid, // -1 -> nothing more to get
+ 2: map<string, OnlineStatus> data, //url to result
+}
+
+
+// exceptions
+
+exception PackageDoesNotExists{
+ 1: PackageID pid
+}
+
+exception FileDoesNotExists{
+ 1: FileID fid
+}
+
+exception ServiceDoesNotExists{
+ 1: string plugin
+ 2: string func
+}
+
+exception ServiceException{
+ 1: string msg
+}
+
+service Pyload {
+
+ //config
+ string getConfigValue(1: string category, 2: string option, 3: string section),
+ void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section),
+ map<string, ConfigSection> getConfig(),
+ map<string, ConfigSection> getPluginConfig(),
+
+ // server status
+ void pauseServer(),
+ void unpauseServer(),
+ bool togglePause(),
+ ServerStatus statusServer(),
+ i64 freeSpace(),
+ string getServerVersion(),
+ void kill(),
+ void restart(),
+ list<string> getLog(1: i32 offset),
+ bool isTimeDownload(),
+ bool isTimeReconnect(),
+ bool toggleReconnect(),
+
+ // download preparing
+
+ // packagename - urls
+ map<string, LinkList> generatePackages(1: LinkList links),
+ map<PluginName, LinkList> checkURLs(1: LinkList urls),
+ map<PluginName, LinkList> parseURLs(1: string html, 2: string url),
+
+ // parses results and generates packages
+ OnlineCheck checkOnlineStatus(1: LinkList urls),
+ OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data)
+
+ // poll results from previosly started online check
+ OnlineCheck pollResults(1: ResultID rid),
+
+ // downloads - information
+ list<DownloadInfo> statusDownloads(),
+ PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e),
+ FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e),
+ list<PackageData> getQueue(),
+ list<PackageData> getCollector(),
+ list<PackageData> getQueueData(),
+ list<PackageData> getCollectorData(),
+ map<i16, PackageID> getPackageOrder(1: Destination destination),
+ map<i16, FileID> getFileOrder(1: PackageID pid)
+
+ // downloads - adding/deleting
+ list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest),
+ PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest),
+ void addFiles(1: PackageID pid, 2: LinkList links),
+ void uploadContainer(1: string filename, 2: binary data),
+ void deleteFiles(1: list<FileID> fids),
+ void deletePackages(1: list<PackageID> pids),
+
+ // downloads - modifying
+ void pushToQueue(1: PackageID pid),
+ void pullFromQueue(1: PackageID pid),
+ void restartPackage(1: PackageID pid),
+ void restartFile(1: FileID fid),
+ void recheckPackage(1: PackageID pid),
+ void stopAllDownloads(),
+ void stopDownloads(1: list<FileID> fids),
+ void setPackageName(1: PackageID pid, 2: string name),
+ void movePackage(1: Destination destination, 2: PackageID pid),
+ void moveFiles(1: list<FileID> fids, 2: PackageID pid),
+ void orderPackage(1: PackageID pid, 2: i16 position),
+ void orderFile(1: FileID fid, 2: i16 position),
+ void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e),
+ list<PackageID> deleteFinished(),
+ void restartFailed(),
+
+ //events
+ list<EventInfo> getEvents(1: string uuid)
+
+ //accounts
+ list<AccountInfo> getAccounts(1: bool refresh),
+ list<string> getAccountTypes()
+ void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options),
+ void removeAccount(1: PluginName plugin, 2: string account),
+
+ //auth
+ bool login(1: string username, 2: string password),
+ UserData getUserData(1: string username, 2:string password),
+ map<string, UserData> getAllUserData(),
+
+ //services
+
+ // servicename: description
+ map<PluginName, map<string, string>> getServices(),
+ bool hasService(1: PluginName plugin, 2: string func),
+ string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e),
+
+
+ //info
+ // {plugin: {name: value}}
+ map<PluginName, map<string, string>> getAllInfo(),
+ map<string, string> getInfoByPlugin(1: PluginName plugin),
+
+ //scheduler
+
+ // TODO
+
+
+ // User interaction
+
+ //captcha
+ bool isCaptchaWaiting(),
+ CaptchaTask getCaptchaTask(1: bool exclusive),
+ string getCaptchaTaskStatus(1: TaskID tid),
+ void setCaptchaResult(1: TaskID tid, 2: string result),
+}
diff --git a/pyload/remote/thriftbackend/thriftgen/__init__.py b/pyload/remote/thriftbackend/thriftgen/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/__init__.py
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
new file mode 100644
index 000000000..ddc1dd451
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote
@@ -0,0 +1,570 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+import sys
+import pprint
+from urlparse import urlparse
+from thrift.transport import TTransport
+from thrift.transport import TSocket
+from thrift.transport import THttpClient
+from thrift.protocol import TBinaryProtocol
+
+import Pyload
+from ttypes import *
+
+if len(sys.argv) <= 1 or sys.argv[1] == '--help':
+ print
+ print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]'
+ print
+ print 'Functions:'
+ print ' string getConfigValue(string category, string option, string section)'
+ print ' void setConfigValue(string category, string option, string value, string section)'
+ print ' getConfig()'
+ print ' getPluginConfig()'
+ print ' void pauseServer()'
+ print ' void unpauseServer()'
+ print ' bool togglePause()'
+ print ' ServerStatus statusServer()'
+ print ' i64 freeSpace()'
+ print ' string getServerVersion()'
+ print ' void kill()'
+ print ' void restart()'
+ print ' getLog(i32 offset)'
+ print ' bool isTimeDownload()'
+ print ' bool isTimeReconnect()'
+ print ' bool toggleReconnect()'
+ print ' generatePackages(LinkList links)'
+ print ' checkURLs(LinkList urls)'
+ print ' parseURLs(string html, string url)'
+ print ' OnlineCheck checkOnlineStatus(LinkList urls)'
+ print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)'
+ print ' OnlineCheck pollResults(ResultID rid)'
+ print ' statusDownloads()'
+ print ' PackageData getPackageData(PackageID pid)'
+ print ' PackageData getPackageInfo(PackageID pid)'
+ print ' FileData getFileData(FileID fid)'
+ print ' getQueue()'
+ print ' getCollector()'
+ print ' getQueueData()'
+ print ' getCollectorData()'
+ print ' getPackageOrder(Destination destination)'
+ print ' getFileOrder(PackageID pid)'
+ print ' generateAndAddPackages(LinkList links, Destination dest)'
+ print ' PackageID addPackage(string name, LinkList links, Destination dest)'
+ print ' void addFiles(PackageID pid, LinkList links)'
+ print ' void uploadContainer(string filename, string data)'
+ print ' void deleteFiles( fids)'
+ print ' void deletePackages( pids)'
+ print ' void pushToQueue(PackageID pid)'
+ print ' void pullFromQueue(PackageID pid)'
+ print ' void restartPackage(PackageID pid)'
+ print ' void restartFile(FileID fid)'
+ print ' void recheckPackage(PackageID pid)'
+ print ' void stopAllDownloads()'
+ print ' void stopDownloads( fids)'
+ print ' void setPackageName(PackageID pid, string name)'
+ print ' void movePackage(Destination destination, PackageID pid)'
+ print ' void moveFiles( fids, PackageID pid)'
+ print ' void orderPackage(PackageID pid, i16 position)'
+ print ' void orderFile(FileID fid, i16 position)'
+ print ' void setPackageData(PackageID pid, data)'
+ print ' deleteFinished()'
+ print ' void restartFailed()'
+ print ' getEvents(string uuid)'
+ print ' getAccounts(bool refresh)'
+ print ' getAccountTypes()'
+ print ' void updateAccount(PluginName plugin, string account, string password, options)'
+ print ' void removeAccount(PluginName plugin, string account)'
+ print ' bool login(string username, string password)'
+ print ' UserData getUserData(string username, string password)'
+ print ' getAllUserData()'
+ print ' getServices()'
+ print ' bool hasService(PluginName plugin, string func)'
+ print ' string call(ServiceCall info)'
+ print ' getAllInfo()'
+ print ' getInfoByPlugin(PluginName plugin)'
+ print ' bool isCaptchaWaiting()'
+ print ' CaptchaTask getCaptchaTask(bool exclusive)'
+ print ' string getCaptchaTaskStatus(TaskID tid)'
+ print ' void setCaptchaResult(TaskID tid, string result)'
+ print
+ sys.exit(0)
+
+pp = pprint.PrettyPrinter(indent = 2)
+host = 'localhost'
+port = 9090
+uri = ''
+framed = False
+http = False
+argi = 1
+
+if sys.argv[argi] == '-h':
+ parts = sys.argv[argi+1].split(':')
+ host = parts[0]
+ if len(parts) > 1:
+ port = int(parts[1])
+ argi += 2
+
+if sys.argv[argi] == '-u':
+ url = urlparse(sys.argv[argi+1])
+ parts = url[1].split(':')
+ host = parts[0]
+ if len(parts) > 1:
+ port = int(parts[1])
+ else:
+ port = 80
+ uri = url[2]
+ if url[4]:
+ uri += '?%s' % url[4]
+ http = True
+ argi += 2
+
+if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':
+ framed = True
+ argi += 1
+
+cmd = sys.argv[argi]
+args = sys.argv[argi+1:]
+
+if http:
+ transport = THttpClient.THttpClient(host, port, uri)
+else:
+ socket = TSocket.TSocket(host, port)
+ if framed:
+ transport = TTransport.TFramedTransport(socket)
+ else:
+ transport = TTransport.TBufferedTransport(socket)
+protocol = TBinaryProtocol.TBinaryProtocol(transport)
+client = Pyload.Client(protocol)
+transport.open()
+
+if cmd == 'getConfigValue':
+ if len(args) != 3:
+ print 'getConfigValue requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.getConfigValue(args[0], args[1], args[2],))
+
+elif cmd == 'setConfigValue':
+ if len(args) != 4:
+ print 'setConfigValue requires 4 args'
+ sys.exit(1)
+ pp.pprint(client.setConfigValue(args[0], args[1], args[2], args[3],))
+
+elif cmd == 'getConfig':
+ if len(args) != 0:
+ print 'getConfig requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getConfig())
+
+elif cmd == 'getPluginConfig':
+ if len(args) != 0:
+ print 'getPluginConfig requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getPluginConfig())
+
+elif cmd == 'pauseServer':
+ if len(args) != 0:
+ print 'pauseServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.pauseServer())
+
+elif cmd == 'unpauseServer':
+ if len(args) != 0:
+ print 'unpauseServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.unpauseServer())
+
+elif cmd == 'togglePause':
+ if len(args) != 0:
+ print 'togglePause requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.togglePause())
+
+elif cmd == 'statusServer':
+ if len(args) != 0:
+ print 'statusServer requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.statusServer())
+
+elif cmd == 'freeSpace':
+ if len(args) != 0:
+ print 'freeSpace requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.freeSpace())
+
+elif cmd == 'getServerVersion':
+ if len(args) != 0:
+ print 'getServerVersion requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getServerVersion())
+
+elif cmd == 'kill':
+ if len(args) != 0:
+ print 'kill requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.kill())
+
+elif cmd == 'restart':
+ if len(args) != 0:
+ print 'restart requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.restart())
+
+elif cmd == 'getLog':
+ if len(args) != 1:
+ print 'getLog requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getLog(eval(args[0]),))
+
+elif cmd == 'isTimeDownload':
+ if len(args) != 0:
+ print 'isTimeDownload requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isTimeDownload())
+
+elif cmd == 'isTimeReconnect':
+ if len(args) != 0:
+ print 'isTimeReconnect requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isTimeReconnect())
+
+elif cmd == 'toggleReconnect':
+ if len(args) != 0:
+ print 'toggleReconnect requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.toggleReconnect())
+
+elif cmd == 'generatePackages':
+ if len(args) != 1:
+ print 'generatePackages requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.generatePackages(eval(args[0]),))
+
+elif cmd == 'checkURLs':
+ if len(args) != 1:
+ print 'checkURLs requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.checkURLs(eval(args[0]),))
+
+elif cmd == 'parseURLs':
+ if len(args) != 2:
+ print 'parseURLs requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.parseURLs(args[0], args[1],))
+
+elif cmd == 'checkOnlineStatus':
+ if len(args) != 1:
+ print 'checkOnlineStatus requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.checkOnlineStatus(eval(args[0]),))
+
+elif cmd == 'checkOnlineStatusContainer':
+ if len(args) != 3:
+ print 'checkOnlineStatusContainer requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.checkOnlineStatusContainer(eval(args[0]), args[1], args[2],))
+
+elif cmd == 'pollResults':
+ if len(args) != 1:
+ print 'pollResults requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pollResults(eval(args[0]),))
+
+elif cmd == 'statusDownloads':
+ if len(args) != 0:
+ print 'statusDownloads requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.statusDownloads())
+
+elif cmd == 'getPackageData':
+ if len(args) != 1:
+ print 'getPackageData requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageData(eval(args[0]),))
+
+elif cmd == 'getPackageInfo':
+ if len(args) != 1:
+ print 'getPackageInfo requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageInfo(eval(args[0]),))
+
+elif cmd == 'getFileData':
+ if len(args) != 1:
+ print 'getFileData requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getFileData(eval(args[0]),))
+
+elif cmd == 'getQueue':
+ if len(args) != 0:
+ print 'getQueue requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getQueue())
+
+elif cmd == 'getCollector':
+ if len(args) != 0:
+ print 'getCollector requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getCollector())
+
+elif cmd == 'getQueueData':
+ if len(args) != 0:
+ print 'getQueueData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getQueueData())
+
+elif cmd == 'getCollectorData':
+ if len(args) != 0:
+ print 'getCollectorData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getCollectorData())
+
+elif cmd == 'getPackageOrder':
+ if len(args) != 1:
+ print 'getPackageOrder requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getPackageOrder(eval(args[0]),))
+
+elif cmd == 'getFileOrder':
+ if len(args) != 1:
+ print 'getFileOrder requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getFileOrder(eval(args[0]),))
+
+elif cmd == 'generateAndAddPackages':
+ if len(args) != 2:
+ print 'generateAndAddPackages requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.generateAndAddPackages(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'addPackage':
+ if len(args) != 3:
+ print 'addPackage requires 3 args'
+ sys.exit(1)
+ pp.pprint(client.addPackage(args[0], eval(args[1]), eval(args[2]),))
+
+elif cmd == 'addFiles':
+ if len(args) != 2:
+ print 'addFiles requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.addFiles(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'uploadContainer':
+ if len(args) != 2:
+ print 'uploadContainer requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.uploadContainer(args[0], args[1],))
+
+elif cmd == 'deleteFiles':
+ if len(args) != 1:
+ print 'deleteFiles requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.deleteFiles(eval(args[0]),))
+
+elif cmd == 'deletePackages':
+ if len(args) != 1:
+ print 'deletePackages requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.deletePackages(eval(args[0]),))
+
+elif cmd == 'pushToQueue':
+ if len(args) != 1:
+ print 'pushToQueue requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pushToQueue(eval(args[0]),))
+
+elif cmd == 'pullFromQueue':
+ if len(args) != 1:
+ print 'pullFromQueue requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.pullFromQueue(eval(args[0]),))
+
+elif cmd == 'restartPackage':
+ if len(args) != 1:
+ print 'restartPackage requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.restartPackage(eval(args[0]),))
+
+elif cmd == 'restartFile':
+ if len(args) != 1:
+ print 'restartFile requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.restartFile(eval(args[0]),))
+
+elif cmd == 'recheckPackage':
+ if len(args) != 1:
+ print 'recheckPackage requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.recheckPackage(eval(args[0]),))
+
+elif cmd == 'stopAllDownloads':
+ if len(args) != 0:
+ print 'stopAllDownloads requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.stopAllDownloads())
+
+elif cmd == 'stopDownloads':
+ if len(args) != 1:
+ print 'stopDownloads requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.stopDownloads(eval(args[0]),))
+
+elif cmd == 'setPackageName':
+ if len(args) != 2:
+ print 'setPackageName requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setPackageName(eval(args[0]), args[1],))
+
+elif cmd == 'movePackage':
+ if len(args) != 2:
+ print 'movePackage requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.movePackage(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'moveFiles':
+ if len(args) != 2:
+ print 'moveFiles requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.moveFiles(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'orderPackage':
+ if len(args) != 2:
+ print 'orderPackage requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.orderPackage(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'orderFile':
+ if len(args) != 2:
+ print 'orderFile requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.orderFile(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'setPackageData':
+ if len(args) != 2:
+ print 'setPackageData requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setPackageData(eval(args[0]), eval(args[1]),))
+
+elif cmd == 'deleteFinished':
+ if len(args) != 0:
+ print 'deleteFinished requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.deleteFinished())
+
+elif cmd == 'restartFailed':
+ if len(args) != 0:
+ print 'restartFailed requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.restartFailed())
+
+elif cmd == 'getEvents':
+ if len(args) != 1:
+ print 'getEvents requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getEvents(args[0],))
+
+elif cmd == 'getAccounts':
+ if len(args) != 1:
+ print 'getAccounts requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getAccounts(eval(args[0]),))
+
+elif cmd == 'getAccountTypes':
+ if len(args) != 0:
+ print 'getAccountTypes requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAccountTypes())
+
+elif cmd == 'updateAccount':
+ if len(args) != 4:
+ print 'updateAccount requires 4 args'
+ sys.exit(1)
+ pp.pprint(client.updateAccount(eval(args[0]), args[1], args[2], eval(args[3]),))
+
+elif cmd == 'removeAccount':
+ if len(args) != 2:
+ print 'removeAccount requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.removeAccount(eval(args[0]), args[1],))
+
+elif cmd == 'login':
+ if len(args) != 2:
+ print 'login requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.login(args[0], args[1],))
+
+elif cmd == 'getUserData':
+ if len(args) != 2:
+ print 'getUserData requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.getUserData(args[0], args[1],))
+
+elif cmd == 'getAllUserData':
+ if len(args) != 0:
+ print 'getAllUserData requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAllUserData())
+
+elif cmd == 'getServices':
+ if len(args) != 0:
+ print 'getServices requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getServices())
+
+elif cmd == 'hasService':
+ if len(args) != 2:
+ print 'hasService requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.hasService(eval(args[0]), args[1],))
+
+elif cmd == 'call':
+ if len(args) != 1:
+ print 'call requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.call(eval(args[0]),))
+
+elif cmd == 'getAllInfo':
+ if len(args) != 0:
+ print 'getAllInfo requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.getAllInfo())
+
+elif cmd == 'getInfoByPlugin':
+ if len(args) != 1:
+ print 'getInfoByPlugin requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getInfoByPlugin(eval(args[0]),))
+
+elif cmd == 'isCaptchaWaiting':
+ if len(args) != 0:
+ print 'isCaptchaWaiting requires 0 args'
+ sys.exit(1)
+ pp.pprint(client.isCaptchaWaiting())
+
+elif cmd == 'getCaptchaTask':
+ if len(args) != 1:
+ print 'getCaptchaTask requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getCaptchaTask(eval(args[0]),))
+
+elif cmd == 'getCaptchaTaskStatus':
+ if len(args) != 1:
+ print 'getCaptchaTaskStatus requires 1 args'
+ sys.exit(1)
+ pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),))
+
+elif cmd == 'setCaptchaResult':
+ if len(args) != 2:
+ print 'setCaptchaResult requires 2 args'
+ sys.exit(1)
+ pp.pprint(client.setCaptchaResult(eval(args[0]), args[1],))
+
+else:
+ print 'Unrecognized method %s' % cmd
+ sys.exit(1)
+
+transport.close()
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
new file mode 100644
index 000000000..f0b356375
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py
@@ -0,0 +1,5533 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+from ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.protocol.TBase import TBase, TExceptionBase
+
+
+class Iface(object):
+ def getConfigValue(self, category, option, section):
+ """
+ Parameters:
+ - category
+ - option
+ - section
+ """
+ pass
+
+ def setConfigValue(self, category, option, value, section):
+ """
+ Parameters:
+ - category
+ - option
+ - value
+ - section
+ """
+ pass
+
+ def getConfig(self,):
+ pass
+
+ def getPluginConfig(self,):
+ pass
+
+ def pauseServer(self,):
+ pass
+
+ def unpauseServer(self,):
+ pass
+
+ def togglePause(self,):
+ pass
+
+ def statusServer(self,):
+ pass
+
+ def freeSpace(self,):
+ pass
+
+ def getServerVersion(self,):
+ pass
+
+ def kill(self,):
+ pass
+
+ def restart(self,):
+ pass
+
+ def getLog(self, offset):
+ """
+ Parameters:
+ - offset
+ """
+ pass
+
+ def isTimeDownload(self,):
+ pass
+
+ def isTimeReconnect(self,):
+ pass
+
+ def toggleReconnect(self,):
+ pass
+
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
+ pass
+
+ def checkURLs(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ pass
+
+ def parseURLs(self, html, url):
+ """
+ Parameters:
+ - html
+ - url
+ """
+ pass
+
+ def checkOnlineStatus(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ pass
+
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ """
+ Parameters:
+ - urls
+ - filename
+ - data
+ """
+ pass
+
+ def pollResults(self, rid):
+ """
+ Parameters:
+ - rid
+ """
+ pass
+
+ def statusDownloads(self,):
+ pass
+
+ def getPackageData(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def getPackageInfo(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def getFileData(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ pass
+
+ def getQueue(self,):
+ pass
+
+ def getCollector(self,):
+ pass
+
+ def getQueueData(self,):
+ pass
+
+ def getCollectorData(self,):
+ pass
+
+ def getPackageOrder(self, destination):
+ """
+ Parameters:
+ - destination
+ """
+ pass
+
+ def getFileOrder(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def generateAndAddPackages(self, links, dest):
+ """
+ Parameters:
+ - links
+ - dest
+ """
+ pass
+
+ def addPackage(self, name, links, dest):
+ """
+ Parameters:
+ - name
+ - links
+ - dest
+ """
+ pass
+
+ def addFiles(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
+ pass
+
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ pass
+
+ def deleteFiles(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ pass
+
+ def deletePackages(self, pids):
+ """
+ Parameters:
+ - pids
+ """
+ pass
+
+ def pushToQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def pullFromQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def restartPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def restartFile(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ pass
+
+ def recheckPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ pass
+
+ def stopAllDownloads(self,):
+ pass
+
+ def stopDownloads(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ pass
+
+ def setPackageName(self, pid, name):
+ """
+ Parameters:
+ - pid
+ - name
+ """
+ pass
+
+ def movePackage(self, destination, pid):
+ """
+ Parameters:
+ - destination
+ - pid
+ """
+ pass
+
+ def moveFiles(self, fids, pid):
+ """
+ Parameters:
+ - fids
+ - pid
+ """
+ pass
+
+ def orderPackage(self, pid, position):
+ """
+ Parameters:
+ - pid
+ - position
+ """
+ pass
+
+ def orderFile(self, fid, position):
+ """
+ Parameters:
+ - fid
+ - position
+ """
+ pass
+
+ def setPackageData(self, pid, data):
+ """
+ Parameters:
+ - pid
+ - data
+ """
+ pass
+
+ def deleteFinished(self,):
+ pass
+
+ def restartFailed(self,):
+ pass
+
+ def getEvents(self, uuid):
+ """
+ Parameters:
+ - uuid
+ """
+ pass
+
+ def getAccounts(self, refresh):
+ """
+ Parameters:
+ - refresh
+ """
+ pass
+
+ def getAccountTypes(self,):
+ pass
+
+ def updateAccount(self, plugin, account, password, options):
+ """
+ Parameters:
+ - plugin
+ - account
+ - password
+ - options
+ """
+ pass
+
+ def removeAccount(self, plugin, account):
+ """
+ Parameters:
+ - plugin
+ - account
+ """
+ pass
+
+ def login(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ pass
+
+ def getUserData(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ pass
+
+ def getAllUserData(self,):
+ pass
+
+ def getServices(self,):
+ pass
+
+ def hasService(self, plugin, func):
+ """
+ Parameters:
+ - plugin
+ - func
+ """
+ pass
+
+ def call(self, info):
+ """
+ Parameters:
+ - info
+ """
+ pass
+
+ def getAllInfo(self,):
+ pass
+
+ def getInfoByPlugin(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ pass
+
+ def isCaptchaWaiting(self,):
+ pass
+
+ def getCaptchaTask(self, exclusive):
+ """
+ Parameters:
+ - exclusive
+ """
+ pass
+
+ def getCaptchaTaskStatus(self, tid):
+ """
+ Parameters:
+ - tid
+ """
+ pass
+
+ def setCaptchaResult(self, tid, result):
+ """
+ Parameters:
+ - tid
+ - result
+ """
+ pass
+
+
+class Client(Iface):
+ def __init__(self, iprot, oprot=None):
+ self._iprot = self._oprot = iprot
+ if oprot is not None:
+ self._oprot = oprot
+ self._seqid = 0
+
+ def getConfigValue(self, category, option, section):
+ """
+ Parameters:
+ - category
+ - option
+ - section
+ """
+ self.send_getConfigValue(category, option, section)
+ return self.recv_getConfigValue()
+
+ def send_getConfigValue(self, category, option, section):
+ self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid)
+ args = getConfigValue_args()
+ args.category = category
+ args.option = option
+ args.section = section
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getConfigValue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getConfigValue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result");
+
+ def setConfigValue(self, category, option, value, section):
+ """
+ Parameters:
+ - category
+ - option
+ - value
+ - section
+ """
+ self.send_setConfigValue(category, option, value, section)
+ self.recv_setConfigValue()
+
+ def send_setConfigValue(self, category, option, value, section):
+ self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid)
+ args = setConfigValue_args()
+ args.category = category
+ args.option = option
+ args.value = value
+ args.section = section
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setConfigValue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setConfigValue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def getConfig(self,):
+ self.send_getConfig()
+ return self.recv_getConfig()
+
+ def send_getConfig(self,):
+ self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid)
+ args = getConfig_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getConfig(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result");
+
+ def getPluginConfig(self,):
+ self.send_getPluginConfig()
+ return self.recv_getPluginConfig()
+
+ def send_getPluginConfig(self,):
+ self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid)
+ args = getPluginConfig_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getPluginConfig(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPluginConfig_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result");
+
+ def pauseServer(self,):
+ self.send_pauseServer()
+ self.recv_pauseServer()
+
+ def send_pauseServer(self,):
+ self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid)
+ args = pauseServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_pauseServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pauseServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def unpauseServer(self,):
+ self.send_unpauseServer()
+ self.recv_unpauseServer()
+
+ def send_unpauseServer(self,):
+ self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid)
+ args = unpauseServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_unpauseServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = unpauseServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def togglePause(self,):
+ self.send_togglePause()
+ return self.recv_togglePause()
+
+ def send_togglePause(self,):
+ self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid)
+ args = togglePause_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_togglePause(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = togglePause_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result");
+
+ def statusServer(self,):
+ self.send_statusServer()
+ return self.recv_statusServer()
+
+ def send_statusServer(self,):
+ self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid)
+ args = statusServer_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_statusServer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = statusServer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result");
+
+ def freeSpace(self,):
+ self.send_freeSpace()
+ return self.recv_freeSpace()
+
+ def send_freeSpace(self,):
+ self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid)
+ args = freeSpace_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_freeSpace(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = freeSpace_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result");
+
+ def getServerVersion(self,):
+ self.send_getServerVersion()
+ return self.recv_getServerVersion()
+
+ def send_getServerVersion(self,):
+ self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid)
+ args = getServerVersion_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getServerVersion(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getServerVersion_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result");
+
+ def kill(self,):
+ self.send_kill()
+ self.recv_kill()
+
+ def send_kill(self,):
+ self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid)
+ args = kill_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_kill(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = kill_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def restart(self,):
+ self.send_restart()
+ self.recv_restart()
+
+ def send_restart(self,):
+ self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid)
+ args = restart_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_restart(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restart_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def getLog(self, offset):
+ """
+ Parameters:
+ - offset
+ """
+ self.send_getLog(offset)
+ return self.recv_getLog()
+
+ def send_getLog(self, offset):
+ self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid)
+ args = getLog_args()
+ args.offset = offset
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getLog(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getLog_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result");
+
+ def isTimeDownload(self,):
+ self.send_isTimeDownload()
+ return self.recv_isTimeDownload()
+
+ def send_isTimeDownload(self,):
+ self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid)
+ args = isTimeDownload_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_isTimeDownload(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isTimeDownload_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result");
+
+ def isTimeReconnect(self,):
+ self.send_isTimeReconnect()
+ return self.recv_isTimeReconnect()
+
+ def send_isTimeReconnect(self,):
+ self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid)
+ args = isTimeReconnect_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_isTimeReconnect(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isTimeReconnect_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result");
+
+ def toggleReconnect(self,):
+ self.send_toggleReconnect()
+ return self.recv_toggleReconnect()
+
+ def send_toggleReconnect(self,):
+ self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid)
+ args = toggleReconnect_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_toggleReconnect(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = toggleReconnect_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result");
+
+ def generatePackages(self, links):
+ """
+ Parameters:
+ - links
+ """
+ self.send_generatePackages(links)
+ return self.recv_generatePackages()
+
+ def send_generatePackages(self, links):
+ self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid)
+ args = generatePackages_args()
+ args.links = links
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_generatePackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = generatePackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result");
+
+ def checkURLs(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ self.send_checkURLs(urls)
+ return self.recv_checkURLs()
+
+ def send_checkURLs(self, urls):
+ self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid)
+ args = checkURLs_args()
+ args.urls = urls
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_checkURLs(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkURLs_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result");
+
+ def parseURLs(self, html, url):
+ """
+ Parameters:
+ - html
+ - url
+ """
+ self.send_parseURLs(html, url)
+ return self.recv_parseURLs()
+
+ def send_parseURLs(self, html, url):
+ self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid)
+ args = parseURLs_args()
+ args.html = html
+ args.url = url
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_parseURLs(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = parseURLs_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result");
+
+ def checkOnlineStatus(self, urls):
+ """
+ Parameters:
+ - urls
+ """
+ self.send_checkOnlineStatus(urls)
+ return self.recv_checkOnlineStatus()
+
+ def send_checkOnlineStatus(self, urls):
+ self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid)
+ args = checkOnlineStatus_args()
+ args.urls = urls
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_checkOnlineStatus(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkOnlineStatus_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result");
+
+ def checkOnlineStatusContainer(self, urls, filename, data):
+ """
+ Parameters:
+ - urls
+ - filename
+ - data
+ """
+ self.send_checkOnlineStatusContainer(urls, filename, data)
+ return self.recv_checkOnlineStatusContainer()
+
+ def send_checkOnlineStatusContainer(self, urls, filename, data):
+ self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid)
+ args = checkOnlineStatusContainer_args()
+ args.urls = urls
+ args.filename = filename
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_checkOnlineStatusContainer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = checkOnlineStatusContainer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result");
+
+ def pollResults(self, rid):
+ """
+ Parameters:
+ - rid
+ """
+ self.send_pollResults(rid)
+ return self.recv_pollResults()
+
+ def send_pollResults(self, rid):
+ self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid)
+ args = pollResults_args()
+ args.rid = rid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_pollResults(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pollResults_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result");
+
+ def statusDownloads(self,):
+ self.send_statusDownloads()
+ return self.recv_statusDownloads()
+
+ def send_statusDownloads(self,):
+ self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid)
+ args = statusDownloads_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_statusDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = statusDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result");
+
+ def getPackageData(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getPackageData(pid)
+ return self.recv_getPackageData()
+
+ def send_getPackageData(self, pid):
+ self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid)
+ args = getPackageData_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getPackageData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result");
+
+ def getPackageInfo(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getPackageInfo(pid)
+ return self.recv_getPackageInfo()
+
+ def send_getPackageInfo(self, pid):
+ self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid)
+ args = getPackageInfo_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getPackageInfo(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result");
+
+ def getFileData(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ self.send_getFileData(fid)
+ return self.recv_getFileData()
+
+ def send_getFileData(self, fid):
+ self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid)
+ args = getFileData_args()
+ args.fid = fid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getFileData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getFileData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result");
+
+ def getQueue(self,):
+ self.send_getQueue()
+ return self.recv_getQueue()
+
+ def send_getQueue(self,):
+ self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid)
+ args = getQueue_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result");
+
+ def getCollector(self,):
+ self.send_getCollector()
+ return self.recv_getCollector()
+
+ def send_getCollector(self,):
+ self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid)
+ args = getCollector_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getCollector(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCollector_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result");
+
+ def getQueueData(self,):
+ self.send_getQueueData()
+ return self.recv_getQueueData()
+
+ def send_getQueueData(self,):
+ self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid)
+ args = getQueueData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getQueueData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getQueueData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result");
+
+ def getCollectorData(self,):
+ self.send_getCollectorData()
+ return self.recv_getCollectorData()
+
+ def send_getCollectorData(self,):
+ self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid)
+ args = getCollectorData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getCollectorData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCollectorData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result");
+
+ def getPackageOrder(self, destination):
+ """
+ Parameters:
+ - destination
+ """
+ self.send_getPackageOrder(destination)
+ return self.recv_getPackageOrder()
+
+ def send_getPackageOrder(self, destination):
+ self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid)
+ args = getPackageOrder_args()
+ args.destination = destination
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getPackageOrder(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getPackageOrder_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result");
+
+ def getFileOrder(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_getFileOrder(pid)
+ return self.recv_getFileOrder()
+
+ def send_getFileOrder(self, pid):
+ self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid)
+ args = getFileOrder_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getFileOrder(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getFileOrder_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result");
+
+ def generateAndAddPackages(self, links, dest):
+ """
+ Parameters:
+ - links
+ - dest
+ """
+ self.send_generateAndAddPackages(links, dest)
+ return self.recv_generateAndAddPackages()
+
+ def send_generateAndAddPackages(self, links, dest):
+ self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid)
+ args = generateAndAddPackages_args()
+ args.links = links
+ args.dest = dest
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_generateAndAddPackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = generateAndAddPackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result");
+
+ def addPackage(self, name, links, dest):
+ """
+ Parameters:
+ - name
+ - links
+ - dest
+ """
+ self.send_addPackage(name, links, dest)
+ return self.recv_addPackage()
+
+ def send_addPackage(self, name, links, dest):
+ self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid)
+ args = addPackage_args()
+ args.name = name
+ args.links = links
+ args.dest = dest
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_addPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = addPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result");
+
+ def addFiles(self, pid, links):
+ """
+ Parameters:
+ - pid
+ - links
+ """
+ self.send_addFiles(pid, links)
+ self.recv_addFiles()
+
+ def send_addFiles(self, pid, links):
+ self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid)
+ args = addFiles_args()
+ args.pid = pid
+ args.links = links
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_addFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = addFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def uploadContainer(self, filename, data):
+ """
+ Parameters:
+ - filename
+ - data
+ """
+ self.send_uploadContainer(filename, data)
+ self.recv_uploadContainer()
+
+ def send_uploadContainer(self, filename, data):
+ self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid)
+ args = uploadContainer_args()
+ args.filename = filename
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_uploadContainer(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = uploadContainer_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def deleteFiles(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ self.send_deleteFiles(fids)
+ self.recv_deleteFiles()
+
+ def send_deleteFiles(self, fids):
+ self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid)
+ args = deleteFiles_args()
+ args.fids = fids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_deleteFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deleteFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def deletePackages(self, pids):
+ """
+ Parameters:
+ - pids
+ """
+ self.send_deletePackages(pids)
+ self.recv_deletePackages()
+
+ def send_deletePackages(self, pids):
+ self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid)
+ args = deletePackages_args()
+ args.pids = pids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_deletePackages(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deletePackages_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def pushToQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_pushToQueue(pid)
+ self.recv_pushToQueue()
+
+ def send_pushToQueue(self, pid):
+ self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid)
+ args = pushToQueue_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_pushToQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pushToQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def pullFromQueue(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_pullFromQueue(pid)
+ self.recv_pullFromQueue()
+
+ def send_pullFromQueue(self, pid):
+ self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid)
+ args = pullFromQueue_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_pullFromQueue(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = pullFromQueue_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def restartPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_restartPackage(pid)
+ self.recv_restartPackage()
+
+ def send_restartPackage(self, pid):
+ self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid)
+ args = restartPackage_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_restartPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def restartFile(self, fid):
+ """
+ Parameters:
+ - fid
+ """
+ self.send_restartFile(fid)
+ self.recv_restartFile()
+
+ def send_restartFile(self, fid):
+ self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid)
+ args = restartFile_args()
+ args.fid = fid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_restartFile(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartFile_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def recheckPackage(self, pid):
+ """
+ Parameters:
+ - pid
+ """
+ self.send_recheckPackage(pid)
+ self.recv_recheckPackage()
+
+ def send_recheckPackage(self, pid):
+ self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid)
+ args = recheckPackage_args()
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_recheckPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = recheckPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def stopAllDownloads(self,):
+ self.send_stopAllDownloads()
+ self.recv_stopAllDownloads()
+
+ def send_stopAllDownloads(self,):
+ self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid)
+ args = stopAllDownloads_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_stopAllDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = stopAllDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def stopDownloads(self, fids):
+ """
+ Parameters:
+ - fids
+ """
+ self.send_stopDownloads(fids)
+ self.recv_stopDownloads()
+
+ def send_stopDownloads(self, fids):
+ self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid)
+ args = stopDownloads_args()
+ args.fids = fids
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_stopDownloads(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = stopDownloads_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def setPackageName(self, pid, name):
+ """
+ Parameters:
+ - pid
+ - name
+ """
+ self.send_setPackageName(pid, name)
+ self.recv_setPackageName()
+
+ def send_setPackageName(self, pid, name):
+ self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid)
+ args = setPackageName_args()
+ args.pid = pid
+ args.name = name
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setPackageName(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPackageName_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def movePackage(self, destination, pid):
+ """
+ Parameters:
+ - destination
+ - pid
+ """
+ self.send_movePackage(destination, pid)
+ self.recv_movePackage()
+
+ def send_movePackage(self, destination, pid):
+ self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid)
+ args = movePackage_args()
+ args.destination = destination
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_movePackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = movePackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def moveFiles(self, fids, pid):
+ """
+ Parameters:
+ - fids
+ - pid
+ """
+ self.send_moveFiles(fids, pid)
+ self.recv_moveFiles()
+
+ def send_moveFiles(self, fids, pid):
+ self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid)
+ args = moveFiles_args()
+ args.fids = fids
+ args.pid = pid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_moveFiles(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = moveFiles_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def orderPackage(self, pid, position):
+ """
+ Parameters:
+ - pid
+ - position
+ """
+ self.send_orderPackage(pid, position)
+ self.recv_orderPackage()
+
+ def send_orderPackage(self, pid, position):
+ self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid)
+ args = orderPackage_args()
+ args.pid = pid
+ args.position = position
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_orderPackage(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = orderPackage_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def orderFile(self, fid, position):
+ """
+ Parameters:
+ - fid
+ - position
+ """
+ self.send_orderFile(fid, position)
+ self.recv_orderFile()
+
+ def send_orderFile(self, fid, position):
+ self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid)
+ args = orderFile_args()
+ args.fid = fid
+ args.position = position
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_orderFile(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = orderFile_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def setPackageData(self, pid, data):
+ """
+ Parameters:
+ - pid
+ - data
+ """
+ self.send_setPackageData(pid, data)
+ self.recv_setPackageData()
+
+ def send_setPackageData(self, pid, data):
+ self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid)
+ args = setPackageData_args()
+ args.pid = pid
+ args.data = data
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setPackageData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setPackageData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.e is not None:
+ raise result.e
+ return
+
+ def deleteFinished(self,):
+ self.send_deleteFinished()
+ return self.recv_deleteFinished()
+
+ def send_deleteFinished(self,):
+ self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid)
+ args = deleteFinished_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_deleteFinished(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = deleteFinished_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result");
+
+ def restartFailed(self,):
+ self.send_restartFailed()
+ self.recv_restartFailed()
+
+ def send_restartFailed(self,):
+ self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid)
+ args = restartFailed_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_restartFailed(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = restartFailed_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def getEvents(self, uuid):
+ """
+ Parameters:
+ - uuid
+ """
+ self.send_getEvents(uuid)
+ return self.recv_getEvents()
+
+ def send_getEvents(self, uuid):
+ self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid)
+ args = getEvents_args()
+ args.uuid = uuid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getEvents(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getEvents_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result");
+
+ def getAccounts(self, refresh):
+ """
+ Parameters:
+ - refresh
+ """
+ self.send_getAccounts(refresh)
+ return self.recv_getAccounts()
+
+ def send_getAccounts(self, refresh):
+ self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid)
+ args = getAccounts_args()
+ args.refresh = refresh
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAccounts(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAccounts_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result");
+
+ def getAccountTypes(self,):
+ self.send_getAccountTypes()
+ return self.recv_getAccountTypes()
+
+ def send_getAccountTypes(self,):
+ self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid)
+ args = getAccountTypes_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAccountTypes(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAccountTypes_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result");
+
+ def updateAccount(self, plugin, account, password, options):
+ """
+ Parameters:
+ - plugin
+ - account
+ - password
+ - options
+ """
+ self.send_updateAccount(plugin, account, password, options)
+ self.recv_updateAccount()
+
+ def send_updateAccount(self, plugin, account, password, options):
+ self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid)
+ args = updateAccount_args()
+ args.plugin = plugin
+ args.account = account
+ args.password = password
+ args.options = options
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_updateAccount(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = updateAccount_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def removeAccount(self, plugin, account):
+ """
+ Parameters:
+ - plugin
+ - account
+ """
+ self.send_removeAccount(plugin, account)
+ self.recv_removeAccount()
+
+ def send_removeAccount(self, plugin, account):
+ self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid)
+ args = removeAccount_args()
+ args.plugin = plugin
+ args.account = account
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_removeAccount(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = removeAccount_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+ def login(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ self.send_login(username, password)
+ return self.recv_login()
+
+ def send_login(self, username, password):
+ self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid)
+ args = login_args()
+ args.username = username
+ args.password = password
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_login(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = login_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result");
+
+ def getUserData(self, username, password):
+ """
+ Parameters:
+ - username
+ - password
+ """
+ self.send_getUserData(username, password)
+ return self.recv_getUserData()
+
+ def send_getUserData(self, username, password):
+ self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid)
+ args = getUserData_args()
+ args.username = username
+ args.password = password
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getUserData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getUserData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result");
+
+ def getAllUserData(self,):
+ self.send_getAllUserData()
+ return self.recv_getAllUserData()
+
+ def send_getAllUserData(self,):
+ self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid)
+ args = getAllUserData_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAllUserData(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllUserData_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result");
+
+ def getServices(self,):
+ self.send_getServices()
+ return self.recv_getServices()
+
+ def send_getServices(self,):
+ self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid)
+ args = getServices_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getServices(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getServices_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result");
+
+ def hasService(self, plugin, func):
+ """
+ Parameters:
+ - plugin
+ - func
+ """
+ self.send_hasService(plugin, func)
+ return self.recv_hasService()
+
+ def send_hasService(self, plugin, func):
+ self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid)
+ args = hasService_args()
+ args.plugin = plugin
+ args.func = func
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_hasService(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = hasService_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result");
+
+ def call(self, info):
+ """
+ Parameters:
+ - info
+ """
+ self.send_call(info)
+ return self.recv_call()
+
+ def send_call(self, info):
+ self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid)
+ args = call_args()
+ args.info = info
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_call(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = call_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ if result.ex is not None:
+ raise result.ex
+ if result.e is not None:
+ raise result.e
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result");
+
+ def getAllInfo(self,):
+ self.send_getAllInfo()
+ return self.recv_getAllInfo()
+
+ def send_getAllInfo(self,):
+ self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid)
+ args = getAllInfo_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getAllInfo(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getAllInfo_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result");
+
+ def getInfoByPlugin(self, plugin):
+ """
+ Parameters:
+ - plugin
+ """
+ self.send_getInfoByPlugin(plugin)
+ return self.recv_getInfoByPlugin()
+
+ def send_getInfoByPlugin(self, plugin):
+ self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid)
+ args = getInfoByPlugin_args()
+ args.plugin = plugin
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getInfoByPlugin(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getInfoByPlugin_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result");
+
+ def isCaptchaWaiting(self,):
+ self.send_isCaptchaWaiting()
+ return self.recv_isCaptchaWaiting()
+
+ def send_isCaptchaWaiting(self,):
+ self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid)
+ args = isCaptchaWaiting_args()
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_isCaptchaWaiting(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = isCaptchaWaiting_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result");
+
+ def getCaptchaTask(self, exclusive):
+ """
+ Parameters:
+ - exclusive
+ """
+ self.send_getCaptchaTask(exclusive)
+ return self.recv_getCaptchaTask()
+
+ def send_getCaptchaTask(self, exclusive):
+ self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid)
+ args = getCaptchaTask_args()
+ args.exclusive = exclusive
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getCaptchaTask(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCaptchaTask_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result");
+
+ def getCaptchaTaskStatus(self, tid):
+ """
+ Parameters:
+ - tid
+ """
+ self.send_getCaptchaTaskStatus(tid)
+ return self.recv_getCaptchaTaskStatus()
+
+ def send_getCaptchaTaskStatus(self, tid):
+ self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid)
+ args = getCaptchaTaskStatus_args()
+ args.tid = tid
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_getCaptchaTaskStatus(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = getCaptchaTaskStatus_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ if result.success is not None:
+ return result.success
+ raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result");
+
+ def setCaptchaResult(self, tid, result):
+ """
+ Parameters:
+ - tid
+ - result
+ """
+ self.send_setCaptchaResult(tid, result)
+ self.recv_setCaptchaResult()
+
+ def send_setCaptchaResult(self, tid, result):
+ self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid)
+ args = setCaptchaResult_args()
+ args.tid = tid
+ args.result = result
+ args.write(self._oprot)
+ self._oprot.writeMessageEnd()
+ self._oprot.trans.flush()
+
+ def recv_setCaptchaResult(self,):
+ (fname, mtype, rseqid) = self._iprot.readMessageBegin()
+ if mtype == TMessageType.EXCEPTION:
+ x = TApplicationException()
+ x.read(self._iprot)
+ self._iprot.readMessageEnd()
+ raise x
+ result = setCaptchaResult_result()
+ result.read(self._iprot)
+ self._iprot.readMessageEnd()
+ return
+
+
+class Processor(Iface, TProcessor):
+ def __init__(self, handler):
+ self._handler = handler
+ self._processMap = {}
+ self._processMap["getConfigValue"] = Processor.process_getConfigValue
+ self._processMap["setConfigValue"] = Processor.process_setConfigValue
+ self._processMap["getConfig"] = Processor.process_getConfig
+ self._processMap["getPluginConfig"] = Processor.process_getPluginConfig
+ self._processMap["pauseServer"] = Processor.process_pauseServer
+ self._processMap["unpauseServer"] = Processor.process_unpauseServer
+ self._processMap["togglePause"] = Processor.process_togglePause
+ self._processMap["statusServer"] = Processor.process_statusServer
+ self._processMap["freeSpace"] = Processor.process_freeSpace
+ self._processMap["getServerVersion"] = Processor.process_getServerVersion
+ self._processMap["kill"] = Processor.process_kill
+ self._processMap["restart"] = Processor.process_restart
+ self._processMap["getLog"] = Processor.process_getLog
+ self._processMap["isTimeDownload"] = Processor.process_isTimeDownload
+ self._processMap["isTimeReconnect"] = Processor.process_isTimeReconnect
+ self._processMap["toggleReconnect"] = Processor.process_toggleReconnect
+ self._processMap["generatePackages"] = Processor.process_generatePackages
+ self._processMap["checkURLs"] = Processor.process_checkURLs
+ self._processMap["parseURLs"] = Processor.process_parseURLs
+ self._processMap["checkOnlineStatus"] = Processor.process_checkOnlineStatus
+ self._processMap["checkOnlineStatusContainer"] = Processor.process_checkOnlineStatusContainer
+ self._processMap["pollResults"] = Processor.process_pollResults
+ self._processMap["statusDownloads"] = Processor.process_statusDownloads
+ self._processMap["getPackageData"] = Processor.process_getPackageData
+ self._processMap["getPackageInfo"] = Processor.process_getPackageInfo
+ self._processMap["getFileData"] = Processor.process_getFileData
+ self._processMap["getQueue"] = Processor.process_getQueue
+ self._processMap["getCollector"] = Processor.process_getCollector
+ self._processMap["getQueueData"] = Processor.process_getQueueData
+ self._processMap["getCollectorData"] = Processor.process_getCollectorData
+ self._processMap["getPackageOrder"] = Processor.process_getPackageOrder
+ self._processMap["getFileOrder"] = Processor.process_getFileOrder
+ self._processMap["generateAndAddPackages"] = Processor.process_generateAndAddPackages
+ self._processMap["addPackage"] = Processor.process_addPackage
+ self._processMap["addFiles"] = Processor.process_addFiles
+ self._processMap["uploadContainer"] = Processor.process_uploadContainer
+ self._processMap["deleteFiles"] = Processor.process_deleteFiles
+ self._processMap["deletePackages"] = Processor.process_deletePackages
+ self._processMap["pushToQueue"] = Processor.process_pushToQueue
+ self._processMap["pullFromQueue"] = Processor.process_pullFromQueue
+ self._processMap["restartPackage"] = Processor.process_restartPackage
+ self._processMap["restartFile"] = Processor.process_restartFile
+ self._processMap["recheckPackage"] = Processor.process_recheckPackage
+ self._processMap["stopAllDownloads"] = Processor.process_stopAllDownloads
+ self._processMap["stopDownloads"] = Processor.process_stopDownloads
+ self._processMap["setPackageName"] = Processor.process_setPackageName
+ self._processMap["movePackage"] = Processor.process_movePackage
+ self._processMap["moveFiles"] = Processor.process_moveFiles
+ self._processMap["orderPackage"] = Processor.process_orderPackage
+ self._processMap["orderFile"] = Processor.process_orderFile
+ self._processMap["setPackageData"] = Processor.process_setPackageData
+ self._processMap["deleteFinished"] = Processor.process_deleteFinished
+ self._processMap["restartFailed"] = Processor.process_restartFailed
+ self._processMap["getEvents"] = Processor.process_getEvents
+ self._processMap["getAccounts"] = Processor.process_getAccounts
+ self._processMap["getAccountTypes"] = Processor.process_getAccountTypes
+ self._processMap["updateAccount"] = Processor.process_updateAccount
+ self._processMap["removeAccount"] = Processor.process_removeAccount
+ self._processMap["login"] = Processor.process_login
+ self._processMap["getUserData"] = Processor.process_getUserData
+ self._processMap["getAllUserData"] = Processor.process_getAllUserData
+ self._processMap["getServices"] = Processor.process_getServices
+ self._processMap["hasService"] = Processor.process_hasService
+ self._processMap["call"] = Processor.process_call
+ self._processMap["getAllInfo"] = Processor.process_getAllInfo
+ self._processMap["getInfoByPlugin"] = Processor.process_getInfoByPlugin
+ self._processMap["isCaptchaWaiting"] = Processor.process_isCaptchaWaiting
+ self._processMap["getCaptchaTask"] = Processor.process_getCaptchaTask
+ self._processMap["getCaptchaTaskStatus"] = Processor.process_getCaptchaTaskStatus
+ self._processMap["setCaptchaResult"] = Processor.process_setCaptchaResult
+
+ def process(self, iprot, oprot):
+ (name, type, seqid) = iprot.readMessageBegin()
+ if name not in self._processMap:
+ iprot.skip(TType.STRUCT)
+ iprot.readMessageEnd()
+ x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+ oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+ x.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+ return
+ else:
+ self._processMap[name](self, seqid, iprot, oprot)
+ return True
+
+ def process_getConfigValue(self, seqid, iprot, oprot):
+ args = getConfigValue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getConfigValue_result()
+ result.success = self._handler.getConfigValue(args.category, args.option, args.section)
+ oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setConfigValue(self, seqid, iprot, oprot):
+ args = setConfigValue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setConfigValue_result()
+ self._handler.setConfigValue(args.category, args.option, args.value, args.section)
+ oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getConfig(self, seqid, iprot, oprot):
+ args = getConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getConfig_result()
+ result.success = self._handler.getConfig()
+ oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getPluginConfig(self, seqid, iprot, oprot):
+ args = getPluginConfig_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPluginConfig_result()
+ result.success = self._handler.getPluginConfig()
+ oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_pauseServer(self, seqid, iprot, oprot):
+ args = pauseServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pauseServer_result()
+ self._handler.pauseServer()
+ oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_unpauseServer(self, seqid, iprot, oprot):
+ args = unpauseServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = unpauseServer_result()
+ self._handler.unpauseServer()
+ oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_togglePause(self, seqid, iprot, oprot):
+ args = togglePause_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = togglePause_result()
+ result.success = self._handler.togglePause()
+ oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_statusServer(self, seqid, iprot, oprot):
+ args = statusServer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = statusServer_result()
+ result.success = self._handler.statusServer()
+ oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_freeSpace(self, seqid, iprot, oprot):
+ args = freeSpace_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = freeSpace_result()
+ result.success = self._handler.freeSpace()
+ oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getServerVersion(self, seqid, iprot, oprot):
+ args = getServerVersion_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getServerVersion_result()
+ result.success = self._handler.getServerVersion()
+ oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_kill(self, seqid, iprot, oprot):
+ args = kill_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = kill_result()
+ self._handler.kill()
+ oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_restart(self, seqid, iprot, oprot):
+ args = restart_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restart_result()
+ self._handler.restart()
+ oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getLog(self, seqid, iprot, oprot):
+ args = getLog_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getLog_result()
+ result.success = self._handler.getLog(args.offset)
+ oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_isTimeDownload(self, seqid, iprot, oprot):
+ args = isTimeDownload_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isTimeDownload_result()
+ result.success = self._handler.isTimeDownload()
+ oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_isTimeReconnect(self, seqid, iprot, oprot):
+ args = isTimeReconnect_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isTimeReconnect_result()
+ result.success = self._handler.isTimeReconnect()
+ oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_toggleReconnect(self, seqid, iprot, oprot):
+ args = toggleReconnect_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = toggleReconnect_result()
+ result.success = self._handler.toggleReconnect()
+ oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_generatePackages(self, seqid, iprot, oprot):
+ args = generatePackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generatePackages_result()
+ result.success = self._handler.generatePackages(args.links)
+ oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_checkURLs(self, seqid, iprot, oprot):
+ args = checkURLs_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkURLs_result()
+ result.success = self._handler.checkURLs(args.urls)
+ oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_parseURLs(self, seqid, iprot, oprot):
+ args = parseURLs_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = parseURLs_result()
+ result.success = self._handler.parseURLs(args.html, args.url)
+ oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_checkOnlineStatus(self, seqid, iprot, oprot):
+ args = checkOnlineStatus_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkOnlineStatus_result()
+ result.success = self._handler.checkOnlineStatus(args.urls)
+ oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_checkOnlineStatusContainer(self, seqid, iprot, oprot):
+ args = checkOnlineStatusContainer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = checkOnlineStatusContainer_result()
+ result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data)
+ oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_pollResults(self, seqid, iprot, oprot):
+ args = pollResults_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pollResults_result()
+ result.success = self._handler.pollResults(args.rid)
+ oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_statusDownloads(self, seqid, iprot, oprot):
+ args = statusDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = statusDownloads_result()
+ result.success = self._handler.statusDownloads()
+ oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getPackageData(self, seqid, iprot, oprot):
+ args = getPackageData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageData_result()
+ try:
+ result.success = self._handler.getPackageData(args.pid)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getPackageInfo(self, seqid, iprot, oprot):
+ args = getPackageInfo_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageInfo_result()
+ try:
+ result.success = self._handler.getPackageInfo(args.pid)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getFileData(self, seqid, iprot, oprot):
+ args = getFileData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getFileData_result()
+ try:
+ result.success = self._handler.getFileData(args.fid)
+ except FileDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getQueue(self, seqid, iprot, oprot):
+ args = getQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getQueue_result()
+ result.success = self._handler.getQueue()
+ oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getCollector(self, seqid, iprot, oprot):
+ args = getCollector_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCollector_result()
+ result.success = self._handler.getCollector()
+ oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getQueueData(self, seqid, iprot, oprot):
+ args = getQueueData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getQueueData_result()
+ result.success = self._handler.getQueueData()
+ oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getCollectorData(self, seqid, iprot, oprot):
+ args = getCollectorData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCollectorData_result()
+ result.success = self._handler.getCollectorData()
+ oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getPackageOrder(self, seqid, iprot, oprot):
+ args = getPackageOrder_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getPackageOrder_result()
+ result.success = self._handler.getPackageOrder(args.destination)
+ oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getFileOrder(self, seqid, iprot, oprot):
+ args = getFileOrder_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getFileOrder_result()
+ result.success = self._handler.getFileOrder(args.pid)
+ oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_generateAndAddPackages(self, seqid, iprot, oprot):
+ args = generateAndAddPackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = generateAndAddPackages_result()
+ result.success = self._handler.generateAndAddPackages(args.links, args.dest)
+ oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_addPackage(self, seqid, iprot, oprot):
+ args = addPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addPackage_result()
+ result.success = self._handler.addPackage(args.name, args.links, args.dest)
+ oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_addFiles(self, seqid, iprot, oprot):
+ args = addFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = addFiles_result()
+ self._handler.addFiles(args.pid, args.links)
+ oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_uploadContainer(self, seqid, iprot, oprot):
+ args = uploadContainer_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = uploadContainer_result()
+ self._handler.uploadContainer(args.filename, args.data)
+ oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_deleteFiles(self, seqid, iprot, oprot):
+ args = deleteFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deleteFiles_result()
+ self._handler.deleteFiles(args.fids)
+ oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_deletePackages(self, seqid, iprot, oprot):
+ args = deletePackages_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deletePackages_result()
+ self._handler.deletePackages(args.pids)
+ oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_pushToQueue(self, seqid, iprot, oprot):
+ args = pushToQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pushToQueue_result()
+ self._handler.pushToQueue(args.pid)
+ oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_pullFromQueue(self, seqid, iprot, oprot):
+ args = pullFromQueue_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = pullFromQueue_result()
+ self._handler.pullFromQueue(args.pid)
+ oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_restartPackage(self, seqid, iprot, oprot):
+ args = restartPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartPackage_result()
+ self._handler.restartPackage(args.pid)
+ oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_restartFile(self, seqid, iprot, oprot):
+ args = restartFile_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartFile_result()
+ self._handler.restartFile(args.fid)
+ oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_recheckPackage(self, seqid, iprot, oprot):
+ args = recheckPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = recheckPackage_result()
+ self._handler.recheckPackage(args.pid)
+ oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_stopAllDownloads(self, seqid, iprot, oprot):
+ args = stopAllDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = stopAllDownloads_result()
+ self._handler.stopAllDownloads()
+ oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_stopDownloads(self, seqid, iprot, oprot):
+ args = stopDownloads_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = stopDownloads_result()
+ self._handler.stopDownloads(args.fids)
+ oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setPackageName(self, seqid, iprot, oprot):
+ args = setPackageName_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPackageName_result()
+ self._handler.setPackageName(args.pid, args.name)
+ oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_movePackage(self, seqid, iprot, oprot):
+ args = movePackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = movePackage_result()
+ self._handler.movePackage(args.destination, args.pid)
+ oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_moveFiles(self, seqid, iprot, oprot):
+ args = moveFiles_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = moveFiles_result()
+ self._handler.moveFiles(args.fids, args.pid)
+ oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_orderPackage(self, seqid, iprot, oprot):
+ args = orderPackage_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = orderPackage_result()
+ self._handler.orderPackage(args.pid, args.position)
+ oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_orderFile(self, seqid, iprot, oprot):
+ args = orderFile_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = orderFile_result()
+ self._handler.orderFile(args.fid, args.position)
+ oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setPackageData(self, seqid, iprot, oprot):
+ args = setPackageData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setPackageData_result()
+ try:
+ self._handler.setPackageData(args.pid, args.data)
+ except PackageDoesNotExists, e:
+ result.e = e
+ oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_deleteFinished(self, seqid, iprot, oprot):
+ args = deleteFinished_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = deleteFinished_result()
+ result.success = self._handler.deleteFinished()
+ oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_restartFailed(self, seqid, iprot, oprot):
+ args = restartFailed_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = restartFailed_result()
+ self._handler.restartFailed()
+ oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getEvents(self, seqid, iprot, oprot):
+ args = getEvents_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getEvents_result()
+ result.success = self._handler.getEvents(args.uuid)
+ oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getAccounts(self, seqid, iprot, oprot):
+ args = getAccounts_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAccounts_result()
+ result.success = self._handler.getAccounts(args.refresh)
+ oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getAccountTypes(self, seqid, iprot, oprot):
+ args = getAccountTypes_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAccountTypes_result()
+ result.success = self._handler.getAccountTypes()
+ oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_updateAccount(self, seqid, iprot, oprot):
+ args = updateAccount_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = updateAccount_result()
+ self._handler.updateAccount(args.plugin, args.account, args.password, args.options)
+ oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_removeAccount(self, seqid, iprot, oprot):
+ args = removeAccount_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = removeAccount_result()
+ self._handler.removeAccount(args.plugin, args.account)
+ oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_login(self, seqid, iprot, oprot):
+ args = login_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = login_result()
+ result.success = self._handler.login(args.username, args.password)
+ oprot.writeMessageBegin("login", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getUserData(self, seqid, iprot, oprot):
+ args = getUserData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getUserData_result()
+ result.success = self._handler.getUserData(args.username, args.password)
+ oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getAllUserData(self, seqid, iprot, oprot):
+ args = getAllUserData_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAllUserData_result()
+ result.success = self._handler.getAllUserData()
+ oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getServices(self, seqid, iprot, oprot):
+ args = getServices_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getServices_result()
+ result.success = self._handler.getServices()
+ oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_hasService(self, seqid, iprot, oprot):
+ args = hasService_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = hasService_result()
+ result.success = self._handler.hasService(args.plugin, args.func)
+ oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_call(self, seqid, iprot, oprot):
+ args = call_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = call_result()
+ try:
+ result.success = self._handler.call(args.info)
+ except ServiceDoesNotExists, ex:
+ result.ex = ex
+ except ServiceException, e:
+ result.e = e
+ oprot.writeMessageBegin("call", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getAllInfo(self, seqid, iprot, oprot):
+ args = getAllInfo_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getAllInfo_result()
+ result.success = self._handler.getAllInfo()
+ oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getInfoByPlugin(self, seqid, iprot, oprot):
+ args = getInfoByPlugin_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getInfoByPlugin_result()
+ result.success = self._handler.getInfoByPlugin(args.plugin)
+ oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_isCaptchaWaiting(self, seqid, iprot, oprot):
+ args = isCaptchaWaiting_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = isCaptchaWaiting_result()
+ result.success = self._handler.isCaptchaWaiting()
+ oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getCaptchaTask(self, seqid, iprot, oprot):
+ args = getCaptchaTask_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCaptchaTask_result()
+ result.success = self._handler.getCaptchaTask(args.exclusive)
+ oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_getCaptchaTaskStatus(self, seqid, iprot, oprot):
+ args = getCaptchaTaskStatus_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = getCaptchaTaskStatus_result()
+ result.success = self._handler.getCaptchaTaskStatus(args.tid)
+ oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+ def process_setCaptchaResult(self, seqid, iprot, oprot):
+ args = setCaptchaResult_args()
+ args.read(iprot)
+ iprot.readMessageEnd()
+ result = setCaptchaResult_result()
+ self._handler.setCaptchaResult(args.tid, args.result)
+ oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid)
+ result.write(oprot)
+ oprot.writeMessageEnd()
+ oprot.trans.flush()
+
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+class getConfigValue_args(TBase):
+ """
+ Attributes:
+ - category
+ - option
+ - section
+ """
+
+ __slots__ = [
+ 'category',
+ 'option',
+ 'section',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'category', None, None,), # 1
+ (2, TType.STRING, 'option', None, None,), # 2
+ (3, TType.STRING, 'section', None, None,), # 3
+ )
+
+ def __init__(self, category=None, option=None, section=None,):
+ self.category = category
+ self.option = option
+ self.section = section
+
+
+class getConfigValue_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class setConfigValue_args(TBase):
+ """
+ Attributes:
+ - category
+ - option
+ - value
+ - section
+ """
+
+ __slots__ = [
+ 'category',
+ 'option',
+ 'value',
+ 'section',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'category', None, None,), # 1
+ (2, TType.STRING, 'option', None, None,), # 2
+ (3, TType.STRING, 'value', None, None,), # 3
+ (4, TType.STRING, 'section', None, None,), # 4
+ )
+
+ def __init__(self, category=None, option=None, value=None, section=None,):
+ self.category = category
+ self.option = option
+ self.value = value
+ self.section = section
+
+
+class setConfigValue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getConfig_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getConfig_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPluginConfig_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getPluginConfig_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class pauseServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pauseServer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class unpauseServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class unpauseServer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class togglePause_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class togglePause_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class statusServer_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class statusServer_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class freeSpace_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class freeSpace_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.I64, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getServerVersion_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getServerVersion_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class kill_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class kill_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restart_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restart_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getLog_args(TBase):
+ """
+ Attributes:
+ - offset
+ """
+
+ __slots__ = [
+ 'offset',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'offset', None, None,), # 1
+ )
+
+ def __init__(self, offset=None,):
+ self.offset = offset
+
+
+class getLog_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRING, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isTimeDownload_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isTimeDownload_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isTimeReconnect_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isTimeReconnect_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class toggleReconnect_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class toggleReconnect_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class generatePackages_args(TBase):
+ """
+ Attributes:
+ - links
+ """
+
+ __slots__ = [
+ 'links',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'links', (TType.STRING, None), None,), # 1
+ )
+
+ def __init__(self, links=None,):
+ self.links = links
+
+
+class generatePackages_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkURLs_args(TBase):
+ """
+ Attributes:
+ - urls
+ """
+
+ __slots__ = [
+ 'urls',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), # 1
+ )
+
+ def __init__(self, urls=None,):
+ self.urls = urls
+
+
+class checkURLs_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class parseURLs_args(TBase):
+ """
+ Attributes:
+ - html
+ - url
+ """
+
+ __slots__ = [
+ 'html',
+ 'url',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'html', None, None,), # 1
+ (2, TType.STRING, 'url', None, None,), # 2
+ )
+
+ def __init__(self, html=None, url=None,):
+ self.html = html
+ self.url = url
+
+
+class parseURLs_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkOnlineStatus_args(TBase):
+ """
+ Attributes:
+ - urls
+ """
+
+ __slots__ = [
+ 'urls',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), # 1
+ )
+
+ def __init__(self, urls=None,):
+ self.urls = urls
+
+
+class checkOnlineStatus_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class checkOnlineStatusContainer_args(TBase):
+ """
+ Attributes:
+ - urls
+ - filename
+ - data
+ """
+
+ __slots__ = [
+ 'urls',
+ 'filename',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'urls', (TType.STRING, None), None,), # 1
+ (2, TType.STRING, 'filename', None, None,), # 2
+ (3, TType.STRING, 'data', None, None,), # 3
+ )
+
+ def __init__(self, urls=None, filename=None, data=None,):
+ self.urls = urls
+ self.filename = filename
+ self.data = data
+
+
+class checkOnlineStatusContainer_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class pollResults_args(TBase):
+ """
+ Attributes:
+ - rid
+ """
+
+ __slots__ = [
+ 'rid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'rid', None, None,), # 1
+ )
+
+ def __init__(self, rid=None,):
+ self.rid = rid
+
+
+class pollResults_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class statusDownloads_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class statusDownloads_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (DownloadInfo, DownloadInfo.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPackageData_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getPackageData_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), # 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), # 1
+ )
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getPackageInfo_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getPackageInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), # 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), # 1
+ )
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getFileData_args(TBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ )
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+
+class getFileData_result(TBase):
+ """
+ Attributes:
+ - success
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None,), # 0
+ (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None,), # 1
+ )
+
+ def __init__(self, success=None, e=None,):
+ self.success = success
+ self.e = e
+
+
+class getQueue_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getQueue_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCollector_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getCollector_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getQueueData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getQueueData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCollectorData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getCollectorData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getPackageOrder_args(TBase):
+ """
+ Attributes:
+ - destination
+ """
+
+ __slots__ = [
+ 'destination',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'destination', None, None,), # 1
+ )
+
+ def __init__(self, destination=None,):
+ self.destination = destination
+
+
+class getPackageOrder_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getFileOrder_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class getFileOrder_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class generateAndAddPackages_args(TBase):
+ """
+ Attributes:
+ - links
+ - dest
+ """
+
+ __slots__ = [
+ 'links',
+ 'dest',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'links', (TType.STRING, None), None,), # 1
+ (2, TType.I32, 'dest', None, None,), # 2
+ )
+
+ def __init__(self, links=None, dest=None,):
+ self.links = links
+ self.dest = dest
+
+
+class generateAndAddPackages_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.I32, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class addPackage_args(TBase):
+ """
+ Attributes:
+ - name
+ - links
+ - dest
+ """
+
+ __slots__ = [
+ 'name',
+ 'links',
+ 'dest',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None,), # 1
+ (2, TType.LIST, 'links', (TType.STRING, None), None,), # 2
+ (3, TType.I32, 'dest', None, None,), # 3
+ )
+
+ def __init__(self, name=None, links=None, dest=None,):
+ self.name = name
+ self.links = links
+ self.dest = dest
+
+
+class addPackage_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.I32, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class addFiles_args(TBase):
+ """
+ Attributes:
+ - pid
+ - links
+ """
+
+ __slots__ = [
+ 'pid',
+ 'links',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ (2, TType.LIST, 'links', (TType.STRING, None), None,), # 2
+ )
+
+ def __init__(self, pid=None, links=None,):
+ self.pid = pid
+ self.links = links
+
+
+class addFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class uploadContainer_args(TBase):
+ """
+ Attributes:
+ - filename
+ - data
+ """
+
+ __slots__ = [
+ 'filename',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'filename', None, None,), # 1
+ (2, TType.STRING, 'data', None, None,), # 2
+ )
+
+ def __init__(self, filename=None, data=None,):
+ self.filename = filename
+ self.data = data
+
+
+class uploadContainer_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deleteFiles_args(TBase):
+ """
+ Attributes:
+ - fids
+ """
+
+ __slots__ = [
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), # 1
+ )
+
+ def __init__(self, fids=None,):
+ self.fids = fids
+
+
+class deleteFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deletePackages_args(TBase):
+ """
+ Attributes:
+ - pids
+ """
+
+ __slots__ = [
+ 'pids',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'pids', (TType.I32, None), None,), # 1
+ )
+
+ def __init__(self, pids=None,):
+ self.pids = pids
+
+
+class deletePackages_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pushToQueue_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class pushToQueue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class pullFromQueue_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class pullFromQueue_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class restartPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartFile_args(TBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ )
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+
+class restartFile_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class recheckPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+
+class recheckPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopAllDownloads_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopAllDownloads_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class stopDownloads_args(TBase):
+ """
+ Attributes:
+ - fids
+ """
+
+ __slots__ = [
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), # 1
+ )
+
+ def __init__(self, fids=None,):
+ self.fids = fids
+
+
+class stopDownloads_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setPackageName_args(TBase):
+ """
+ Attributes:
+ - pid
+ - name
+ """
+
+ __slots__ = [
+ 'pid',
+ 'name',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ (2, TType.STRING, 'name', None, None,), # 2
+ )
+
+ def __init__(self, pid=None, name=None,):
+ self.pid = pid
+ self.name = name
+
+
+class setPackageName_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class movePackage_args(TBase):
+ """
+ Attributes:
+ - destination
+ - pid
+ """
+
+ __slots__ = [
+ 'destination',
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'destination', None, None,), # 1
+ (2, TType.I32, 'pid', None, None,), # 2
+ )
+
+ def __init__(self, destination=None, pid=None,):
+ self.destination = destination
+ self.pid = pid
+
+
+class movePackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class moveFiles_args(TBase):
+ """
+ Attributes:
+ - fids
+ - pid
+ """
+
+ __slots__ = [
+ 'fids',
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.LIST, 'fids', (TType.I32, None), None,), # 1
+ (2, TType.I32, 'pid', None, None,), # 2
+ )
+
+ def __init__(self, fids=None, pid=None,):
+ self.fids = fids
+ self.pid = pid
+
+
+class moveFiles_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class orderPackage_args(TBase):
+ """
+ Attributes:
+ - pid
+ - position
+ """
+
+ __slots__ = [
+ 'pid',
+ 'position',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ (2, TType.I16, 'position', None, None,), # 2
+ )
+
+ def __init__(self, pid=None, position=None,):
+ self.pid = pid
+ self.position = position
+
+
+class orderPackage_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class orderFile_args(TBase):
+ """
+ Attributes:
+ - fid
+ - position
+ """
+
+ __slots__ = [
+ 'fid',
+ 'position',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ (2, TType.I16, 'position', None, None,), # 2
+ )
+
+ def __init__(self, fid=None, position=None,):
+ self.fid = fid
+ self.position = position
+
+
+class orderFile_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class setPackageData_args(TBase):
+ """
+ Attributes:
+ - pid
+ - data
+ """
+
+ __slots__ = [
+ 'pid',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ (2, TType.MAP, 'data', (TType.STRING, None, TType.STRING, None), None,), # 2
+ )
+
+ def __init__(self, pid=None, data=None,):
+ self.pid = pid
+ self.data = data
+
+
+class setPackageData_result(TBase):
+ """
+ Attributes:
+ - e
+ """
+
+ __slots__ = [
+ 'e',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), # 1
+ )
+
+ def __init__(self, e=None,):
+ self.e = e
+
+
+class deleteFinished_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class deleteFinished_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.I32, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class restartFailed_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class restartFailed_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getEvents_args(TBase):
+ """
+ Attributes:
+ - uuid
+ """
+
+ __slots__ = [
+ 'uuid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'uuid', None, None,), # 1
+ )
+
+ def __init__(self, uuid=None,):
+ self.uuid = uuid
+
+
+class getEvents_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (EventInfo, EventInfo.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAccounts_args(TBase):
+ """
+ Attributes:
+ - refresh
+ """
+
+ __slots__ = [
+ 'refresh',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.BOOL, 'refresh', None, None,), # 1
+ )
+
+ def __init__(self, refresh=None,):
+ self.refresh = refresh
+
+
+class getAccounts_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRUCT, (AccountInfo, AccountInfo.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAccountTypes_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAccountTypes_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.LIST, 'success', (TType.STRING, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class updateAccount_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - account
+ - password
+ - options
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'account',
+ 'password',
+ 'options',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ (2, TType.STRING, 'account', None, None,), # 2
+ (3, TType.STRING, 'password', None, None,), # 3
+ (4, TType.MAP, 'options', (TType.STRING, None, TType.STRING, None), None,), # 4
+ )
+
+ def __init__(self, plugin=None, account=None, password=None, options=None,):
+ self.plugin = plugin
+ self.account = account
+ self.password = password
+ self.options = options
+
+
+class updateAccount_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class removeAccount_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - account
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'account',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ (2, TType.STRING, 'account', None, None,), # 2
+ )
+
+ def __init__(self, plugin=None, account=None,):
+ self.plugin = plugin
+ self.account = account
+
+
+class removeAccount_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class login_args(TBase):
+ """
+ Attributes:
+ - username
+ - password
+ """
+
+ __slots__ = [
+ 'username',
+ 'password',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'username', None, None,), # 1
+ (2, TType.STRING, 'password', None, None,), # 2
+ )
+
+ def __init__(self, username=None, password=None,):
+ self.username = username
+ self.password = password
+
+
+class login_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getUserData_args(TBase):
+ """
+ Attributes:
+ - username
+ - password
+ """
+
+ __slots__ = [
+ 'username',
+ 'password',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'username', None, None,), # 1
+ (2, TType.STRING, 'password', None, None,), # 2
+ )
+
+ def __init__(self, username=None, password=None,):
+ self.username = username
+ self.password = password
+
+
+class getUserData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getAllUserData_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAllUserData_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (UserData, UserData.thrift_spec)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getServices_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getServices_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class hasService_args(TBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ (2, TType.STRING, 'func', None, None,), # 2
+ )
+
+ def __init__(self, plugin=None, func=None,):
+ self.plugin = plugin
+ self.func = func
+
+
+class hasService_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class call_args(TBase):
+ """
+ Attributes:
+ - info
+ """
+
+ __slots__ = [
+ 'info',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None,), # 1
+ )
+
+ def __init__(self, info=None,):
+ self.info = info
+
+
+class call_result(TBase):
+ """
+ Attributes:
+ - success
+ - ex
+ - e
+ """
+
+ __slots__ = [
+ 'success',
+ 'ex',
+ 'e',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), # 0
+ (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None,), # 1
+ (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None,), # 2
+ )
+
+ def __init__(self, success=None, ex=None, e=None,):
+ self.success = success
+ self.ex = ex
+ self.e = e
+
+
+class getAllInfo_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class getAllInfo_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getInfoByPlugin_args(TBase):
+ """
+ Attributes:
+ - plugin
+ """
+
+ __slots__ = [
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ )
+
+ def __init__(self, plugin=None,):
+ self.plugin = plugin
+
+
+class getInfoByPlugin_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.MAP, 'success', (TType.STRING, None, TType.STRING, None), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class isCaptchaWaiting_args(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
+
+
+class isCaptchaWaiting_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.BOOL, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCaptchaTask_args(TBase):
+ """
+ Attributes:
+ - exclusive
+ """
+
+ __slots__ = [
+ 'exclusive',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.BOOL, 'exclusive', None, None,), # 1
+ )
+
+ def __init__(self, exclusive=None,):
+ self.exclusive = exclusive
+
+
+class getCaptchaTask_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class getCaptchaTaskStatus_args(TBase):
+ """
+ Attributes:
+ - tid
+ """
+
+ __slots__ = [
+ 'tid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'tid', None, None,), # 1
+ )
+
+ def __init__(self, tid=None,):
+ self.tid = tid
+
+
+class getCaptchaTaskStatus_result(TBase):
+ """
+ Attributes:
+ - success
+ """
+
+ __slots__ = [
+ 'success',
+ ]
+
+ thrift_spec = (
+ (0, TType.STRING, 'success', None, None,), # 0
+ )
+
+ def __init__(self, success=None,):
+ self.success = success
+
+
+class setCaptchaResult_args(TBase):
+ """
+ Attributes:
+ - tid
+ - result
+ """
+
+ __slots__ = [
+ 'tid',
+ 'result',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'tid', None, None,), # 1
+ (2, TType.STRING, 'result', None, None,), # 2
+ )
+
+ def __init__(self, tid=None, result=None,):
+ self.tid = tid
+ self.result = result
+
+
+class setCaptchaResult_result(TBase):
+
+ __slots__ = [
+ ]
+
+ thrift_spec = (
+ )
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py
new file mode 100644
index 000000000..9a0fb88bf
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+__all__ = ['ttypes', 'constants', 'Pyload']
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/constants.py b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
new file mode 100644
index 000000000..3bdd64cc1
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py
@@ -0,0 +1,10 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+from ttypes import *
diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
new file mode 100644
index 000000000..c2c50924e
--- /dev/null
+++ b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py
@@ -0,0 +1,834 @@
+#
+# Autogenerated by Thrift Compiler (0.9.0-dev)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+# options string: py:slots, dynamic
+#
+
+from thrift.Thrift import TType, TMessageType, TException
+
+from thrift.protocol.TBase import TBase, TExceptionBase
+
+
+class DownloadStatus(TBase):
+ Finished = 0
+ Offline = 1
+ Online = 2
+ Queued = 3
+ Skipped = 4
+ Waiting = 5
+ TempOffline = 6
+ Starting = 7
+ Failed = 8
+ Aborted = 9
+ Decrypting = 10
+ Custom = 11
+ Downloading = 12
+ Processing = 13
+ Unknown = 14
+
+ _VALUES_TO_NAMES = {
+ 0: "Finished",
+ 1: "Offline",
+ 2: "Online",
+ 3: "Queued",
+ 4: "Skipped",
+ 5: "Waiting",
+ 6: "TempOffline",
+ 7: "Starting",
+ 8: "Failed",
+ 9: "Aborted",
+ 10: "Decrypting",
+ 11: "Custom",
+ 12: "Downloading",
+ 13: "Processing",
+ 14: "Unknown",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Finished": 0,
+ "Offline": 1,
+ "Online": 2,
+ "Queued": 3,
+ "Skipped": 4,
+ "Waiting": 5,
+ "TempOffline": 6,
+ "Starting": 7,
+ "Failed": 8,
+ "Aborted": 9,
+ "Decrypting": 10,
+ "Custom": 11,
+ "Downloading": 12,
+ "Processing": 13,
+ "Unknown": 14,
+ }
+
+class Destination(TBase):
+ Collector = 0
+ Queue = 1
+
+ _VALUES_TO_NAMES = {
+ 0: "Collector",
+ 1: "Queue",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Collector": 0,
+ "Queue": 1,
+ }
+
+class ElementType(TBase):
+ Package = 0
+ File = 1
+
+ _VALUES_TO_NAMES = {
+ 0: "Package",
+ 1: "File",
+ }
+
+ _NAMES_TO_VALUES = {
+ "Package": 0,
+ "File": 1,
+ }
+
+class Input(TBase):
+ NONE = 0
+ TEXT = 1
+ TEXTBOX = 2
+ PASSWORD = 3
+ BOOL = 4
+ CLICK = 5
+ CHOICE = 6
+ MULTIPLE = 7
+ LIST = 8
+ TABLE = 9
+
+ _VALUES_TO_NAMES = {
+ 0: "NONE",
+ 1: "TEXT",
+ 2: "TEXTBOX",
+ 3: "PASSWORD",
+ 4: "BOOL",
+ 5: "CLICK",
+ 6: "CHOICE",
+ 7: "MULTIPLE",
+ 8: "LIST",
+ 9: "TABLE",
+ }
+
+ _NAMES_TO_VALUES = {
+ "NONE": 0,
+ "TEXT": 1,
+ "TEXTBOX": 2,
+ "PASSWORD": 3,
+ "BOOL": 4,
+ "CLICK": 5,
+ "CHOICE": 6,
+ "MULTIPLE": 7,
+ "LIST": 8,
+ "TABLE": 9,
+ }
+
+class Output(TBase):
+ CAPTCHA = 1
+ QUESTION = 2
+ NOTIFICATION = 4
+
+ _VALUES_TO_NAMES = {
+ 1: "CAPTCHA",
+ 2: "QUESTION",
+ 4: "NOTIFICATION",
+ }
+
+ _NAMES_TO_VALUES = {
+ "CAPTCHA": 1,
+ "QUESTION": 2,
+ "NOTIFICATION": 4,
+ }
+
+
+class DownloadInfo(TBase):
+ """
+ Attributes:
+ - fid
+ - name
+ - speed
+ - eta
+ - format_eta
+ - bleft
+ - size
+ - format_size
+ - percent
+ - status
+ - statusmsg
+ - format_wait
+ - wait_until
+ - packageID
+ - packageName
+ - plugin
+ """
+
+ __slots__ = [
+ 'fid',
+ 'name',
+ 'speed',
+ 'eta',
+ 'format_eta',
+ 'bleft',
+ 'size',
+ 'format_size',
+ 'percent',
+ 'status',
+ 'statusmsg',
+ 'format_wait',
+ 'wait_until',
+ 'packageID',
+ 'packageName',
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ (2, TType.STRING, 'name', None, None,), # 2
+ (3, TType.I64, 'speed', None, None,), # 3
+ (4, TType.I32, 'eta', None, None,), # 4
+ (5, TType.STRING, 'format_eta', None, None,), # 5
+ (6, TType.I64, 'bleft', None, None,), # 6
+ (7, TType.I64, 'size', None, None,), # 7
+ (8, TType.STRING, 'format_size', None, None,), # 8
+ (9, TType.BYTE, 'percent', None, None,), # 9
+ (10, TType.I32, 'status', None, None,), # 10
+ (11, TType.STRING, 'statusmsg', None, None,), # 11
+ (12, TType.STRING, 'format_wait', None, None,), # 12
+ (13, TType.I64, 'wait_until', None, None,), # 13
+ (14, TType.I32, 'packageID', None, None,), # 14
+ (15, TType.STRING, 'packageName', None, None,), # 15
+ (16, TType.STRING, 'plugin', None, None,), # 16
+ )
+
+ def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,):
+ self.fid = fid
+ self.name = name
+ self.speed = speed
+ self.eta = eta
+ self.format_eta = format_eta
+ self.bleft = bleft
+ self.size = size
+ self.format_size = format_size
+ self.percent = percent
+ self.status = status
+ self.statusmsg = statusmsg
+ self.format_wait = format_wait
+ self.wait_until = wait_until
+ self.packageID = packageID
+ self.packageName = packageName
+ self.plugin = plugin
+
+
+class ServerStatus(TBase):
+ """
+ Attributes:
+ - pause
+ - active
+ - queue
+ - total
+ - speed
+ - download
+ - reconnect
+ """
+
+ __slots__ = [
+ 'pause',
+ 'active',
+ 'queue',
+ 'total',
+ 'speed',
+ 'download',
+ 'reconnect',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.BOOL, 'pause', None, None,), # 1
+ (2, TType.I16, 'active', None, None,), # 2
+ (3, TType.I16, 'queue', None, None,), # 3
+ (4, TType.I16, 'total', None, None,), # 4
+ (5, TType.I64, 'speed', None, None,), # 5
+ (6, TType.BOOL, 'download', None, None,), # 6
+ (7, TType.BOOL, 'reconnect', None, None,), # 7
+ )
+
+ def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,):
+ self.pause = pause
+ self.active = active
+ self.queue = queue
+ self.total = total
+ self.speed = speed
+ self.download = download
+ self.reconnect = reconnect
+
+
+class ConfigItem(TBase):
+ """
+ Attributes:
+ - name
+ - description
+ - value
+ - type
+ """
+
+ __slots__ = [
+ 'name',
+ 'description',
+ 'value',
+ 'type',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None,), # 1
+ (2, TType.STRING, 'description', None, None,), # 2
+ (3, TType.STRING, 'value', None, None,), # 3
+ (4, TType.STRING, 'type', None, None,), # 4
+ )
+
+ def __init__(self, name=None, description=None, value=None, type=None,):
+ self.name = name
+ self.description = description
+ self.value = value
+ self.type = type
+
+
+class ConfigSection(TBase):
+ """
+ Attributes:
+ - name
+ - description
+ - items
+ - outline
+ """
+
+ __slots__ = [
+ 'name',
+ 'description',
+ 'items',
+ 'outline',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None,), # 1
+ (2, TType.STRING, 'description', None, None,), # 2
+ (3, TType.LIST, 'items', (TType.STRUCT, (ConfigItem, ConfigItem.thrift_spec)), None,), # 3
+ (4, TType.STRING, 'outline', None, None,), # 4
+ )
+
+ def __init__(self, name=None, description=None, items=None, outline=None,):
+ self.name = name
+ self.description = description
+ self.items = items
+ self.outline = outline
+
+
+class FileData(TBase):
+ """
+ Attributes:
+ - fid
+ - url
+ - name
+ - plugin
+ - size
+ - format_size
+ - status
+ - statusmsg
+ - packageID
+ - error
+ - order
+ """
+
+ __slots__ = [
+ 'fid',
+ 'url',
+ 'name',
+ 'plugin',
+ 'size',
+ 'format_size',
+ 'status',
+ 'statusmsg',
+ 'packageID',
+ 'error',
+ 'order',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ (2, TType.STRING, 'url', None, None,), # 2
+ (3, TType.STRING, 'name', None, None,), # 3
+ (4, TType.STRING, 'plugin', None, None,), # 4
+ (5, TType.I64, 'size', None, None,), # 5
+ (6, TType.STRING, 'format_size', None, None,), # 6
+ (7, TType.I32, 'status', None, None,), # 7
+ (8, TType.STRING, 'statusmsg', None, None,), # 8
+ (9, TType.I32, 'packageID', None, None,), # 9
+ (10, TType.STRING, 'error', None, None,), # 10
+ (11, TType.I16, 'order', None, None,), # 11
+ )
+
+ def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,):
+ self.fid = fid
+ self.url = url
+ self.name = name
+ self.plugin = plugin
+ self.size = size
+ self.format_size = format_size
+ self.status = status
+ self.statusmsg = statusmsg
+ self.packageID = packageID
+ self.error = error
+ self.order = order
+
+
+class PackageData(TBase):
+ """
+ Attributes:
+ - pid
+ - name
+ - folder
+ - site
+ - password
+ - dest
+ - order
+ - linksdone
+ - sizedone
+ - sizetotal
+ - linkstotal
+ - links
+ - fids
+ """
+
+ __slots__ = [
+ 'pid',
+ 'name',
+ 'folder',
+ 'site',
+ 'password',
+ 'dest',
+ 'order',
+ 'linksdone',
+ 'sizedone',
+ 'sizetotal',
+ 'linkstotal',
+ 'links',
+ 'fids',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ (2, TType.STRING, 'name', None, None,), # 2
+ (3, TType.STRING, 'folder', None, None,), # 3
+ (4, TType.STRING, 'site', None, None,), # 4
+ (5, TType.STRING, 'password', None, None,), # 5
+ (6, TType.I32, 'dest', None, None,), # 6
+ (7, TType.I16, 'order', None, None,), # 7
+ (8, TType.I16, 'linksdone', None, None,), # 8
+ (9, TType.I64, 'sizedone', None, None,), # 9
+ (10, TType.I64, 'sizetotal', None, None,), # 10
+ (11, TType.I16, 'linkstotal', None, None,), # 11
+ (12, TType.LIST, 'links', (TType.STRUCT, (FileData, FileData.thrift_spec)), None,), # 12
+ (13, TType.LIST, 'fids', (TType.I32, None), None,), # 13
+ )
+
+ def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,):
+ self.pid = pid
+ self.name = name
+ self.folder = folder
+ self.site = site
+ self.password = password
+ self.dest = dest
+ self.order = order
+ self.linksdone = linksdone
+ self.sizedone = sizedone
+ self.sizetotal = sizetotal
+ self.linkstotal = linkstotal
+ self.links = links
+ self.fids = fids
+
+
+class InteractionTask(TBase):
+ """
+ Attributes:
+ - iid
+ - input
+ - structure
+ - preset
+ - output
+ - data
+ - title
+ - description
+ - plugin
+ """
+
+ __slots__ = [
+ 'iid',
+ 'input',
+ 'structure',
+ 'preset',
+ 'output',
+ 'data',
+ 'title',
+ 'description',
+ 'plugin',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'iid', None, None,), # 1
+ (2, TType.I32, 'input', None, None,), # 2
+ (3, TType.LIST, 'structure', (TType.STRING, None), None,), # 3
+ (4, TType.LIST, 'preset', (TType.STRING, None), None,), # 4
+ (5, TType.I32, 'output', None, None,), # 5
+ (6, TType.LIST, 'data', (TType.STRING, None), None,), # 6
+ (7, TType.STRING, 'title', None, None,), # 7
+ (8, TType.STRING, 'description', None, None,), # 8
+ (9, TType.STRING, 'plugin', None, None,), # 9
+ )
+
+ def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,):
+ self.iid = iid
+ self.input = input
+ self.structure = structure
+ self.preset = preset
+ self.output = output
+ self.data = data
+ self.title = title
+ self.description = description
+ self.plugin = plugin
+
+
+class CaptchaTask(TBase):
+ """
+ Attributes:
+ - tid
+ - data
+ - type
+ - resultType
+ """
+
+ __slots__ = [
+ 'tid',
+ 'data',
+ 'type',
+ 'resultType',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I16, 'tid', None, None,), # 1
+ (2, TType.STRING, 'data', None, None,), # 2
+ (3, TType.STRING, 'type', None, None,), # 3
+ (4, TType.STRING, 'resultType', None, None,), # 4
+ )
+
+ def __init__(self, tid=None, data=None, type=None, resultType=None,):
+ self.tid = tid
+ self.data = data
+ self.type = type
+ self.resultType = resultType
+
+
+class EventInfo(TBase):
+ """
+ Attributes:
+ - eventname
+ - id
+ - type
+ - destination
+ """
+
+ __slots__ = [
+ 'eventname',
+ 'id',
+ 'type',
+ 'destination',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'eventname', None, None,), # 1
+ (2, TType.I32, 'id', None, None,), # 2
+ (3, TType.I32, 'type', None, None,), # 3
+ (4, TType.I32, 'destination', None, None,), # 4
+ )
+
+ def __init__(self, eventname=None, id=None, type=None, destination=None,):
+ self.eventname = eventname
+ self.id = id
+ self.type = type
+ self.destination = destination
+
+
+class UserData(TBase):
+ """
+ Attributes:
+ - name
+ - email
+ - role
+ - permission
+ - templateName
+ """
+
+ __slots__ = [
+ 'name',
+ 'email',
+ 'role',
+ 'permission',
+ 'templateName',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None,), # 1
+ (2, TType.STRING, 'email', None, None,), # 2
+ (3, TType.I32, 'role', None, None,), # 3
+ (4, TType.I32, 'permission', None, None,), # 4
+ (5, TType.STRING, 'templateName', None, None,), # 5
+ )
+
+ def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,):
+ self.name = name
+ self.email = email
+ self.role = role
+ self.permission = permission
+ self.templateName = templateName
+
+
+class AccountInfo(TBase):
+ """
+ Attributes:
+ - validuntil
+ - login
+ - options
+ - valid
+ - trafficleft
+ - maxtraffic
+ - premium
+ - type
+ """
+
+ __slots__ = [
+ 'validuntil',
+ 'login',
+ 'options',
+ 'valid',
+ 'trafficleft',
+ 'maxtraffic',
+ 'premium',
+ 'type',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I64, 'validuntil', None, None,), # 1
+ (2, TType.STRING, 'login', None, None,), # 2
+ (3, TType.MAP, 'options', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), # 3
+ (4, TType.BOOL, 'valid', None, None,), # 4
+ (5, TType.I64, 'trafficleft', None, None,), # 5
+ (6, TType.I64, 'maxtraffic', None, None,), # 6
+ (7, TType.BOOL, 'premium', None, None,), # 7
+ (8, TType.STRING, 'type', None, None,), # 8
+ )
+
+ def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,):
+ self.validuntil = validuntil
+ self.login = login
+ self.options = options
+ self.valid = valid
+ self.trafficleft = trafficleft
+ self.maxtraffic = maxtraffic
+ self.premium = premium
+ self.type = type
+
+
+class ServiceCall(TBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ - arguments
+ - parseArguments
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ 'arguments',
+ 'parseArguments',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ (2, TType.STRING, 'func', None, None,), # 2
+ (3, TType.LIST, 'arguments', (TType.STRING, None), None,), # 3
+ (4, TType.BOOL, 'parseArguments', None, None,), # 4
+ )
+
+ def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,):
+ self.plugin = plugin
+ self.func = func
+ self.arguments = arguments
+ self.parseArguments = parseArguments
+
+
+class OnlineStatus(TBase):
+ """
+ Attributes:
+ - name
+ - plugin
+ - packagename
+ - status
+ - size
+ """
+
+ __slots__ = [
+ 'name',
+ 'plugin',
+ 'packagename',
+ 'status',
+ 'size',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'name', None, None,), # 1
+ (2, TType.STRING, 'plugin', None, None,), # 2
+ (3, TType.STRING, 'packagename', None, None,), # 3
+ (4, TType.I32, 'status', None, None,), # 4
+ (5, TType.I64, 'size', None, None,), # 5
+ )
+
+ def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,):
+ self.name = name
+ self.plugin = plugin
+ self.packagename = packagename
+ self.status = status
+ self.size = size
+
+
+class OnlineCheck(TBase):
+ """
+ Attributes:
+ - rid
+ - data
+ """
+
+ __slots__ = [
+ 'rid',
+ 'data',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'rid', None, None,), # 1
+ (2, TType.MAP, 'data', (TType.STRING, None, TType.STRUCT, (OnlineStatus, OnlineStatus.thrift_spec)), None,), # 2
+ )
+
+ def __init__(self, rid=None, data=None,):
+ self.rid = rid
+ self.data = data
+
+
+class PackageDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - pid
+ """
+
+ __slots__ = [
+ 'pid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'pid', None, None,), # 1
+ )
+
+ def __init__(self, pid=None,):
+ self.pid = pid
+
+ def __str__(self):
+ return repr(self)
+
+
+class FileDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - fid
+ """
+
+ __slots__ = [
+ 'fid',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.I32, 'fid', None, None,), # 1
+ )
+
+ def __init__(self, fid=None,):
+ self.fid = fid
+
+ def __str__(self):
+ return repr(self)
+
+
+class ServiceDoesNotExists(TExceptionBase):
+ """
+ Attributes:
+ - plugin
+ - func
+ """
+
+ __slots__ = [
+ 'plugin',
+ 'func',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'plugin', None, None,), # 1
+ (2, TType.STRING, 'func', None, None,), # 2
+ )
+
+ def __init__(self, plugin=None, func=None,):
+ self.plugin = plugin
+ self.func = func
+
+ def __str__(self):
+ return repr(self)
+
+
+class ServiceException(TExceptionBase):
+ """
+ Attributes:
+ - msg
+ """
+
+ __slots__ = [
+ 'msg',
+ ]
+
+ thrift_spec = (
+ None, # 0
+ (1, TType.STRING, 'msg', None, None,), # 1
+ )
+
+ def __init__(self, msg=None,):
+ self.msg = msg
+
+ def __str__(self):
+ return repr(self)
diff --git a/pyload/utils/JsEngine.py b/pyload/utils/JsEngine.py
new file mode 100644
index 000000000..ef5bc9be9
--- /dev/null
+++ b/pyload/utils/JsEngine.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 -*-
+
+import subprocess
+import sys
+
+from os import path
+from urllib import quote
+
+from pyload.utils import encode, uniqify
+
+
+class JsEngine:
+ """ JS Engine superclass """
+
+ def __init__(self, core, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class"""
+
+ self.core = core
+ self.engine = None #: Default engine Instance
+
+ if not ENGINES:
+ self.core.log.critical("No JS Engine found!")
+ return
+
+ if not engine:
+ engine = self.core.config.get("general", "jsengine")
+
+ if engine != "auto" and self.set(engine) is False:
+ engine = "auto"
+ self.core.log.warning("JS Engine set to \"auto\" for safely")
+
+ if engine == "auto":
+ for E in self.find():
+ if self.set(E) is True:
+ break
+ else:
+ self.core.log.error("No JS Engine available")
+
+
+ @classmethod
+ def find(cls):
+ """ Check if there is any engine available """
+ return [E for E in ENGINES if E.find()]
+
+
+ def get(self, engine):
+ """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """
+ if isinstance(engine, basestring):
+ engine_name = engine.lower()
+ for E in ENGINES:
+ if E.NAME == engine_name: #: doesn't check if E(NGINE) is available, just convert string to class
+ JSE = E
+ break
+ else:
+ JSE = None
+ elif issubclass(engine, AbstractEngine):
+ JSE = engine
+ else:
+ JSE = None
+ return JSE
+
+
+ def set(self, engine):
+ """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """
+ if isinstance(engine, basestring):
+ self.set(self.get(engine))
+ elif issubclass(engine, AbstractEngine) and engine.find():
+ self.engine = engine
+ return True
+ else:
+ return False
+
+
+ def eval(self, script, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class"""
+ if not engine:
+ JSE = self.engine
+ else:
+ JSE = self.get(engine)
+
+ if not JSE:
+ return None
+
+ script = encode(script)
+
+ out, err = JSE.eval(script)
+
+ results = [out]
+
+ if self.core.config.get("general", "debug"):
+ if err:
+ self.core.log.debug(JSE.NAME + ":", err)
+
+ engines = self.find()
+ engines.remove(JSE)
+ for E in engines:
+ out, err = E.eval(script)
+ res = err or out
+ self.core.log.debug(E.NAME + ":", res)
+ results.append(res)
+
+ if len(results) > 1 and len(uniqify(results)) > 1:
+ self.core.log.warning("JS output of two or more engines mismatch")
+
+ return results[0]
+
+
+class AbstractEngine:
+ """ JSE base class """
+
+ NAME = ""
+
+ def __init__(self):
+ self.setup()
+ self.available = self.find()
+
+ def setup(self):
+ pass
+
+ @classmethod
+ def find(cls):
+ """ Check if the engine is available """
+ try:
+ __import__(cls.NAME)
+ except ImportError:
+ try:
+ out, err = cls().eval("print(23+19)")
+ except:
+ res = False
+ else:
+ res = out == "42"
+ else:
+ res = True
+ finally:
+ return res
+
+ def _eval(args):
+ if not self.available:
+ return None, "JS Engine \"%s\" not found" % self.NAME
+
+ try:
+ p = subprocess.Popen(args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=-1)
+ return map(lambda x: x.strip(), p.communicate())
+ except Exception, e:
+ return None, e
+
+
+ def eval(script):
+ raise NotImplementedError
+
+
+class Pyv8Engine(AbstractEngine):
+
+ NAME = "pyv8"
+
+ def eval(self, script):
+ if not self.available:
+ return None, "JS Engine \"%s\" not found" % self.NAME
+
+ try:
+ rt = PyV8.JSContext()
+ rt.enter()
+ res = rt.eval(script), None #@TODO: parse stderr
+ except Exception, e:
+ res = None, e
+ finally:
+ return res
+
+
+class CommonEngine(AbstractEngine):
+
+ NAME = "js"
+
+ def setup(self):
+ subprocess.Popen(["js", "-v"], bufsize=-1).communicate()
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = ["js", "-e", script]
+ return self._eval(args)
+
+
+class NodeEngine(AbstractEngine):
+
+ NAME = "nodejs"
+
+ def setup(self):
+ subprocess.Popen(["node", "-v"], bufsize=-1).communicate()
+
+ def eval(self, script):
+ script = "console.log(eval(unescape('%s')))" % quote(script)
+ args = ["node", "-e", script]
+ return self._eval(args)
+
+
+class RhinoEngine(AbstractEngine):
+
+ NAME = "rhino"
+
+ def setup(self):
+ jspath = [
+ "/usr/share/java*/js.jar",
+ "js.jar",
+ path.join(pypath, "js.jar")
+ ]
+ for p in jspath:
+ if path.exists(p):
+ self.path = p
+ break
+ else:
+ self.path = ""
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = ["java", "-cp", self.path, "org.mozilla.javascript.tools.shell.Main", "-e", script]
+ return self._eval(args).decode("utf8").encode("ISO-8859-1")
+
+
+class JscEngine(AbstractEngine):
+
+ NAME = "javascriptcore"
+
+ def setup(self):
+ jspath = "/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc"
+ self.path = jspath if path.exists(jspath) else ""
+
+ def eval(self, script):
+ script = "print(eval(unescape('%s')))" % quote(script)
+ args = [self.path, "-e", script]
+ return self._eval(args)
+
+
+#@NOTE: Priority ordered
+ENGINES = [CommonEngine, Pyv8Engine, NodeEngine, RhinoEngine]
+
+if sys.platform == "darwin":
+ ENGINES.insert(JscEngine)
diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py
new file mode 100644
index 000000000..a13aa75d5
--- /dev/null
+++ b/pyload/utils/__init__.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+
+""" Store all useful functions here """
+
+import os
+import sys
+import time
+import re
+from os.path import join
+from string import maketrans
+from htmlentitydefs import name2codepoint
+
+# abstraction layer for json operations
+try:
+ import simplejson as json
+except ImportError:
+ import json
+
+json_loads = json.loads
+json_dumps = json.dumps
+
+
+def chmod(*args):
+ try:
+ os.chmod(*args)
+ except:
+ pass
+
+
+def decode(string):
+ """ Decode string to unicode with utf8 """
+ if type(string) == str:
+ return string.decode("utf8", "replace")
+ else:
+ return string
+
+
+def encode(string):
+ """ Decode string to utf8 """
+ if type(string) == unicode:
+ return string.encode("utf8", "replace")
+ else:
+ return string
+
+
+def remove_chars(string, repl):
+ """ removes all chars in repl from string"""
+ if type(repl) == unicode:
+ for badc in list(repl):
+ string = string.replace(badc, "")
+ return string
+ else:
+ if type(string) == str:
+ return string.translate(maketrans("", ""), repl)
+ elif type(string) == unicode:
+ return string.translate(dict([(ord(s), None) for s in repl]))
+
+
+def safe_filename(name):
+ """ remove bad chars """
+ name = name.encode('ascii', 'replace') # Non-ASCII chars usually breaks file saving. Replacing.
+ if os.name == 'nt':
+ return remove_chars(name, u'\00\01\02\03\04\05\06\07\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32'
+ u'\33\34\35\36\37/\\?%*:|"<>')
+ else:
+ return remove_chars(name, u'\0/\\"')
+
+#: Deprecated method
+def save_path(name):
+ return safe_filename(name)
+
+
+def safe_join(*args):
+ """ joins a path, encoding aware """
+ return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args]))
+
+#: Deprecated method
+def save_join(*args):
+ return safe_join(*args)
+
+
+# File System Encoding functions:
+# Use fs_encode before accesing files on disk, it will encode the string properly
+
+if sys.getfilesystemencoding().startswith('ANSI'):
+ def fs_encode(string):
+ try:
+ string = string.encode('utf-8')
+ finally:
+ return string
+
+ fs_decode = decode #decode utf8
+
+else:
+ fs_encode = fs_decode = lambda x: x # do nothing
+
+
+def get_console_encoding(enc):
+ if os.name == "nt":
+ if enc == "cp65001": # aka UTF-8
+ print "WARNING: Windows codepage 65001 is not supported."
+ enc = "cp850"
+ else:
+ enc = "utf8"
+
+ return enc
+
+
+def compare_time(start, end):
+ start = map(int, start)
+ end = map(int, end)
+
+ if start == end: return True
+
+ now = list(time.localtime()[3:5])
+ if start < now < end: return True
+ elif start > end and (now > start or now < end): return True
+ elif start < now > end < start: return True
+ else: return False
+
+
+def formatSize(size):
+ """formats size of bytes"""
+ size = int(size)
+ steps = 0
+ sizes = ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB")
+ while size > 1000:
+ size /= 1024.0
+ steps += 1
+ return "%.2f %s" % (size, sizes[steps])
+
+
+def formatSpeed(speed):
+ return formatSize(speed) + "/s"
+
+
+def freeSpace(folder):
+ if os.name == "nt":
+ import ctypes
+
+ free_bytes = ctypes.c_ulonglong(0)
+ ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes))
+ return free_bytes.value
+ else:
+ s = os.statvfs(folder)
+ return s.f_bsize * s.f_bavail
+
+
+def fs_bsize(path):
+ """ get optimal file system buffer size (in bytes) for I/O calls """
+ path = fs_encode(path)
+
+ if os.name == "nt":
+ import ctypes
+
+ drive = "%s\\" % os.path.splitdrive(path)[0]
+ cluster_sectors, sector_size = ctypes.c_longlong(0)
+ ctypes.windll.kernel32.GetDiskFreeSpaceW(ctypes.c_wchar_p(drive), ctypes.pointer(cluster_sectors), ctypes.pointer(sector_size), None, None)
+ return cluster_sectors * sector_size
+ else:
+ return os.statvfs(path).f_bsize
+
+
+def uniqify(seq): #: Originally by Dave Kirby
+ """ Remove duplicates from list preserving order """
+ seen = set()
+ seen_add = seen.add
+ return [x for x in seq if x not in seen and not seen_add(x)]
+
+
+def parseFileSize(string, unit=None): #returns bytes
+ if not unit:
+ m = re.match(r"([\d.,]+) *([a-zA-Z]*)", string.strip().lower())
+ if m:
+ traffic = float(m.group(1).replace(",", "."))
+ unit = m.group(2)
+ else:
+ return 0
+ else:
+ if isinstance(string, basestring):
+ traffic = float(string.replace(",", "."))
+ else:
+ traffic = string
+
+ #ignore case
+ unit = unit.lower().strip()
+
+ if unit in ("eb", "ebyte", "exabyte", "eib", "e"):
+ traffic *= 1 << 60
+ elif unit in ("pb", "pbyte", "petabyte", "pib", "p"):
+ traffic *= 1 << 50
+ elif unit in ("tb", "tbyte", "terabyte", "tib", "t"):
+ traffic *= 1 << 40
+ elif unit in ("gb", "gbyte", "gigabyte", "gib", "g", "gig"):
+ traffic *= 1 << 30
+ elif unit in ("mb", "mbyte", "megabyte", "mib", "m"):
+ traffic *= 1 << 20
+ elif unit in ("kb", "kbyte", "kilobyte", "kib", "k"):
+ traffic *= 1 << 10
+
+ return traffic
+
+
+def lock(func):
+ def new(*args):
+ #print "Handler: %s args: %s" % (func, args[1:])
+ args[0].lock.acquire()
+ try:
+ return func(*args)
+ finally:
+ args[0].lock.release()
+
+ return new
+
+
+def fixup(m):
+ text = m.group(0)
+ if text[:2] == "&#":
+ # character reference
+ try:
+ if text[:3] == "&#x":
+ return unichr(int(text[3:-1], 16))
+ else:
+ return unichr(int(text[2:-1]))
+ except ValueError:
+ pass
+ else:
+ # named entity
+ try:
+ name = text[1:-1]
+ text = unichr(name2codepoint[name])
+ except KeyError:
+ pass
+
+ return text # leave as is
+
+
+def html_unescape(text):
+ """Removes HTML or XML character references and entities from a text string"""
+ return re.sub("&#?\w+;", fixup, text)
+
+
+def versiontuple(v): #: By kindall (http://stackoverflow.com/a/11887825)
+ return tuple(map(int, (v.split("."))))
diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py
new file mode 100644
index 000000000..d5ab4d182
--- /dev/null
+++ b/pyload/utils/packagetools.py
@@ -0,0 +1,136 @@
+# JDownloader/src/jd/controlling/LinkGrabberPackager.java
+
+import re
+from urlparse import urlparse
+
+def matchFirst(string, *args):
+ """ matches against list of regexp and returns first match"""
+ for patternlist in args:
+ for pattern in patternlist:
+ r = pattern.search(string)
+ if r is not None:
+ name = r.group(1)
+ return name
+
+ return string
+
+
+def parseNames(files):
+ """ Generates packages names from name, data lists
+
+ :param files: list of (name, data)
+ :return: packagenames mapt to data lists (eg. urls)
+ """
+ packs = {}
+
+ endings = "\\.(3gp|7zip|7z|abr|ac3|aiff|aifc|aif|ai|au|avi|bin|bz2|cbr|cbz|ccf|cue|cvd|chm|dta|deb|divx|djvu|dlc|dmg|doc|docx|dot|eps|exe|ff|flv|f4v|gsd|gif|gz|iwd|iso|ipsw|java|jar|jpg|jpeg|jdeatme|load|mws|mw|m4v|m4a|mkv|mp2|mp3|mp4|mov|movie|mpeg|mpe|mpg|msi|msu|msp|nfo|npk|oga|ogg|ogv|otrkey|pkg|png|pdf|pptx|ppt|pps|ppz|pot|psd|qt|rmvb|rm|rar|ram|ra|rev|rnd|r\\d+|rpm|run|rsdf|rtf|sh(!?tml)|srt|snd|sfv|swf|tar|tif|tiff|ts|txt|viv|vivo|vob|wav|wmv|xla|xls|xpi|zeno|zip|z\\d+|_[_a-z]{2}|\\d+$)"
+
+ rarPats = [re.compile("(.*)(\\.|_|-)pa?r?t?\\.?[0-9]+.(rar|exe)$", re.I),
+ re.compile("(.*)(\\.|_|-)part\\.?[0]*[1].(rar|exe)$", re.I),
+ re.compile("(.*)\\.rar$", re.I),
+ re.compile("(.*)\\.r\\d+$", re.I),
+ re.compile("(.*)(\\.|_|-)\\d+$", re.I)]
+
+ zipPats = [re.compile("(.*)\\.zip$", re.I),
+ re.compile("(.*)\\.z\\d+$", re.I),
+ re.compile("(?is).*\\.7z\\.[\\d]+$", re.I),
+ re.compile("(.*)\\.a.$", re.I)]
+
+ ffsjPats = [re.compile("(.*)\\._((_[a-z])|([a-z]{2}))(\\.|$)"),
+ re.compile("(.*)(\\.|_|-)[\\d]+(" + endings + "$)", re.I)]
+
+ iszPats = [re.compile("(.*)\\.isz$", re.I),
+ re.compile("(.*)\\.i\\d{2}$", re.I)]
+
+ pat1 = re.compile("(\\.?CD\\d+)", re.I)
+ pat2 = re.compile("(\\.?part\\d+)", re.I)
+
+ pat3 = re.compile("(.+)[\\.\\-_]+$")
+ pat4 = re.compile("(.+)\\.\\d+\\.xtm$")
+
+ for file, url in files:
+ patternMatch = False
+
+ if file is None:
+ continue
+
+ # remove trailing /
+ name = file.rstrip('/')
+
+ # extract last path part .. if there is a path
+ split = name.rsplit("/", 1)
+ if len(split) > 1:
+ name = split.pop(1)
+
+ #check if a already existing package may be ok for this file
+ # found = False
+ # for pack in packs:
+ # if pack in file:
+ # packs[pack].append(url)
+ # found = True
+ # break
+ #
+ # if found: continue
+
+ # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern
+ before = name
+ name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats)
+ if before != name:
+ patternMatch = True
+
+ # xtremsplit pattern
+ r = pat4.search(name)
+ if r is not None:
+ name = r.group(1)
+
+ # remove part and cd pattern
+ r = pat1.search(name)
+ if r is not None:
+ name = name.replace(r.group(0), "")
+ patternMatch = True
+
+ r = pat2.search(name)
+ if r is not None:
+ name = name.replace(r.group(0), "")
+ patternMatch = True
+
+ # additional checks if extension pattern matched
+ if patternMatch:
+ # remove extension
+ index = name.rfind(".")
+ if index <= 0:
+ index = name.rfind("_")
+ if index > 0:
+ length = len(name) - index
+ if length <= 4:
+ name = name[:-length]
+
+ # remove endings like . _ -
+ r = pat3.search(name)
+ if r is not None:
+ name = r.group(1)
+
+ # replace . and _ with space
+ name = name.replace(".", " ")
+ name = name.replace("_", " ")
+
+ name = name.strip()
+ else:
+ name = ""
+
+ # fallback: package by hoster
+ if not name:
+ name = urlparse(file).hostname
+ if name: name = name.replace("www.", "")
+
+ # fallback : default name
+ if not name:
+ name = "unknown"
+
+ # build mapping
+ if name in packs:
+ packs[name].append(url)
+ else:
+ packs[name] = [url]
+
+ return packs
diff --git a/pyload/utils/printer.py b/pyload/utils/printer.py
new file mode 100644
index 000000000..488f42d4a
--- /dev/null
+++ b/pyload/utils/printer.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+import colorama
+
+colorama.init(autoreset=True)
+
+def color(color, text):
+ return colorama.Fore.(c.upper())(text)
+
+for c in colorama.Fore:
+ eval("%(color) = lambda msg: color(%(color), msg)" % {'color': c.lower()}
+
+
+def overline(line, msg):
+ print "\033[%(line)s;0H\033[2K%(msg)s" % {'line': str(line), 'msg': msg}
diff --git a/pyload/utils/pylgettext.py b/pyload/utils/pylgettext.py
new file mode 100644
index 000000000..cab631cf4
--- /dev/null
+++ b/pyload/utils/pylgettext.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+from gettext import *
+
+_searchdirs = None
+
+origfind = find
+
+def setpaths(pathlist):
+ global _searchdirs
+ if isinstance(pathlist, list):
+ _searchdirs = pathlist
+ else:
+ _searchdirs = list(pathlist)
+
+
+def addpath(path):
+ global _searchdirs
+ if _searchdirs is None:
+ _searchdirs = list(path)
+ else:
+ if path not in _searchdirs:
+ _searchdirs.append(path)
+
+
+def delpath(path):
+ global _searchdirs
+ if _searchdirs is not None:
+ if path in _searchdirs:
+ _searchdirs.remove(path)
+
+
+def clearpath():
+ global _searchdirs
+ if _searchdirs is not None:
+ _searchdirs = None
+
+
+def find(domain, localedir=None, languages=None, all=False):
+ if _searchdirs is None:
+ return origfind(domain, localedir, languages, all)
+ searches = [localedir] + _searchdirs
+ results = list()
+ for dir in searches:
+ res = origfind(domain, dir, languages, all)
+ if all is False:
+ results.append(res)
+ else:
+ results.extend(res)
+ if all is False:
+ results = filter(lambda x: x is not None, results)
+ if len(results) == 0:
+ return None
+ else:
+ return results[0]
+ else:
+ return results
+
+#Is there a smarter/cleaner pythonic way for this?
+translation.func_globals['find'] = find
diff --git a/pyload/webui/__init__.py b/pyload/webui/__init__.py
new file mode 100644
index 000000000..fe569eac5
--- /dev/null
+++ b/pyload/webui/__init__.py
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+import sys
+import pyload.utils.pylgettext as gettext
+
+import os
+from os.path import join, abspath, dirname, exists
+from os import makedirs
+
+THEME_DIR = abspath(join(dirname(__file__), "themes"))
+PYLOAD_DIR = abspath(join(THEME_DIR, "..", "..", ".."))
+
+sys.path.append(PYLOAD_DIR)
+
+from pyload import InitHomeDir
+from pyload.utils import decode, formatSize
+
+import bottle
+from bottle import run, app
+
+from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache
+from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware
+
+SETUP = None
+PYLOAD = None
+
+from pyload.manager.thread import ServerThread
+from pyload.utils.JsEngine import JsEngine
+
+if not ServerThread.core:
+ if ServerThread.setup:
+ SETUP = ServerThread.setup
+ config = SETUP.config
+ JS = JsEngine(SETUP)
+ else:
+ raise Exception("Could not access pyLoad Core")
+else:
+ PYLOAD = ServerThread.core.api
+ config = ServerThread.core.config
+ JS = JsEngine(ServerThread.core)
+
+THEME = config.get('webinterface', 'theme')
+DL_ROOT = config.get('general', 'download_folder')
+LOG_ROOT = config.get('log', 'log_folder')
+PREFIX = config.get('webinterface', 'prefix')
+
+if PREFIX:
+ PREFIX = PREFIX.rstrip("/")
+ if not PREFIX.startswith("/"):
+ PREFIX = "/" + PREFIX
+
+DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv
+bottle.debug(DEBUG)
+
+cache = join("tmp", "jinja_cache")
+if not exists(cache):
+ makedirs(cache)
+
+bcc = FileSystemBytecodeCache(cache, '%s.cache')
+
+loader = FileSystemLoader(THEME_DIR)
+
+env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False,
+ bytecode_cache=bcc)
+
+from filters import quotepath, path_make_relative, path_make_absolute, truncate, date
+
+env.filters["quotepath"] = quotepath
+env.filters["truncate"] = truncate
+env.filters["date"] = date
+env.filters["path_make_relative"] = path_make_relative
+env.filters["path_make_absolute"] = path_make_absolute
+env.filters["decode"] = decode
+env.filters["type"] = lambda x: str(type(x))
+env.filters["formatsize"] = formatSize
+env.filters["getitem"] = lambda x, y: x.__getitem__(y)
+if PREFIX:
+ env.filters["url"] = lambda x: x
+else:
+ env.filters["url"] = lambda x: PREFIX + x if x.startswith("/") else x
+
+gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])
+translation = gettext.translation("django", join(PYLOAD_DIR, "locale"),
+ languages=[config.get("general", "language"), "en"],fallback=True)
+translation.install(True)
+env.install_gettext_translations(translation)
+
+from beaker.middleware import SessionMiddleware
+
+session_opts = {
+ 'session.type': 'file',
+ 'session.cookie_expires': False,
+ 'session.data_dir': './tmp',
+ 'session.auto': False
+}
+
+web = StripPathMiddleware(SessionMiddleware(app(), session_opts))
+web = GZipMiddleWare(web)
+
+if PREFIX:
+ web = PrefixMiddleware(web, prefix=PREFIX)
+
+import pyload.webui.app
+
+def run_simple(host="0.0.0.0", port="8000"):
+ run(app=web, host=host, port=port, quiet=True)
+
+
+def run_lightweight(host="0.0.0.0", port="8000"):
+ run(app=web, host=host, port=port, server="bjoern", quiet=True)
+
+
+def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""):
+ from wsgiserver import CherryPyWSGIServer
+
+ if cert and key:
+ CherryPyWSGIServer.ssl_certificate = cert
+ CherryPyWSGIServer.ssl_private_key = key
+
+ CherryPyWSGIServer.numthreads = theads
+
+ from pyload.webui.app.utils import CherryPyWSGI
+
+ run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True)
+
+
+def run_fcgi(host="0.0.0.0", port="8000"):
+ from bottle import FlupFCGIServer
+
+ run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True)
diff --git a/pyload/webui/app/__init__.py b/pyload/webui/app/__init__.py
new file mode 100644
index 000000000..39d0fadd5
--- /dev/null
+++ b/pyload/webui/app/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from pyload.webui.app import api, cnl, json, pyload
diff --git a/pyload/webui/app/api.py b/pyload/webui/app/api.py
new file mode 100644
index 000000000..286061c2a
--- /dev/null
+++ b/pyload/webui/app/api.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+from urllib import unquote
+from itertools import chain
+from traceback import format_exc, print_exc
+
+from bottle import route, request, response, HTTPError
+
+from utils import toDict, set_session
+from pyload.webui import PYLOAD
+
+from pyload.utils import json
+from SafeEval import const_eval as literal_eval
+from pyload.api import BaseObject
+
+# json encoder that accepts TBase objects
+class TBaseEncoder(json.JSONEncoder):
+
+ def default(self, o):
+ if isinstance(o, BaseObject):
+ return toDict(o)
+ return json.JSONEncoder.default(self, o)
+
+
+# accepting positional arguments, as well as kwargs via post and get
+
+@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{}]*>')
+@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{}]*>', method='POST')
+def call_api(func, args=""):
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ s = request.environ.get('beaker.session')
+ if 'session' in request.POST:
+ s = s.get_by_id(request.POST['session'])
+
+ if not s or not s.get("authenticated", False):
+ return HTTPError(403, json.dumps("Forbidden"))
+
+ if not PYLOAD.isAuthorized(func, {"role": s["role"], "permission": s["perms"]}):
+ return HTTPError(401, json.dumps("Unauthorized"))
+
+ args = args.split("/")[1:]
+ kwargs = {}
+
+ for x, y in chain(request.GET.iteritems(), request.POST.iteritems()):
+ if x == "session": continue
+ kwargs[x] = unquote(y)
+
+ try:
+ return callApi(func, *args, **kwargs)
+ except Exception, e:
+ print_exc()
+ return HTTPError(500, json.dumps({"error": e.message, "traceback": format_exc()}))
+
+
+def callApi(func, *args, **kwargs):
+ if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"):
+ print "Invalid API call", func
+ return HTTPError(404, json.dumps("Not Found"))
+
+ result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args],
+ **dict([(x, literal_eval(y)) for x, y in kwargs.iteritems()]))
+
+ # null is invalid json response
+ if result is None: result = True
+
+ return json.dumps(result, cls=TBaseEncoder)
+
+
+#post -> username, password
+@route('/api/login', method='POST')
+def login():
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ user = request.forms.get("username")
+ password = request.forms.get("password")
+
+ info = PYLOAD.checkAuth(user, password)
+
+ if not info:
+ return json.dumps(False)
+
+ s = set_session(request, info)
+
+ # get the session id by dirty way, documentations seems wrong
+ try:
+ sid = s._headers["cookie_out"].split("=")[1].split(";")[0]
+ return json.dumps(sid)
+ except:
+ return json.dumps(True)
+
+
+@route('/api/logout')
+def logout():
+ response.headers.replace("Content-type", "application/json")
+ response.headers.append("Cache-Control", "no-cache, must-revalidate")
+
+ s = request.environ.get('beaker.session')
+ s.delete()
diff --git a/pyload/webui/app/cnl.py b/pyload/webui/app/cnl.py
new file mode 100644
index 000000000..47c38f4ab
--- /dev/null
+++ b/pyload/webui/app/cnl.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+from os.path import join
+import re
+from urllib import unquote
+from base64 import standard_b64decode
+from binascii import unhexlify
+
+from bottle import route, request, HTTPError
+from pyload.webui import PYLOAD, DL_ROOT, JS
+
+
+try:
+ from Crypto.Cipher import AES
+except:
+ pass
+
+
+def local_check(function):
+ def _view(*args, **kwargs):
+ if request.environ.get("REMOTE_ADDR", "0") in ("127.0.0.1", "localhost") \
+ or request.environ.get("HTTP_HOST", "0") in ("127.0.0.1:9666", "localhost:9666"):
+ return function(*args, **kwargs)
+ else:
+ return HTTPError(403, "Forbidden")
+
+ return _view
+
+
+@route('/flash')
+@route('/flash/<id>')
+@route('/flash', method='POST')
+@local_check
+def flash(id="0"):
+ return "JDownloader\r\n"
+
+
+@route('/flash/add', method='POST')
+@local_check
+def add(request):
+ package = request.POST.get('referer', None)
+ urls = filter(lambda x: x != "", request.POST['urls'].split("\n"))
+
+ if package:
+ PYLOAD.addPackage(package, urls, 0)
+ else:
+ PYLOAD.generateAndAddPackages(urls, 0)
+
+ return ""
+
+
+@route('/flash/addcrypted', method='POST')
+@local_check
+def addcrypted():
+ package = request.forms.get('referer', 'ClickAndLoad Package')
+ dlc = request.forms['crypted'].replace(" ", "+")
+
+ dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc")
+ dlc_file = open(dlc_path, "wb")
+ dlc_file.write(dlc)
+ dlc_file.close()
+
+ try:
+ PYLOAD.addPackage(package, [dlc_path], 0)
+ except:
+ return HTTPError()
+ else:
+ return "success\r\n"
+
+
+@route('/flash/addcrypted2', method='POST')
+@local_check
+def addcrypted2():
+ package = request.forms.get("source", None)
+ crypted = request.forms["crypted"]
+ jk = request.forms["jk"]
+
+ crypted = standard_b64decode(unquote(crypted.replace(" ", "+")))
+ if JS:
+ jk = "%s f()" % jk
+ jk = JS.eval(jk)
+
+ else:
+ try:
+ jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1]
+ except:
+ ## Test for some known js functions to decode
+ if jk.find("dec") > -1 and jk.find("org") > -1:
+ org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1]
+ jk = list(org)
+ jk.reverse()
+ jk = "".join(jk)
+ else:
+ print "Could not decrypt key, please install py-spidermonkey or ossp-js"
+
+ try:
+ Key = unhexlify(jk)
+ except:
+ print "Could not decrypt key, please install py-spidermonkey or ossp-js"
+ return "failed"
+
+ IV = Key
+
+ obj = AES.new(Key, AES.MODE_CBC, IV)
+ result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n")
+
+ result = filter(lambda x: x != "", result)
+
+ try:
+ if package:
+ PYLOAD.addPackage(package, result, 0)
+ else:
+ PYLOAD.generateAndAddPackages(result, 0)
+ except:
+ return "failed can't add"
+ else:
+ return "success\r\n"
+
+
+@route('/flashgot_pyload')
+@route('/flashgot_pyload', method='POST')
+@route('/flashgot')
+@route('/flashgot', method='POST')
+@local_check
+def flashgot():
+ if request.environ['HTTP_REFERER'] != "http://localhost:9666/flashgot" and \
+ request.environ['HTTP_REFERER'] != "http://127.0.0.1:9666/flashgot":
+ return HTTPError()
+
+ autostart = int(request.forms.get('autostart', 0))
+ package = request.forms.get('package', None)
+ urls = filter(lambda x: x != "", request.forms['urls'].split("\n"))
+ folder = request.forms.get('dir', None)
+
+ if package:
+ PYLOAD.addPackage(package, urls, autostart)
+ else:
+ PYLOAD.generateAndAddPackages(urls, autostart)
+
+ return ""
+
+
+@route('/crossdomain.xml')
+@local_check
+def crossdomain():
+ rep = "<?xml version=\"1.0\"?>\n"
+ rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n"
+ rep += "<cross-domain-policy>\n"
+ rep += "<allow-access-from domain=\"*\" />\n"
+ rep += "</cross-domain-policy>"
+ return rep
+
+
+@route('/flash/checkSupportForUrl')
+@local_check
+def checksupport():
+ url = request.GET.get("url")
+ res = PYLOAD.checkURLs([url])
+ supported = (not res[0][1] is None)
+
+ return str(supported).lower()
+
+
+@route('/jdcheck.js')
+@local_check
+def jdcheck():
+ rep = "jdownloader=true;\n"
+ rep += "var version='9.581;'"
+ return rep
diff --git a/pyload/webui/app/json.py b/pyload/webui/app/json.py
new file mode 100644
index 000000000..8fa675865
--- /dev/null
+++ b/pyload/webui/app/json.py
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+
+from os.path import join
+from traceback import print_exc
+from shutil import copyfileobj
+
+from bottle import route, request, HTTPError
+
+from pyload.webui import PYLOAD
+
+from utils import login_required, render_to_response, toDict
+
+from pyload.utils import decode, formatSize
+
+
+def format_time(seconds):
+ seconds = int(seconds)
+
+ hours, seconds = divmod(seconds, 3600)
+ minutes, seconds = divmod(seconds, 60)
+ return "%.2i:%.2i:%.2i" % (hours, minutes, seconds)
+
+
+def get_sort_key(item):
+ return item["order"]
+
+
+@route('/json/status')
+@route('/json/status', method='POST')
+@login_required('LIST')
+def status():
+ try:
+ status = toDict(PYLOAD.statusServer())
+ status['captcha'] = PYLOAD.isCaptchaWaiting()
+ return status
+ except:
+ return HTTPError()
+
+
+@route('/json/links')
+@route('/json/links', method='POST')
+@login_required('LIST')
+def links():
+ try:
+ links = [toDict(x) for x in PYLOAD.statusDownloads()]
+ ids = []
+ for link in links:
+ ids.append(link['fid'])
+
+ if link['status'] == 12:
+ link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed']))
+ elif link['status'] == 5:
+ link['percent'] = 0
+ link['size'] = 0
+ link['bleft'] = 0
+ link['info'] = _("waiting %s") % link['format_wait']
+ else:
+ link['info'] = ""
+
+ data = {'links': links, 'ids': ids}
+ return data
+ except Exception, e:
+ print_exc()
+ return HTTPError()
+
+
+@route('/json/packages')
+@login_required('LIST')
+def packages():
+ print "/json/packages"
+ try:
+ data = PYLOAD.getQueue()
+
+ for package in data:
+ package['links'] = []
+ for file in PYLOAD.get_package_files(package['id']):
+ package['links'].append(PYLOAD.get_file_info(file))
+
+ return data
+
+ except:
+ return HTTPError()
+
+
+@route('/json/package/<id:int>')
+@login_required('LIST')
+def package(id):
+ try:
+ data = toDict(PYLOAD.getPackageData(id))
+ data["links"] = [toDict(x) for x in data["links"]]
+
+ for pyfile in data["links"]:
+ if pyfile["status"] == 0:
+ pyfile["icon"] = "status_finished.png"
+ elif pyfile["status"] in (2, 3):
+ pyfile["icon"] = "status_queue.png"
+ elif pyfile["status"] in (9, 1):
+ pyfile["icon"] = "status_offline.png"
+ elif pyfile["status"] == 5:
+ pyfile["icon"] = "status_waiting.png"
+ elif pyfile["status"] == 8:
+ pyfile["icon"] = "status_failed.png"
+ elif pyfile["status"] == 4:
+ pyfile["icon"] = "arrow_right.png"
+ elif pyfile["status"] in (11, 13):
+ pyfile["icon"] = "status_proc.png"
+ else:
+ pyfile["icon"] = "status_downloading.png"
+
+ tmp = data["links"]
+ tmp.sort(key=get_sort_key)
+ data["links"] = tmp
+ return data
+
+ except:
+ print_exc()
+ return HTTPError()
+
+
+@route('/json/package_order/<ids>')
+@login_required('ADD')
+def package_order(ids):
+ try:
+ pid, pos = ids.split("|")
+ PYLOAD.orderPackage(int(pid), int(pos))
+ return {"response": "success"}
+ except:
+ return HTTPError()
+
+
+@route('/json/abort_link/<id:int>')
+@login_required('DELETE')
+def abort_link(id):
+ try:
+ PYLOAD.stopDownloads([id])
+ return {"response": "success"}
+ except:
+ return HTTPError()
+
+
+@route('/json/link_order/<ids>')
+@login_required('ADD')
+def link_order(ids):
+ try:
+ pid, pos = ids.split("|")
+ PYLOAD.orderFile(int(pid), int(pos))
+ return {"response": "success"}
+ except:
+ return HTTPError()
+
+
+@route('/json/add_package')
+@route('/json/add_package', method='POST')
+@login_required('ADD')
+def add_package():
+ name = request.forms.get("add_name", "New Package").strip()
+ queue = int(request.forms['add_dest'])
+ links = decode(request.forms['add_links'])
+ links = links.split("\n")
+ pw = request.forms.get("add_password", "").strip("\n\r")
+
+ try:
+ f = request.files['add_file']
+
+ if not name or name == "New Package":
+ name = f.name
+
+ fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename)
+ destination = open(fpath, 'wb')
+ copyfileobj(f.file, destination)
+ destination.close()
+ links.insert(0, fpath)
+ except:
+ pass
+
+ name = name.decode("utf8", "ignore")
+
+ links = map(lambda x: x.strip(), links)
+ links = filter(lambda x: x != "", links)
+
+ pack = PYLOAD.addPackage(name, links, queue)
+ if pw:
+ pw = pw.decode("utf8", "ignore")
+ data = {"password": pw}
+ PYLOAD.setPackageData(pack, data)
+
+
+@route('/json/move_package/<dest:int>/<id:int>')
+@login_required('MODIFY')
+def move_package(dest, id):
+ try:
+ PYLOAD.movePackage(dest, id)
+ return {"response": "success"}
+ except:
+ return HTTPError()
+
+
+@route('/json/edit_package', method='POST')
+@login_required('MODIFY')
+def edit_package():
+ try:
+ id = int(request.forms.get("pack_id"))
+ data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"),
+ "folder": request.forms.get("pack_folder").decode("utf8", "ignore"),
+ "password": request.forms.get("pack_pws").decode("utf8", "ignore")}
+
+ PYLOAD.setPackageData(id, data)
+ return {"response": "success"}
+
+ except:
+ return HTTPError()
+
+
+@route('/json/set_captcha')
+@route('/json/set_captcha', method='POST')
+@login_required('ADD')
+def set_captcha():
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ try:
+ PYLOAD.setCaptchaResult(request.forms["cap_id"], request.forms["cap_result"])
+ except:
+ pass
+
+ task = PYLOAD.getCaptchaTask()
+
+ if task.tid >= 0:
+ src = "data:image/%s;base64,%s" % (task.type, task.data)
+
+ return {'captcha': True, 'id': task.tid, 'src': src, 'result_type' : task.resultType}
+ else:
+ return {'captcha': False}
+
+
+@route('/json/load_config/<category>/<section>')
+@login_required("SETTINGS")
+def load_config(category, section):
+ conf = None
+ if category == "general":
+ conf = PYLOAD.getConfigDict()
+ elif category == "plugin":
+ conf = PYLOAD.getPluginConfigDict()
+
+ for key, option in conf[section].iteritems():
+ if key in ("desc", "outline"): continue
+
+ if ";" in option["type"]:
+ option["list"] = option["type"].split(";")
+
+ option["value"] = decode(option["value"])
+
+ return render_to_response("settings_item.html", {"skey": section, "section": conf[section]})
+
+
+@route('/json/save_config/<category>', method='POST')
+@login_required("SETTINGS")
+def save_config(category):
+ for key, value in request.POST.iteritems():
+ try:
+ section, option = key.split("|")
+ except:
+ continue
+
+ if category == "general": category = "core"
+
+ PYLOAD.setConfigValue(section, option, decode(value), category)
+
+
+@route('/json/add_account', method='POST')
+@login_required("ACCOUNTS")
+def add_account():
+ login = request.POST["account_login"]
+ password = request.POST["account_password"]
+ type = request.POST["account_type"]
+
+ PYLOAD.updateAccount(type, login, password)
+
+
+@route('/json/update_accounts', method='POST')
+@login_required("ACCOUNTS")
+def update_accounts():
+ deleted = [] #dont update deleted accs or they will be created again
+
+ for name, value in request.POST.iteritems():
+ value = value.strip()
+ if not value: continue
+
+ tmp, user = name.split(";")
+ plugin, action = tmp.split("|")
+
+ if (plugin, user) in deleted: continue
+
+ if action == "password":
+ PYLOAD.updateAccount(plugin, user, value)
+ elif action == "time" and "-" in value:
+ PYLOAD.updateAccount(plugin, user, options={"time": [value]})
+ elif action == "limitdl" and value.isdigit():
+ PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]})
+ elif action == "delete":
+ deleted.append((plugin,user))
+ PYLOAD.removeAccount(plugin, user)
+
+@route('/json/change_password', method='POST')
+def change_password():
+
+ user = request.POST["user_login"]
+ oldpw = request.POST["login_current_password"]
+ newpw = request.POST["login_new_password"]
+
+ if not PYLOAD.changePassword(user, oldpw, newpw):
+ print "Wrong password"
+ return HTTPError()
diff --git a/pyload/webui/app/pyload.py b/pyload/webui/app/pyload.py
new file mode 100644
index 000000000..6887d71f2
--- /dev/null
+++ b/pyload/webui/app/pyload.py
@@ -0,0 +1,544 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+from datetime import datetime
+from operator import itemgetter, attrgetter
+
+import time
+import os
+import sys
+from os import listdir
+from os.path import isdir, isfile, join, abspath
+from sys import getfilesystemencoding
+from urllib import unquote
+
+from bottle import route, static_file, request, response, redirect, error
+
+from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, SETUP, env
+
+from utils import render_to_response, parse_permissions, parse_userdata, \
+ login_required, get_permission, set_permission, permlist, toDict, set_session
+
+from pyload.webui.filters import relpath, unquotepath
+
+from pyload.utils import formatSize, safe_join, fs_encode, fs_decode
+
+# Helper
+
+def pre_processor():
+ s = request.environ.get('beaker.session')
+ user = parse_userdata(s)
+ perms = parse_permissions(s)
+ status = {}
+ captcha = False
+ update = False
+ plugins = False
+ if user["is_authenticated"]:
+ status = PYLOAD.statusServer()
+ info = PYLOAD.getInfoByPlugin("UpdateManager")
+ captcha = PYLOAD.isCaptchaWaiting()
+
+ # check if update check is available
+ if info:
+ if info["pyload"] == "True":
+ update = info["version"]
+ if info["plugins"] == "True":
+ plugins = True
+
+
+ return {"user": user,
+ 'status': status,
+ 'captcha': captcha,
+ 'perms': perms,
+ 'url': request.url,
+ 'update': update,
+ 'plugins': plugins}
+
+
+def base(messages):
+ return render_to_response('base.html', {'messages': messages}, [pre_processor])
+
+
+## Views
+@error(403)
+def error403(code):
+ return "The parameter you passed has the wrong format"
+
+
+@error(404)
+def error404(code):
+ return "Sorry, this page does not exist"
+
+
+@error(500)
+def error500(error):
+ traceback = error.traceback
+ if traceback:
+ print traceback
+ return base(["An Error occured, please enable debug mode to get more details.", error,
+ traceback.replace("\n", "<br>") if traceback else "No Traceback"])
+
+
+@route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>')
+def server_min(theme, file):
+ filename = join(THEME_DIR, theme, file)
+ if not isfile(filename):
+ file = file.replace(".min.", ".")
+ if file.endswith(".js"):
+ return server_js(theme, file)
+ else:
+ return server_static(theme, file)
+
+
+@route('/<theme>/<file_static:re:.+\.js>')
+def server_js(theme, file):
+ response.headers['Content-Type'] = "text/javascript; charset=UTF-8"
+
+ if "/render/" in file or ".render." in file:
+ response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime(time.time() + 24 * 7 * 60 * 60))
+ response.headers['Cache-control'] = "public"
+
+ path = join(theme, file)
+ return env.get_template(path).render()
+ else:
+ return server_static(theme, file)
+
+
+@route('/<theme>/<file:path>')
+def server_static(theme, file):
+ response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
+ time.gmtime(time.time() + 24 * 7 * 60 * 60))
+ response.headers['Cache-control'] = "public"
+
+ return static_file(file, root=join(THEME_DIR, theme))
+
+
+@route('/favicon.ico')
+def favicon():
+ return static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources"))
+
+
+@route('/login', method="GET")
+def login():
+ if not PYLOAD and SETUP:
+ redirect("/setup")
+ else:
+ return render_to_response("login.html", proc=[pre_processor])
+
+
+@route('/nopermission')
+def nopermission():
+ return base([_("You dont have permission to access this page.")])
+
+
+@route('/login', method='POST')
+def login_post():
+ user = request.forms.get("username")
+ password = request.forms.get("password")
+
+ info = PYLOAD.checkAuth(user, password)
+
+ if not info:
+ return render_to_response("login.html", {"errors": True}, [pre_processor])
+
+ set_session(request, info)
+ return redirect("/")
+
+
+@route('/logout')
+def logout():
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return render_to_response("logout.html", proc=[pre_processor])
+
+
+@route('/')
+@route('/home')
+@login_required("LIST")
+def home():
+ try:
+ res = [toDict(x) for x in PYLOAD.statusDownloads()]
+ except:
+ s = request.environ.get('beaker.session')
+ s.delete()
+ return redirect("/login")
+
+ for link in res:
+ if link["status"] == 12:
+ link["information"] = "%s kB @ %s kB/s" % (link["size"] - link["bleft"], link["speed"])
+
+ return render_to_response("home.html", {"res": res}, [pre_processor])
+
+
+@route('/queue')
+@login_required("LIST")
+def queue():
+ queue = PYLOAD.getQueue()
+
+ queue.sort(key=attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor])
+
+
+@route('/collector')
+@login_required('LIST')
+def collector():
+ queue = PYLOAD.getCollector()
+
+ queue.sort(key=attrgetter("order"))
+
+ return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor])
+
+
+@route('/downloads')
+@login_required('DOWNLOAD')
+def downloads():
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ if not isdir(root):
+ return base([_('Download directory not found.')])
+ data = {
+ 'folder': [],
+ 'files': []
+ }
+
+ items = listdir(fs_encode(root))
+
+ for item in sorted([fs_decode(x) for x in items]):
+ if isdir(safe_join(root, item)):
+ folder = {
+ 'name': item,
+ 'path': item,
+ 'files': []
+ }
+ files = listdir(safe_join(root, item))
+ for file in sorted([fs_decode(x) for x in files]):
+ try:
+ if isfile(safe_join(root, item, file)):
+ folder['files'].append(file)
+ except:
+ pass
+
+ data['folder'].append(folder)
+ elif isfile(join(root, item)):
+ data['files'].append(item)
+
+ return render_to_response('downloads.html', {'files': data}, [pre_processor])
+
+
+@route('/downloads/get/<path:path>')
+@login_required("DOWNLOAD")
+def get_download(path):
+ path = unquote(path).decode("utf8")
+ #@TODO some files can not be downloaded
+
+ root = PYLOAD.getConfigValue("general", "download_folder")
+
+ path = path.replace("..", "")
+ return static_file(fs_encode(path), fs_encode(root))
+
+
+
+@route('/settings')
+@login_required('SETTINGS')
+def config():
+ conf = PYLOAD.getConfig()
+ plugin = PYLOAD.getPluginConfig()
+
+ conf_menu = []
+ plugin_menu = []
+
+ for entry in sorted(conf.keys()):
+ conf_menu.append((entry, conf[entry].description))
+
+ for entry in sorted(plugin.keys()):
+ plugin_menu.append((entry, plugin[entry].description))
+
+ accs = PYLOAD.getAccounts(False)
+
+ for data in accs:
+ if data.trafficleft == -1:
+ data.trafficleft = _("unlimited")
+ elif not data.trafficleft:
+ data.trafficleft = _("not available")
+ else:
+ data.trafficleft = formatSize(data.trafficleft * 1024)
+
+ if data.validuntil == -1:
+ data.validuntil = _("unlimited")
+ elif not data.validuntil :
+ data.validuntil = _("not available")
+ else:
+ t = time.localtime(data.validuntil)
+ data.validuntil = time.strftime("%d.%m.%Y - %H:%M:%S", t)
+
+ try:
+ data.options["time"] = data.options["time"][0]
+ except:
+ data.options["time"] = "0:00-0:00"
+
+ if "limitDL" in data.options:
+ data.options["limitdl"] = data.options["limitDL"][0]
+ else:
+ data.options["limitdl"] = "0"
+
+ return render_to_response('settings.html',
+ {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, 'types': PYLOAD.getAccountTypes()},
+ [pre_processor])
+
+
+@route('/filechooser')
+@route('/pathchooser')
+@route('/filechooser/<file:path>')
+@route('/pathchooser/<path:path>')
+@login_required('STATUS')
+def path(file="", path=""):
+ if file:
+ type = "file"
+ else:
+ type = "folder"
+
+ path = os.path.normpath(unquotepath(path))
+
+ if os.path.isfile(path):
+ oldfile = path
+ path = os.path.dirname(path)
+ else:
+ oldfile = ''
+
+ abs = False
+
+ if os.path.isdir(path):
+ if os.path.isabs(path):
+ cwd = os.path.abspath(path)
+ abs = True
+ else:
+ cwd = relpath(path)
+ else:
+ cwd = os.getcwd()
+
+ try:
+ cwd = cwd.encode("utf8")
+ except:
+ pass
+
+ cwd = os.path.normpath(os.path.abspath(cwd))
+ parentdir = os.path.dirname(cwd)
+ if not abs:
+ if os.path.abspath(cwd) == "/":
+ cwd = relpath(cwd)
+ else:
+ cwd = relpath(cwd) + os.path.sep
+ parentdir = relpath(parentdir) + os.path.sep
+
+ if os.path.abspath(cwd) == "/":
+ parentdir = ""
+
+ try:
+ folders = os.listdir(cwd)
+ except:
+ folders = []
+
+ files = []
+
+ for f in folders:
+ try:
+ f = f.decode(getfilesystemencoding())
+ data = {'name': f, 'fullpath': join(cwd, f)}
+ data['sort'] = data['fullpath'].lower()
+ data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f))))
+ data['ext'] = os.path.splitext(f)[1]
+ except:
+ continue
+
+ if os.path.isdir(join(cwd, f)):
+ data['type'] = 'dir'
+ else:
+ data['type'] = 'file'
+
+ if os.path.isfile(join(cwd, f)):
+ data['size'] = os.path.getsize(join(cwd, f))
+
+ power = 0
+ while (data['size'] / 1024) > 0.3:
+ power += 1
+ data['size'] /= 1024.
+ units = ('', 'K', 'M', 'G', 'T')
+ data['unit'] = units[power] + 'Byte'
+ else:
+ data['size'] = ''
+
+ files.append(data)
+
+ files = sorted(files, key=itemgetter('type', 'sort'))
+
+ return render_to_response('pathchooser.html',
+ {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile,
+ 'absolute': abs}, [])
+
+
+@route('/logs')
+@route('/logs', method='POST')
+@route('/logs/<item>')
+@route('/logs/<item>', method='POST')
+@login_required('LOGS')
+def logs(item=-1):
+ s = request.environ.get('beaker.session')
+
+ perpage = s.get('perpage', 34)
+ reversed = s.get('reversed', False)
+
+ warning = ""
+ conf = PYLOAD.getConfigValue("log", "file_log")
+ if not conf:
+ warning = "Warning: File log is disabled, see settings page."
+
+ perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all'))
+ fro = None
+
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ try:
+ fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S')
+ except:
+ pass
+ try:
+ perpage = int(request.forms['perpage'])
+ s['perpage'] = perpage
+
+ reversed = bool(request.forms.get('reversed', False))
+ s['reversed'] = reversed
+ except:
+ pass
+
+ s.save()
+
+ try:
+ item = int(item)
+ except:
+ pass
+
+ log = PYLOAD.getLog()
+ if not perpage:
+ item = 0
+
+ if item < 1 or type(item) is not int:
+ item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1
+
+ if type(fro) is datetime: # we will search for datetime
+ item = -1
+
+ data = []
+ counter = 0
+ perpagecheck = 0
+ for l in log:
+ counter += 1
+
+ if counter >= item:
+ try:
+ date, time, level, message = l.decode("utf8", "ignore").split(" ", 3)
+ dtime = datetime.strptime(date + ' ' + time, '%d.%m.%Y %H:%M:%S')
+ except:
+ dtime = None
+ date = '?'
+ time = ' '
+ level = '?'
+ message = l
+ if item == -1 and dtime is not None and fro <= dtime:
+ item = counter #found our datetime
+ if item >= 0:
+ data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message})
+ perpagecheck += 1
+ if fro is None and dtime is not None: #if fro not set set it to first showed line
+ fro = dtime
+ if perpagecheck >= perpage > 0:
+ break
+
+ if fro is None: #still not set, empty log?
+ fro = datetime.now()
+ if reversed:
+ data.reverse()
+ return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'),
+ 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p),
+ 'iprev': 1 if item - perpage < 1 else item - perpage,
+ 'inext': (item + perpage) if item + perpage < len(log) else item},
+ [pre_processor])
+
+
+@route('/admin')
+@route('/admin', method='POST')
+@login_required("ADMIN")
+def admin():
+ # convert to dict
+ user = dict([(name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()])
+ perms = permlist()
+
+ for data in user.itervalues():
+ data["perms"] = {}
+ get_permission(data["perms"], data["permission"])
+ data["perms"]["admin"] = True if data["role"] is 0 else False
+
+
+ s = request.environ.get('beaker.session')
+ if request.environ.get('REQUEST_METHOD', "GET") == "POST":
+ for name in user:
+ if request.POST.get("%s|admin" % name, False):
+ user[name]["role"] = 0
+ user[name]["perms"]["admin"] = True
+ elif name != s["name"]:
+ user[name]["role"] = 1
+ user[name]["perms"]["admin"] = False
+
+ # set all perms to false
+ for perm in perms:
+ user[name]["perms"][perm] = False
+
+ for perm in request.POST.getall("%s|perms" % name):
+ user[name]["perms"][perm] = True
+
+ user[name]["permission"] = set_permission(user[name]["perms"])
+
+ PYLOAD.setUserPermission(name, user[name]["permission"], user[name]["role"])
+
+ return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor])
+
+
+@route('/setup')
+def setup():
+ return base([_("Run pyload.py -s to access the setup.")])
+
+
+@route('/info')
+def info():
+ conf = PYLOAD.getConfigDict()
+
+ if hasattr(os, "uname"):
+ extra = os.uname()
+ else:
+ extra = tuple()
+
+ data = {"python": sys.version,
+ "os": " ".join((os.name, sys.platform) + extra),
+ "version": PYLOAD.getServerVersion(),
+ "folder": abspath(PYLOAD_DIR), "config": abspath(""),
+ "download": abspath(conf["general"]["download_folder"]["value"]),
+ "freespace": formatSize(PYLOAD.freeSpace()),
+ "remote": conf["remote"]["port"]["value"],
+ "webif": conf["webinterface"]["port"]["value"],
+ "language": conf["general"]["language"]["value"]}
+
+ return render_to_response("info.html", data, [pre_processor])
diff --git a/pyload/webui/app/utils.py b/pyload/webui/app/utils.py
new file mode 100644
index 000000000..d5fa66a35
--- /dev/null
+++ b/pyload/webui/app/utils.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License,
+ or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this plrogram; if not, see <http://www.gnu.org/licenses/>.
+
+ @author: RaNaN
+"""
+
+from os.path import join
+
+from bottle import request, HTTPError, redirect, ServerAdapter
+
+from pyload.webui import env, THEME
+
+from pyload.api import has_permission, PERMS, ROLE
+
+def render_to_response(file, args={}, proc=[]):
+ for p in proc:
+ args.update(p())
+ path = join(THEME, "tml", file)
+ return env.get_template(path).render(**args)
+
+
+def parse_permissions(session):
+ perms = dict([(x, False) for x in dir(PERMS) if not x.startswith("_")])
+ perms["ADMIN"] = False
+ perms["is_admin"] = False
+
+ if not session.get("authenticated", False):
+ return perms
+
+ if session.get("role") == ROLE.ADMIN:
+ for k in perms.iterkeys():
+ perms[k] = True
+
+ elif session.get("perms"):
+ p = session.get("perms")
+ get_permission(perms, p)
+
+ return perms
+
+
+def permlist():
+ return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"]
+
+
+def get_permission(perms, p):
+ """Returns a dict with permission key
+
+ :param perms: dictionary
+ :param p: bits
+ """
+ for name in permlist():
+ perms[name] = has_permission(p, getattr(PERMS, name))
+
+
+def set_permission(perms):
+ """generates permission bits from dictionary
+
+ :param perms: dict
+ """
+ permission = 0
+ for name in dir(PERMS):
+ if name.startswith("_"): continue
+
+ if name in perms and perms[name]:
+ permission |= getattr(PERMS, name)
+
+ return permission
+
+
+def set_session(request, info):
+ s = request.environ.get('beaker.session')
+ s["authenticated"] = True
+ s["user_id"] = info["id"]
+ s["name"] = info["name"]
+ s["role"] = info["role"]
+ s["perms"] = info["permission"]
+ s["template"] = info["template"]
+ s.save()
+
+ return s
+
+
+def parse_userdata(session):
+ return {"name": session.get("name", "Anonymous"),
+ "is_admin": True if session.get("role", 1) == 0 else False,
+ "is_authenticated": session.get("authenticated", False)}
+
+
+def login_required(perm=None):
+ def _dec(func):
+ def _view(*args, **kwargs):
+ s = request.environ.get('beaker.session')
+ if s.get("name", None) and s.get("authenticated", False):
+ if perm:
+ perms = parse_permissions(s)
+ if perm not in perms or not perms[perm]:
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ return HTTPError(403, "Forbidden")
+ else:
+ return redirect("/nopermission")
+
+ return func(*args, **kwargs)
+ else:
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ return HTTPError(403, "Forbidden")
+ else:
+ return redirect("/login")
+
+ return _view
+
+ return _dec
+
+
+def toDict(obj):
+ ret = {}
+ for att in obj.__slots__:
+ ret[att] = getattr(obj, att)
+ return ret
+
+
+class CherryPyWSGI(ServerAdapter):
+ def run(self, handler):
+ from wsgiserver import CherryPyWSGIServer
+
+ server = CherryPyWSGIServer((self.host, self.port), handler)
+ server.start()
diff --git a/pyload/webui/filters.py b/pyload/webui/filters.py
new file mode 100644
index 000000000..c5e9447ee
--- /dev/null
+++ b/pyload/webui/filters.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+import os
+from os.path import abspath, commonprefix, join
+
+quotechar = "::/"
+
+try:
+ from os.path import relpath
+except:
+ from posixpath import curdir, sep, pardir
+ def relpath(path, start=curdir):
+ """Return a relative version of a path"""
+ if not path:
+ raise ValueError("no path specified")
+ start_list = abspath(start).split(sep)
+ path_list = abspath(path).split(sep)
+ # Work out how much of the filepath is shared by start and path.
+ i = len(commonprefix([start_list, path_list]))
+ rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return curdir
+ return join(*rel_list)
+
+
+def quotepath(path):
+ try:
+ return path.replace("../", quotechar)
+ except AttributeError:
+ return path
+ except:
+ return ""
+
+def unquotepath(path):
+ try:
+ return path.replace(quotechar, "../")
+ except AttributeError:
+ return path
+ except:
+ return ""
+
+def path_make_absolute(path):
+ p = os.path.abspath(path)
+ if p[-1] == os.path.sep:
+ return p
+ else:
+ return p + os.path.sep
+
+def path_make_relative(path):
+ p = relpath(path)
+ if p[-1] == os.path.sep:
+ return p
+ else:
+ return p + os.path.sep
+
+def truncate(value, n):
+ if (n - len(value)) < 3:
+ return value[:n]+"..."
+ return value
+
+def date(date, format):
+ return date
diff --git a/pyload/webui/middlewares.py b/pyload/webui/middlewares.py
new file mode 100644
index 000000000..5f56f81ee
--- /dev/null
+++ b/pyload/webui/middlewares.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+
+import gzip
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+class StripPathMiddleware(object):
+ def __init__(self, app):
+ self.app = app
+
+ def __call__(self, e, h):
+ e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
+ return self.app(e, h)
+
+
+class PrefixMiddleware(object):
+ def __init__(self, app, prefix="/pyload"):
+ self.app = app
+ self.prefix = prefix
+
+ def __call__(self, e, h):
+ path = e["PATH_INFO"]
+ if path.startswith(self.prefix):
+ e['PATH_INFO'] = path.replace(self.prefix, "", 1)
+ return self.app(e, h)
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+# WSGI middleware
+# Gzip-encodes the response.
+
+class GZipMiddleWare(object):
+
+ def __init__(self, application, compress_level=6):
+ self.application = application
+ self.compress_level = int(compress_level)
+
+ def __call__(self, environ, start_response):
+ if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
+ # nothing for us to do, so this middleware will
+ # be a no-op:
+ return self.application(environ, start_response)
+ response = GzipResponse(start_response, self.compress_level)
+ app_iter = self.application(environ,
+ response.gzip_start_response)
+ if app_iter is not None:
+ response.finish_response(app_iter)
+
+ return response.write()
+
+def header_value(headers, key):
+ for header, value in headers:
+ if key.lower() == header.lower():
+ return value
+
+def update_header(headers, key, value):
+ remove_header(headers, key)
+ headers.append((key, value))
+
+def remove_header(headers, key):
+ for header, value in headers:
+ if key.lower() == header.lower():
+ headers.remove((header, value))
+ break
+
+class GzipResponse(object):
+
+ def __init__(self, start_response, compress_level):
+ self.start_response = start_response
+ self.compress_level = compress_level
+ self.buffer = StringIO()
+ self.compressible = False
+ self.content_length = None
+ self.headers = ()
+
+ def gzip_start_response(self, status, headers, exc_info=None):
+ self.headers = headers
+ ct = header_value(headers,'content-type')
+ ce = header_value(headers,'content-encoding')
+ cl = header_value(headers, 'content-length')
+ if cl:
+ cl = int(cl)
+ else:
+ cl = 201
+ self.compressible = False
+ if ct and (ct.startswith('text/') or ct.startswith('application/')) \
+ and 'zip' not in ct and cl > 200:
+ self.compressible = True
+ if ce:
+ self.compressible = False
+ if self.compressible:
+ headers.append(('content-encoding', 'gzip'))
+ remove_header(headers, 'content-length')
+ self.headers = headers
+ self.status = status
+ return self.buffer.write
+
+ def write(self):
+ out = self.buffer
+ out.seek(0)
+ s = out.getvalue()
+ out.close()
+ return [s]
+
+ def finish_response(self, app_iter):
+ if self.compressible:
+ output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level,
+ fileobj=self.buffer)
+ else:
+ output = self.buffer
+ try:
+ for s in app_iter:
+ output.write(s)
+ if self.compressible:
+ output.close()
+ finally:
+ if hasattr(app_iter, 'close'):
+ try:
+ app_iter.close()
+ except :
+ pass
+
+ content_length = self.buffer.tell()
+ update_header(self.headers, "Content-Length" , str(content_length))
+ self.start_response(self.status, self.headers)
diff --git a/pyload/webui/servers/lighttpd_default.conf b/pyload/webui/servers/lighttpd_default.conf
new file mode 100644
index 000000000..d4cc00629
--- /dev/null
+++ b/pyload/webui/servers/lighttpd_default.conf
@@ -0,0 +1,153 @@
+# lighttpd configuration file
+#
+# use it as a base for lighttpd 1.0.0 and above
+#
+# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $
+
+############ Options you really have to take care of ####################
+
+## modules to load
+# at least mod_access and mod_accesslog should be loaded
+# all other module should only be loaded if really neccesary
+# - saves some time
+# - saves memory
+server.modules = (
+ "mod_rewrite",
+ "mod_redirect",
+ "mod_alias",
+ "mod_access",
+# "mod_trigger_b4_dl",
+# "mod_auth",
+# "mod_status",
+# "mod_setenv",
+ "mod_fastcgi",
+# "mod_proxy",
+# "mod_simple_vhost",
+# "mod_evhost",
+# "mod_userdir",
+# "mod_cgi",
+# "mod_compress",
+# "mod_ssi",
+# "mod_usertrack",
+# "mod_expire",
+# "mod_secdownload",
+# "mod_rrdtool",
+# "mod_accesslog"
+ )
+
+## A static document-root. For virtual hosting take a look at the
+## mod_simple_vhost pyload.
+server.document-root = "%(path)"
+
+## where to send error-messages to
+server.errorlog = "%(path)/error.log"
+
+# files to check for if .../ is requested
+index-file.names = ( "index.php", "index.html",
+ "index.htm", "default.htm" )
+
+## set the event-handler (read the performance section in the manual)
+# server.event-handler = "freebsd-kqueue" # needed on OS X
+
+# mimetype mapping
+mimetype.assign = (
+ ".pdf" => "application/pdf",
+ ".sig" => "application/pgp-signature",
+ ".spl" => "application/futuresplash",
+ ".class" => "application/octet-stream",
+ ".ps" => "application/postscript",
+ ".torrent" => "application/x-bittorrent",
+ ".dvi" => "application/x-dvi",
+ ".gz" => "application/x-gzip",
+ ".pac" => "application/x-ns-proxy-autoconfig",
+ ".swf" => "application/x-shockwave-flash",
+ ".tar.gz" => "application/x-tgz",
+ ".tgz" => "application/x-tgz",
+ ".tar" => "application/x-tar",
+ ".zip" => "application/zip",
+ ".mp3" => "audio/mpeg",
+ ".m3u" => "audio/x-mpegurl",
+ ".wma" => "audio/x-ms-wma",
+ ".wax" => "audio/x-ms-wax",
+ ".ogg" => "application/ogg",
+ ".wav" => "audio/x-wav",
+ ".gif" => "image/gif",
+ ".jar" => "application/x-java-archive",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".xbm" => "image/x-xbitmap",
+ ".xpm" => "image/x-xpixmap",
+ ".xwd" => "image/x-xwindowdump",
+ ".css" => "text/css",
+ ".html" => "text/html",
+ ".htm" => "text/html",
+ ".js" => "text/javascript",
+ ".asc" => "text/plain",
+ ".c" => "text/plain",
+ ".cpp" => "text/plain",
+ ".log" => "text/plain",
+ ".conf" => "text/plain",
+ ".text" => "text/plain",
+ ".txt" => "text/plain",
+ ".dtd" => "text/xml",
+ ".xml" => "text/xml",
+ ".mpeg" => "video/mpeg",
+ ".mpg" => "video/mpeg",
+ ".mov" => "video/quicktime",
+ ".qt" => "video/quicktime",
+ ".avi" => "video/x-msvideo",
+ ".asf" => "video/x-ms-asf",
+ ".asx" => "video/x-ms-asf",
+ ".wmv" => "video/x-ms-wmv",
+ ".bz2" => "application/x-bzip",
+ ".tbz" => "application/x-bzip-compressed-tar",
+ ".tar.bz2" => "application/x-bzip-compressed-tar",
+ # default mime type
+ "" => "application/octet-stream",
+ )
+
+# Use the "Content-Type" extended attribute to obtain mime type if possible
+#mimetype.use-xattr = "enable"
+
+#### accesslog module
+accesslog.filename = "%(path)/access.log"
+
+url.access-deny = ( "~", ".inc" )
+
+$HTTP["url"] =~ "\.pdf$" {
+ server.range-requests = "disable"
+}
+static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
+
+server.pid-file = "%(path)/lighttpd.pid"
+
+server.bind = "%(host)"
+server.port = %(port)
+
+#server.document-root = "/home/user/public_html"
+fastcgi.server = (
+ "/pyload.fcgi" => (
+ "main" => (
+ "host" => "127.0.0.1",
+ "port" => 9295,
+ "check-local" => "disable",
+ "docroot" => "/",
+ )
+ ),
+)
+
+alias.url = (
+ "/media/" => "%(media)/",
+ "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/",
+)
+
+url.rewrite-once = (
+ "^(/media.*)$" => "$1",
+ "^(/admin/media.*)$" => "$1",
+ "^/favicon\.ico$" => "/media/img/favicon.ico",
+ "^(/pyload.fcgi.*)$" => "$1",
+ "^(/.*)$" => "/pyload.fcgi$1",
+)
+
+%(ssl)
diff --git a/module/web/servers/nginx_default.conf b/pyload/webui/servers/nginx_default.conf
index b4ebd1e02..b4ebd1e02 100644
--- a/module/web/servers/nginx_default.conf
+++ b/pyload/webui/servers/nginx_default.conf
diff --git a/pyload/webui/themes/dark/css/MooDialog.css b/pyload/webui/themes/dark/css/MooDialog.css
new file mode 100644
index 000000000..dd0c0a601
--- /dev/null
+++ b/pyload/webui/themes/dark/css/MooDialog.css
@@ -0,0 +1,94 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+ color:white;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background: url(../img/dark-bg.jpg);
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../img/MooDialog/dialog-close.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+ color:white;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+ color:white;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/dark/css/dark.css b/pyload/webui/themes/dark/css/dark.css
new file mode 100644
index 000000000..ca16d0621
--- /dev/null
+++ b/pyload/webui/themes/dark/css/dark.css
@@ -0,0 +1,962 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+
+
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+
+.dokuwiki div.plugin_translation ul li a:hover img {
+ opacity:1.0;
+ height:15px;
+}
+
+body {
+ margin:0;
+ padding:0;
+ background-image: url(../img/dark-bg.jpg);
+ color:white;
+ font-size:12px;
+ font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
+ font-family:sans-serif;
+ font-size:99, 96%;
+ font-size-adjust:none;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:normal;
+ line-height:normal;
+}
+hr {
+ border-width:0;
+ border-bottom:1px #aaa dotted;
+}
+img {
+ border:none;
+}
+form {
+ margin:0px;
+ padding:0px;
+ border:none;
+ display:inline;
+ background:transparent;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+a {
+ color:#3465a4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+
+option {
+ border:0 none #fff;
+}
+strong.highlight {
+ background-color:#fc9;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ height:1px;
+ color:#c0c0c0;
+ background-color:#c0c0c0;
+ border:none;
+ margin:.2em 0 .2em 0;
+}
+
+.invisible {
+ margin:0px;
+ border:0px;
+ padding:0px;
+ height:0px;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ padding:40px 40px 10px 40px;
+ font-size:127%;
+}
+div#content {
+ margin-top:-20px;
+ padding:0;
+ font-size:14px;
+ color:white;
+ line-height:1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ background:transparent none repeat scroll 0 0;
+ border-bottom:1px solid #aaa;
+ color:white;
+ font-weight:normal;
+ margin:0;
+ padding:0;
+ padding-bottom:0.17em;
+ padding-top:0.5em;
+}
+h1 {
+ font-size:188%;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ padding-bottom:0;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ float:right;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ white-space: nowrap;
+ border-radius:5px;
+ -moz-border-radius:5px;
+ border:1px solid grey;
+}
+ul#user-actions {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ -moz-border-radius:3px;
+ border-radius:3px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ text-decoration:none;
+ color:white;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+ /*text-decoration:underline;*/
+}
+/***************************/
+ul#page-actions2 {
+ float:left;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ border-radius:5px;
+ -moz-border-radius:5px;
+ border:1px solid grey;
+}
+ul#user-actions2 {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:white;
+ background-color:#202020;
+ list-style-type:none;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ text-decoration:none;
+ color:white;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
+ul#page-actions-more a:hover, ul#page-actions-more a:focus{
+ color: #4e7bb4;
+}
+/****************************/
+.hidden {
+ display:none;
+}
+
+a.logout {
+ background:transparent url(../img/default/user-actions-logout.png) 0px 1px no-repeat;
+}
+
+a.info {
+ background:transparent url(../img/default/user-info.png) 0px 1px no-repeat;
+}
+
+a.admin {
+ background:transparent url(../img/default/user-actions-admin.png) 0px 1px no-repeat;
+}
+a.profile {
+ background:transparent url(../img/default/user-actions-profile.png) 0px 1px no-repeat;
+}
+a.create, a.edit {
+ background:transparent url(../img/default/page-tools-edit.png) 0px 1px no-repeat;
+}
+a.source, a.show {
+ background:transparent url(../img/default/page-tools-source.png) 0px 1px no-repeat;
+}
+a.revisions {
+ background:transparent url(../img/default/page-tools-revisions.png) 0px 1px no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background:transparent url(../img/default/page-tools-subscribe.png) 0px 1px no-repeat;
+}
+a.backlink {
+ background:transparent url(../img/default/page-tools-backlinks.png) 0px 1px no-repeat;
+}
+a.play {
+ background:transparent url(../img/default/control_play.png) 0px 1px no-repeat;
+}
+.time {
+ background:transparent url(../img/default/status_None.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+.reconnect {
+ background:transparent url(../img/default/reconnect.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+a.play:hover {
+ background:transparent url(../img/default/control_play_blue.png) 0px 1px no-repeat;
+}
+a.cancel {
+ background:transparent url(../img/default/control_cancel.png) 0px 1px no-repeat;
+}
+a.cancel:hover {
+ background:transparent url(../img/default/control_cancel_blue.png) 0px 1px no-repeat;
+}
+a.pause {
+ background:transparent url(../img/default/control_pause.png) 0px 1px no-repeat;
+}
+a.pause:hover {
+ background:transparent url(../img/default/control_pause_blue.png) 0px 1px no-repeat;
+ font-weight: bold;
+}
+a.stop {
+ background:transparent url(../img/default/control_stop.png) 0px 1px no-repeat;
+}
+a.stop:hover {
+ background:transparent url(../img/default/control_stop_blue.png) 0px 1px no-repeat;
+}
+a.add {
+ background:transparent url(../img/default/control_add.png) 0px 1px no-repeat;
+}
+a.add:hover {
+ background:transparent url(../img/default/control_add_blue.png) 0px 1px no-repeat;
+}
+a.cog {
+ background:transparent url(../img/default/cog.png) 0px 1px no-repeat;
+}
+#head-panel {
+ background:#525252 url(../img/default/head_bg1.png) bottom left repeat-x;
+}
+#head-panel h1 {
+ display:none;
+ margin:0;
+ text-decoration:none;
+ padding-top:0.8em;
+ padding-left:3.3em;
+ font-size:2.6em;
+ color:#eeeeec;
+}
+#head-panel #head-logo {
+ float:left;
+ margin:5px 0 -15px 5px;
+ padding:0;
+ overflow:visible;
+}
+#head-menu {
+ background:transparent url(../img/default/tabs-border-bottom.png) 0 100% repeat-x;
+ width:100%;
+ float:left;
+ margin:0;
+ padding:0;
+ padding-top:0.8em;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ margin:0;
+ margin-left:0.3em;
+ font-size:14px;
+ margin-bottom:4px;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0px;
+}
+#head-menu ul li a img {
+ height:22px;
+ width:22px;
+ vertical-align:middle;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ float:left;
+ text-decoration:none;
+ color:white;
+ background: url(../img/dark-bg.jpg) 0 100% repeat-x;
+ padding:3px 7px 3px 7px;
+ border:2px solid #ccc;
+ border-bottom:0px solid transparent;
+ padding-bottom:3px;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ color:#3465a4;
+ background-image: url(../img/dark-bg.jpg);
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ outline:none;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ color:white;
+ background-image: url(../img/dark-bg.jpg);
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#3465a4;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 1em 0 0;
+ background-color:#222;
+ padding:7px 7px 5px 5px;
+ color:white;
+ white-space: nowrap;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright:6px;
+ -moz-border-radius-bottomleft:6px;
+ border-right:1px solid grey;
+ border-left:1px solid grey;
+ border-bottom:1px solid grey;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ border:2px solid #888;
+ background:#eee;
+ font-size:14px;
+ padding:2px;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+div#head-search-and-login form input:focus {
+ background:#fff;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ width:80px;
+ font-size:14px;
+}
+#pageinfo {
+ clear:both;
+ color:#888;
+ padding:0.6em 0;
+ margin:0;
+}
+#foot {
+ font-style:normal;
+ color:#888;
+ text-align:center;
+}
+#foot a {
+ color:#aaf;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ border:1px dotted #888;
+ background:#f0f0f0;
+ margin:1em 0 1em 1em;
+ float:right;
+ font-size:95%;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0;
+ padding:0;
+ margin-left:1em;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ font-size:90%;
+ border:1px dotted #888;
+ padding:0ex 1ex 1ex 1ex;
+ background:#f7f6f2;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ text-align:right;
+ font-weight:bold;
+}
+label.simple {
+ display:block;
+ text-align:left;
+ font-weight:normal;
+}
+label.block input.edit {
+ width:50%;
+}
+/*fieldset {
+ width:300px;
+ text-align:center;
+ padding:0.5em;
+ margin:auto;
+}
+*/
+div.editor {
+ margin:0 0 0 0;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ padding:0 2em;
+ margin:0 0 1em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ clear:both;
+ padding:5px 10px;
+ background-color:#eee;
+ border-bottom:2px solid #ccc;
+}
+#message p {
+ margin:5px 0;
+ padding:0;
+ font-weight:bold;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:bold;
+}
+.boxtext {
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ color:#000;
+ float:none;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ width:32px;
+ height:32px;
+ float:left;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ cursor:pointer
+}
+.dlsize {
+ float:left;
+ padding-right: 8px;
+}
+.dlspeed {
+ float:left;
+ padding-right: 8px;
+}
+.package {
+ margin-bottom: 10px;
+}
+.packagename {
+ font-weight: bold;
+}
+
+.child {
+ margin-left: 20px;
+}
+.child_status {
+ margin-right: 10px;
+}
+.child_secrow {
+ font-size: 10px;
+}
+
+.header, .header th {
+ text-align: left;
+ font-weight: normal;
+ background-color:#202020;
+ border-top:1px solid grey;
+ border-bottom:1px solid grey;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+.progress_bar {
+ background: #0C0;
+ height: 5px;
+
+}
+
+.queue {
+ border: none
+}
+
+.queue tr td {
+ border: none
+}
+
+.header, .header th{
+ text-align: left;
+ font-weight: normal;
+}
+
+
+.clearer
+{
+ clear: both;
+ height: 1px;
+}
+
+.left
+{
+ float: left;
+}
+
+.right
+{
+ float: right;
+}
+
+
+.setfield
+{
+ display: table-cell;
+}
+
+ul.tabs li a
+{
+ padding: 5px 16px 4px 15px;
+ border: none;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+
+#tabs span
+{
+ display: none;
+}
+
+#tabs span.selected
+{
+ display: inline;
+}
+
+#tabsback
+{
+ background-color: #525252;
+ margin: 2px 0 0;
+ padding: 6px 4px 1px 4px;
+
+ border-top-right-radius: 30px;
+ border-top-left-radius: 3px;
+ -moz-border-radius-topright: 30px;
+ -moz-border-radius-topleft: 3px;
+}
+ul.tabs
+{
+ list-style-type: none;
+ margin:0;
+ padding: 0 40px 0 0;
+}
+
+ul.tabs li
+{
+ display: inline;
+ margin-left: 8px;
+}
+
+ul.tabs li a
+{
+ color: white;
+ background-color: #202020;
+ border: 1px solid grey;
+ border-bottom:none;
+ margin: 0;
+ text-decoration: none;
+
+ outline: 0;
+
+ padding: 5px 16px 4px 15px;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+ul.tabs li a.selected, ul.tabs li a:hover
+{
+ color: #3465a4;
+ background-color: white;
+
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ -moz-border-radius-bottomleft: 0;
+}
+
+ul.tabs li a:hover
+{
+ background-color: #202020;
+}
+
+ul.tabs li a.selected
+{
+ font-weight: bold;
+ background-color: #525252;
+ padding-bottom: 5px;
+ color: white;
+}
+
+
+#tabs-body {
+ position: relative;
+ overflow: hidden;
+}
+
+
+span.tabContent
+{
+ border: 2px solid #525252;
+ margin: 0;
+ padding: 0;
+ padding-bottom: 10px;
+}
+
+#tabs-body > span {
+ display: none;
+}
+
+#tabs-body > span.active {
+ display: block;
+}
+
+.hide
+{
+ display: none;
+}
+
+.settable
+{
+ color:white;
+ margin: 20px;
+ border: none;
+}
+.settable td
+{
+ border: none;
+ margin: 0;
+ padding: 5px;
+}
+
+.settable th{
+ padding-bottom: 8px;
+}
+
+.settable.wide td , .settable.wide th {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+.settable input {
+background-color:#202020;
+color:white;
+}
+.settable select {
+background-color:#202020;
+color:white;
+}
+
+ul.nav {
+ margin: -30px 0 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+}
+
+
+ul.nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+ul.nav > li a {
+ background: #202020;
+ -moz-border-radius: 4px 4px 4px 4px;
+ border: 1px solid grey;
+ border-bottom: medium none;
+ color: white;
+}
+
+ul.nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ border: 1px solid #AAA;
+ background: #202020;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+ cursor: pointer;
+}
+
+ul.nav .open {
+ display: block;
+}
+
+ul.nav .close {
+ display: none;
+}
+
+ul.nav ul li {
+ float: none;
+ padding: 0;
+}
+
+ul.nav ul li a {
+ width: 130px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ font-weight: normal;
+}
+
+ul.nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+ul.nav ul ul {
+ left: 137px;
+ top: 0;
+}
+
+.purr-wrapper{
+ margin:10px;
+}
+
+/*Purr alert styles*/
+
+.purr-alert{
+ margin-bottom:10px;
+ padding:10px;
+ background:#000;
+ font-size:13px;
+ font-weight:bold;
+ color:#FFF;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+ /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
+ width:300px;
+}
+.purr-alert.error{
+ color:#F55;
+ padding-left:30px;
+ background:url(../img/default/error.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.success{
+ color:#5F5;
+ padding-left:30px;
+ background:url(../img/default/success.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.notice{
+ color:#99F;
+ padding-left:30px;
+ background:url(../img/default/notice.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+
+table.system {
+ border: none;
+ margin-left: 10px;
+}
+
+table.system td {
+ border: none
+}
+
+table.system tr > td:first-child {
+ font-weight: bold;
+ padding-right: 10px;
+}
+
+#foot {
+color:white;
+}
+
+#login_table {
+margin-left:auto;
+margin-right:auto;
+}
+
+#login_table td {
+padding:5px;
+border:1px solid grey;
+}
+
+#login_table input[type=text], #login_table input[type=password] {
+width:120px;
+background-color:transparent;
+-moz-opacity: 0.10;
+color:white;
+border: 1px solid grey;
+-moz-border-radius: 2px;
+-webkit-border-radius: 2px;
+border-radius: 18px;
+padding-left:5px;
+padding-right:5px;
+}
+
+#login_table input[type=text]:focus, #login_table input[type=password]:focus {
+border:1px solid #3465a4;
+}
+
+#login_table input[type=submit] {
+background-color:transparent;
+color:white;
+border: 1px solid grey;
+-moz-border-radius: 2px;
+-webkit-border-radius: 2px;
+border-radius: 18px;
+}
+
+#login_table input[type=submit]:hover {
+border:1px solid #3465a4;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/css/log.css b/pyload/webui/themes/dark/css/log.css
new file mode 100644
index 000000000..41aa19616
--- /dev/null
+++ b/pyload/webui/themes/dark/css/log.css
@@ -0,0 +1,75 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+ background-color:#202020;
+ color:white;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+ color: white;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #202020;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+} \ No newline at end of file
diff --git a/module/web/media/default/css/pathchooser.css b/pyload/webui/themes/dark/css/pathchooser.css
index 894cc335e..894cc335e 100644
--- a/module/web/media/default/css/pathchooser.css
+++ b/pyload/webui/themes/dark/css/pathchooser.css
diff --git a/pyload/webui/themes/dark/css/window.css b/pyload/webui/themes/dark/css/window.css
new file mode 100644
index 000000000..11ba84b39
--- /dev/null
+++ b/pyload/webui/themes/dark/css/window.css
@@ -0,0 +1,92 @@
+/* ----------- stylized ----------- */
+.window_table td {
+border:none;
+text-align:left;
+}
+#add_box {
+background-image: url(../img/dark-bg.jpg);
+color:white;
+}
+#pack_box {
+background-image: url(../img/dark-bg.jpg);
+color:white;
+}
+.window_box h1{
+ font-size:14px;
+ font-weight:bold;
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:white;
+ margin-bottom:20px;
+ border-bottom:solid 1px #b7ddf2;
+ padding-bottom:10px;
+}
+.window_box label{ /*Linke Seite*/
+ display:block;
+ font-weight:bold;
+ text-align:right;
+ width:240px;
+ float:left;
+ color:white;
+}
+.window_box .small{
+ color:grey;
+ display:block;
+ font-size:11px;
+ font-weight:normal;
+ text-align:right;
+ width:240px;
+}
+.window_box select, .window_box input{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+ background-color:#202020;
+ color:white;
+}
+.window_box .cont{
+ float:left;
+ font-size:12px;
+ padding: 0px 10px 15px 0px;
+ width:300px;
+ margin:0px 0px 0px 10px;
+ color:white;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+ color:white;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+ background-color:#202020;
+ color:white;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background:#666666 url(../img/button.png) no-repeat;
+ text-align:center;
+ line-height:31px;
+ color:#FFFFFF;
+ font-size:11px;
+ font-weight:bold;
+ border: 0px;
+}
+
+.styled_button {
+ margin-left: 15px;
+ cursor: pointer;
+}
diff --git a/module/web/media/img/dialog-close.png b/pyload/webui/themes/dark/img/MooDialog/dialog-close.png
index 81ebb88b2..81ebb88b2 100644
--- a/module/web/media/img/dialog-close.png
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/MooDialog/dialog-error.png b/pyload/webui/themes/dark/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/module/web/media/img/dialog-question.png b/pyload/webui/themes/dark/img/MooDialog/dialog-question.png
index b0af3db5b..b0af3db5b 100644
--- a/module/web/media/img/dialog-question.png
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/MooDialog/dialog-warning.png b/pyload/webui/themes/dark/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/dark/img/MooDialog/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/button.png b/pyload/webui/themes/dark/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/dark/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/dark-bg.jpg b/pyload/webui/themes/dark/img/dark-bg.jpg
new file mode 100644
index 000000000..637fa6b93
--- /dev/null
+++ b/pyload/webui/themes/dark/img/dark-bg.jpg
Binary files differ
diff --git a/module/web/media/default/img/add_folder.png b/pyload/webui/themes/dark/img/default/add_folder.png
index 8acbc411b..8acbc411b 100644
--- a/module/web/media/default/img/add_folder.png
+++ b/pyload/webui/themes/dark/img/default/add_folder.png
Binary files differ
diff --git a/module/web/media/default/img/ajax-loader.gif b/pyload/webui/themes/dark/img/default/ajax-loader.gif
index 2fd8e0737..2fd8e0737 100644
--- a/module/web/media/default/img/ajax-loader.gif
+++ b/pyload/webui/themes/dark/img/default/ajax-loader.gif
Binary files differ
diff --git a/module/web/media/default/img/arrow_refresh.png b/pyload/webui/themes/dark/img/default/arrow_refresh.png
index 0de26566d..0de26566d 100644
--- a/module/web/media/default/img/arrow_refresh.png
+++ b/pyload/webui/themes/dark/img/default/arrow_refresh.png
Binary files differ
diff --git a/module/web/media/default/img/arrow_right.png b/pyload/webui/themes/dark/img/default/arrow_right.png
index b1a181923..b1a181923 100644
--- a/module/web/media/default/img/arrow_right.png
+++ b/pyload/webui/themes/dark/img/default/arrow_right.png
Binary files differ
diff --git a/module/web/media/default/img/big_button.gif b/pyload/webui/themes/dark/img/default/big_button.gif
index 7680490ea..7680490ea 100644
--- a/module/web/media/default/img/big_button.gif
+++ b/pyload/webui/themes/dark/img/default/big_button.gif
Binary files differ
diff --git a/module/web/media/default/img/big_button_over.gif b/pyload/webui/themes/dark/img/default/big_button_over.gif
index 2e3ee10d2..2e3ee10d2 100644
--- a/module/web/media/default/img/big_button_over.gif
+++ b/pyload/webui/themes/dark/img/default/big_button_over.gif
Binary files differ
diff --git a/module/web/media/default/img/body.png b/pyload/webui/themes/dark/img/default/body.png
index 7ff1043e0..7ff1043e0 100644
--- a/module/web/media/default/img/body.png
+++ b/pyload/webui/themes/dark/img/default/body.png
Binary files differ
diff --git a/module/web/media/default/img/closebtn.gif b/pyload/webui/themes/dark/img/default/closebtn.gif
index 3e27e6030..3e27e6030 100644
--- a/module/web/media/default/img/closebtn.gif
+++ b/pyload/webui/themes/dark/img/default/closebtn.gif
Binary files differ
diff --git a/module/web/media/default/img/cog.png b/pyload/webui/themes/dark/img/default/cog.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/cog.png
+++ b/pyload/webui/themes/dark/img/default/cog.png
Binary files differ
diff --git a/module/web/media/default/img/control_add.png b/pyload/webui/themes/dark/img/default/control_add.png
index d39886893..d39886893 100644
--- a/module/web/media/default/img/control_add.png
+++ b/pyload/webui/themes/dark/img/default/control_add.png
Binary files differ
diff --git a/module/web/media/default/img/control_add_blue.png b/pyload/webui/themes/dark/img/default/control_add_blue.png
index d11b7f41d..d11b7f41d 100644
--- a/module/web/media/default/img/control_add_blue.png
+++ b/pyload/webui/themes/dark/img/default/control_add_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel.png b/pyload/webui/themes/dark/img/default/control_cancel.png
index 7b9bc3fba..7b9bc3fba 100644
--- a/module/web/media/default/img/control_cancel.png
+++ b/pyload/webui/themes/dark/img/default/control_cancel.png
Binary files differ
diff --git a/module/web/media/default/img/control_cancel_blue.png b/pyload/webui/themes/dark/img/default/control_cancel_blue.png
index 0c5c96ce3..0c5c96ce3 100644
--- a/module/web/media/default/img/control_cancel_blue.png
+++ b/pyload/webui/themes/dark/img/default/control_cancel_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause.png b/pyload/webui/themes/dark/img/default/control_pause.png
index 2d9ce9c4e..2d9ce9c4e 100644
--- a/module/web/media/default/img/control_pause.png
+++ b/pyload/webui/themes/dark/img/default/control_pause.png
Binary files differ
diff --git a/module/web/media/default/img/control_pause_blue.png b/pyload/webui/themes/dark/img/default/control_pause_blue.png
index ec61099b0..ec61099b0 100644
--- a/module/web/media/default/img/control_pause_blue.png
+++ b/pyload/webui/themes/dark/img/default/control_pause_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_play.png b/pyload/webui/themes/dark/img/default/control_play.png
index 0846555d0..0846555d0 100644
--- a/module/web/media/default/img/control_play.png
+++ b/pyload/webui/themes/dark/img/default/control_play.png
Binary files differ
diff --git a/module/web/media/default/img/control_play_blue.png b/pyload/webui/themes/dark/img/default/control_play_blue.png
index f8c8ec683..f8c8ec683 100644
--- a/module/web/media/default/img/control_play_blue.png
+++ b/pyload/webui/themes/dark/img/default/control_play_blue.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop.png b/pyload/webui/themes/dark/img/default/control_stop.png
index 893bb60e5..893bb60e5 100644
--- a/module/web/media/default/img/control_stop.png
+++ b/pyload/webui/themes/dark/img/default/control_stop.png
Binary files differ
diff --git a/module/web/media/default/img/control_stop_blue.png b/pyload/webui/themes/dark/img/default/control_stop_blue.png
index e6f75d232..e6f75d232 100644
--- a/module/web/media/default/img/control_stop_blue.png
+++ b/pyload/webui/themes/dark/img/default/control_stop_blue.png
Binary files differ
diff --git a/module/web/media/default/img/delete.png b/pyload/webui/themes/dark/img/default/delete.png
index 08f249365..08f249365 100644
--- a/module/web/media/default/img/delete.png
+++ b/pyload/webui/themes/dark/img/default/delete.png
Binary files differ
diff --git a/module/web/media/default/img/drag_corner.gif b/pyload/webui/themes/dark/img/default/drag_corner.gif
index befb1adf1..befb1adf1 100644
--- a/module/web/media/default/img/drag_corner.gif
+++ b/pyload/webui/themes/dark/img/default/drag_corner.gif
Binary files differ
diff --git a/module/web/media/default/img/error.png b/pyload/webui/themes/dark/img/default/error.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/error.png
+++ b/pyload/webui/themes/dark/img/default/error.png
Binary files differ
diff --git a/module/web/media/default/img/folder.png b/pyload/webui/themes/dark/img/default/folder.png
index 784e8fa48..784e8fa48 100644
--- a/module/web/media/default/img/folder.png
+++ b/pyload/webui/themes/dark/img/default/folder.png
Binary files differ
diff --git a/module/web/media/default/img/full.png b/pyload/webui/themes/dark/img/default/full.png
index fea52af76..fea52af76 100644
--- a/module/web/media/default/img/full.png
+++ b/pyload/webui/themes/dark/img/default/full.png
Binary files differ
diff --git a/module/web/media/default/img/head-login.png b/pyload/webui/themes/dark/img/default/head-login.png
index b59b7cbbf..b59b7cbbf 100644
--- a/module/web/media/default/img/head-login.png
+++ b/pyload/webui/themes/dark/img/default/head-login.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-collector.png b/pyload/webui/themes/dark/img/default/head-menu-collector.png
index 861be40bc..861be40bc 100644
--- a/module/web/media/default/img/head-menu-collector.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-collector.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-config.png b/pyload/webui/themes/dark/img/default/head-menu-config.png
index bbf43d4f3..bbf43d4f3 100644
--- a/module/web/media/default/img/head-menu-config.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-config.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-development.png b/pyload/webui/themes/dark/img/default/head-menu-development.png
index fad150fe1..fad150fe1 100644
--- a/module/web/media/default/img/head-menu-development.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-development.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-download.png b/pyload/webui/themes/dark/img/default/head-menu-download.png
index 98c5da9db..98c5da9db 100644
--- a/module/web/media/default/img/head-menu-download.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-download.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-home.png b/pyload/webui/themes/dark/img/default/head-menu-home.png
index 9d62109aa..9d62109aa 100644
--- a/module/web/media/default/img/head-menu-home.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-home.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-index.png b/pyload/webui/themes/dark/img/default/head-menu-index.png
index 44d631064..44d631064 100644
--- a/module/web/media/default/img/head-menu-index.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-index.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-news.png b/pyload/webui/themes/dark/img/default/head-menu-news.png
index 43950ebc9..43950ebc9 100644
--- a/module/web/media/default/img/head-menu-news.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-news.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-queue.png b/pyload/webui/themes/dark/img/default/head-menu-queue.png
index be98793ce..be98793ce 100644
--- a/module/web/media/default/img/head-menu-queue.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-queue.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-recent.png b/pyload/webui/themes/dark/img/default/head-menu-recent.png
index fc9b0497f..fc9b0497f 100644
--- a/module/web/media/default/img/head-menu-recent.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-recent.png
Binary files differ
diff --git a/module/web/media/default/img/head-menu-wiki.png b/pyload/webui/themes/dark/img/default/head-menu-wiki.png
index 07cf0102d..07cf0102d 100644
--- a/module/web/media/default/img/head-menu-wiki.png
+++ b/pyload/webui/themes/dark/img/default/head-menu-wiki.png
Binary files differ
diff --git a/module/web/media/default/img/head-search-noshadow.png b/pyload/webui/themes/dark/img/default/head-search-noshadow.png
index aafdae015..aafdae015 100644
--- a/module/web/media/default/img/head-search-noshadow.png
+++ b/pyload/webui/themes/dark/img/default/head-search-noshadow.png
Binary files differ
diff --git a/module/web/media/default/img/head_bg1.png b/pyload/webui/themes/dark/img/default/head_bg1.png
index f2848c3cc..f2848c3cc 100644
--- a/module/web/media/default/img/head_bg1.png
+++ b/pyload/webui/themes/dark/img/default/head_bg1.png
Binary files differ
diff --git a/module/web/media/default/img/images.png b/pyload/webui/themes/dark/img/default/images.png
index 184860d1e..184860d1e 100644
--- a/module/web/media/default/img/images.png
+++ b/pyload/webui/themes/dark/img/default/images.png
Binary files differ
diff --git a/module/web/media/default/img/notice.png b/pyload/webui/themes/dark/img/default/notice.png
index 12cd1aef9..12cd1aef9 100644
--- a/module/web/media/default/img/notice.png
+++ b/pyload/webui/themes/dark/img/default/notice.png
Binary files differ
diff --git a/module/web/media/default/img/package_go.png b/pyload/webui/themes/dark/img/default/package_go.png
index aace63ad6..aace63ad6 100644
--- a/module/web/media/default/img/package_go.png
+++ b/pyload/webui/themes/dark/img/default/package_go.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-backlinks.png b/pyload/webui/themes/dark/img/default/page-tools-backlinks.png
index 3eb6a9ce3..3eb6a9ce3 100644
--- a/module/web/media/default/img/page-tools-backlinks.png
+++ b/pyload/webui/themes/dark/img/default/page-tools-backlinks.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-edit.png b/pyload/webui/themes/dark/img/default/page-tools-edit.png
index 188e1c12b..188e1c12b 100644
--- a/module/web/media/default/img/page-tools-edit.png
+++ b/pyload/webui/themes/dark/img/default/page-tools-edit.png
Binary files differ
diff --git a/module/web/media/default/img/page-tools-revisions.png b/pyload/webui/themes/dark/img/default/page-tools-revisions.png
index 5c3b8587f..5c3b8587f 100644
--- a/module/web/media/default/img/page-tools-revisions.png
+++ b/pyload/webui/themes/dark/img/default/page-tools-revisions.png
Binary files differ
diff --git a/module/web/media/default/img/parseUri.png b/pyload/webui/themes/dark/img/default/parseUri.png
index 937bded9d..937bded9d 100644
--- a/module/web/media/default/img/parseUri.png
+++ b/pyload/webui/themes/dark/img/default/parseUri.png
Binary files differ
diff --git a/module/web/media/default/img/pencil.png b/pyload/webui/themes/dark/img/default/pencil.png
index 0bfecd50e..0bfecd50e 100644
--- a/module/web/media/default/img/pencil.png
+++ b/pyload/webui/themes/dark/img/default/pencil.png
Binary files differ
diff --git a/module/web/media/default/img/reconnect.png b/pyload/webui/themes/dark/img/default/reconnect.png
index 49b269145..49b269145 100644
--- a/module/web/media/default/img/reconnect.png
+++ b/pyload/webui/themes/dark/img/default/reconnect.png
Binary files differ
diff --git a/module/web/media/default/img/status_None.png b/pyload/webui/themes/dark/img/default/status_None.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_None.png
+++ b/pyload/webui/themes/dark/img/default/status_None.png
Binary files differ
diff --git a/module/web/media/default/img/status_downloading.png b/pyload/webui/themes/dark/img/default/status_downloading.png
index fb4ebc850..fb4ebc850 100644
--- a/module/web/media/default/img/status_downloading.png
+++ b/pyload/webui/themes/dark/img/default/status_downloading.png
Binary files differ
diff --git a/module/web/media/default/img/status_failed.png b/pyload/webui/themes/dark/img/default/status_failed.png
index c37bd062e..c37bd062e 100644
--- a/module/web/media/default/img/status_failed.png
+++ b/pyload/webui/themes/dark/img/default/status_failed.png
Binary files differ
diff --git a/module/web/media/default/img/status_finished.png b/pyload/webui/themes/dark/img/default/status_finished.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/status_finished.png
+++ b/pyload/webui/themes/dark/img/default/status_finished.png
Binary files differ
diff --git a/module/web/media/default/img/status_offline.png b/pyload/webui/themes/dark/img/default/status_offline.png
index 0cfd58596..0cfd58596 100644
--- a/module/web/media/default/img/status_offline.png
+++ b/pyload/webui/themes/dark/img/default/status_offline.png
Binary files differ
diff --git a/module/web/media/default/img/status_proc.png b/pyload/webui/themes/dark/img/default/status_proc.png
index 67de2c6cc..67de2c6cc 100644
--- a/module/web/media/default/img/status_proc.png
+++ b/pyload/webui/themes/dark/img/default/status_proc.png
Binary files differ
diff --git a/module/web/media/default/img/status_queue.png b/pyload/webui/themes/dark/img/default/status_queue.png
index 293b13f77..293b13f77 100644
--- a/module/web/media/default/img/status_queue.png
+++ b/pyload/webui/themes/dark/img/default/status_queue.png
Binary files differ
diff --git a/module/web/media/default/img/status_waiting.png b/pyload/webui/themes/dark/img/default/status_waiting.png
index 2842cc338..2842cc338 100644
--- a/module/web/media/default/img/status_waiting.png
+++ b/pyload/webui/themes/dark/img/default/status_waiting.png
Binary files differ
diff --git a/module/web/media/default/img/success.png b/pyload/webui/themes/dark/img/default/success.png
index 89c8129a4..89c8129a4 100644
--- a/module/web/media/default/img/success.png
+++ b/pyload/webui/themes/dark/img/default/success.png
Binary files differ
diff --git a/module/web/media/default/img/tabs-border-bottom.png b/pyload/webui/themes/dark/img/default/tabs-border-bottom.png
index 02440f428..02440f428 100644
--- a/module/web/media/default/img/tabs-border-bottom.png
+++ b/pyload/webui/themes/dark/img/default/tabs-border-bottom.png
Binary files differ
diff --git a/module/web/media/default/img/user-actions-logout.png b/pyload/webui/themes/dark/img/default/user-actions-logout.png
index 0010931e2..0010931e2 100644
--- a/module/web/media/default/img/user-actions-logout.png
+++ b/pyload/webui/themes/dark/img/default/user-actions-logout.png
Binary files differ
diff --git a/module/web/media/default/img/user-actions-profile.png b/pyload/webui/themes/dark/img/default/user-actions-profile.png
index 46573fff6..46573fff6 100644
--- a/module/web/media/default/img/user-actions-profile.png
+++ b/pyload/webui/themes/dark/img/default/user-actions-profile.png
Binary files differ
diff --git a/module/web/media/default/img/user-info.png b/pyload/webui/themes/dark/img/default/user-info.png
index 6e643100f..6e643100f 100644
--- a/module/web/media/default/img/user-info.png
+++ b/pyload/webui/themes/dark/img/default/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/pyload-logo.png b/pyload/webui/themes/dark/img/pyload-logo.png
new file mode 100644
index 000000000..e878afee5
--- /dev/null
+++ b/pyload/webui/themes/dark/img/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/dark/img/tab-background.png b/pyload/webui/themes/dark/img/tab-background.png
new file mode 100644
index 000000000..ee96b8407
--- /dev/null
+++ b/pyload/webui/themes/dark/img/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/dark/js/render/admin.coffee b/pyload/webui/themes/dark/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/dark/js/render/admin.min.js b/pyload/webui/themes/dark/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/dark/js/render/base.coffee b/pyload/webui/themes/dark/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/dark/js/render/base.min.js b/pyload/webui/themes/dark/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/dark/js/render/package.js b/pyload/webui/themes/dark/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/dark/js/render/settings.coffee b/pyload/webui/themes/dark/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/dark/js/render/settings.min.js b/pyload/webui/themes/dark/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/dark/js/render/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/dark/js/static/MooDialog.js b/pyload/webui/themes/dark/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/dark/js/static/MooDialog.min.js b/pyload/webui/themes/dark/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,onInitialize:function(){this.wrapper.setStyle("display","none")},onBeforeOpen:function(){this.wrapper.setStyle("display","block"),this.fireEvent("show")},onBeforeClose:function(){this.wrapper.setStyle("display","none"),this.fireEvent("hide")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/MooDropMenu.js b/pyload/webui/themes/dark/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/dark/js/static/MooDropMenu.min.js b/pyload/webui/themes/dark/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/mootools-core.js b/pyload/webui/themes/dark/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+...
+*/
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios') {
+ UA[1] = 'chrome';
+ }
+
+ var platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.ie){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e) {
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
+ return node.hasAttribute(attribute);
+ } : function(node, attribute) {
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector) {
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll) {
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e) {
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError) {}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown"></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return this.className.clean().contains(className, ' ');
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e) {}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ clearTimeout(this.timer);
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ clearTimeout(this.timer);
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (ready) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
+
diff --git a/pyload/webui/themes/dark/js/static/mootools-core.min.js b/pyload/webui/themes/dark/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){return i.$family();
+}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if("callee" in i){return"arguments";
+}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
+while(s){if(s===i){return true;}s=s.parent;}if(!t.hasOwnProperty){return false;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;
+}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];}f.prototype.overloadSetter=function(s){var i=this;
+return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);
+}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;return function(u){var v,t;if(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;
+}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;
+};};Array.from=function(i){if(i==null){return[];}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);
+return isFinite(i)?i:null;};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;
+return this;}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
+}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
+};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
+u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
+});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
+}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("String",String,["charAt","charCodeAt","concat","contains","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
+Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
+}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
+}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
+return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
+while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
+}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
+y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
+v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
+});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;
+}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);
+return this.map(function(c){return c[a].apply(c,b);});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];
+}return d;},link:function(c){var a={};for(var e=0,b=this.length;e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;
+},append:function(a){this.push.apply(this,a);return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
+},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
+},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
+for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
+}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
+}var a=this.map(function(c){if(c.length==1){c+=c;}return parseInt(c,16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;
+}if(this.length==4&&this[3]==0&&!d){return"transparent";}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");
+}});String.implement({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
+},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
+});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
+});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
+},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
+return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
+}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
+return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
+}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
+};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
+b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
+},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
+g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
+if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
+},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
+for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
+}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
+}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
+}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
+}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
+}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
+break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
+this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
+}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
+var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
+this.key=b[d];if(i=="keydown"||i=="keyup"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
+}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
+this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
+if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
+while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
+this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
+this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
+},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
+}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
+})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
+}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
+g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
+}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
+};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
+break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
+}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
+return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
+}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
+return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
+for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
+return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
+return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
+this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
+}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
+},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
+}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
+if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
+(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
+var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
+return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
+}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
+o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
+};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
+function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
+if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
+}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
+}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
+}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
+break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
+case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
+};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
+};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
+k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
+};k.setDocument=function(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);
+if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);
+}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;
+}E=U.getElementsByTagName(v);if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;
+}A=U.getElementById(v);if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
+}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
+if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
+for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
+}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
+}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
+}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
+A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
+}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
+return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
+if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
+if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
+this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
+}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
+}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
+if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
+}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
+if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
+return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
+}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
+}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
+if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
+}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
+k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
+}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
+if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
+}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
+}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
+if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=this.getAttribute(o,"class");
+if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
+}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
+if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
+if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
+}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
+if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
+}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
+}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
+if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
+this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
+}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
+break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
+return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
+},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+(c+1));
+},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
+while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
+}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
+}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
+},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");
+},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");
+},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;},type:function(){return this.getAttribute("type");
+},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});
+e.version="1.1.7";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);};e.contains=function(c,n){k.setDocument(c);
+return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);return k.hasAttribute(n,c);
+};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
+return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
+};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
+return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
+var Element=this.Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};
+}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;
+f<c;f++){a=d[f];if(g[a.key]!=null){continue;}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
+}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;}var b={};b[a]=function(){var h=[],e=arguments,j=true;
+for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");}return(j)?new Elements(h):h;};Elements.implement(b);
+});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$constructor":Element,"$family":Function.from("element").hide()};Element.mirror(function(a,b){Element.Prototype[a]=b;
+});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
+}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
+b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
+for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
+}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
+b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
+c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
+for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
+b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
+return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();
+}};Element.Properties.html={set:function(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundSize:"@px",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});
+if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);
+}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);
+};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);
+}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);
+if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);
+}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);}return this;}var c=this.retrieve("events");if(!c){return this;
+}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);},this);delete c[b];
+}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
+}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
+}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};var a=function(b){var c=b.relatedTarget;
+if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}if(!window.addEventListener){Element.NativeEvents.propertychange=2;
+Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";},condition:function(b){return b.type!="propertychange"||b.event.propertyName=="checked";
+}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);
+}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
+var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
+n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
+if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
+}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
+}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
+}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
+});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
+}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(n.condition||w&&w.condition){var l=q,m=n.condition||w.condition;
+q=function(C,B){return l(C,B)&&m.call(C,B,v);};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);
+}}:function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);
+},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);
+}delete p[u];q[m]=p;return j.call(this,r,w,l.capture);}var o,v;if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);
+}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});
+})();(function(){var h=document.createElement("div"),e=document.createElement("div");h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;
+var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);
+}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight};
+},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(a(this)){return this.getWindow().getScroll();
+}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;
+n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}var n=(k(m,"position")=="static")?i:l;
+while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}try{return m.offsetParent;
+}catch(n){}return null;},getOffsets:function(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();
+return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();}var m=this.getPosition(o),n=this.getSize();
+var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
+},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
+},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
+return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
+return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
+}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
+}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
+},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
+},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
+},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
+this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
+this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
+}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
+},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
+return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
+this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
+this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
+d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
+}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
+this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
+},resume:function(){if(this.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};
+var e={},c={};var a=function(){var h=Date.now();for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);
+if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);
+}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;
+}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
+});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
+});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
+}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
+});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
+},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
+b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
+}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
+}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
+this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
+}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
+b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
+},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
+for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
+for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
+return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
+return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
+a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
+var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
+}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
+},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
+return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
+},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
+});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
+this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
+this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
+}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
+}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
+}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
+},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
+},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
+return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
+}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
+}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
+o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
+break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
+j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
+}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
+}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
+}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
+}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}else{if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
+}}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
+if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
+if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
+return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
+this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
+Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
+c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
+c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
+}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
+}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
+},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
+}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
+}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
+};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
+return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
+}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
+Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
+case"null":return"null";}return null;};JSON.secure=true;JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure==null){secure=JSON.secure;
+}if(secure){if(JSON.parse){return JSON.parse(string);}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");
+}}return eval("("+string+")");};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
+},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
+}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
+this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
+}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
+}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
+return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
+Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
+};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
+k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
+if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
+c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
+}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
+}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/mootools-more.js b/pyload/webui/themes/dark/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = event.page;
+
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {};
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/dark/js/static/mootools-more.min.js b/pyload/webui/themes/dark/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+MooTools.More={version:"1.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
+}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
+if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
+if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
+c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
+return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
+});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
+};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
+g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
+}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
+},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
+};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
+},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
+if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
+},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
+e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
+a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
+},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
+},setOffsetOption:function(f,d){var c={x:0,y:0};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;
+});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});
+},getPosition:function(e,d){var c={};d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);
+}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();
+a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);
+}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
+switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
+break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
+["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
+},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
+},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
+c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
+},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
+break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
+c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
+}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
+return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);(function(){var a=false;
+this.IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.y}).position({relativeTo:this.element,offset:this.options.offset});
+return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
+}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
+}});})();window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
+this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
+if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||(this.target==document.body?"inside":"after");
+b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
+this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
+this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
+},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
+var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
+return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
+return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
+}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
+this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
+},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
+}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
+this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
+},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
+this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
+this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
+}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","true");this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
+}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
+b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","false");this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
+}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
+if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
+if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
+},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
+},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
+}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
+}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
+if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
+}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
+return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
+}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
+}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
+while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
+if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
+if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
+}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
+}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
+},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
+e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
+}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
+["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
+while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
+})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
+if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
+}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
+});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
+},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
+}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
+var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
+},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
+if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
+b.addEvent("onFormValidate",this.onFormValidate);b.validate(a);return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
+if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
+this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
+if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
+this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
+}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
+},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
+},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
+},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
+},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
+if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
+if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
+};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:"opacity" in document.documentElement,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
+},opacity:1,hideInputs:!("opacity" in document.documentElement)?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
+this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
+var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
+this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
+}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
+}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
+this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
+}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
+Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
+this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
+}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
+}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
+}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
+if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
+return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
+Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
+return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
+}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
+b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
+}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
+this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection="selectstart" in document?"selectstart":"mousedown";if("ondragstart" in document&&!("FileReader" in window)&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
+Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
+this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
+return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
+}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
+}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
+}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
+if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
+}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
+}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
+this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
+}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
+}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
+}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
+}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
+if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
+a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
+this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
+b=this.element;this.droppables=$$(this.options.droppables);this.setContainer(this.options.container);if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");
+if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);
+this.overed=null;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
+});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
+["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
+g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
+if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
+n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};
+},getDroppableCoordinates:function(c){var b=c.getCoordinates();if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;
+b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
+var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
+}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
+}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
+this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{},unDraggableTags:["button","input","a","textarea","select","option"]},initialize:function(a,b){this.setOptions(b);
+this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
+}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
+var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
+},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
+},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
+return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
+},this));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);
+}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
+});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
+}});}return c.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
+if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
+this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
+},start:function(b,a){if(!this.idle||b.rightClick||(!this.options.handle&&this.options.unDraggableTags.contains(b.target.get("tag")))){return;}this.idle=false;
+this.element=a;this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
+this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
+}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
+},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.reset();}},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
+}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
+}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
+}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
+this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
+}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
+}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
+break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
+var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
+}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
+},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
+}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
+};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
+if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
+},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
+var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
+case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
+return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/purr.js b/pyload/webui/themes/dark/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/purr.min.js b/pyload/webui/themes/dark/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/purr.min.js
@@ -0,0 +1 @@
+var Purr=new Class({options:{mode:"top",position:"left",elementAlertClass:"purr-element-alert",elements:{wrapper:"div",alert:"div",buttonWrapper:"div",button:"button"},elementOptions:{wrapper:{styles:{position:"fixed","z-index":"9999"},"class":"purr-wrapper"},alert:{"class":"purr-alert",styles:{opacity:".85"}},buttonWrapper:{"class":"purr-button-wrapper"},button:{"class":"purr-button"}},alert:{buttons:[],clickDismiss:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/tinytab.js b/pyload/webui/themes/dark/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/js/static/tinytab.min.js b/pyload/webui/themes/dark/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/dark/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/admin.html b/pyload/webui/themes/dark/tml/admin.html
new file mode 100644
index 000000000..42118eda4
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/dark/js/render/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/dark/tml/base.html b/pyload/webui/themes/dark/tml/base.html
new file mode 100644
index 000000000..e7179acfa
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/base.html
@@ -0,0 +1,177 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/dark/css/dark.min.css"/>
+<link rel="stylesheet" type="text/css" href="/dark/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/dark/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/dark/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/dark/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/dark/js/render/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/dark/img/default/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/dark/img/default/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/dark/img/pyload-logo.png" alt="pyLoad" /></a>
+{% if user.is_authenticated %}
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/dark/img/default/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/dark/img/default/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/dark/img/default/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/dark/img/default/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/dark/img/default/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/dark/img/default/head-menu-config.png" alt="" />{{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>{% endif %}
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/dark/img/default/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div id="window_popup" style="display: none;">
+ {% include '/dark/tml/window.html' %}
+ {% include '/dark/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/dark/tml/captcha.html b/pyload/webui/themes/dark/tml/captcha.html
new file mode 100644
index 000000000..ae1afe444
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/downloads.html b/pyload/webui/themes/dark/tml/downloads.html
new file mode 100644
index 000000000..0c7fb9209
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/folder.html b/pyload/webui/themes/dark/tml/folder.html
new file mode 100644
index 000000000..05176d51e
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/dark/img/default/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/dark/img/default/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/dark/img/default/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/home.html b/pyload/webui/themes/dark/tml/home.html
new file mode 100644
index 000000000..b350b705e
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/dark/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/dark/img/default/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/dark/img/default/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/dark/img/default/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/dark/img/default/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/dark/img/default/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/dark/img/default/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/dark/img/default/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/dark/img/default/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/info.html b/pyload/webui/themes/dark/tml/info.html
new file mode 100644
index 000000000..7ff2b639b
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/info.html
@@ -0,0 +1,76 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block head %}
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+
+ <ul id="twitter_update_list"></ul>
+ <script type="text/javascript" src="http://twitter.com/javascripts/blogger.min.js"></script>
+ <script type="text/javascript" src="http://api.twitter.com/1/statuses/user_timeline.json?screen_name=pyLoad&include_rts=true&count=5&callback=twitterCallback2"></script>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Webinterface Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/login.html b/pyload/webui/themes/dark/tml/login.html
new file mode 100644
index 000000000..9f5e2cb2f
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/login.html
@@ -0,0 +1,37 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+{% if errors %}
+<p style="color:red;">{{_("Your username and password didn't match. Please try again.")}}</p>
+{% endif %}
+ <table id="login_table">
+ <tr>
+ <td>{{_("Username")}}</td>
+ <td><input type="text" size="20" name="username" /></td>
+ </tr>
+ <tr>
+ <td>{{_("Password")}}</td>
+ <td><input type="password" size="20" name="password" /></td>
+ </tr>
+<tr>
+<td>&nbsp;</td>
+ <td><input type="submit" value="Login" class="button" /></td>
+</tr>
+</table>
+ </fieldset>
+ </div>
+</form>
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/dark/tml/logout.html b/pyload/webui/themes/dark/tml/logout.html
new file mode 100644
index 000000000..5320e07f5
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/logs.html b/pyload/webui/themes/dark/tml/logs.html
new file mode 100644
index 000000000..e178c6c5c
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/dark/css/log.min.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/pathchooser.html b/pyload/webui/themes/dark/tml/pathchooser.html
new file mode 100644
index 000000000..2b94f1019
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/dark/css/pathchooser.min.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/queue.html b/pyload/webui/themes/dark/tml/queue.html
new file mode 100644
index 000000000..f68079106
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/dark/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/dark/js/render/package.min.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/dark/img/default/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/dark/img/default/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/dark/img/default/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 4px; border: 1px solid grey; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #525252;"></div>
+ <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/settings.html b/pyload/webui/themes/dark/tml/settings.html
new file mode 100644
index 000000000..c9c0bed8a
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/dark/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/dark/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/dark/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/dark/js/render/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li style="color:white;" id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:grey;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" value="{{account.password}}" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.time}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.limitdl}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/settings_item.html b/pyload/webui/themes/dark/tml/settings_item.html
new file mode 100644
index 000000000..e417e564c
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/settings_item.html
@@ -0,0 +1,48 @@
+<table class="settable">
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in section.iteritems() %}
+ {% if okey not in ("desc","outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:white;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+</table> \ No newline at end of file
diff --git a/pyload/webui/themes/dark/tml/window.html b/pyload/webui/themes/dark/tml/window.html
new file mode 100644
index 000000000..0b4f5362b
--- /dev/null
+++ b/pyload/webui/themes/dark/tml/window.html
@@ -0,0 +1,52 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/dark/img/default/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+<table class="window_table">
+<tr>
+ <td>{{_("Queue")}}</td>
+ <td><input type="radio" name="add_dest" id="add_dest" value="1" checked="checked" /></td>
+</tr>
+<tr>
+ <td>{{_("Collector")}}</td>
+ <td><input type="radio" name="add_dest" id="add_dest2" value="0" /></td>
+</tr>
+</table>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div> \ No newline at end of file
diff --git a/pyload/webui/themes/default/css/MooDialog.css b/pyload/webui/themes/default/css/MooDialog.css
new file mode 100644
index 000000000..d26bf2ff2
--- /dev/null
+++ b/pyload/webui/themes/default/css/MooDialog.css
@@ -0,0 +1,91 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background: #eef5f8;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+ border-radius: 7px;
+ -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+ box-shadow: 1px 1px 5px rgba(0,0,0,0.8);
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../img/MooDialog/dialog-close.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/default/css/default.css b/pyload/webui/themes/default/css/default.css
new file mode 100644
index 000000000..5d4b9ebf2
--- /dev/null
+++ b/pyload/webui/themes/default/css/default.css
@@ -0,0 +1,902 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+
+
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ color:#fff !important;
+ text-decoration:none;
+ padding:0 0.2em;
+ margin:0.1em 0.2em;
+ border:none !important;
+}
+
+.dokuwiki div.plugin_translation ul li a:hover img {
+ opacity:1.0;
+ height:15px;
+}
+
+body {
+ margin:0;
+ padding:0;
+ background-color:white;
+ color:black;
+ font-size:12px;
+ font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif;
+ font-family:sans-serif;
+ font-size:99, 96%;
+ font-size-adjust:none;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:normal;
+ line-height:normal;
+}
+hr {
+ border-width:0;
+ border-bottom:1px #aaa dotted;
+}
+img {
+ border:none;
+}
+form {
+ margin:0px;
+ padding:0px;
+ border:none;
+ display:inline;
+ background:transparent;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+a {
+ color:#3465a4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+
+option {
+ border:0 none #fff;
+}
+strong.highlight {
+ background-color:#fc9;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ height:1px;
+ color:#c0c0c0;
+ background-color:#c0c0c0;
+ border:none;
+ margin:.2em 0 .2em 0;
+}
+
+.invisible {
+ margin:0px;
+ border:0px;
+ padding:0px;
+ height:0px;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ padding:40px 40px 10px 40px;
+ font-size:127%;
+}
+div#content {
+ margin-top:-20px;
+ padding:0;
+ font-size:14px;
+ color:black;
+ line-height:1.5em;
+}
+h1, h2, h3, h4, h5, h6 {
+ background:transparent none repeat scroll 0 0;
+ border-bottom:1px solid #aaa;
+ color:black;
+ font-weight:normal;
+ margin:0;
+ padding:0;
+ padding-bottom:0.17em;
+ padding-top:0.5em;
+}
+h1 {
+ font-size:188%;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ padding-bottom:0;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ float:right;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ white-space: nowrap;
+ border-radius:5px;
+ -moz-border-radius:5px;
+}
+ul#user-actions {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ -moz-border-radius:3px;
+ border-radius:3px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ text-decoration:none;
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+ /*text-decoration:underline;*/
+}
+/***************************/
+ul#page-actions2 {
+ float:left;
+ margin:10px 10px 0 10px;
+ padding:6px;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ border-radius:5px;
+ -moz-border-radius:5px;
+}
+ul#user-actions2 {
+ padding:5px;
+ margin:0;
+ display:inline;
+ color:black;
+ background-color:#ececec;
+ list-style-type:none;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ text-decoration:none;
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0px 2px 18px;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus,
+ul#page-actions-more a:hover, ul#page-actions-more a:focus{
+ color: #4e7bb4;
+}
+/****************************/
+.hidden {
+ display:none;
+}
+
+a.logout {
+ background:transparent url(../img/user-actions-logout.png) 0px 1px no-repeat;
+}
+
+a.info {
+ background:transparent url(../img/user-info.png) 0px 1px no-repeat;
+}
+
+a.admin {
+ background:transparent url(../img/user-actions-admin.png) 0px 1px no-repeat;
+}
+a.profile {
+ background:transparent url(../img/user-actions-profile.png) 0px 1px no-repeat;
+}
+a.create, a.edit {
+ background:transparent url(../img/page-tools-edit.png) 0px 1px no-repeat;
+}
+a.source, a.show {
+ background:transparent url(../img/page-tools-source.png) 0px 1px no-repeat;
+}
+a.revisions {
+ background:transparent url(../img/page-tools-revisions.png) 0px 1px no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background:transparent url(../img/page-tools-subscribe.png) 0px 1px no-repeat;
+}
+a.backlink {
+ background:transparent url(../img/page-tools-backlinks.png) 0px 1px no-repeat;
+}
+a.play {
+ background:transparent url(../img/control_play.png) 0px 1px no-repeat;
+}
+.time {
+ background:transparent url(../img/status_None.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+.reconnect {
+ background:transparent url(../img/reconnect.png) 0px 1px no-repeat;
+ padding: 2px 0px 2px 18px;
+ margin: 0px 3px;
+}
+a.play:hover {
+ background:transparent url(../img/control_play_blue.png) 0px 1px no-repeat;
+}
+a.cancel {
+ background:transparent url(../img/control_cancel.png) 0px 1px no-repeat;
+}
+a.cancel:hover {
+ background:transparent url(../img/control_cancel_blue.png) 0px 1px no-repeat;
+}
+a.pause {
+ background:transparent url(../img/control_pause.png) 0px 1px no-repeat;
+}
+a.pause:hover {
+ background:transparent url(../img/control_pause_blue.png) 0px 1px no-repeat;
+ font-weight: bold;
+}
+a.stop {
+ background:transparent url(../img/control_stop.png) 0px 1px no-repeat;
+}
+a.stop:hover {
+ background:transparent url(../img/control_stop_blue.png) 0px 1px no-repeat;
+}
+a.add {
+ background:transparent url(../img/control_add.png) 0px 1px no-repeat;
+}
+a.add:hover {
+ background:transparent url(../img/control_add_blue.png) 0px 1px no-repeat;
+}
+a.cog {
+ background:transparent url(../img/cog.png) 0px 1px no-repeat;
+}
+#head-panel {
+ background:#525252 url(../img/head_bg1.png) bottom left repeat-x;
+}
+#head-panel h1 {
+ display:none;
+ margin:0;
+ text-decoration:none;
+ padding-top:0.8em;
+ padding-left:3.3em;
+ font-size:2.6em;
+ color:#eeeeec;
+}
+#head-panel #head-logo {
+ float:left;
+ margin:5px 0 -15px 5px;
+ padding:0;
+ overflow:visible;
+}
+#head-menu {
+ background:transparent url(../img/tabs-border-bottom.png) 0 100% repeat-x;
+ width:100%;
+ float:left;
+ margin:0;
+ padding:0;
+ padding-top:0.8em;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ margin:0;
+ margin-left:0.3em;
+ font-size:14px;
+ margin-bottom:4px;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0px;
+}
+#head-menu ul li a img {
+ height:22px;
+ width:22px;
+ vertical-align:middle;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ float:left;
+ text-decoration:none;
+ color:#555;
+ background:#eaeaea url(../img/tab-background.png) 0 100% repeat-x;
+ padding:3px 7px 3px 7px;
+ border:2px solid #ccc;
+ border-bottom:0px solid transparent;
+ padding-bottom:3px;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ color:#111;
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ outline:none;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ color:#3566A5;
+ background:#fff;
+ padding-bottom:7px;
+ border-bottom:0px none transparent;
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ -moz-border-radius-bottomright:0px;
+ -moz-border-radius-bottomleft:0px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111;
+}
+div#head-search-and-login {
+ float:right;
+ margin:0 1em 0 0;
+ background-color:#222;
+ padding:7px 7px 5px 5px;
+ color:white;
+ white-space: nowrap;
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright:6px;
+ -moz-border-radius-bottomleft:6px;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ border:2px solid #888;
+ background:#eee;
+ font-size:14px;
+ padding:2px;
+ border-radius:3px;
+ -moz-border-radius:3px;
+}
+div#head-search-and-login form input:focus {
+ background:#fff;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ width:80px;
+ font-size:14px;
+}
+#pageinfo {
+ clear:both;
+ color:#888;
+ padding:0.6em 0;
+ margin:0;
+}
+#foot {
+ font-style:normal;
+ color:#888;
+ text-align:center;
+}
+#foot a {
+ color:#aaf;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ border:1px dotted #888;
+ background:#f0f0f0;
+ margin:1em 0 1em 1em;
+ float:right;
+ font-size:95%;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0;
+ padding:0;
+ margin-left:1em;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ font-size:90%;
+ border:1px dotted #888;
+ padding:0ex 1ex 1ex 1ex;
+ background:#f7f6f2;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ text-align:right;
+ font-weight:bold;
+}
+label.simple {
+ display:block;
+ text-align:left;
+ font-weight:normal;
+}
+label.block input.edit {
+ width:50%;
+}
+/*fieldset {
+ width:300px;
+ text-align:center;
+ padding:0.5em;
+ margin:auto;
+}
+*/
+div.editor {
+ margin:0 0 0 0;
+}
+table {
+ margin:0.5em 0;
+ border-collapse:collapse;
+}
+td {
+ padding:0.25em;
+ border:1pt solid #ADB9CC;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ padding:0 2em;
+ margin:0 0 1em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ clear:both;
+ padding:5px 10px;
+ background-color:#eee;
+ border-bottom:2px solid #ccc;
+}
+#message p {
+ margin:5px 0;
+ padding:0;
+ font-weight:bold;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:bold;
+}
+.boxtext {
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ color:#000;
+ float:none;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ width:32px;
+ height:32px;
+ float:left;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ cursor:pointer
+}
+.dlsize {
+ float:left;
+ padding-right: 8px;
+}
+.dlspeed {
+ float:left;
+ padding-right: 8px;
+}
+.package {
+ margin-bottom: 10px;
+}
+.packagename {
+ font-weight: bold;
+}
+
+.child {
+ margin-left: 20px;
+}
+.child_status {
+ margin-right: 10px;
+}
+.child_secrow {
+ font-size: 10px;
+}
+
+.header, .header th {
+ text-align: left;
+ font-weight: normal;
+ background-color:#ececec;
+ -moz-border-radius:5px;
+ border-radius:5px;
+}
+.progress_bar {
+ background: #0C0;
+ height: 5px;
+
+}
+
+.queue {
+ border: none
+}
+
+.queue tr td {
+ border: none
+}
+
+.header, .header th{
+ text-align: left;
+ font-weight: normal;
+}
+
+
+.clearer
+{
+ clear: both;
+ height: 1px;
+}
+
+.left
+{
+ float: left;
+}
+
+.right
+{
+ float: right;
+}
+
+
+.setfield
+{
+ display: table-cell;
+}
+
+ul.tabs li a
+{
+ padding: 5px 16px 4px 15px;
+ border: none;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+
+#tabs span
+{
+ display: none;
+}
+
+#tabs span.selected
+{
+ display: inline;
+}
+
+#tabsback
+{
+ background-color: #525252;
+ margin: 2px 0 0;
+ padding: 6px 4px 1px 4px;
+
+ border-top-right-radius: 30px;
+ border-top-left-radius: 3px;
+ -moz-border-radius-topright: 30px;
+ -moz-border-radius-topleft: 3px;
+}
+ul.tabs
+{
+ list-style-type: none;
+ margin:0;
+ padding: 0 40px 0 0;
+}
+
+ul.tabs li
+{
+ display: inline;
+ margin-left: 8px;
+}
+
+
+ul.tabs li a
+{
+ color: #42454a;
+ background-color: #eaeaea;
+ border: 1px none #c9c3ba;
+ margin: 0;
+ text-decoration: none;
+
+ outline: 0;
+
+ padding: 5px 16px 4px 15px;
+ font-weight: bold;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+
+}
+
+ul.tabs li a.selected, ul.tabs li a:hover
+{
+ color: #000;
+ background-color: white;
+
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-border-radius-bottomright: 0;
+ -moz-border-radius-bottomleft: 0;
+}
+
+ul.tabs li a:hover
+{
+ background-color: #f1f4ee;
+}
+
+ul.tabs li a.selected
+{
+ font-weight: bold;
+ background-color: #525252;
+ padding-bottom: 5px;
+ color: white;
+}
+
+
+#tabs-body {
+ position: relative;
+ overflow: hidden;
+}
+
+
+span.tabContent
+{
+ border: 2px solid #525252;
+ margin: 0;
+ padding: 0;
+ padding-bottom: 10px;
+}
+
+#tabs-body > span {
+ display: none;
+}
+
+#tabs-body > span.active {
+ display: block;
+}
+
+.hide
+{
+ display: none;
+}
+
+.settable
+{
+ margin: 20px;
+ border: none;
+}
+.settable td
+{
+ border: none;
+ margin: 0;
+ padding: 5px;
+}
+
+.settable th{
+ padding-bottom: 8px;
+}
+
+.settable.wide td , .settable.wide th {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+
+
+/*settings navbar*/
+ul.nav {
+ margin: -30px 0 0;
+ padding: 0;
+ list-style: none;
+ position: absolute;
+}
+
+
+ul.nav li {
+ position: relative;
+ float: left;
+ padding: 5px;
+}
+
+ul.nav > li a {
+ background: white;
+ -moz-border-radius: 4px 4px 4px 4px;
+ border: 1px solid #C9C3BA;
+ border-bottom: medium none;
+ color: black;
+}
+
+ul.nav ul {
+ position: absolute;
+ top: 26px;
+ left: 10px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ border: 1px solid #AAA;
+ background: #f1f1f1;
+ -webkit-box-shadow: 1px 1px 5px #AAA;
+ -moz-box-shadow: 1px 1px 5px #AAA;
+ box-shadow: 1px 1px 5px #AAA;
+ cursor: pointer;
+}
+
+ul.nav .open {
+ display: block;
+}
+
+ul.nav .close {
+ display: none;
+}
+
+ul.nav ul li {
+ float: none;
+ padding: 0;
+}
+
+ul.nav ul li a {
+ width: 130px;
+ background: #f1f1f1;
+ padding: 3px;
+ display: block;
+ font-weight: normal;
+}
+
+ul.nav ul li a:hover {
+ background: #CDCDCD;
+}
+
+ul.nav ul ul {
+ left: 137px;
+ top: 0;
+}
+
+.purr-wrapper{
+ margin:10px;
+}
+
+/*Purr alert styles*/
+
+.purr-alert{
+ margin-bottom:10px;
+ padding:10px;
+ background:#000;
+ font-size:13px;
+ font-weight:bold;
+ color:#FFF;
+ -moz-border-radius:5px;
+ -webkit-border-radius:5px;
+ /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/
+ width:300px;
+}
+.purr-alert.error{
+ color:#F55;
+ padding-left:30px;
+ background:url(../img/error.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.success{
+ color:#5F5;
+ padding-left:30px;
+ background:url(../img/success.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+.purr-alert.notice{
+ color:#99F;
+ padding-left:30px;
+ background:url(../img/notice.png) no-repeat #000 7px 10px;
+ width:280px;
+}
+
+table.system {
+ border: none;
+ margin-left: 10px;
+}
+
+table.system td {
+ border: none
+}
+
+table.system tr > td:first-child {
+ font-weight: bold;
+ padding-right: 10px;
+}
diff --git a/pyload/webui/themes/default/css/log.css b/pyload/webui/themes/default/css/log.css
new file mode 100644
index 000000000..26449b244
--- /dev/null
+++ b/pyload/webui/themes/default/css/log.css
@@ -0,0 +1,71 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #EEE;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+}
diff --git a/pyload/webui/themes/default/css/pathchooser.css b/pyload/webui/themes/default/css/pathchooser.css
new file mode 100644
index 000000000..894cc335e
--- /dev/null
+++ b/pyload/webui/themes/default/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #F0F0F0;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/module/web/media/default/css/window.css b/pyload/webui/themes/default/css/window.css
index 12829868b..12829868b 100644
--- a/module/web/media/default/css/window.css
+++ b/pyload/webui/themes/default/css/window.css
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-close.png b/pyload/webui/themes/default/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-error.png b/pyload/webui/themes/default/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-question.png b/pyload/webui/themes/default/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/MooDialog/dialog-warning.png b/pyload/webui/themes/default/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/default/img/MooDialog/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/add_folder.png b/pyload/webui/themes/default/img/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/default/img/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/ajax-loader.gif b/pyload/webui/themes/default/img/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/default/img/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/default/img/arrow_refresh.png b/pyload/webui/themes/default/img/arrow_refresh.png
new file mode 100644
index 000000000..0de26566d
--- /dev/null
+++ b/pyload/webui/themes/default/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/arrow_right.png b/pyload/webui/themes/default/img/arrow_right.png
new file mode 100644
index 000000000..b1a181923
--- /dev/null
+++ b/pyload/webui/themes/default/img/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/big_button.gif b/pyload/webui/themes/default/img/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/default/img/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/default/img/big_button_over.gif b/pyload/webui/themes/default/img/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/default/img/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/default/img/body.png b/pyload/webui/themes/default/img/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/default/img/body.png
Binary files differ
diff --git a/module/web/media/default/img/button.png b/pyload/webui/themes/default/img/button.png
index 890160614..890160614 100644
--- a/module/web/media/default/img/button.png
+++ b/pyload/webui/themes/default/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/closebtn.gif b/pyload/webui/themes/default/img/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/default/img/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/default/img/cog.png b/pyload/webui/themes/default/img/cog.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/default/img/cog.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_add.png b/pyload/webui/themes/default/img/control_add.png
new file mode 100644
index 000000000..d39886893
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_add_blue.png b/pyload/webui/themes/default/img/control_add_blue.png
new file mode 100644
index 000000000..d11b7f41d
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_cancel.png b/pyload/webui/themes/default/img/control_cancel.png
new file mode 100644
index 000000000..7b9bc3fba
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_cancel_blue.png b/pyload/webui/themes/default/img/control_cancel_blue.png
new file mode 100644
index 000000000..0c5c96ce3
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_pause.png b/pyload/webui/themes/default/img/control_pause.png
new file mode 100644
index 000000000..2d9ce9c4e
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_pause_blue.png b/pyload/webui/themes/default/img/control_pause_blue.png
new file mode 100644
index 000000000..ec61099b0
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_play.png b/pyload/webui/themes/default/img/control_play.png
new file mode 100644
index 000000000..0846555d0
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_play_blue.png b/pyload/webui/themes/default/img/control_play_blue.png
new file mode 100644
index 000000000..f8c8ec683
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_stop.png b/pyload/webui/themes/default/img/control_stop.png
new file mode 100644
index 000000000..893bb60e5
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/control_stop_blue.png b/pyload/webui/themes/default/img/control_stop_blue.png
new file mode 100644
index 000000000..e6f75d232
--- /dev/null
+++ b/pyload/webui/themes/default/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/delete.png b/pyload/webui/themes/default/img/delete.png
new file mode 100644
index 000000000..08f249365
--- /dev/null
+++ b/pyload/webui/themes/default/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/drag_corner.gif b/pyload/webui/themes/default/img/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/default/img/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/default/img/error.png b/pyload/webui/themes/default/img/error.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/default/img/error.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/folder.png b/pyload/webui/themes/default/img/folder.png
new file mode 100644
index 000000000..784e8fa48
--- /dev/null
+++ b/pyload/webui/themes/default/img/folder.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/full.png b/pyload/webui/themes/default/img/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/default/img/full.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-login.png b/pyload/webui/themes/default/img/head-login.png
new file mode 100644
index 000000000..b59b7cbbf
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-collector.png b/pyload/webui/themes/default/img/head-menu-collector.png
new file mode 100644
index 000000000..861be40bc
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-config.png b/pyload/webui/themes/default/img/head-menu-config.png
new file mode 100644
index 000000000..bbf43d4f3
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-development.png b/pyload/webui/themes/default/img/head-menu-development.png
new file mode 100644
index 000000000..fad150fe1
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-download.png b/pyload/webui/themes/default/img/head-menu-download.png
new file mode 100644
index 000000000..98c5da9db
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-home.png b/pyload/webui/themes/default/img/head-menu-home.png
new file mode 100644
index 000000000..9d62109aa
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-index.png b/pyload/webui/themes/default/img/head-menu-index.png
new file mode 100644
index 000000000..44d631064
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-news.png b/pyload/webui/themes/default/img/head-menu-news.png
new file mode 100644
index 000000000..43950ebc9
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-queue.png b/pyload/webui/themes/default/img/head-menu-queue.png
new file mode 100644
index 000000000..be98793ce
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-recent.png b/pyload/webui/themes/default/img/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-menu-wiki.png b/pyload/webui/themes/default/img/head-menu-wiki.png
new file mode 100644
index 000000000..07cf0102d
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head-search-noshadow.png b/pyload/webui/themes/default/img/head-search-noshadow.png
new file mode 100644
index 000000000..aafdae015
--- /dev/null
+++ b/pyload/webui/themes/default/img/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/head_bg1.png b/pyload/webui/themes/default/img/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/default/img/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/images.png b/pyload/webui/themes/default/img/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/default/img/images.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/notice.png b/pyload/webui/themes/default/img/notice.png
new file mode 100644
index 000000000..12cd1aef9
--- /dev/null
+++ b/pyload/webui/themes/default/img/notice.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/package_go.png b/pyload/webui/themes/default/img/package_go.png
new file mode 100644
index 000000000..aace63ad6
--- /dev/null
+++ b/pyload/webui/themes/default/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/page-tools-backlinks.png b/pyload/webui/themes/default/img/page-tools-backlinks.png
new file mode 100644
index 000000000..3eb6a9ce3
--- /dev/null
+++ b/pyload/webui/themes/default/img/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/page-tools-edit.png b/pyload/webui/themes/default/img/page-tools-edit.png
new file mode 100644
index 000000000..188e1c12b
--- /dev/null
+++ b/pyload/webui/themes/default/img/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/page-tools-revisions.png b/pyload/webui/themes/default/img/page-tools-revisions.png
new file mode 100644
index 000000000..5c3b8587f
--- /dev/null
+++ b/pyload/webui/themes/default/img/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/parseUri.png b/pyload/webui/themes/default/img/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/default/img/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/pencil.png b/pyload/webui/themes/default/img/pencil.png
new file mode 100644
index 000000000..0bfecd50e
--- /dev/null
+++ b/pyload/webui/themes/default/img/pencil.png
Binary files differ
diff --git a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png b/pyload/webui/themes/default/img/pyload-logo.png
index 2443cd8b1..2443cd8b1 100644
--- a/module/web/media/default/img/pyload-logo-edited3.5-new-font-small.png
+++ b/pyload/webui/themes/default/img/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/reconnect.png b/pyload/webui/themes/default/img/reconnect.png
new file mode 100644
index 000000000..49b269145
--- /dev/null
+++ b/pyload/webui/themes/default/img/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_None.png b/pyload/webui/themes/default/img/status_None.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_downloading.png b/pyload/webui/themes/default/img/status_downloading.png
new file mode 100644
index 000000000..fb4ebc850
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_failed.png b/pyload/webui/themes/default/img/status_failed.png
new file mode 100644
index 000000000..c37bd062e
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_finished.png b/pyload/webui/themes/default/img/status_finished.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_offline.png b/pyload/webui/themes/default/img/status_offline.png
new file mode 100644
index 000000000..0cfd58596
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_proc.png b/pyload/webui/themes/default/img/status_proc.png
new file mode 100644
index 000000000..67de2c6cc
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_queue.png b/pyload/webui/themes/default/img/status_queue.png
new file mode 100644
index 000000000..293b13f77
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/status_waiting.png b/pyload/webui/themes/default/img/status_waiting.png
new file mode 100644
index 000000000..2842cc338
--- /dev/null
+++ b/pyload/webui/themes/default/img/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/success.png b/pyload/webui/themes/default/img/success.png
new file mode 100644
index 000000000..89c8129a4
--- /dev/null
+++ b/pyload/webui/themes/default/img/success.png
Binary files differ
diff --git a/module/web/media/default/img/tab-background.png b/pyload/webui/themes/default/img/tab-background.png
index 29a5d1991..29a5d1991 100644
--- a/module/web/media/default/img/tab-background.png
+++ b/pyload/webui/themes/default/img/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/tabs-border-bottom.png b/pyload/webui/themes/default/img/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/default/img/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/user-actions-logout.png b/pyload/webui/themes/default/img/user-actions-logout.png
new file mode 100644
index 000000000..0010931e2
--- /dev/null
+++ b/pyload/webui/themes/default/img/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/user-actions-profile.png b/pyload/webui/themes/default/img/user-actions-profile.png
new file mode 100644
index 000000000..46573fff6
--- /dev/null
+++ b/pyload/webui/themes/default/img/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/default/img/user-info.png b/pyload/webui/themes/default/img/user-info.png
new file mode 100644
index 000000000..6e643100f
--- /dev/null
+++ b/pyload/webui/themes/default/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/default/js/render/admin.coffee b/pyload/webui/themes/default/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/default/js/render/admin.min.js b/pyload/webui/themes/default/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/default/js/render/base.coffee b/pyload/webui/themes/default/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/default/js/render/base.min.js b/pyload/webui/themes/default/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/default/js/render/filemanager.js b/pyload/webui/themes/default/js/render/filemanager.js
new file mode 100644
index 000000000..f1ebed93f
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/filemanager.js
@@ -0,0 +1,291 @@
+var load, rename_box, confirm_box;
+
+document.addEvent("domready", function() {
+ load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ load.set("opacity", 0);
+
+ rename_box = new Fx.Tween($('rename_box'));
+ confirm_box = new Fx.Tween($('confirm_box'));
+ $('rename_reset').addEvent('click', function() {
+ hide_rename_box()
+ });
+ $('delete_reset').addEvent('click', function() {
+ hide_confirm_box()
+ });
+
+ /*$('filemanager_actions_list').getChildren("li").each(function(action) {
+ var action_name = action.className;
+ if(functions[action.className] != undefined)
+ {
+ action.addEvent('click', functions[action.className]);
+ }
+ });*/
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+function show_rename_box() {
+ bg_show();
+ $("rename_box").setStyle('display', 'block');
+ rename_box.start('opacity', 1)
+}
+
+function hide_rename_box() {
+ bg_hide();
+ rename_box.start('opacity', 0).chain(function() {
+ $('rename_box').setStyle('display', 'none');
+ });
+}
+
+function show_confirm_box() {
+ bg_show();
+ $("confirm_box").setStyle('display', 'block');
+ confirm_box.start('opacity', 1)
+}
+
+function hide_confirm_box() {
+ bg_hide();
+ confirm_box.start('opacity', 0).chain(function() {
+ $('confirm_box').setStyle('display', 'none');
+ });
+}
+
+var FilemanagerUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.directories = [];
+ this.files = [];
+ this.parseChildren();
+ },
+
+ parseChildren: function() {
+ $("directories-list").getChildren("li.folder").each(function(ele) {
+ var path = ele.getElements("input.path")[0].get("value");
+ var name = ele.getElements("input.name")[0].get("value");
+ this.directories.push(new Item(this, path, name, ele))
+ }.bind(this));
+
+ $("directories-list").getChildren("li.file").each(function(ele) {
+ var path = ele.getElements("input.path")[0].get("value");
+ var name = ele.getElements("input.name")[0].get("value");
+ this.files.push(new Item(this, path, name, ele))
+ }.bind(this));
+ }
+});
+
+var Item = new Class({
+ initialize: function(ui, path, name, ele) {
+ this.ui = ui;
+ this.path = path;
+ this.name = name;
+ this.ele = ele;
+ this.directories = [];
+ this.files = [];
+ this.actions = new Array();
+ this.actions["delete"] = this.del;
+ this.actions["rename"] = this.rename;
+ this.actions["mkdir"] = this.mkdir;
+ this.parseElement();
+
+ var pname = this.ele.getElements("span")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+ },
+
+ parseElement: function() {
+ this.ele.getChildren('span span.buttons img').each(function(img) {
+ img.addEvent('click', this.actions[img.className].bind(this));
+ }, this);
+
+ //click on the directory name must open the directory itself
+ this.ele.getElements('b')[0].addEvent('click', this.toggle.bind(this));
+
+ //iterate over child directories
+ var uls = this.ele.getElements('ul');
+ if(uls.length > 0)
+ {
+ uls[0].getChildren("li.folder").each(function(fld) {
+ var path = fld.getElements("input.path")[0].get("value");
+ var name = fld.getElements("input.name")[0].get("value");
+ this.directories.push(new Item(this, path, name, fld));
+ }.bind(this));
+ uls[0].getChildren("li.file").each(function(fld) {
+ var path = fld.getElements("input.path")[0].get("value");
+ var name = fld.getElements("input.name")[0].get("value");
+ this.files.push(new Item(this, path, name, fld));
+ }.bind(this));
+ }
+ },
+
+ reorderElements: function() {
+ //TODO sort the main ul again (to keep data ordered after renaming something)
+ },
+
+ del: function(event) {
+ $("confirm_form").removeEvents("submit");
+ $("confirm_form").addEvent("submit", this.deleteDirectory.bind(this));
+
+ $$("#confirm_form p").set('html', '{{_(("Are you sure you want to delete the selected item?"))}}');
+
+ show_confirm_box();
+ event.stop();
+ },
+
+ deleteDirectory: function(event) {
+ hide_confirm_box();
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/delete",
+ data: {'path': this.path, 'name': this.name},
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ new Fx.Tween(this.ele).start('opacity', 0);
+ var ul = this.ele.parentNode;
+ this.ele.dispose();
+ //if this was the only child, add a "empty folder" div
+ if(!ul.getChildren('li')[0])
+ {
+ var div = new Element("div", { 'html': '{{ _("Folder is empty") }}' });
+ div.replaces(ul);
+ }
+
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+
+ event.stop();
+ },
+
+ rename: function(event) {
+ $("rename_form").removeEvents("submit");
+ $("rename_form").addEvent("submit", this.renameDirectory.bind(this));
+
+ $("path").set("value", this.path);
+ $("old_name").set("value", this.name);
+ $("new_name").set("value", this.name);
+
+ show_rename_box();
+ event.stop();
+ },
+
+ renameDirectory: function(event) {
+ hide_rename_box();
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/rename",
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ this.name = $("new_name").get("value");
+ this.ele.getElements("b")[0].set('html', $("new_name").get("value"));
+ this.reorderElements();
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send($("rename_form").toQueryString());
+
+ event.stop();
+ },
+
+ mkdir: function(event) {
+ new Request.JSON({
+ method: 'POST',
+ url: "/json/filemanager/mkdir",
+ data: {'path': this.path + "/" + this.name, 'name': '{{_("New folder")}}'},
+ onSuccess: function(data) {
+ if(data.response == "success")
+ {
+ new Request.HTML({
+ method: 'POST',
+ url: "/filemanager/get_dir",
+ data: {'path': data.path, 'name': data.name},
+ onSuccess: function(li) {
+ //add node as first child of ul
+ var ul = this.ele.getChildren('ul')[0];
+ if(!ul)
+ {
+ //remove the "Folder Empty" div
+ this.ele.getChildren('div').dispose();
+
+ //create new ul to contain subfolder
+ ul = new Element("ul");
+ ul.inject(this.ele, 'bottom');
+ }
+ li[0].inject(ul, 'top');
+
+ //add directory as a subdirectory of the current item
+ this.directories.push(new Item(this.ui, data.path, data.name, ul.firstChild));
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ indicateSuccess();
+ } else
+ {
+ //error from json code...
+ indicateFail();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+
+ event.stop();
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('ul');
+ if(child == null)
+ child = this.ele.getElement('div');
+
+ if(child != null)
+ {
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ child.reveal();
+ }
+ }
+ }
+});
diff --git a/pyload/webui/themes/default/js/render/package.js b/pyload/webui/themes/default/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/default/js/render/settings.coffee b/pyload/webui/themes/default/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/default/js/render/settings.min.js b/pyload/webui/themes/default/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/default/js/render/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/default/js/static/MooDialog.js b/pyload/webui/themes/default/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/default/js/static/MooDialog.min.js b/pyload/webui/themes/default/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,onInitialize:function(){this.wrapper.setStyle("display","none")},onBeforeOpen:function(){this.wrapper.setStyle("display","block"),this.fireEvent("show")},onBeforeClose:function(){this.wrapper.setStyle("display","none"),this.fireEvent("hide")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/MooDropMenu.js b/pyload/webui/themes/default/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/default/js/static/MooDropMenu.min.js b/pyload/webui/themes/default/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/mootools-core.js b/pyload/webui/themes/default/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+...
+*/
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios') {
+ UA[1] = 'chrome';
+ }
+
+ var platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.ie){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e) {
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
+ return node.hasAttribute(attribute);
+ } : function(node, attribute) {
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector) {
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll) {
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e) {
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError) {}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown"></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return this.className.clean().contains(className, ' ');
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e) {}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ clearTimeout(this.timer);
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ clearTimeout(this.timer);
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (ready) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
+
diff --git a/pyload/webui/themes/default/js/static/mootools-core.min.js b/pyload/webui/themes/default/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){return i.$family();
+}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if("callee" in i){return"arguments";
+}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
+while(s){if(s===i){return true;}s=s.parent;}if(!t.hasOwnProperty){return false;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;
+}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];}f.prototype.overloadSetter=function(s){var i=this;
+return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);
+}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;return function(u){var v,t;if(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;
+}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;
+};};Array.from=function(i){if(i==null){return[];}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);
+return isFinite(i)?i:null;};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;
+return this;}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
+}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
+};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
+u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
+});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
+}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("String",String,["charAt","charCodeAt","concat","contains","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
+Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
+}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
+}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
+return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
+while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
+}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
+y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
+v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
+});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;
+}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);
+return this.map(function(c){return c[a].apply(c,b);});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];
+}return d;},link:function(c){var a={};for(var e=0,b=this.length;e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;
+},append:function(a){this.push.apply(this,a);return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
+},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
+},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
+for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
+}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
+}var a=this.map(function(c){if(c.length==1){c+=c;}return parseInt(c,16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;
+}if(this.length==4&&this[3]==0&&!d){return"transparent";}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");
+}});String.implement({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
+},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
+});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
+});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
+},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
+return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
+}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
+return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
+}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
+};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
+b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
+},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
+g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
+if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
+},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
+for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
+}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
+}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
+}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
+}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
+}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
+break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
+this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
+}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
+var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
+this.key=b[d];if(i=="keydown"||i=="keyup"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
+}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
+this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
+if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
+while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
+this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
+this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
+},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
+}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
+})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
+}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
+g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
+}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
+};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
+break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
+}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
+return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
+}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
+return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
+for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
+return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
+return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
+this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
+}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
+},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
+}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
+if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
+(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
+var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
+return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
+}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
+o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
+};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
+function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
+if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
+}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
+}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
+}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
+break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
+case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
+};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
+};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
+k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
+};k.setDocument=function(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);
+if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);
+}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;
+}E=U.getElementsByTagName(v);if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;
+}A=U.getElementById(v);if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
+}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
+if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
+for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
+}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
+}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
+}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
+A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
+}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
+return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
+if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
+if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
+this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
+}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
+}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
+if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
+}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
+if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
+return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
+}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
+}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
+if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
+}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
+k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
+}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
+if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
+}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
+}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
+if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=this.getAttribute(o,"class");
+if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
+}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
+if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
+if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
+}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
+if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
+}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
+}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
+if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
+this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
+}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
+break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
+return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
+},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+(c+1));
+},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
+while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
+}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
+}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
+},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");
+},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");
+},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;},type:function(){return this.getAttribute("type");
+},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});
+e.version="1.1.7";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);};e.contains=function(c,n){k.setDocument(c);
+return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);return k.hasAttribute(n,c);
+};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
+return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
+};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
+return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
+var Element=this.Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};
+}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;
+f<c;f++){a=d[f];if(g[a.key]!=null){continue;}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
+}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;}var b={};b[a]=function(){var h=[],e=arguments,j=true;
+for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");}return(j)?new Elements(h):h;};Elements.implement(b);
+});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$constructor":Element,"$family":Function.from("element").hide()};Element.mirror(function(a,b){Element.Prototype[a]=b;
+});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
+}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
+b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
+for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
+}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
+b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
+c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
+for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
+b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
+return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();
+}};Element.Properties.html={set:function(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundSize:"@px",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});
+if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);
+}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);
+};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);
+}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);
+if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);
+}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);}return this;}var c=this.retrieve("events");if(!c){return this;
+}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);},this);delete c[b];
+}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
+}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
+}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};var a=function(b){var c=b.relatedTarget;
+if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}if(!window.addEventListener){Element.NativeEvents.propertychange=2;
+Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";},condition:function(b){return b.type!="propertychange"||b.event.propertyName=="checked";
+}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);
+}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
+var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
+n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
+if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
+}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
+}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
+}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
+});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
+}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(n.condition||w&&w.condition){var l=q,m=n.condition||w.condition;
+q=function(C,B){return l(C,B)&&m.call(C,B,v);};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);
+}}:function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);
+},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);
+}delete p[u];q[m]=p;return j.call(this,r,w,l.capture);}var o,v;if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);
+}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});
+})();(function(){var h=document.createElement("div"),e=document.createElement("div");h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;
+var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);
+}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight};
+},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(a(this)){return this.getWindow().getScroll();
+}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;
+n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}var n=(k(m,"position")=="static")?i:l;
+while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}try{return m.offsetParent;
+}catch(n){}return null;},getOffsets:function(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();
+return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();}var m=this.getPosition(o),n=this.getSize();
+var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
+},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
+},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
+return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
+return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
+}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
+}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
+},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
+},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
+},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
+this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
+this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
+}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
+},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
+return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
+this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
+this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
+d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
+}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
+this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
+},resume:function(){if(this.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};
+var e={},c={};var a=function(){var h=Date.now();for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);
+if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);
+}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;
+}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
+});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
+});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
+}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
+});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
+},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
+b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
+}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
+}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
+this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
+}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
+b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
+},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
+for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
+for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
+return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
+return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
+a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
+var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
+}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
+},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
+return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
+},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
+});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
+this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
+this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
+}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
+}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
+}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
+},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
+},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
+return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
+}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
+}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
+o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
+break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
+j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
+}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
+}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
+}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
+}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}else{if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
+}}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
+if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
+if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
+return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
+this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
+Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
+c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
+c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
+}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
+}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
+},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
+}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
+}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
+};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
+return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
+}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
+Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
+case"null":return"null";}return null;};JSON.secure=true;JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure==null){secure=JSON.secure;
+}if(secure){if(JSON.parse){return JSON.parse(string);}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");
+}}return eval("("+string+")");};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
+},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
+}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
+this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
+}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
+}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
+return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
+Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
+};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
+k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
+if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
+c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
+}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
+}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/mootools-more.js b/pyload/webui/themes/default/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = event.page;
+
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {};
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/default/js/static/mootools-more.min.js b/pyload/webui/themes/default/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+MooTools.More={version:"1.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
+}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
+if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
+if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
+c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
+return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
+});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
+};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
+g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
+}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
+},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
+};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
+},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
+if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
+},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
+e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
+a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
+},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
+},setOffsetOption:function(f,d){var c={x:0,y:0};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;
+});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});
+},getPosition:function(e,d){var c={};d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);
+}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();
+a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);
+}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
+switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
+break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
+["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
+},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
+},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
+c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
+},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
+break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
+c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
+}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
+return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);(function(){var a=false;
+this.IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.y}).position({relativeTo:this.element,offset:this.options.offset});
+return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
+}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
+}});})();window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
+this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
+if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||(this.target==document.body?"inside":"after");
+b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
+this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
+this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
+},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
+var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
+return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
+return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
+}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
+this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
+},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
+}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
+this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
+},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
+this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
+this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
+}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","true");this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
+}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
+b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","false");this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
+}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
+if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
+if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
+},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
+},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
+}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
+}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
+if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
+}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
+return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
+}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
+}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
+while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
+if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
+if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
+}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
+}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
+},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
+e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
+}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
+["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
+while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
+})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
+if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
+}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
+});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
+},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
+}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
+var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
+},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
+if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
+b.addEvent("onFormValidate",this.onFormValidate);b.validate(a);return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
+if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
+this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
+if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
+this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
+}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
+},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
+},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
+},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
+},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
+if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
+if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
+};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:"opacity" in document.documentElement,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
+},opacity:1,hideInputs:!("opacity" in document.documentElement)?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
+this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
+var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
+this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
+}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
+}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
+this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
+}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
+Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
+this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
+}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
+}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
+}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
+if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
+return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
+Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
+return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
+}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
+b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
+}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
+this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection="selectstart" in document?"selectstart":"mousedown";if("ondragstart" in document&&!("FileReader" in window)&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
+Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
+this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
+return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
+}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
+}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
+}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
+if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
+}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
+}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
+this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
+}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
+}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
+}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
+}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
+if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
+a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
+this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
+b=this.element;this.droppables=$$(this.options.droppables);this.setContainer(this.options.container);if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");
+if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);
+this.overed=null;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
+});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
+["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
+g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
+if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
+n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};
+},getDroppableCoordinates:function(c){var b=c.getCoordinates();if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;
+b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
+var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
+}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
+}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
+this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{},unDraggableTags:["button","input","a","textarea","select","option"]},initialize:function(a,b){this.setOptions(b);
+this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
+}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
+var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
+},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
+},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
+return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
+},this));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);
+}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
+});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
+}});}return c.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
+if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
+this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
+},start:function(b,a){if(!this.idle||b.rightClick||(!this.options.handle&&this.options.unDraggableTags.contains(b.target.get("tag")))){return;}this.idle=false;
+this.element=a;this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
+this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
+}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
+},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.reset();}},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
+}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
+}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
+}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
+this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
+}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
+}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
+break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
+var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
+}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
+},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
+}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
+};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
+if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
+},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
+var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
+case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
+return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/purr.js b/pyload/webui/themes/default/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/purr.min.js b/pyload/webui/themes/default/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/purr.min.js
@@ -0,0 +1 @@
+var Purr=new Class({options:{mode:"top",position:"left",elementAlertClass:"purr-element-alert",elements:{wrapper:"div",alert:"div",buttonWrapper:"div",button:"button"},elementOptions:{wrapper:{styles:{position:"fixed","z-index":"9999"},"class":"purr-wrapper"},alert:{"class":"purr-alert",styles:{opacity:".85"}},buttonWrapper:{"class":"purr-button-wrapper"},button:{"class":"purr-button"}},alert:{buttons:[],clickDismiss:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/tinytab.js b/pyload/webui/themes/default/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/default/js/static/tinytab.min.js b/pyload/webui/themes/default/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/default/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/default/tml/admin.html b/pyload/webui/themes/default/tml/admin.html
new file mode 100644
index 000000000..ba94f7a74
--- /dev/null
+++ b/pyload/webui/themes/default/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/default/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/default/js/render/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/base.html b/pyload/webui/themes/default/tml/base.html
new file mode 100644
index 000000000..e2a62a116
--- /dev/null
+++ b/pyload/webui/themes/default/tml/base.html
@@ -0,0 +1,180 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/default/css/default.min.css"/>
+<link rel="stylesheet" type="text/css" href="/default/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/default/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/default/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/default/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/default/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/default/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/default/js/render/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("New pyLoad version %s available!") % update}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/default/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/default/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/default/img/pyload-logo.png" alt="pyLoad" /></a>
+
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+{# <li {{ selected('filemanager') }}>#}
+{# <a href="/filemanager/" title=""><img src="/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
+{# </li>#}
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/default/img/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div style="display: none;">
+ {% include '/default/tml/window.html' %}
+ {% include '/default/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/default/tml/captcha.html b/pyload/webui/themes/default/tml/captcha.html
new file mode 100644
index 000000000..56892593f
--- /dev/null
+++ b/pyload/webui/themes/default/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
diff --git a/pyload/webui/themes/default/tml/downloads.html b/pyload/webui/themes/default/tml/downloads.html
new file mode 100644
index 000000000..ba0f77c18
--- /dev/null
+++ b/pyload/webui/themes/default/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/filemanager.html b/pyload/webui/themes/default/tml/filemanager.html
new file mode 100644
index 000000000..7a370d04c
--- /dev/null
+++ b/pyload/webui/themes/default/tml/filemanager.html
@@ -0,0 +1,78 @@
+{% extends '/default/tml/base.html' %}
+
+{% block head %}
+
+<script type="text/javascript" src="/default/js/render/filemanager.min.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var fmUI = new FilemanagerUI("url",1);
+});
+</script>
+{% endblock %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+
+{% block subtitle %}
+{{_("FileManager")}}
+{% endblock %}
+
+{% macro display_file(file) %}
+ <li class="file">
+ <input type="hidden" name="path" class="path" value="{{ file.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ file.name }}" />
+ <span>
+ <b>{{ file.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ </span>
+ </span>
+ </li>
+{%- endmacro %}
+
+{% macro display_folder(fld, open = false) -%}
+ <li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ fld.path }}" />
+ <input type="hidden" name="name" class="name" value="{{ fld.name }}" />
+ <span>
+ <b>{{ fld.name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/add_folder.png" />
+ </span>
+ </span>
+ {% if (fld.folders|length + fld.files|length) > 0 %}
+ {% if open %}
+ <ul>
+ {% else %}
+ <ul style="display:none">
+ {% endif %}
+ {% for child in fld.folders %}
+ {{ display_folder(child) }}
+ {% endfor %}
+ {% for child in fld.files %}
+ {{ display_file(child) }}
+ {% endfor %}
+ </ul>
+ {% else %}
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+ {% endif %}
+ </li>
+{%- endmacro %}
+
+{% block content %}
+
+<div style="clear:both"><!-- --></div>
+
+<ul id="directories-list">
+{{ display_folder(root, true) }}
+</ul>
+
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/folder.html b/pyload/webui/themes/default/tml/folder.html
new file mode 100644
index 000000000..227a46ba0
--- /dev/null
+++ b/pyload/webui/themes/default/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/default/img/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li>
diff --git a/pyload/webui/themes/default/tml/home.html b/pyload/webui/themes/default/tml/home.html
new file mode 100644
index 000000000..0fff703b5
--- /dev/null
+++ b/pyload/webui/themes/default/tml/home.html
@@ -0,0 +1,266 @@
+{% extends '/default/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/default/img/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/default/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/default/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/default/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/default/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+{#<li>#}
+{# <a href="/filemanager/" title=""><img src="/default/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a>#}
+{#</li>#}
+<li class="right">
+ <a href="/logs/" title=""><img src="/default/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/default/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/default/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/info.html b/pyload/webui/themes/default/tml/info.html
new file mode 100644
index 000000000..2deaa6dce
--- /dev/null
+++ b/pyload/webui/themes/default/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/default/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript">
+ window.addEvent("domready", function() {
+ var ul = new Element('ul#twitter_update_list');
+ var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]');
+ var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
+ $("twitter").adopt(ul, script1, script2);
+ });
+ </script>
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+ <div id="twitter"></div>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Webinterface Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/login.html b/pyload/webui/themes/default/tml/login.html
new file mode 100644
index 000000000..089275219
--- /dev/null
+++ b/pyload/webui/themes/default/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span>{{_("Username")}}</span>
+ <input type="text" size="20" name="username" />
+ </label>
+ <br />
+ <label>
+ <span>{{_("Password")}}</span>
+ <input type="password" size="20" name="password" />
+ </label>
+ <br />
+ <input type="submit" value="Login" class="button" />
+ </fieldset>
+ </div>
+</form>
+
+{% if errors %}
+<p>{{_("Your username and password didn't match. Please try again.")}}</p>
+ {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/logout.html b/pyload/webui/themes/default/tml/logout.html
new file mode 100644
index 000000000..196676de5
--- /dev/null
+++ b/pyload/webui/themes/default/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/default/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/logs.html b/pyload/webui/themes/default/tml/logs.html
new file mode 100644
index 000000000..1706be8a6
--- /dev/null
+++ b/pyload/webui/themes/default/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/default/css/log.min.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/pathchooser.html b/pyload/webui/themes/default/tml/pathchooser.html
new file mode 100644
index 000000000..8ce9ab072
--- /dev/null
+++ b/pyload/webui/themes/default/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/default/css/pathchooser.min.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html>
diff --git a/pyload/webui/themes/default/tml/queue.html b/pyload/webui/themes/default/tml/queue.html
new file mode 100644
index 000000000..035ee1808
--- /dev/null
+++ b/pyload/webui/themes/default/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/default/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/default/js/render/package.min.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/default/img/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/default/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/default/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/default/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/default/img/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
+ <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/settings.html b/pyload/webui/themes/default/tml/settings.html
new file mode 100644
index 000000000..fddc6e35c
--- /dev/null
+++ b/pyload/webui/themes/default/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/default/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/default/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/default/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/default/js/render/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:#424242;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.options['time']}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.options['limitdl']}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %}
diff --git a/pyload/webui/themes/default/tml/settings_item.html b/pyload/webui/themes/default/tml/settings_item.html
new file mode 100644
index 000000000..6642d34b4
--- /dev/null
+++ b/pyload/webui/themes/default/tml/settings_item.html
@@ -0,0 +1,48 @@
+<table class="settable">
+ {% if section.outline %}
+ <tr><th colspan="2">{{ section.outline }}</th></tr>
+ {% endif %}
+ {% for okey, option in section.iteritems() %}
+ {% if okey not in ("desc", "outline") %}
+ <tr>
+ <td><label for="{{skey}}|{{okey}}"
+ style="color:#424242;">{{ option.desc }}:</label></td>
+ <td>
+ {% if option.type == "bool" %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ <option {% if option.value %} selected="selected"
+ {% endif %}value="True">{{ _("on") }}</option>
+ <option {% if not option.value %} selected="selected"
+ {% endif %}value="False">{{ _("off") }}</option>
+ </select>
+ {% elif ";" in option.type %}
+ <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}">
+ {% for entry in option.list %}
+ <option {% if option.value == entry %}
+ selected="selected" {% endif %}>{{ entry }}</option>
+ {% endfor %}
+ </select>
+ {% elif option.type == "folder" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "file" %}
+ <input name="{{skey}}|{{okey}}" type="text"
+ id="{{skey}}|{{okey}}" value="{{option.value}}"/>
+ <input name="browsebutton" type="button"
+ onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;"
+ value="{{_("Browse")}}"/>
+ {% elif option.type == "password" %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="password" value="{{option.value}}"/>
+ {% else %}
+ <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"
+ type="text" value="{{option.value}}"/>
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+</table>
diff --git a/pyload/webui/themes/default/tml/window.html b/pyload/webui/themes/default/tml/window.html
new file mode 100644
index 000000000..e73eba2bd
--- /dev/null
+++ b/pyload/webui/themes/default/tml/window.html
@@ -0,0 +1,46 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/default/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+ {{_("Queue")}}
+ <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
+ {{_("Collector")}}
+ <input type="radio" name="add_dest" id="add_dest2" value="0"/>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
diff --git a/pyload/webui/themes/flat/css/MooDialog.css b/pyload/webui/themes/flat/css/MooDialog.css
new file mode 100644
index 000000000..3ba94cafd
--- /dev/null
+++ b/pyload/webui/themes/flat/css/MooDialog.css
@@ -0,0 +1,84 @@
+/* Created by Arian Stolwijk <http://www.aryweb.nl> */
+
+.MooDialog {
+/* position: fixed;*/
+ margin: 0 auto 0 -350px;
+ width:600px;
+ padding:14px;
+ left:50%;
+ top: 100px;
+
+ position: absolute;
+ left: 50%;
+ z-index: 50000;
+
+ background: #CDCDCD;
+ color: black;
+}
+
+.MooDialogTitle {
+ padding-top: 30px;
+}
+
+.MooDialog .title {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ padding: 3px 20px;
+ background: #b7c4dc;
+ border-bottom: 1px solid #a1aec5;
+ font-weight: bold;
+ text-shadow: 1px 1px 0 #fff;
+ color: black;
+ border-radius: 7px;
+ -moz-border-radius: 7px;
+ -webkit-border-radius: 7px;
+}
+
+.MooDialog .close {
+ background: url(../img/control_cancel.png) no-repeat;
+ width: 16px;
+ height: 16px;
+ display: block;
+ cursor: pointer;
+ top: -5px;
+ left: -5px;
+ position: absolute;
+}
+
+.MooDialog .buttons {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ background: none;
+}
+
+.MooDialog .iframe {
+ width: 100%;
+ height: 100%;
+}
+
+.MooDialog .textInput {
+ width: 200px;
+ float: left;
+}
+
+.MooDialog .MooDialogAlert,
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPrompt,
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-warning.png) no-repeat;
+ padding-left: 40px;
+ min-height: 40px;
+}
+
+.MooDialog .MooDialogConfirm,
+.MooDialog .MooDialogPromt {
+ background: url(../img/MooDialog/dialog-question.png) no-repeat;
+}
+
+.MooDialog .MooDialogError {
+ background: url(../img/MooDialog/dialog-error.png) no-repeat;
+}
diff --git a/pyload/webui/themes/flat/css/flat.css b/pyload/webui/themes/flat/css/flat.css
new file mode 100644
index 000000000..1a542962f
--- /dev/null
+++ b/pyload/webui/themes/flat/css/flat.css
@@ -0,0 +1,863 @@
+.hidden {
+ display:none;
+}
+.leftalign {
+ text-align:left;
+}
+.centeralign {
+ text-align:center;
+}
+.rightalign {
+ text-align:right;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited {
+ background-color:#000080;
+ border:none !important;
+ color:#FFFFFF !important;
+ margin:0.1em 0.2em;
+ padding:0 0.2em;
+ text-decoration:none;
+}
+.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited {
+ background-color:#808080;
+ border:none !important;
+ color:#FFFFFF !important;
+ margin:0.1em 0.2em;
+ padding:0 0.2em;
+ text-decoration:none;
+}
+.dokuwiki div.plugin_translation ul li a:hover img {
+ height:15px;
+ opacity:1;
+}
+body {
+ background-color:white;
+ color:black;
+ font-family:'Open Sans', sans-serif;
+ font-size:12px;
+ font-style:normal;
+ font-variant:normal;
+ font-weight:300;
+ line-height:normal;
+ margin:0;
+ padding:0;
+}
+hr {
+ border-bottom-color:#AAAAAA;
+ border-bottom-style:dotted;
+}
+img {
+ border:none;
+}
+form {
+ background-color:transparent;
+ border:none;
+ display:inline;
+ margin:0;
+ padding:0;
+}
+ul li {
+ margin:5px;
+}
+textarea {
+ font-family:monospace;
+}
+table {
+ border-collapse:collapse;
+ margin:0.5em 0;
+}
+td {
+ border:1pt solid #ADB9CC;
+ padding:0.25em;
+}
+a {
+ color:#3465A4;
+ text-decoration:none;
+}
+a:hover {
+ text-decoration:underline;
+}
+option {
+ border:0 none #FFFFFF;
+}
+strong.highlight {
+ background-color:#FFCC99;
+ padding:1pt;
+}
+#pagebottom {
+ clear:both;
+}
+hr {
+ background-color:#C0C0C0;
+ border:none;
+ color:#C0C0C0;
+ margin:0.2em 0;
+}
+.invisible {
+ border:0;
+ height:0;
+ margin:0;
+ padding:0;
+ visibility:hidden;
+}
+.left {
+ float:left !important;
+}
+.right {
+ float:right !important;
+}
+.center {
+ text-align:center;
+}
+div#body-wrapper {
+ font-size:127%;
+ padding:40px 40px 10px;
+}
+div#content {
+ color:black;
+ font-size:14px;
+ line-height:1.5em;
+ margin-top:-20px;
+ padding:0;
+}
+h1, h2, h3, h4, h5, h6 {
+ background-attachment:scroll;
+ background-color:transparent;
+ background-image:none;
+ background-position:0 0;
+ background-repeat:repeat repeat;
+ color:black;
+ font-family:'Open Sans', sans-serif;
+ font-weight:normal;
+ margin:0;
+ padding:0.5em 0 0.17em;
+}
+h1 {
+ font-family:'Open Sans', sans-serif;
+ font-weight:300;
+ line-height:1.2em;
+ margin-bottom:0.1em;
+ margin-left:-25px;
+ padding-bottom:0;
+ margin-left:-25px;
+}
+h2 {
+ font-size:150%;
+}
+h3, h4, h5, h6 {
+ border-bottom-style:none;
+ font-weight:bold;
+}
+h3 {
+ font-size:132%;
+}
+h4 {
+ font-size:116%;
+}
+h5 {
+ font-size:100%;
+}
+h6 {
+ font-size:80%;
+}
+ul#page-actions, ul#page-actions-more {
+ background-color:#ECECEC;
+ color:black;
+ float:right;
+ list-style-type:none;
+ margin:10px 10px 0;
+ padding:6px;
+ white-space:nowrap;
+}
+ul#user-actions {
+ background-color:#ECECEC;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:5px;
+}
+ul#page-actions li, ul#user-actions li, ul#page-actions-more li {
+ display:inline;
+}
+ul#page-actions a, ul#user-actions a, ul#page-actions-more a {
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+ text-decoration:none;
+}
+ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus {
+}
+ul#page-actions2 {
+ background-color:#ECECEC;
+ color:black;
+ float:left;
+ list-style-type:none;
+ margin:10px 10px 0;
+ padding:6px;
+}
+ul#user-actions2 {
+ background-color:#ECECEC;
+ border-bottom-left-radius:3px;
+ border-bottom-right-radius:3px;
+ border-top-left-radius:3px;
+ border-top-right-radius:3px;
+ color:black;
+ display:inline;
+ list-style-type:none;
+ margin:0;
+ padding:5px;
+}
+ul#page-actions2 li, ul#user-actions2 li {
+ display:inline;
+}
+ul#page-actions2 a, ul#user-actions2 a {
+ color:black;
+ display:inline;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+ text-decoration:none;
+}
+ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, ul#page-actions-more a:hover, ul#page-actions-more a:focus {
+ color:#4E7BB4;
+}
+.hidden {
+ display:none;
+}
+a.logout {
+ background-color:transparent;
+ background-image:url(../img/user-actions-logout.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.info {
+ background-color:transparent;
+ background-image:url(../img/user-info.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.admin {
+ background-color:transparent;
+ background-image:url(../img/user-actions-admin.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.profile {
+ background-color:transparent;
+ background-image:url(../img/user-actions-profile.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.create, a.edit {
+ background-color:transparent;
+ background-image:url(../img/page-tools-edit.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.source, a.show {
+ background-color:transparent;
+ background-image:url(../img/page-tools-source.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.revisions {
+ background-color:transparent;
+ background-image:url(../img/page-tools-revisions.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.subscribe, a.unsubscribe {
+ background-color:transparent;
+ background-image:url(../img/page-tools-subscribe.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.backlink {
+ background-color:transparent;
+ background-image:url(../img/page-tools-backlinks.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.play {
+ background-color:transparent;
+ background-image:url(../img/control_play.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+.time {
+ background-color:transparent;
+ background-image:url(../img/status_None.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+}
+.reconnect {
+ background-color:transparent;
+ background-image:url(../img/reconnect.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ margin:0 3px;
+ padding:2px 0 2px 18px;
+}
+a.play:hover {
+ background-color:transparent;
+ background-image:url(../img/control_play_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cancel {
+ background-color:transparent;
+ background-image:url(../img/control_cancel.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cancel:hover {
+ background-color:transparent;
+ background-image:url(../img/control_cancel_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.pause {
+ background-color:transparent;
+ background-image:url(../img/control_pause.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.pause:hover {
+ background-color:transparent;
+ background-image:url(../img/control_pause_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+ font-weight:bold;
+}
+a.stop {
+ background-color:transparent;
+ background-image:url(../img/control_stop.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.stop:hover {
+ background-color:transparent;
+ background-image:url(../img/control_stop_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.add {
+ background-color:transparent;
+ background-image:url(../img/control_add.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.add:hover {
+ background-color:transparent;
+ background-image:url(../img/control_add_blue.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+a.cog {
+ background-color:transparent;
+ background-image:url(../img/cog.png);
+ background-position:0 1px;
+ background-repeat:no-repeat no-repeat;
+}
+#head-panel {
+ background-color:#DDDDDD;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+}
+#head-panel h1 {
+ color:#EEEEEC;
+ display:none;
+ font-size:2.6em;
+ margin:0;
+ padding-left:3.3em;
+ padding-top:0.8em;
+ text-decoration:none;
+}
+#head-panel #head-logo {
+ float:left;
+ overflow:visible;
+ padding:11px 8px 0;
+}
+#head-menu {
+ float:left;
+ margin:0;
+ padding:1em 0 0;
+ width:100%;
+}
+#head-menu ul {
+ list-style:none;
+ margin:0 1em 0 2em;
+}
+#head-menu ul li {
+ float:left;
+ font-size:14px;
+ margin:0 0 4px 0.3em;
+}
+#head-menu ul li.selected, #head-menu ul li:hover {
+ margin-bottom:0;
+}
+#head-menu ul li a img {
+ height:22px;
+ padding-right:4px;
+ vertical-align:middle;
+ width:22px;
+}
+#head-menu ul li a, #head-menu ul li a:link {
+ background-color:#EAEAEA;
+ background-position:0 100%;
+ background-repeat:repeat no-repeat;
+ border-color:#CCCCCC #CCCCCC transparent;
+ color:#555555;
+ padding:7px 15px 8px;
+ text-decoration:none;
+}
+#head-menu ul li a:hover, #head-menu ul li a:focus {
+ border-bottom-color:transparent;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-bottom-style:none;
+ border-bottom-width:0;
+ color:#111111;
+ outline:none;
+ padding-bottom:7px;
+}
+#head-menu ul li a:focus {
+ margin-bottom:-4px;
+}
+#head-menu ul li.selected a {
+ background-color:#FFFFFF;
+ border-bottom-color:transparent;
+ color:#3566A5;
+ padding:7px 15px 8px;
+}
+#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus {
+ color:#111111;
+}
+div#head-search-and-login {
+ color:white;
+ float:right;
+ margin:0 1em 0 0;
+ padding:7px 7px 5px 5px;
+ white-space:nowrap;
+}
+div#head-search-and-login form {
+ display:inline;
+ padding:0 3px;
+}
+div#head-search-and-login form input {
+ background-color:#EEEEEE;
+ border:2px solid #888888;
+ border-bottom-left-radius:3px;
+ border-bottom-right-radius:3px;
+ border-top-left-radius:3px;
+ border-top-right-radius:3px;
+ font-size:14px;
+ padding:2px;
+}
+div#head-search-and-login form input:focus {
+ background-color:#FFFFFF;
+}
+#head-search {
+ font-size:14px;
+}
+#head-username, #head-password {
+ font-size:14px;
+ width:80px;
+}
+#pageinfo {
+ clear:both;
+ color:#888888;
+ margin:0;
+ padding:0.6em 0;
+}
+#foot {
+ color:#888888;
+ font-style:normal;
+ text-align:center;
+}
+#foot a {
+ color:#AAAAFF;
+}
+#foot img {
+ vertical-align:middle;
+}
+div.toc {
+ background-color:#F0F0F0;
+ border:1px dotted #888888;
+ float:right;
+ font-size:95%;
+ margin:1em 0 1em 1em;
+}
+div.toc .tocheader {
+ font-weight:bold;
+ margin:0.5em 1em;
+}
+div.toc ol {
+ margin:1em 0.5em 1em 1em;
+ padding:0;
+}
+div.toc ol li {
+ margin:0 0 0 1em;
+ padding:0;
+}
+div.toc ol ol {
+ margin:0.5em 0.5em 0.5em 1em;
+ padding:0;
+}
+div.recentchanges table {
+ clear:both;
+}
+div#editor-help {
+ background-color:#F7F6F2;
+ border:1px dotted #888888;
+ font-size:90%;
+ padding:0 1ex 1ex;
+}
+div#preview {
+ margin-top:1em;
+}
+label.block {
+ display:block;
+ font-weight:bold;
+ text-align:right;
+}
+label.simple {
+ display:block;
+ font-weight:normal;
+ text-align:left;
+}
+label.block input.edit {
+ width:50%;
+}
+div.editor {
+ margin:0;
+}
+table {
+ border-collapse:collapse;
+ margin:0.5em 0;
+}
+td {
+ border:1pt solid #ADB9CC;
+ padding:0.25em;
+}
+td p {
+ margin:0;
+ padding:0;
+}
+.u {
+ text-decoration:underline;
+}
+.footnotes ul {
+ margin:0 0 1em;
+ padding:0 2em;
+}
+.footnotes li {
+ list-style:none;
+}
+.userpref table, .userpref td {
+ border:none;
+}
+#message {
+ background-color:#EEEEEE;
+ border-bottom-color:#CCCCCC;
+ border-bottom-style:solid;
+ border-bottom-width:2px;
+ clear:both;
+ padding:5px 10px;
+}
+#message p {
+ font-weight:bold;
+ margin:5px 0;
+ padding:0;
+}
+#message div.buttons {
+ font-weight:normal;
+}
+.diff {
+ width:99%;
+}
+.diff-title {
+ background-color:#C0C0C0;
+}
+.searchresult dd span {
+ font-weight:400;
+}
+.boxtext {
+ color:#000000;
+ float:none;
+ font-family:tahoma, arial, sans-serif;
+ font-size:11px;
+ padding:3px 0 0 10px;
+}
+.statusbutton {
+ cursor:pointer;
+ float:left;
+ height:32px;
+ margin-left:-32px;
+ margin-right:5px;
+ opacity:0;
+ width:32px;
+}
+.dlsize {
+ float:left;
+ padding-right:8px;
+}
+.dlspeed {
+ float:left;
+ padding-right:8px;
+}
+.package {
+ margin-bottom:10px;
+}
+.packagename {
+ font-weight:300;
+}
+.child {
+ margin-left:20px;
+}
+.child_status {
+ margin-right:10px;
+}
+.child_secrow {
+ font-size:10px;
+}
+.header, .header th {
+ background-color:#ECECEC;
+ font-weight:300;
+ text-align:left;
+}
+.progress_bar {
+ background-color:#00CC00;
+ height:5px;
+}
+.queue {
+ border:none;
+}
+.queue tr td {
+ border:none;
+}
+.header, .header th {
+ font-weight:normal;
+ text-align:left;
+}
+.clearer {
+ clear:both;
+ height:1px;
+}
+.left {
+ float:left;
+}
+.right {
+ float:right;
+}
+.setfield {
+ display:table-cell;
+}
+ul.tabs li a {
+ border:none;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ font-weight:bold;
+ padding:5px 16px 4px 15px;
+}
+#tabs span {
+ display:none;
+}
+#tabs span.selected {
+ display:inline;
+}
+#tabsback {
+ background-color:#525252;
+ border-top-left-radius:3px;
+ border-top-right-radius:30px;
+ margin:2px 0 0;
+ padding:6px 4px 1px;
+}
+ul.tabs {
+ list-style-type:none;
+ margin:0;
+ padding:0 40px 0 0;
+}
+ul.tabs li {
+ display:inline;
+ margin-left:8px;
+}
+ul.tabs li a {
+ background-color:#EAEAEA;
+ border:1px none #C9C3BA;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ color:#42454A;
+ font-weight:bold;
+ margin:0;
+ outline:0;
+ padding:5px 16px 4px 15px;
+ text-decoration:none;
+}
+ul.tabs li a.selected, ul.tabs li a:hover {
+ background-color:white;
+ border-bottom-left-radius:0;
+ border-bottom-right-radius:0;
+ color:#000000;
+}
+ul.tabs li a:hover {
+ background-color:#F1F4EE;
+}
+ul.tabs li a.selected {
+ background-color:#525252;
+ color:white;
+ font-weight:bold;
+ padding-bottom:5px;
+}
+#tabs-body {
+ overflow:hidden;
+ position:relative;
+}
+span.tabContent {
+ border:2px solid #525252;
+ margin:0;
+ padding:0 0 10px;
+}
+#tabs-body > span {
+ display:none;
+}
+#tabs-body > span.active {
+ display:block;
+}
+.hide {
+ display:none;
+}
+.settable {
+ border:none;
+ margin:20px;
+}
+.settable td {
+ border:none;
+ margin:0;
+ padding:5px;
+}
+.settable th {
+ padding-bottom:8px;
+}
+.settable.wide td, .settable.wide th {
+ padding-left:15px;
+ padding-right:15px;
+}
+ul.nav {
+ list-style:none;
+ margin:-30px 0 0;
+ padding:0;
+ position:absolute;
+}
+ul.nav li {
+ float:left;
+ padding:5px;
+ position:relative;
+}
+ul.nav > li a {
+ background-color:white;
+ border-left-color:#C9C3BA;
+ border-right-color:#C9C3BA;
+ border-style:solid solid none;
+ border-top-color:#C9C3BA;
+ border-width:1px 1px medium;
+ color:black;
+}
+ul.nav ul {
+ -webkit-box-shadow:#AAAAAA 1px 1px 5px;
+ background-color:#F1F1F1;
+ border:1px solid #AAAAAA;
+ box-shadow:#AAAAAA 1px 1px 5px;
+ cursor:pointer;
+ left:10px;
+ list-style:none;
+ margin:0;
+ padding:0;
+ position:absolute;
+ top:26px;
+}
+ul.nav .open {
+ display:block;
+}
+ul.nav .close {
+ display:none;
+}
+ul.nav ul li {
+ float:none;
+ padding:0;
+}
+ul.nav ul li a {
+ background-color:#F1F1F1;
+ display:block;
+ font-weight:normal;
+ padding:3px;
+ width:130px;
+}
+ul.nav ul li a:hover {
+ background-color:#CDCDCD;
+}
+ul.nav ul ul {
+ left:137px;
+ top:0;
+}
+.purr-wrapper {
+ margin:10px;
+}
+.purr-alert {
+ background-color:#000000;
+ border-bottom-left-radius:5px;
+ border-bottom-right-radius:5px;
+ border-top-left-radius:5px;
+ border-top-right-radius:5px;
+ color:#FFFFFF;
+ font-size:13px;
+ font-weight:bold;
+ margin-bottom:10px;
+ padding:10px;
+ width:300px;
+}
+.purr-alert.error {
+ background-color:#000000;
+ background-image:url(../img/error.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#FF5555;
+ padding-left:30px;
+ width:280px;
+}
+.purr-alert.success {
+ background-color:#000000;
+ background-image:url(../img/success.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#55FF55;
+ padding-left:30px;
+ width:280px;
+}
+.purr-alert.notice {
+ background-color:#000000;
+ background-image:url(../img/notice.png);
+ background-position:7px 10px;
+ background-repeat:no-repeat no-repeat;
+ color:#9999FF;
+ padding-left:30px;
+ width:280px;
+}
+table.system {
+ border:none;
+ margin-left:10px;
+}
+table.system td {
+ border:none;
+}
+table.system tr > td:first-child {
+ font-weight:bold;
+ padding-right:10px;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/css/log.css b/pyload/webui/themes/flat/css/log.css
new file mode 100644
index 000000000..af2ea4fe8
--- /dev/null
+++ b/pyload/webui/themes/flat/css/log.css
@@ -0,0 +1,72 @@
+
+html, body, #content
+{
+ height: 100%;
+}
+#body-wrapper
+{
+ height: 70%;
+}
+.logdiv
+{
+ height: 90%;
+ width: 100%;
+ overflow: auto;
+ border: 2px solid #CCC;
+ outline: 1px solid #666;
+ background-color: #FFE;
+ margin-right: auto;
+ margin-left: auto;
+}
+.logform
+{
+ display: table;
+ margin: 0 auto 0 auto;
+ padding-top: 5px;
+}
+.logtable
+{
+
+ margin: 0px;
+}
+.logtable td
+{
+ border: none;
+ white-space: nowrap;
+
+
+ font-family: monospace;
+ font-size: 16px;
+ margin: 0px;
+ padding: 0px 10px 0px 10px;
+ line-height: 110%;
+}
+td.logline
+{
+ background-color: #EEE;
+ text-align:right;
+ padding: 0px 5px 0px 5px;
+}
+td.loglevel
+{
+ text-align:right;
+}
+.logperpage
+{
+ float: right;
+ padding-bottom: 8px;
+}
+.logpaginator
+{
+ float: left;
+ padding-top: 5px;
+}
+.logpaginator a
+{
+ padding: 0px 8px 0px 8px;
+}
+.logwarn
+{
+ text-align: center;
+ color: red;
+} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/css/pathchooser.css b/pyload/webui/themes/flat/css/pathchooser.css
new file mode 100644
index 000000000..894cc335e
--- /dev/null
+++ b/pyload/webui/themes/flat/css/pathchooser.css
@@ -0,0 +1,68 @@
+table {
+ width: 90%;
+ border: 1px dotted #888888;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+th {
+ background-color: #525252;
+ color: #E0E0E0;
+}
+
+table, tr, td {
+ background-color: #F0F0F0;
+}
+
+a, a:visited {
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#paths {
+ width: 90%;
+ text-align: left;
+}
+
+.file_directory {
+ color: #c0c0c0;
+}
+.path_directory {
+ color: #3c3c3c;
+}
+.file_file {
+ color: #3c3c3c;
+}
+.path_file {
+ color: #c0c0c0;
+}
+
+.parentdir {
+ color: #000000;
+ font-size: 10pt;
+}
+.name {
+ text-align: left;
+}
+.size {
+ text-align: right;
+}
+.type {
+ text-align: left;
+}
+.mtime {
+ text-align: center;
+}
+
+.path_abs_rel {
+ color: #3c3c3c;
+ text-decoration: none;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 10pt;
+}
+
+.path_abs_rel a {
+ color: #3c3c3c;
+ font-style: italic;
+}
diff --git a/pyload/webui/themes/flat/css/window.css b/pyload/webui/themes/flat/css/window.css
new file mode 100644
index 000000000..12829868b
--- /dev/null
+++ b/pyload/webui/themes/flat/css/window.css
@@ -0,0 +1,73 @@
+/* ----------- stylized ----------- */
+.window_box h1{
+ font-size:14px;
+ font-weight:bold;
+ margin-bottom:8px;
+}
+.window_box p{
+ font-size:11px;
+ color:#666666;
+ margin-bottom:20px;
+ border-bottom:solid 1px #b7ddf2;
+ padding-bottom:10px;
+}
+.window_box label{
+ display:block;
+ font-weight:bold;
+ text-align:right;
+ width:240px;
+ float:left;
+}
+.window_box .small{
+ color:#666666;
+ display:block;
+ font-size:11px;
+ font-weight:normal;
+ text-align:right;
+ width:240px;
+}
+.window_box select, .window_box input{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box .cont{
+ float:left;
+ font-size:12px;
+ padding: 0px 10px 15px 0px;
+ width:300px;
+ margin:0px 0px 0px 10px;
+}
+.window_box .cont input{
+ float: none;
+ margin: 0px 15px 0px 1px;
+}
+.window_box textarea{
+ float:left;
+ font-size:12px;
+ padding:4px 2px;
+ border:solid 1px #aacfe4;
+ width:300px;
+ margin:2px 0 20px 10px;
+}
+.window_box button, .styled_button{
+ clear:both;
+ margin-left:150px;
+ width:125px;
+ height:31px;
+ background:#666666 url(../img/button.png) no-repeat;
+ text-align:center;
+ line-height:31px;
+ color:#FFFFFF;
+ font-size:11px;
+ font-weight:bold;
+ border: 0px;
+}
+
+.styled_button {
+ margin-left: 15px;
+ cursor: pointer;
+}
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-close.png b/pyload/webui/themes/flat/img/MooDialog/dialog-close.png
new file mode 100644
index 000000000..81ebb88b2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-close.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-error.png b/pyload/webui/themes/flat/img/MooDialog/dialog-error.png
new file mode 100644
index 000000000..d70328403
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-error.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-question.png b/pyload/webui/themes/flat/img/MooDialog/dialog-question.png
new file mode 100644
index 000000000..b0af3db5b
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-question.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png b/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png
new file mode 100644
index 000000000..aad64d4be
--- /dev/null
+++ b/pyload/webui/themes/flat/img/MooDialog/dialog-warning.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/arrow_refresh.png b/pyload/webui/themes/flat/img/arrow_refresh.png
new file mode 100644
index 000000000..b1b6fa4dc
--- /dev/null
+++ b/pyload/webui/themes/flat/img/arrow_refresh.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/arrow_right.png b/pyload/webui/themes/flat/img/arrow_right.png
new file mode 100644
index 000000000..68f379fc7
--- /dev/null
+++ b/pyload/webui/themes/flat/img/arrow_right.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/button.png b/pyload/webui/themes/flat/img/button.png
new file mode 100644
index 000000000..bb408a7d6
--- /dev/null
+++ b/pyload/webui/themes/flat/img/button.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/cog.png b/pyload/webui/themes/flat/img/cog.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/flat/img/cog.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_add.png b/pyload/webui/themes/flat/img/control_add.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_add.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_add_blue.png b/pyload/webui/themes/flat/img/control_add_blue.png
new file mode 100644
index 000000000..e3f29fab2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_add_blue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_cancel.png b/pyload/webui/themes/flat/img/control_cancel.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_cancel.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_cancel_blue.png b/pyload/webui/themes/flat/img/control_cancel_blue.png
new file mode 100644
index 000000000..07c9cad30
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_cancel_blue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_pause.png b/pyload/webui/themes/flat/img/control_pause.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_pause.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_pause_blue.png b/pyload/webui/themes/flat/img/control_pause_blue.png
new file mode 100644
index 000000000..24e3705fa
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_pause_blue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_play.png b/pyload/webui/themes/flat/img/control_play.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_play.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_play_blue.png b/pyload/webui/themes/flat/img/control_play_blue.png
new file mode 100644
index 000000000..15ced1e21
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_play_blue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_stop.png b/pyload/webui/themes/flat/img/control_stop.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_stop.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/control_stop_blue.png b/pyload/webui/themes/flat/img/control_stop_blue.png
new file mode 100644
index 000000000..71215ef67
--- /dev/null
+++ b/pyload/webui/themes/flat/img/control_stop_blue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/add_folder.png b/pyload/webui/themes/flat/img/default/add_folder.png
new file mode 100644
index 000000000..8acbc411b
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/add_folder.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/ajax-loader.gif b/pyload/webui/themes/flat/img/default/ajax-loader.gif
new file mode 100644
index 000000000..2fd8e0737
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/ajax-loader.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/big_button.gif b/pyload/webui/themes/flat/img/default/big_button.gif
new file mode 100644
index 000000000..7680490ea
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/big_button.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/big_button_over.gif b/pyload/webui/themes/flat/img/default/big_button_over.gif
new file mode 100644
index 000000000..2e3ee10d2
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/big_button_over.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/body.png b/pyload/webui/themes/flat/img/default/body.png
new file mode 100644
index 000000000..7ff1043e0
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/body.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/closebtn.gif b/pyload/webui/themes/flat/img/default/closebtn.gif
new file mode 100644
index 000000000..3e27e6030
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/closebtn.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/drag_corner.gif b/pyload/webui/themes/flat/img/default/drag_corner.gif
new file mode 100644
index 000000000..befb1adf1
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/drag_corner.gif
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/full.png b/pyload/webui/themes/flat/img/default/full.png
new file mode 100644
index 000000000..fea52af76
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/full.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/head-menu-recent.png b/pyload/webui/themes/flat/img/default/head-menu-recent.png
new file mode 100644
index 000000000..fc9b0497f
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/head-menu-recent.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/head_bg1.png b/pyload/webui/themes/flat/img/default/head_bg1.png
new file mode 100644
index 000000000..f2848c3cc
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/head_bg1.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/images.png b/pyload/webui/themes/flat/img/default/images.png
new file mode 100644
index 000000000..184860d1e
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/images.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/parseUri.png b/pyload/webui/themes/flat/img/default/parseUri.png
new file mode 100644
index 000000000..937bded9d
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/parseUri.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/pyload-logo.png b/pyload/webui/themes/flat/img/default/pyload-logo.png
new file mode 100644
index 000000000..2443cd8b1
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/pyload-logo.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/tab-background.png b/pyload/webui/themes/flat/img/default/tab-background.png
new file mode 100644
index 000000000..29a5d1991
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/tab-background.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/default/tabs-border-bottom.png b/pyload/webui/themes/flat/img/default/tabs-border-bottom.png
new file mode 100644
index 000000000..02440f428
--- /dev/null
+++ b/pyload/webui/themes/flat/img/default/tabs-border-bottom.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/delete.png b/pyload/webui/themes/flat/img/delete.png
new file mode 100644
index 000000000..4539cff12
--- /dev/null
+++ b/pyload/webui/themes/flat/img/delete.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/error.png b/pyload/webui/themes/flat/img/error.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/flat/img/error.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/folder.png b/pyload/webui/themes/flat/img/folder.png
new file mode 100644
index 000000000..0b067dd3c
--- /dev/null
+++ b/pyload/webui/themes/flat/img/folder.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-login.png b/pyload/webui/themes/flat/img/head-login.png
new file mode 100644
index 000000000..6b57515bc
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-login.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-collector.png b/pyload/webui/themes/flat/img/head-menu-collector.png
new file mode 100644
index 000000000..bbcbe6406
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-collector.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-config.png b/pyload/webui/themes/flat/img/head-menu-config.png
new file mode 100644
index 000000000..93e8f83ac
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-config.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-development.png b/pyload/webui/themes/flat/img/head-menu-development.png
new file mode 100644
index 000000000..33d8b062f
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-development.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-download.png b/pyload/webui/themes/flat/img/head-menu-download.png
new file mode 100644
index 000000000..3691deebc
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-download.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-home.png b/pyload/webui/themes/flat/img/head-menu-home.png
new file mode 100644
index 000000000..b77bef5eb
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-home.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-index.png b/pyload/webui/themes/flat/img/head-menu-index.png
new file mode 100644
index 000000000..8bc6e9604
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-index.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-news.png b/pyload/webui/themes/flat/img/head-menu-news.png
new file mode 100644
index 000000000..44e79a9a9
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-news.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-queue.png b/pyload/webui/themes/flat/img/head-menu-queue.png
new file mode 100644
index 000000000..e4fa41ad8
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-queue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-menu-wiki.png b/pyload/webui/themes/flat/img/head-menu-wiki.png
new file mode 100644
index 000000000..61b0e54ea
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-menu-wiki.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/head-search-noshadow.png b/pyload/webui/themes/flat/img/head-search-noshadow.png
new file mode 100644
index 000000000..16d39bd06
--- /dev/null
+++ b/pyload/webui/themes/flat/img/head-search-noshadow.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/notice.png b/pyload/webui/themes/flat/img/notice.png
new file mode 100644
index 000000000..a52b067cb
--- /dev/null
+++ b/pyload/webui/themes/flat/img/notice.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/package_go.png b/pyload/webui/themes/flat/img/package_go.png
new file mode 100644
index 000000000..80b2c42ee
--- /dev/null
+++ b/pyload/webui/themes/flat/img/package_go.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/page-tools-backlinks.png b/pyload/webui/themes/flat/img/page-tools-backlinks.png
new file mode 100644
index 000000000..fb8f55b38
--- /dev/null
+++ b/pyload/webui/themes/flat/img/page-tools-backlinks.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/page-tools-edit.png b/pyload/webui/themes/flat/img/page-tools-edit.png
new file mode 100644
index 000000000..67177cf89
--- /dev/null
+++ b/pyload/webui/themes/flat/img/page-tools-edit.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/page-tools-revisions.png b/pyload/webui/themes/flat/img/page-tools-revisions.png
new file mode 100644
index 000000000..088fe0087
--- /dev/null
+++ b/pyload/webui/themes/flat/img/page-tools-revisions.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/pencil.png b/pyload/webui/themes/flat/img/pencil.png
new file mode 100644
index 000000000..e39c93cd8
--- /dev/null
+++ b/pyload/webui/themes/flat/img/pencil.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/reconnect.png b/pyload/webui/themes/flat/img/reconnect.png
new file mode 100644
index 000000000..cd35c9325
--- /dev/null
+++ b/pyload/webui/themes/flat/img/reconnect.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_None.png b/pyload/webui/themes/flat/img/status_None.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_None.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_downloading.png b/pyload/webui/themes/flat/img/status_downloading.png
new file mode 100644
index 000000000..db8ad8cd6
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_downloading.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_failed.png b/pyload/webui/themes/flat/img/status_failed.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_failed.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_finished.png b/pyload/webui/themes/flat/img/status_finished.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_finished.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_offline.png b/pyload/webui/themes/flat/img/status_offline.png
new file mode 100644
index 000000000..6c565c99c
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_offline.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_proc.png b/pyload/webui/themes/flat/img/status_proc.png
new file mode 100644
index 000000000..833f779ac
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_proc.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_queue.png b/pyload/webui/themes/flat/img/status_queue.png
new file mode 100644
index 000000000..1400d3eb3
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_queue.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/status_waiting.png b/pyload/webui/themes/flat/img/status_waiting.png
new file mode 100644
index 000000000..fd038175e
--- /dev/null
+++ b/pyload/webui/themes/flat/img/status_waiting.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/success.png b/pyload/webui/themes/flat/img/success.png
new file mode 100644
index 000000000..2c4aca40d
--- /dev/null
+++ b/pyload/webui/themes/flat/img/success.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/user-actions-logout.png b/pyload/webui/themes/flat/img/user-actions-logout.png
new file mode 100644
index 000000000..d4ef360e8
--- /dev/null
+++ b/pyload/webui/themes/flat/img/user-actions-logout.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/user-actions-profile.png b/pyload/webui/themes/flat/img/user-actions-profile.png
new file mode 100644
index 000000000..9ec410b13
--- /dev/null
+++ b/pyload/webui/themes/flat/img/user-actions-profile.png
Binary files differ
diff --git a/pyload/webui/themes/flat/img/user-info.png b/pyload/webui/themes/flat/img/user-info.png
new file mode 100644
index 000000000..345ed52e4
--- /dev/null
+++ b/pyload/webui/themes/flat/img/user-info.png
Binary files differ
diff --git a/pyload/webui/themes/flat/js/render/admin.coffee b/pyload/webui/themes/flat/js/render/admin.coffee
new file mode 100644
index 000000000..5afbcbb66
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/admin.coffee
@@ -0,0 +1,58 @@
+root = this
+
+window.addEvent "domready", ->
+
+ root.passwordDialog = new MooDialog {destroyOnHide: false}
+ root.passwordDialog.setContent $ 'password_box'
+
+ $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close()
+ $("login_password_button").addEvent "click", (e) ->
+
+ newpw = $("login_new_password").get("value")
+ newpw2 = $("login_new_password2").get("value")
+
+ if newpw is newpw2
+ form = $("password_form")
+ form.set "send", {
+ onSuccess: (data) ->
+ root.notify.alert "Success", {
+ 'className': 'success'
+ }
+ onFailure: (data) ->
+ root.notify.alert "Error", {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+
+ root.passwordDialog.close()
+ else
+ alert '{{_("Passwords did not match.")}}'
+
+ e.stop()
+
+ for item in $$(".change_password")
+ id = item.get("id")
+ user = id.split("|")[1]
+ $("user_login").set("value", user)
+ item.addEvent "click", (e) -> root.passwordDialog.open()
+
+ $('quit-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/kill'
+ method: 'get'
+ }).send()
+ , ->
+ e.stop()
+
+ $('restart-pyload').addEvent "click", (e) ->
+ new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", ->
+ new Request.JSON({
+ url: '/api/restart'
+ method: 'get'
+ onSuccess: (data) -> alert "{{_('pyLoad restarted')}}"
+ }).send()
+ , ->
+ e.stop()
diff --git a/pyload/webui/themes/flat/js/render/admin.min.js b/pyload/webui/themes/flat/js/render/admin.min.js
new file mode 100644
index 000000000..94a5e494d
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/admin.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})});
+{% endautoescape %}
diff --git a/pyload/webui/themes/flat/js/render/base.coffee b/pyload/webui/themes/flat/js/render/base.coffee
new file mode 100644
index 000000000..07b8bfb6f
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/base.coffee
@@ -0,0 +1,177 @@
+# External scope
+root = this
+
+# helper functions
+humanFileSize = (size) ->
+ filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB")
+ loga = Math.log(size) / Math.log(1024)
+ i = Math.floor(loga)
+ a = Math.pow(1024, i)
+ if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i])
+
+
+parseUri = () ->
+ oldString = $("add_links").value
+ regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g')
+ resu = oldString.match regxp
+ return if resu == null
+ res = ""
+
+ for part in resu
+ if part.indexOf(" ") != -1
+ res = res + part.replace(" ", " \n")
+ else if part.indexOf("\t") != -1
+ res = res + part.replace("\t", " \n")
+ else if part.indexOf("\r") != -1
+ res = res + part.replace("\r", " \n")
+ else if part.indexOf("\"") != -1
+ res = res + part.replace("\"", " \n")
+ else if part.indexOf("<") != -1
+ res = res + part.replace("<", " \n")
+ else if part.indexOf("'") != -1
+ res = res + part.replace("'", " \n")
+ else
+ res = res + part.replace("\n", " \n")
+
+ $("add_links").value = res
+
+
+Array::remove = (from, to) ->
+ rest = this.slice((to || from) + 1 || this.length)
+ this.length = from < 0 ? this.length + from : from
+ return [] if this.length == 0
+ return this.push.apply(this, rest)
+
+
+document.addEvent "domready", ->
+
+ # global notification
+ root.notify = new Purr {
+ 'mode': 'top'
+ 'position': 'center'
+ }
+
+ root.captchaBox = new MooDialog {destroyOnHide: false}
+ root.captchaBox.setContent $ 'cap_box'
+
+ root.addBox = new MooDialog {destroyOnHide: false}
+ root.addBox.setContent $ 'add_box'
+
+ $('add_form').onsubmit = ->
+ $('add_form').target = 'upload_target'
+ if $('add_name').value is "" and $('add_file').value is ""
+ alert '{{_("Please Enter a packagename.")}}'
+ return false
+ else
+ root.addBox.close()
+ return true
+
+ $('add_reset').addEvent 'click', -> root.addBox.close()
+
+ $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open()
+ $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send()
+ $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send()
+ $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send()
+
+
+ # captcha events
+
+ $('cap_info').addEvent 'click', ->
+ load_captcha "get", ""
+ root.captchaBox.open()
+ $('cap_reset').addEvent 'click', -> root.captchaBox.close()
+ $('cap_form').addEvent 'submit', (e) ->
+ submit_captcha()
+ e.stop()
+
+ $('cap_positional').addEvent 'click', on_captcha_click
+
+ new Request.JSON({
+ url: "/json/status"
+ onSuccess: LoadJsonToContent
+ secure: false
+ async: true
+ initialDelay: 0
+ delay: 4000
+ limit: 3000
+ }).startTimer()
+
+
+LoadJsonToContent = (data) ->
+ $("speed").set 'text', humanFileSize(data.speed)+"/s"
+ $("aktiv").set 'text', data.active
+ $("aktiv_from").set 'text', data.queue
+ $("aktiv_total").set 'text', data.total
+
+ if data.captcha
+ if $("cap_info").getStyle("display") != "inline"
+ $("cap_info").setStyle 'display', 'inline'
+ root.notify.alert '{{_("New Captcha Request")}}', {
+ 'className': 'notify'
+ }
+ else
+ $("cap_info").setStyle 'display', 'none'
+
+
+ if data.download
+ $("time").set 'text', ' {{_("on")}}'
+ $("time").setStyle 'background-color', "#8ffc25"
+ else
+ $("time").set 'text', ' {{_("off")}}'
+ $("time").setStyle 'background-color', "#fc6e26"
+
+ if data.reconnect
+ $("reconnect").set 'text', ' {{_("on")}}'
+ $("reconnect").setStyle 'background-color', "#8ffc25"
+ else
+ $("reconnect").set 'text', ' {{_("off")}}'
+ $("reconnect").setStyle 'background-color', "#fc6e26"
+
+ return null
+
+
+set_captcha = (data) ->
+ $('cap_id').set 'value', data.id
+ if (data.result_type is 'textual')
+ $('cap_textual_img').set 'src', data.src
+ $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}'
+ $('cap_submit').setStyle 'display', 'inline'
+ $('cap_textual').setStyle 'display', 'block'
+ $('cap_positional').setStyle 'display', 'none'
+
+ else if (data.result_type == 'positional')
+ $('cap_positional_img').set('src', data.src)
+ $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}')
+ $('cap_submit').setStyle('display', 'none')
+ $('cap_textual').setStyle('display', 'none')
+
+
+load_captcha = (method, post) ->
+ new Request.JSON({
+ url: "/json/set_captcha"
+ onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha()
+ secure: false
+ async: true
+ method: method
+ }).send(post)
+
+
+clear_captcha = ->
+ $('cap_textual').setStyle 'display', 'none'
+ $('cap_textual_img').set 'src', ''
+ $('cap_positional').setStyle 'display', 'none'
+ $('cap_positional_img').set 'src', ''
+ $('cap_title').set 'text', '{{_("No Captchas to read.")}}'
+
+
+submit_captcha = ->
+ load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') )
+ $('cap_result').set('value', '')
+
+
+on_captcha_click = (e) ->
+ position = e.target.getPosition()
+ x = e.page.x - position.x
+ y = e.page.y - position.y
+ $('cap_result').value = x + "," + y
+ submit_captcha()
diff --git a/pyload/webui/themes/flat/js/render/base.min.js b/pyload/webui/themes/flat/js/render/base.min.js
new file mode 100644
index 000000000..1ba1d73f9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/base.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()};
+{% endautoescape %}
diff --git a/pyload/webui/themes/flat/js/render/package.js b/pyload/webui/themes/flat/js/render/package.js
new file mode 100644
index 000000000..659a8e6fc
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/package.js
@@ -0,0 +1,376 @@
+var root = this;
+
+document.addEvent("domready", function() {
+ root.load = new Fx.Tween($("load-indicator"), {link: "cancel"});
+ root.load.set("opacity", 0);
+
+
+ root.packageBox = new MooDialog({destroyOnHide: false});
+ root.packageBox.setContent($('pack_box'));
+
+ $('pack_reset').addEvent('click', function() {
+ $('pack_form').reset();
+ root.packageBox.close();
+ });
+});
+
+function indicateLoad() {
+ //$("load-indicator").reveal();
+ root.load.start("opacity", 1)
+}
+
+function indicateFinish() {
+ root.load.start("opacity", 0)
+}
+
+function indicateSuccess() {
+ indicateFinish();
+ root.notify.alert('{{_("Success")}}.', {
+ 'className': 'success'
+ });
+}
+
+function indicateFail() {
+ indicateFinish();
+ root.notify.alert('{{_("Failed")}}.', {
+ 'className': 'error'
+ });
+}
+
+var PackageUI = new Class({
+ initialize: function(url, type) {
+ this.url = url;
+ this.type = type;
+ this.packages = [];
+ this.parsePackages();
+
+ this.sorts = new Sortables($("package-list"), {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".package_drag",
+ onComplete: this.saveSort.bind(this)
+ });
+
+ $("del_finished").addEvent("click", this.deleteFinished.bind(this));
+ $("restart_failed").addEvent("click", this.restartFailed.bind(this));
+
+ },
+
+ parsePackages: function() {
+ $("package-list").getChildren("li").each(function(ele) {
+ var id = ele.getFirst().get("id").match(/[0-9]+/);
+ this.packages.push(new Package(this, id, ele))
+ }.bind(this))
+ },
+
+ loadPackages: function() {
+ },
+
+ deleteFinished: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/deleteFinished',
+ onSuccess: function(data) {
+ if (data.length > 0) {
+ window.location.reload()
+ } else {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ restartFailed: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/api/restartFailed',
+ onSuccess: function(data) {
+ this.packages.each(function(pack) {
+ pack.close();
+ });
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ startSort: function(ele, copy) {
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("pid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
+
+var Package = new Class({
+ initialize: function(ui, id, ele, data) {
+ this.ui = ui;
+ this.id = id;
+ this.linksLoaded = false;
+
+ if (!ele) {
+ this.createElement(data);
+ } else {
+ this.ele = ele;
+ this.order = ele.getElements("div.order")[0].get("html");
+ this.ele.store("order", this.order);
+ this.ele.store("pid", this.id);
+ this.parseElement();
+ }
+
+ var pname = this.ele.getElements(".packagename")[0];
+ this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"});
+ this.buttons.set("opacity", 0);
+
+ pname.addEvent("mouseenter", function(e) {
+ this.buttons.start("opacity", 1)
+ }.bind(this));
+
+ pname.addEvent("mouseleave", function(e) {
+ this.buttons.start("opacity", 0)
+ }.bind(this));
+
+
+ },
+
+ createElement: function() {
+ alert("create")
+ },
+
+ parseElement: function() {
+ var imgs = this.ele.getElements('img');
+
+ this.name = this.ele.getElements('.name')[0];
+ this.folder = this.ele.getElements('.folder')[0];
+ this.password = this.ele.getElements('.password')[0];
+
+ imgs[1].addEvent('click', this.deletePackage.bind(this));
+ imgs[2].addEvent('click', this.restartPackage.bind(this));
+ imgs[3].addEvent('click', this.editPackage.bind(this));
+ imgs[4].addEvent('click', this.movePackage.bind(this));
+
+ this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this));
+
+ },
+
+ loadLinks: function() {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/package/' + this.id,
+ onSuccess: this.createLinks.bind(this),
+ onFailure: indicateFail
+ }).send();
+ },
+
+ createLinks: function(data) {
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.set("html", "");
+ data.links.each(function(link) {
+ link.id = link.fid;
+ var li = new Element("li", {
+ 'style': {
+ 'margin-left': 0
+ }
+ });
+
+ var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon});
+ html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name});
+ html += "<span class='child_status'>{statusmsg}</span>{error}&nbsp;".substitute({'statusmsg': link.statusmsg, 'error':link.error});
+ html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size});
+ html += "<span class='child_status'>{plugin}</span>&nbsp;&nbsp;".substitute({'plugin': link.plugin});
+ html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' />&nbsp;&nbsp;";
+ html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>";
+
+ var div = new Element("div", {
+ 'id': "file_" + link.id,
+ 'class': "child",
+ 'html': html
+ });
+
+ li.store("order", link.order);
+ li.store("lid", link.id);
+
+ li.adopt(div);
+ ul.adopt(li);
+ });
+ this.sorts = new Sortables(ul, {
+ constrain: false,
+ clone: true,
+ revert: true,
+ opacity: 0.4,
+ handle: ".sorthandle",
+ onComplete: this.saveSort.bind(this)
+ });
+ this.registerLinkEvents();
+ this.linksLoaded = true;
+ indicateFinish();
+ this.toggle();
+ },
+
+ registerLinkEvents: function() {
+ this.ele.getElements('.child').each(function(child) {
+ var lid = child.get('id').match(/[0-9]+/);
+ var imgs = child.getElements('.child_secrow img');
+ imgs[0].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/deleteFiles/[' + this + "]",
+ onSuccess: function() {
+ $('file_' + this).nix()
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+
+ imgs[1].addEvent('click', function(e) {
+ new Request({
+ method: 'get',
+ url: '/api/restartFile/' + this,
+ onSuccess: function() {
+ var ele = $('file_' + this);
+ var imgs = ele.getElements("img");
+ imgs[0].set("src", "../img/status_queue.png");
+ var spans = ele.getElements(".child_status");
+ spans[1].set("html", "queued");
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ }.bind(lid));
+ });
+ },
+
+ toggle: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ } else {
+ if (!this.linksLoaded) {
+ this.loadLinks();
+ } else {
+ child.reveal();
+ }
+ }
+ },
+
+
+ deletePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/deletePackages/[' + this.id + "]",
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ //hide_pack();
+ event.stop();
+ },
+
+ restartPackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/api/restartPackage/' + this.id,
+ onSuccess: function() {
+ this.close();
+ indicateSuccess();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ close: function() {
+ var child = this.ele.getElement('.children');
+ if (child.getStyle('display') == "block") {
+ child.dissolve();
+ }
+ var ul = $("sort_children_{id}".substitute({'id': this.id}));
+ ul.erase("html");
+ this.linksLoaded = false;
+ },
+
+ movePackage: function(event) {
+ indicateLoad();
+ new Request({
+ method: 'get',
+ url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id,
+ onSuccess: function() {
+ this.ele.nix();
+ indicateFinish();
+ }.bind(this),
+ onFailure: indicateFail
+ }).send();
+ event.stop();
+ },
+
+ editPackage: function(event) {
+ $("pack_form").removeEvents("submit");
+ $("pack_form").addEvent("submit", this.savePackage.bind(this));
+
+ $("pack_id").set("value", this.id);
+ $("pack_name").set("value", this.name.get("text"));
+ $("pack_folder").set("value", this.folder.get("text"));
+ $("pack_pws").set("value", this.password.get("text"));
+
+ root.packageBox.open();
+ event.stop();
+ },
+
+ savePackage: function(event) {
+ $("pack_form").send();
+ this.name.set("text", $("pack_name").get("value"));
+ this.folder.set("text", $("pack_folder").get("value"));
+ this.password.set("text", $("pack_pws").get("value"));
+ root.packageBox.close();
+ event.stop();
+ },
+
+ saveSort: function(ele, copy) {
+ var order = [];
+ this.sorts.serialize(function(li, pos) {
+ if (li == ele && ele.retrieve("order") != pos) {
+ order.push(ele.retrieve("lid") + "|" + pos)
+ }
+ li.store("order", pos)
+ });
+ if (order.length > 0) {
+ indicateLoad();
+ new Request.JSON({
+ method: 'get',
+ url: '/json/link_order/' + order[0],
+ onSuccess: indicateFinish,
+ onFailure: indicateFail
+ }).send();
+ }
+ }
+
+});
diff --git a/pyload/webui/themes/flat/js/render/settings.coffee b/pyload/webui/themes/flat/js/render/settings.coffee
new file mode 100644
index 000000000..d522741b9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/settings.coffee
@@ -0,0 +1,107 @@
+root = this
+
+window.addEvent 'domready', ->
+ root.accountDialog = new MooDialog {destroyOnHide: false}
+ root.accountDialog.setContent $ 'account_box'
+
+ new TinyTab $$('#toptabs li a'), $$('#tabs-body > span')
+
+ $$('ul.nav').each (nav) ->
+ new MooDropMenu nav, {
+ onOpen: (el) -> el.fade 'in'
+ onClose: (el) -> el.fade 'out'
+ onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500}
+ }
+
+ new SettingsUI()
+
+
+class SettingsUI
+ constructor: ->
+ @menu = $$ "#general-menu li"
+ @menu.append $$ "#plugin-menu li"
+
+ @name = $ "tabsback"
+ @general = $ "general_form_content"
+ @plugin = $ "plugin_form_content"
+
+ el.addEvent 'click', @menuClick.bind(this) for el in @menu
+
+ $("general|submit").addEvent "click", @configSubmit.bind(this)
+ $("plugin|submit").addEvent "click", @configSubmit.bind(this)
+
+ $("account_add").addEvent "click", (e) ->
+ root.accountDialog.open()
+ e.stop()
+
+ $("account_reset").addEvent "click", (e) ->
+ root.accountDialog.close()
+
+ $("account_add_button").addEvent "click", @addAccount.bind(this)
+ $("account_submit").addEvent "click", @submitAccounts.bind(this)
+
+
+ menuClick: (e) ->
+ [category, section] = e.target.get("id").split("|")
+ name = e.target.get "text"
+
+
+ target = if category is "general" then @general else @plugin
+ target.dissolve()
+
+ new Request({
+ "method" : "get"
+ "url" : "/json/load_config/#{category}/#{section}"
+ 'onSuccess': (data) =>
+ target.set "html", data
+ target.reveal()
+ this.name.set "text", name
+ }).send()
+
+
+ configSubmit: (e) ->
+ category = e.target.get("id").split("|")[0]
+ form = $("#{category}_form")
+
+ form.set "send", {
+ 'method': "post"
+ 'url': "/json/save_config/#{category}"
+ "onSuccess" : ->
+ root.notify.alert '{{ _("Settings saved.")}}', {
+ 'className': 'success'
+ }
+ 'onFailure': ->
+ root.notify.alert '{{ _("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+ form.send()
+ e.stop()
+
+ addAccount: (e) ->
+ form = $ "add_account_form"
+ form.set "send", {
+ 'method': "post"
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert '{{_("Error occured.")}}', {
+ 'className': 'error'
+ }
+ }
+
+ form.send()
+ e.stop()
+
+ submitAccounts: (e) ->
+ form = $ "account_form"
+ form.set "send", {
+ 'method': "post",
+ "onSuccess" : -> window.location.reload()
+ 'onFailure': ->
+ root.notify.alert('{{ _("Error occured.") }}', {
+ 'className': 'error'
+ })
+ }
+
+ form.send()
+ e.stop()
diff --git a/pyload/webui/themes/flat/js/render/settings.min.js b/pyload/webui/themes/flat/js/render/settings.min.js
new file mode 100644
index 000000000..41d1cb25a
--- /dev/null
+++ b/pyload/webui/themes/flat/js/render/settings.min.js
@@ -0,0 +1,3 @@
+{% autoescape true %}
+var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})();
+{% endautoescape %}
diff --git a/pyload/webui/themes/flat/js/static/MooDialog.js b/pyload/webui/themes/flat/js/static/MooDialog.js
new file mode 100644
index 000000000..45a52496f
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDialog.js
@@ -0,0 +1,140 @@
+/*
+---
+name: MooDialog
+description: The base class of MooDialog
+authors: Arian Stolwijk
+license: MIT-style license
+requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event]
+provides: [MooDialog, Element.MooDialog]
+...
+*/
+
+
+var MooDialog = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ 'class': 'MooDialog',
+ title: null,
+ scroll: true, // IE
+ forceScroll: false,
+ useEscKey: true,
+ destroyOnHide: true,
+ autoOpen: true,
+ closeButton: true,
+ onInitialize: function(){
+ this.wrapper.setStyle('display', 'none');
+ },
+ onBeforeOpen: function(){
+ this.wrapper.setStyle('display', 'block');
+ this.fireEvent('show');
+ },
+ onBeforeClose: function(){
+ this.wrapper.setStyle('display', 'none');
+ this.fireEvent('hide');
+ }/*,
+ onOpen: function(){},
+ onClose: function(){},
+ onShow: function(){},
+ onHide: function(){},
+ onInitialize: function(wrapper){},
+ onContentChange: function(content){}*/
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.options.inject = this.options.inject || document.body;
+ options = this.options;
+
+ var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject);
+ this.content = new Element('div.content').inject(wrapper);
+
+ if (options.title){
+ this.title = new Element('div.title').set('text', options.title).inject(wrapper);
+ wrapper.addClass('MooDialogTitle');
+ }
+
+ if (options.closeButton){
+ this.closeButton = new Element('a.close', {
+ events: {click: this.close.bind(this)}
+ }).inject(wrapper);
+ }
+
+
+ /*<ie6>*/// IE 6 scroll
+ if ((options.scroll && Browser.ie6) || options.forceScroll){
+ wrapper.setStyle('position', 'absolute');
+ var position = wrapper.getPosition(options.inject);
+ window.addEvent('scroll', function(){
+ var scroll = document.getScroll();
+ wrapper.setPosition({
+ x: position.x + scroll.x,
+ y: position.y + scroll.y
+ });
+ });
+ }
+ /*</ie6>*/
+
+ if (options.useEscKey){
+ // Add event for the esc key
+ document.addEvent('keydown', function(e){
+ if (e.key == 'esc') this.close();
+ }.bind(this));
+ }
+
+ this.addEvent('hide', function(){
+ if (options.destroyOnHide) this.destroy();
+ }.bind(this));
+
+ this.fireEvent('initialize', wrapper);
+ },
+
+ setContent: function(){
+ var content = Array.from(arguments);
+ if (content.length == 1) content = content[0];
+
+ this.content.empty();
+
+ var type = typeOf(content);
+ if (['string', 'number'].contains(type)) this.content.set('text', content);
+ else this.content.adopt(content);
+
+ this.fireEvent('contentChange', this.content);
+
+ return this;
+ },
+
+ open: function(){
+ this.fireEvent('beforeOpen', this.wrapper).fireEvent('open');
+ this.opened = true;
+ return this;
+ },
+
+ close: function(){
+ this.fireEvent('beforeClose', this.wrapper).fireEvent('close');
+ this.opened = false;
+ return this;
+ },
+
+ destroy: function(){
+ this.wrapper.destroy();
+ },
+
+ toElement: function(){
+ return this.wrapper;
+ }
+
+});
+
+
+Element.implement({
+
+ MooDialog: function(options){
+ this.store('MooDialog',
+ new MooDialog(options).setContent(this).open()
+ );
+ return this;
+ }
+
+});
diff --git a/pyload/webui/themes/flat/js/static/MooDialog.min.js b/pyload/webui/themes/flat/js/static/MooDialog.min.js
new file mode 100644
index 000000000..90b3ae100
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDialog.min.js
@@ -0,0 +1 @@
+var MooDialog=new Class({Implements:[Options,Events],options:{"class":"MooDialog",title:null,scroll:!0,forceScroll:!1,useEscKey:!0,destroyOnHide:!0,autoOpen:!0,closeButton:!0,onInitialize:function(){this.wrapper.setStyle("display","none")},onBeforeOpen:function(){this.wrapper.setStyle("display","block"),this.fireEvent("show")},onBeforeClose:function(){this.wrapper.setStyle("display","none"),this.fireEvent("hide")}},initialize:function(t){this.setOptions(t),this.options.inject=this.options.inject||document.body,t=this.options;var e=this.wrapper=new Element("div."+t["class"].replace(" ",".")).inject(t.inject);if(this.content=new Element("div.content").inject(e),t.title&&(this.title=new Element("div.title").set("text",t.title).inject(e),e.addClass("MooDialogTitle")),t.closeButton&&(this.closeButton=new Element("a.close",{events:{click:this.close.bind(this)}}).inject(e)),t.scroll&&Browser.ie6||t.forceScroll){e.setStyle("position","absolute");var n=e.getPosition(t.inject);window.addEvent("scroll",function(){var t=document.getScroll();e.setPosition({x:n.x+t.x,y:n.y+t.y})})}t.useEscKey&&document.addEvent("keydown",function(t){"esc"==t.key&&this.close()}.bind(this)),this.addEvent("hide",function(){t.destroyOnHide&&this.destroy()}.bind(this)),this.fireEvent("initialize",e)},setContent:function(){var t=Array.from(arguments);1==t.length&&(t=t[0]),this.content.empty();var e=typeOf(t);return["string","number"].contains(e)?this.content.set("text",t):this.content.adopt(t),this.fireEvent("contentChange",this.content),this},open:function(){return this.fireEvent("beforeOpen",this.wrapper).fireEvent("open"),this.opened=!0,this},close:function(){return this.fireEvent("beforeClose",this.wrapper).fireEvent("close"),this.opened=!1,this},destroy:function(){this.wrapper.destroy()},toElement:function(){return this.wrapper}});Element.implement({MooDialog:function(t){return this.store("MooDialog",new MooDialog(t).setContent(this).open()),this}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/MooDropMenu.js b/pyload/webui/themes/flat/js/static/MooDropMenu.js
new file mode 100644
index 000000000..ac0fa1874
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDropMenu.js
@@ -0,0 +1,86 @@
+/*
+---
+description: This provides a simple Drop Down menu with infinit levels
+
+license: MIT-style
+
+authors:
+- Arian Stolwijk
+
+requires:
+ - Core/Class.Extras
+ - Core/Element.Event
+ - Core/Selectors
+
+provides: [MooDropMenu, Element.MooDropMenu]
+
+...
+*/
+
+var MooDropMenu = new Class({
+
+ Implements: [Options, Events],
+
+ options: {
+ onOpen: function(el){
+ el.removeClass('close').addClass('open');
+ },
+ onClose: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ onInitialize: function(el){
+ el.removeClass('open').addClass('close');
+ },
+ mouseoutDelay: 200,
+ mouseoverDelay: 0,
+ listSelector: 'ul',
+ itemSelector: 'li',
+ openEvent: 'mouseenter',
+ closeEvent: 'mouseleave'
+ },
+
+ initialize: function(menu, options, level){
+ this.setOptions(options);
+ options = this.options;
+
+ var menu = this.menu = document.id(menu);
+
+ menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){
+
+ this.fireEvent('initialize', el);
+
+ var parent = el.getParent(options.itemSelector),
+ timer;
+
+ parent.addEvent(options.openEvent, function(){
+ parent.store('DropDownOpen', true);
+
+ clearTimeout(timer);
+ if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]);
+ else this.fireEvent('open', el);
+
+ }.bind(this)).addEvent(options.closeEvent, function(){
+ parent.store('DropDownOpen', false);
+
+ clearTimeout(timer);
+ timer = (function(){
+ if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el);
+ }).delay(options.mouseoutDelay, this);
+
+ }.bind(this));
+
+ }, this);
+ },
+
+ toElement: function(){
+ return this.menu
+ }
+
+});
+
+/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */
+Element.implement({
+ MooDropMenu: function(options){
+ return this.store('MooDropMenu', new MooDropMenu(this, options));
+ }
+});
diff --git a/pyload/webui/themes/flat/js/static/MooDropMenu.min.js b/pyload/webui/themes/flat/js/static/MooDropMenu.min.js
new file mode 100644
index 000000000..552ae247a
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/MooDropMenu.min.js
@@ -0,0 +1 @@
+var MooDropMenu=new Class({Implements:[Options,Events],options:{onOpen:function(e){e.removeClass("close").addClass("open")},onClose:function(e){e.removeClass("open").addClass("close")},onInitialize:function(e){e.removeClass("open").addClass("close")},mouseoutDelay:200,mouseoverDelay:0,listSelector:"ul",itemSelector:"li",openEvent:"mouseenter",closeEvent:"mouseleave"},initialize:function(e,o){this.setOptions(o),o=this.options;var e=this.menu=document.id(e);e.getElements(o.itemSelector+" > "+o.listSelector).each(function(e){this.fireEvent("initialize",e);var n,t=e.getParent(o.itemSelector);t.addEvent(o.openEvent,function(){t.store("DropDownOpen",!0),clearTimeout(n),o.mouseoverDelay?n=this.fireEvent.delay(o.mouseoverDelay,this,["open",e]):this.fireEvent("open",e)}.bind(this)).addEvent(o.closeEvent,function(){t.store("DropDownOpen",!1),clearTimeout(n),n=function(){t.retrieve("DropDownOpen")||this.fireEvent("close",e)}.delay(o.mouseoutDelay,this)}.bind(this))},this)},toElement:function(){return this.menu}});Element.implement({MooDropMenu:function(e){return this.store("MooDropMenu",new MooDropMenu(this,e))}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/mootools-core.js b/pyload/webui/themes/flat/js/static/mootools-core.js
new file mode 100644
index 000000000..db83850fd
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-core.js
@@ -0,0 +1,5977 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+...
+*/
+
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [Core, MooTools, Type, typeOf, instanceOf, Native]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.5.0',
+ build: '0f7b690afee9349b15909f33016a25d2e4d9f4e3'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family != null) return item.$family();
+
+ if (item.nodeName){
+ if (item.nodeType == 1) return 'element';
+ if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+ } else if (typeof item.length == 'number'){
+ if ('callee' in item) return 'arguments';
+ if ('item' in item) return 'collection';
+ }
+
+ return typeof item;
+};
+
+var instanceOf = this.instanceOf = function(item, object){
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor){
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ /*<ltIE8>*/
+ if (!item.hasOwnProperty) return false;
+ /*</ltIE8>*/
+ return item instanceof object;
+};
+
+// Function overloading
+
+var Function = this.Function;
+
+var enumerables = true;
+for (var i in {toString: 1}) enumerables = null;
+if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor'];
+
+Function.prototype.overloadSetter = function(usePlural){
+ var self = this;
+ return function(a, b){
+ if (a == null) return this;
+ if (usePlural || typeof a != 'string'){
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--;){
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+};
+
+Function.prototype.overloadGetter = function(usePlural){
+ var self = this;
+ return function(a){
+ var args, result;
+ if (typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ else if (usePlural) args = [a];
+ if (args){
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+};
+
+Function.prototype.extend = function(key, value){
+ this[key] = value;
+}.overloadSetter();
+
+Function.prototype.implement = function(key, value){
+ this.prototype[key] = value;
+}.overloadSetter();
+
+// From
+
+var slice = Array.prototype.slice;
+
+Function.from = function(item){
+ return (typeOf(item) == 'function') ? item : function(){
+ return item;
+ };
+};
+
+Array.from = function(item){
+ if (item == null) return [];
+ return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item];
+};
+
+Number.from = function(item){
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+};
+
+String.from = function(item){
+ return item + '';
+};
+
+// hide, protect
+
+Function.implement({
+
+ hide: function(){
+ this.$hidden = true;
+ return this;
+ },
+
+ protect: function(){
+ this.$protected = true;
+ return this;
+ }
+
+});
+
+// Type
+
+var Type = this.Type = function(name, object){
+ if (name){
+ var lower = name.toLowerCase();
+ var typeCheck = function(item){
+ return (typeOf(item) == lower);
+ };
+
+ Type['is' + name] = typeCheck;
+ if (object != null){
+ object.prototype.$family = (function(){
+ return lower;
+ }).hide();
+
+ }
+ }
+
+ if (object == null) return null;
+
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+
+ return object;
+};
+
+var toString = Object.prototype.toString;
+
+Type.isEnumerable = function(item){
+ return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' );
+};
+
+var hooks = {};
+
+var hooksOf = function(object){
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+};
+
+var implement = function(name, method){
+ if (method && method.$hidden) return;
+
+ var hooks = hooksOf(this);
+
+ for (var i = 0; i < hooks.length; i++){
+ var hook = hooks[i];
+ if (typeOf(hook) == 'type') implement.call(hook, name, method);
+ else hook.call(this, name, method);
+ }
+
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+
+ if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){
+ return method.apply(item, slice.call(arguments, 1));
+ });
+};
+
+var extend = function(name, method){
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+};
+
+Type.implement({
+
+ implement: implement.overloadSetter(),
+
+ extend: extend.overloadSetter(),
+
+ alias: function(name, existing){
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+
+ mirror: function(hook){
+ hooksOf(this).push(hook);
+ return this;
+ }
+
+});
+
+new Type('Type', Type);
+
+// Default Types
+
+var force = function(name, object, methods){
+ var isType = (object != Object),
+ prototype = object.prototype;
+
+ if (isType) object = new Type(name, object);
+
+ for (var i = 0, l = methods.length; i < l; i++){
+ var key = methods[i],
+ generic = object[key],
+ proto = prototype[key];
+
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+
+ if (isType){
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn){
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase'
+])('Array', Array, [
+ 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
+ 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
+])('Number', Number, [
+ 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision'
+])('Function', Function, [
+ 'apply', 'call', 'bind'
+])('RegExp', RegExp, [
+ 'exec', 'test'
+])('Object', Object, [
+ 'create', 'defineProperty', 'defineProperties', 'keys',
+ 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames',
+ 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen'
+])('Date', Date, ['now']);
+
+Object.extend = extend.overloadSetter();
+
+Date.extend('now', function(){
+ return +(new Date);
+});
+
+new Type('Boolean', Boolean);
+
+// fixes NaN returning as Number
+
+Number.prototype.$family = function(){
+ return isFinite(this) ? 'number' : 'null';
+}.hide();
+
+// Number.random
+
+Number.extend('random', function(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+});
+
+// forEach, each
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+Object.extend('forEach', function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+});
+
+Object.each = Object.forEach;
+
+Array.implement({
+
+ /*<!ES5>*/
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ /*</!ES5>*/
+
+ each: function(fn, bind){
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+
+});
+
+// Array & Object cloning, Object merging and appending
+
+var cloneOf = function(item){
+ switch (typeOf(item)){
+ case 'array': return item.clone();
+ case 'object': return Object.clone(item);
+ default: return item;
+ }
+};
+
+Array.implement('clone', function(){
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+});
+
+var mergeOne = function(source, key, current){
+ switch (typeOf(current)){
+ case 'object':
+ if (typeOf(source[key]) == 'object') Object.merge(source[key], current);
+ else source[key] = Object.clone(current);
+ break;
+ case 'array': source[key] = current.clone(); break;
+ default: source[key] = current;
+ }
+ return source;
+};
+
+Object.extend({
+
+ merge: function(source, k, v){
+ if (typeOf(k) == 'string') return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+
+ clone: function(object){
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+
+ append: function(original){
+ for (var i = 1, l = arguments.length; i < l; i++){
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+
+});
+
+// Object-less types
+
+['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){
+ new Type(name);
+});
+
+// Unique ID
+
+var UID = Date.now();
+
+String.extend('uniqueID', function(){
+ return (UID++).toString(36);
+});
+
+
+
+})();
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: [Type]
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ /*<!ES5>*/
+ every: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+
+ indexOf: function(item, from){
+ var length = this.length >>> 0;
+ for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++){
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length >>> 0; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ /*</!ES5>*/
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ append: function(array){
+ this.push.apply(this, array);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[Number.random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--;){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = typeOf(this[i]);
+ if (type == 'null') continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ pick: function(){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: [Type, Array]
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ //<!ES6>
+ contains: function(string, index){
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ //</!ES6>
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ trim: function(){
+ return String(this).replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return String(this).replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return String(this).replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return String(this).replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return String(this).replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = String(this).match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Number
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Number
+
+...
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('each', 'times');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+---
+
+name: Function
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Function
+
+...
+*/
+
+Function.extend({
+
+ attempt: function(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch (e){}
+ }
+ return null;
+ }
+
+});
+
+Function.implement({
+
+ attempt: function(args, bind){
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e){}
+
+ return null;
+ },
+
+ /*<!ES5-bind>*/
+ bind: function(that){
+ var self = this,
+ args = arguments.length > 1 ? Array.slice(arguments, 1) : null,
+ F = function(){};
+
+ var bound = function(){
+ var context = that, length = arguments.length;
+ if (this instanceof bound){
+ F.prototype = self.prototype;
+ context = new F;
+ }
+ var result = (!args && !length)
+ ? self.call(context)
+ : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ /*</!ES5-bind>*/
+
+ pass: function(args, bind){
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function(){
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ delay: function(delay, bind, args){
+ return setTimeout(this.pass((args == null ? [] : args), bind), delay);
+ },
+
+ periodical: function(periodical, bind, args){
+ return setInterval(this.pass((args == null ? [] : args), bind), periodical);
+ }
+
+});
+
+
+
+
+/*
+---
+
+name: Object
+
+description: Object generic methods
+
+license: MIT-style license.
+
+requires: Type
+
+provides: [Object, Hash]
+
+...
+*/
+
+(function(){
+
+var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+Object.extend({
+
+ subset: function(object, keys){
+ var results = {};
+ for (var i = 0, l = keys.length; i < l; i++){
+ var k = keys[i];
+ if (k in object) results[k] = object[k];
+ }
+ return results;
+ },
+
+ map: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object);
+ }
+ return results;
+ },
+
+ filter: function(object, fn, bind){
+ var results = {};
+ for (var key in object){
+ var value = object[key];
+ if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value;
+ }
+ return results;
+ },
+
+ every: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;
+ }
+ return true;
+ },
+
+ some: function(object, fn, bind){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true;
+ }
+ return false;
+ },
+
+ keys: function(object){
+ var keys = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) keys.push(key);
+ }
+ return keys;
+ },
+
+ values: function(object){
+ var values = [];
+ for (var key in object){
+ if (hasOwnProperty.call(object, key)) values.push(object[key]);
+ }
+ return values;
+ },
+
+ getLength: function(object){
+ return Object.keys(object).length;
+ },
+
+ keyOf: function(object, value){
+ for (var key in object){
+ if (hasOwnProperty.call(object, key) && object[key] === value) return key;
+ }
+ return null;
+ },
+
+ contains: function(object, value){
+ return Object.keyOf(object, value) != null;
+ },
+
+ toQueryString: function(object, base){
+ var queryString = [];
+
+ Object.each(object, function(value, key){
+ if (base) key = base + '[' + key + ']';
+ var result;
+ switch (typeOf(value)){
+ case 'object': result = Object.toQueryString(value, key); break;
+ case 'array':
+ var qs = {};
+ value.each(function(val, i){
+ qs[i] = val;
+ });
+ result = Object.toQueryString(qs, key);
+ break;
+ default: result = key + '=' + encodeURIComponent(value);
+ }
+ if (value != null) queryString.push(result);
+ });
+
+ return queryString.join('&');
+ }
+
+});
+
+})();
+
+
+
+
+/*
+---
+
+name: Browser
+
+description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: [Array, Function, Number, String]
+
+provides: [Browser, Window, Document]
+
+...
+*/
+
+(function(){
+
+var document = this.document;
+var window = document.window = this;
+
+var parse = function(ua, platform){
+ ua = ua.toLowerCase();
+ platform = (platform ? platform.toLowerCase() : '');
+
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0];
+
+ if (UA[1] == 'trident'){
+ UA[1] = 'ie';
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == 'crios') {
+ UA[1] = 'chrome';
+ }
+
+ var platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0];
+ if (platform == 'win') platform = 'windows';
+
+ return {
+ extend: Function.prototype.extend,
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+ version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+ platform: platform
+ };
+};
+
+var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+
+if (Browser.ie){
+ Browser.version = document.documentMode;
+}
+
+Browser.extend({
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+ parseUA: parse
+});
+
+
+
+// Request
+
+Browser.Request = (function(){
+
+ var XMLHTTP = function(){
+ return new XMLHttpRequest();
+ };
+
+ var MSXML2 = function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ };
+
+ var MSXML = function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ };
+
+ return Function.attempt(function(){
+ XMLHTTP();
+ return XMLHTTP;
+ }, function(){
+ MSXML2();
+ return MSXML2;
+ }, function(){
+ MSXML();
+ return MSXML;
+ });
+
+})();
+
+Browser.Features.xhr = !!(Browser.Request);
+
+
+
+// String scripts
+
+Browser.exec = function(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+String.implement('stripScripts', function(exec){
+ var scripts = '';
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){
+ scripts += code + '\n';
+ return '';
+ });
+ if (exec === true) Browser.exec(scripts);
+ else if (typeOf(exec) == 'function') exec(scripts, text);
+ return text;
+});
+
+// Window, Document
+
+Browser.extend({
+ Document: this.Document,
+ Window: this.Window,
+ Element: this.Element,
+ Event: this.Event
+});
+
+this.Window = this.$constructor = new Type('Window', function(){});
+
+this.$family = Function.from('window').hide();
+
+Window.mirror(function(name, method){
+ window[name] = method;
+});
+
+this.Document = document.$constructor = new Type('Document', function(){});
+
+document.$family = Function.from('document').hide();
+
+Document.mirror(function(name, method){
+ document[name] = method;
+});
+
+document.html = document.documentElement;
+if (!document.head) document.head = document.getElementsByTagName('head')[0];
+
+if (document.execCommand) try {
+ document.execCommand("BackgroundImageCache", false, true);
+} catch (e){}
+
+/*<ltIE9>*/
+if (this.attachEvent && !this.addEventListener){
+ var unloadEvent = function(){
+ this.detachEvent('onunload', unloadEvent);
+ document.head = document.html = document.window = null;
+ };
+ this.attachEvent('onunload', unloadEvent);
+}
+
+// IE fails on collections and <select>.options (refers to <select>)
+var arrayFrom = Array.from;
+try {
+ arrayFrom(document.html.childNodes);
+} catch(e){
+ Array.from = function(item){
+ if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){
+ var i = item.length, array = new Array(i);
+ while (i--) array[i] = item[i];
+ return array;
+ }
+ return arrayFrom(item);
+ };
+
+ var prototype = Array.prototype,
+ slice = prototype.slice;
+ ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){
+ var method = prototype[name];
+ Array[name] = function(item){
+ return method.apply(Array.from(item), slice.call(arguments, 1));
+ };
+ });
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Event
+
+description: Contains the Event Type, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, Function, String, Object]
+
+provides: Event
+
+...
+*/
+
+(function() {
+
+var _keys = {};
+
+var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){
+ if (!win) win = window;
+ event = event || win.event;
+ if (event.$extended) return event;
+ this.event = event;
+ this.$extended = true;
+ this.shift = event.shiftKey;
+ this.control = event.ctrlKey;
+ this.alt = event.altKey;
+ this.meta = event.metaKey;
+ var type = this.type = event.type;
+ var target = event.target || event.srcElement;
+ while (target && target.nodeType == 3) target = target.parentNode;
+ this.target = document.id(target);
+
+ if (type.indexOf('key') == 0){
+ var code = this.code = (event.which || event.keyCode);
+ this.key = _keys[code];
+ if (type == 'keydown' || type == 'keyup'){
+ if (code > 111 && code < 124) this.key = 'f' + (code - 111);
+ else if (code > 95 && code < 106) this.key = code - 96;
+ }
+ if (this.key == null) this.key = String.fromCharCode(code).toLowerCase();
+ } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){
+ var doc = win.document;
+ doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+ this.page = {
+ x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft,
+ y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop
+ };
+ this.client = {
+ x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX,
+ y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY
+ };
+ if (type == 'DOMMouseScroll' || type == 'mousewheel')
+ this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+
+ this.rightClick = (event.which == 3 || event.button == 2);
+ if (type == 'mouseover' || type == 'mouseout'){
+ var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element'];
+ while (related && related.nodeType == 3) related = related.parentNode;
+ this.relatedTarget = document.id(related);
+ }
+ } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){
+ this.rotation = event.rotation;
+ this.scale = event.scale;
+ this.targetTouches = event.targetTouches;
+ this.changedTouches = event.changedTouches;
+ var touches = this.touches = event.touches;
+ if (touches && touches[0]){
+ var touch = touches[0];
+ this.page = {x: touch.pageX, y: touch.pageY};
+ this.client = {x: touch.clientX, y: touch.clientY};
+ }
+ }
+
+ if (!this.client) this.client = {};
+ if (!this.page) this.page = {};
+});
+
+DOMEvent.implement({
+
+ stop: function(){
+ return this.preventDefault().stopPropagation();
+ },
+
+ stopPropagation: function(){
+ if (this.event.stopPropagation) this.event.stopPropagation();
+ else this.event.cancelBubble = true;
+ return this;
+ },
+
+ preventDefault: function(){
+ if (this.event.preventDefault) this.event.preventDefault();
+ else this.event.returnValue = false;
+ return this;
+ }
+
+});
+
+DOMEvent.defineKey = function(code, key){
+ _keys[code] = key;
+ return this;
+};
+
+DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true);
+
+DOMEvent.defineKeys({
+ '38': 'up', '40': 'down', '37': 'left', '39': 'right',
+ '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab',
+ '46': 'delete', '13': 'enter'
+});
+
+})();
+
+
+
+
+
+
+/*
+---
+
+name: Class
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires: [Array, String, Function, Number]
+
+provides: Class
+
+...
+*/
+
+(function(){
+
+var Class = this.Class = new Type('Class', function(params){
+ if (instanceOf(params, Function)) params = {initialize: params};
+
+ var newClass = function(){
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+
+ return newClass;
+});
+
+var parent = function(){
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name,
+ parent = this.$caller.$owner.parent,
+ previous = (parent) ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+};
+
+var reset = function(object){
+ for (var key in object){
+ var value = object[key];
+ switch (typeOf(value)){
+ case 'object':
+ var F = function(){};
+ F.prototype = value;
+ object[key] = reset(new F);
+ break;
+ case 'array': object[key] = value.clone(); break;
+ }
+ }
+ return object;
+};
+
+var wrap = function(self, key, method){
+ if (method.$origin) method = method.$origin;
+ var wrapper = function(){
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current; this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current; this.caller = caller;
+ return result;
+ }.extend({$owner: self, $origin: method, $name: key});
+ return wrapper;
+};
+
+var implement = function(key, value, retain){
+ if (Class.Mutators.hasOwnProperty(key)){
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+
+ if (typeOf(value) == 'function'){
+ if (value.$hidden) return this;
+ this.prototype[key] = (retain) ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+
+ return this;
+};
+
+var getInstance = function(klass){
+ klass.$prototyping = true;
+ var proto = new klass;
+ delete klass.$prototyping;
+ return proto;
+};
+
+Class.implement('implement', implement.overloadSetter());
+
+Class.Mutators = {
+
+ Extends: function(parent){
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+
+ Implements: function(items){
+ Array.from(items).each(function(item){
+ var instance = new item;
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Class.Extras
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires: Class
+
+provides: [Class.Extras, Chain, Events, Options]
+
+...
+*/
+
+(function(){
+
+this.Chain = new Class({
+
+ $chain: [],
+
+ chain: function(){
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+
+ callChain: function(){
+ return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+ },
+
+ clearChain: function(){
+ this.$chain.empty();
+ return this;
+ }
+
+});
+
+var removeOn = function(string){
+ return string.replace(/^on([A-Z])/, function(full, first){
+ return first.toLowerCase();
+ });
+};
+
+this.Events = new Class({
+
+ $events: {},
+
+ addEvent: function(type, fn, internal){
+ type = removeOn(type);
+
+
+
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+
+ addEvents: function(events){
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal){
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events){
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--;) if (i in fns){
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+
+});
+
+this.Options = new Class({
+
+ setOptions: function(){
+ var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments));
+ if (this.addEvent) for (var option in options){
+ if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+name: Slick.Parser
+description: Standalone CSS3 Selector parser
+provides: Slick.Parser
+...
+*/
+
+;(function(){
+
+var parsed,
+ separatorIndex,
+ combinatorIndex,
+ reversed,
+ cache = {},
+ reverseCache = {},
+ reUnescape = /\\/g;
+
+var parse = function(expression, isReversed){
+ if (expression == null) return null;
+ if (expression.Slick === true) return expression;
+ expression = ('' + expression).replace(/^\s+|\s+$/g, '');
+ reversed = !!isReversed;
+ var currentCache = (reversed) ? reverseCache : cache;
+ if (currentCache[expression]) return currentCache[expression];
+ parsed = {
+ Slick: true,
+ expressions: [],
+ raw: expression,
+ reverse: function(){
+ return parse(this.raw, true);
+ }
+ };
+ separatorIndex = -1;
+ while (expression != (expression = expression.replace(regexp, parser)));
+ parsed.length = parsed.expressions.length;
+ return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed;
+};
+
+var reverseCombinator = function(combinator){
+ if (combinator === '!') return ' ';
+ else if (combinator === ' ') return '!';
+ else if ((/^!/).test(combinator)) return combinator.replace(/^!/, '');
+ else return '!' + combinator;
+};
+
+var reverse = function(expression){
+ var expressions = expression.expressions;
+ for (var i = 0; i < expressions.length; i++){
+ var exp = expressions[i];
+ var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)};
+
+ for (var j = 0; j < exp.length; j++){
+ var cexp = exp[j];
+ if (!cexp.reverseCombinator) cexp.reverseCombinator = ' ';
+ cexp.combinator = cexp.reverseCombinator;
+ delete cexp.reverseCombinator;
+ }
+
+ exp.reverse().push(last);
+ }
+ return expression;
+};
+
+var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License
+ return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){
+ return '\\' + match;
+ });
+};
+
+var regexp = new RegExp(
+/*
+#!/usr/bin/env ruby
+puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'')
+__END__
+ "(?x)^(?:\
+ \\s* ( , ) \\s* # Separator \n\
+ | \\s* ( <combinator>+ ) \\s* # Combinator \n\
+ | ( \\s+ ) # CombinatorChildren \n\
+ | ( <unicode>+ | \\* ) # Tag \n\
+ | \\# ( <unicode>+ ) # ID \n\
+ | \\. ( <unicode>+ ) # ClassName \n\
+ | # Attribute \n\
+ \\[ \
+ \\s* (<unicode1>+) (?: \
+ \\s* ([*^$!~|]?=) (?: \
+ \\s* (?:\
+ ([\"']?)(.*?)\\9 \
+ )\
+ ) \
+ )? \\s* \
+ \\](?!\\]) \n\
+ | :+ ( <unicode>+ )(?:\
+ \\( (?:\
+ (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\
+ ) \\)\
+ )?\
+ )"
+*/
+ "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)"
+ .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']')
+ .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+ .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])')
+);
+
+function parser(
+ rawMatch,
+
+ separator,
+ combinator,
+ combinatorChildren,
+
+ tagName,
+ id,
+ className,
+
+ attributeKey,
+ attributeOperator,
+ attributeQuote,
+ attributeValue,
+
+ pseudoMarker,
+ pseudoClass,
+ pseudoQuote,
+ pseudoClassQuotedValue,
+ pseudoClassValue
+){
+ if (separator || separatorIndex === -1){
+ parsed.expressions[++separatorIndex] = [];
+ combinatorIndex = -1;
+ if (separator) return '';
+ }
+
+ if (combinator || combinatorChildren || combinatorIndex === -1){
+ combinator = combinator || ' ';
+ var currentSeparator = parsed.expressions[separatorIndex];
+ if (reversed && currentSeparator[combinatorIndex])
+ currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator);
+ currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'};
+ }
+
+ var currentParsed = parsed.expressions[separatorIndex][combinatorIndex];
+
+ if (tagName){
+ currentParsed.tag = tagName.replace(reUnescape, '');
+
+ } else if (id){
+ currentParsed.id = id.replace(reUnescape, '');
+
+ } else if (className){
+ className = className.replace(reUnescape, '');
+
+ if (!currentParsed.classList) currentParsed.classList = [];
+ if (!currentParsed.classes) currentParsed.classes = [];
+ currentParsed.classList.push(className);
+ currentParsed.classes.push({
+ value: className,
+ regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)')
+ });
+
+ } else if (pseudoClass){
+ pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue;
+ pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null;
+
+ if (!currentParsed.pseudos) currentParsed.pseudos = [];
+ currentParsed.pseudos.push({
+ key: pseudoClass.replace(reUnescape, ''),
+ value: pseudoClassValue,
+ type: pseudoMarker.length == 1 ? 'class' : 'element'
+ });
+
+ } else if (attributeKey){
+ attributeKey = attributeKey.replace(reUnescape, '');
+ attributeValue = (attributeValue || '').replace(reUnescape, '');
+
+ var test, regexp;
+
+ switch (attributeOperator){
+ case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break;
+ case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break;
+ case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break;
+ case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break;
+ case '=' : test = function(value){
+ return attributeValue == value;
+ }; break;
+ case '*=' : test = function(value){
+ return value && value.indexOf(attributeValue) > -1;
+ }; break;
+ case '!=' : test = function(value){
+ return attributeValue != value;
+ }; break;
+ default : test = function(value){
+ return !!value;
+ };
+ }
+
+ if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){
+ return false;
+ };
+
+ if (!test) test = function(value){
+ return value && regexp.test(value);
+ };
+
+ if (!currentParsed.attributes) currentParsed.attributes = [];
+ currentParsed.attributes.push({
+ key: attributeKey,
+ operator: attributeOperator,
+ value: attributeValue,
+ test: test
+ });
+
+ }
+
+ return '';
+};
+
+// Slick NS
+
+var Slick = (this.Slick || {});
+
+Slick.parse = function(expression){
+ return parse(expression);
+};
+
+Slick.escapeRegExp = escapeRegExp;
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+name: Slick.Finder
+description: The new, superfast css selector engine.
+provides: Slick.Finder
+requires: Slick.Parser
+...
+*/
+
+;(function(){
+
+var local = {},
+ featuresCache = {},
+ toString = Object.prototype.toString;
+
+// Feature / Bug detection
+
+local.isNativeCode = function(fn){
+ return (/\{\s*\[native code\]\s*\}/).test('' + fn);
+};
+
+local.isXML = function(document){
+ return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
+ (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
+};
+
+local.setDocument = function(document){
+
+ // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
+ var nodeType = document.nodeType;
+ if (nodeType == 9); // document
+ else if (nodeType) document = document.ownerDocument; // node
+ else if (document.navigator) document = document.document; // window
+ else return;
+
+ // check if it's the old document
+
+ if (this.document === document) return;
+ this.document = document;
+
+ // check if we have done feature detection on this document before
+
+ var root = document.documentElement,
+ rootUid = this.getUIDXML(root),
+ features = featuresCache[rootUid],
+ feature;
+
+ if (features){
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+ return;
+ }
+
+ features = featuresCache[rootUid] = {};
+
+ features.root = root;
+ features.isXMLDocument = this.isXML(document);
+
+ features.brokenStarGEBTN
+ = features.starSelectsClosedQSA
+ = features.idGetsName
+ = features.brokenMixedCaseQSA
+ = features.brokenGEBCN
+ = features.brokenCheckedQSA
+ = features.brokenEmptyAttributeQSA
+ = features.isHTMLDocument
+ = features.nativeMatchesSelector
+ = false;
+
+ var starSelectsClosed, starSelectsComments,
+ brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
+ brokenFormAttributeGetter;
+
+ var selected, id = 'slick_uniqueid';
+ var testNode = document.createElement('div');
+
+ var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
+ testRoot.appendChild(testNode);
+
+ // on non-HTML documents innerHTML and getElementsById doesnt work properly
+ try {
+ testNode.innerHTML = '<a id="'+id+'"></a>';
+ features.isHTMLDocument = !!document.getElementById(id);
+ } catch(e){};
+
+ if (features.isHTMLDocument){
+
+ testNode.style.display = 'none';
+
+ // IE returns comment nodes for getElementsByTagName('*') for some documents
+ testNode.appendChild(document.createComment(''));
+ starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
+
+ // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.getElementsByTagName('*');
+ starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
+
+ // IE returns elements with the name instead of just id for getElementsById for some documents
+ try {
+ testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
+ features.idGetsName = document.getElementById(id) === testNode.firstChild;
+ } catch(e){};
+
+ if (testNode.getElementsByClassName){
+
+ // Safari 3.2 getElementsByClassName caches results
+ try {
+ testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
+ testNode.getElementsByClassName('b').length;
+ testNode.firstChild.className = 'b';
+ cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
+ } catch(e){};
+
+ // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
+ try {
+ testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
+ brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
+ } catch(e){};
+
+ features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
+ }
+
+ if (testNode.querySelectorAll){
+ // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
+ try {
+ testNode.innerHTML = 'foo</foo>';
+ selected = testNode.querySelectorAll('*');
+ features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
+ } catch(e){};
+
+ // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
+ try {
+ testNode.innerHTML = '<a class="MiX"></a>';
+ features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
+ } catch(e){};
+
+ // Webkit and Opera dont return selected options on querySelectorAll
+ try {
+ testNode.innerHTML = '<select><option selected="selected">a</option></select>';
+ features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
+ } catch(e){};
+
+ // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
+ try {
+ testNode.innerHTML = '<a class=""></a>';
+ features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
+ } catch(e){};
+
+ }
+
+ // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
+ try {
+ testNode.innerHTML = '<form action="s"><input id="action"/></form>';
+ brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
+ } catch(e){};
+
+ // native matchesSelector function
+
+ features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
+ if (features.nativeMatchesSelector) try {
+ // if matchesSelector trows errors on incorrect sintaxes we can use it
+ features.nativeMatchesSelector.call(root, ':slick');
+ features.nativeMatchesSelector = null;
+ } catch(e){};
+
+ }
+
+ try {
+ root.slick_expando = 1;
+ delete root.slick_expando;
+ features.getUID = this.getUIDHTML;
+ } catch(e) {
+ features.getUID = this.getUIDXML;
+ }
+
+ testRoot.removeChild(testNode);
+ testNode = selected = testRoot = null;
+
+ // getAttribute
+
+ features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
+ var method = this.attributeGetters[name];
+ if (method) return method.call(node);
+ var attributeNode = node.getAttributeNode(name);
+ return (attributeNode) ? attributeNode.nodeValue : null;
+ } : function(node, name){
+ var method = this.attributeGetters[name];
+ return (method) ? method.call(node) : node.getAttribute(name);
+ };
+
+ // hasAttribute
+
+ features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) {
+ return node.hasAttribute(attribute);
+ } : function(node, attribute) {
+ node = node.getAttributeNode(attribute);
+ return !!(node && (node.specified || node.nodeValue));
+ };
+
+ // contains
+ // FIXME: Add specs: local.contains should be different for xml and html documents?
+ var nativeRootContains = root && this.isNativeCode(root.contains),
+ nativeDocumentContains = document && this.isNativeCode(document.contains);
+
+ features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
+ return context.contains(node);
+ } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
+ // IE8 does not have .contains on document.
+ return context === node || ((context === document) ? document.documentElement : context).contains(node);
+ } : (root && root.compareDocumentPosition) ? function(context, node){
+ return context === node || !!(context.compareDocumentPosition(node) & 16);
+ } : function(context, node){
+ if (node) do {
+ if (node === context) return true;
+ } while ((node = node.parentNode));
+ return false;
+ };
+
+ // document order sorting
+ // credits to Sizzle (http://sizzlejs.com/)
+
+ features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
+ if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
+ return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ } : ('sourceIndex' in root) ? function(a, b){
+ if (!a.sourceIndex || !b.sourceIndex) return 0;
+ return a.sourceIndex - b.sourceIndex;
+ } : (document.createRange) ? function(a, b){
+ if (!a.ownerDocument || !b.ownerDocument) return 0;
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ } : null ;
+
+ root = null;
+
+ for (feature in features){
+ this[feature] = features[feature];
+ }
+};
+
+// Main Method
+
+var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
+ reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
+ qsaFailExpCache = {};
+
+local.search = function(context, expression, append, first){
+
+ var found = this.found = (first) ? null : (append || []);
+
+ if (!context) return found;
+ else if (context.navigator) context = context.document; // Convert the node from a window to a document
+ else if (!context.nodeType) return found;
+
+ // setup
+
+ var parsed, i,
+ uniques = this.uniques = {},
+ hasOthers = !!(append && append.length),
+ contextIsDocument = (context.nodeType == 9);
+
+ if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
+
+ // avoid duplicating items already in the append array
+ if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
+
+ // expression checks
+
+ if (typeof expression == 'string'){ // expression is a string
+
+ /*<simple-selectors-override>*/
+ var simpleSelector = expression.match(reSimpleSelector);
+ simpleSelectors: if (simpleSelector) {
+
+ var symbol = simpleSelector[1],
+ name = simpleSelector[2],
+ node, nodes;
+
+ if (!symbol){
+
+ if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
+ nodes = context.getElementsByTagName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ } else if (symbol == '#'){
+
+ if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
+ node = context.getElementById(name);
+ if (!node) return found;
+ if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
+ if (first) return node || null;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+
+ } else if (symbol == '.'){
+
+ if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
+ if (context.getElementsByClassName && !this.brokenGEBCN){
+ nodes = context.getElementsByClassName(name);
+ if (first) return nodes[0] || null;
+ for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ } else {
+ var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
+ nodes = context.getElementsByTagName('*');
+ for (i = 0; node = nodes[i++];){
+ className = node.className;
+ if (!(className && matchClass.test(className))) continue;
+ if (first) return node;
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+ }
+
+ }
+
+ if (hasOthers) this.sort(found);
+ return (first) ? null : found;
+
+ }
+ /*</simple-selectors-override>*/
+
+ /*<query-selector-override>*/
+ querySelector: if (context.querySelectorAll) {
+
+ if (!this.isHTMLDocument
+ || qsaFailExpCache[expression]
+ //TODO: only skip when expression is actually mixed case
+ || this.brokenMixedCaseQSA
+ || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
+ || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
+ || (!contextIsDocument //Abort when !contextIsDocument and...
+ // there are multiple expressions in the selector
+ // since we currently only fix non-document rooted QSA for single expression selectors
+ && expression.indexOf(',') > -1
+ )
+ || Slick.disableQSA
+ ) break querySelector;
+
+ var _expression = expression, _context = context;
+ if (!contextIsDocument){
+ // non-document rooted QSA
+ // credits to Andrew Dupont
+ var currentId = _context.getAttribute('id'), slickid = 'slickid__';
+ _context.setAttribute('id', slickid);
+ _expression = '#' + slickid + ' ' + _expression;
+ context = _context.parentNode;
+ }
+
+ try {
+ if (first) return context.querySelector(_expression) || null;
+ else nodes = context.querySelectorAll(_expression);
+ } catch(e) {
+ qsaFailExpCache[expression] = 1;
+ break querySelector;
+ } finally {
+ if (!contextIsDocument){
+ if (currentId) _context.setAttribute('id', currentId);
+ else _context.removeAttribute('id');
+ context = _context;
+ }
+ }
+
+ if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
+ if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ } else for (i = 0; node = nodes[i++];){
+ if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
+ }
+
+ if (hasOthers) this.sort(found);
+ return found;
+
+ }
+ /*</query-selector-override>*/
+
+ parsed = this.Slick.parse(expression);
+ if (!parsed.length) return found;
+ } else if (expression == null){ // there is no expression
+ return found;
+ } else if (expression.Slick){ // expression is a parsed Slick object
+ parsed = expression;
+ } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
+ (found) ? found.push(expression) : found = expression;
+ return found;
+ } else { // other junk
+ return found;
+ }
+
+ /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+ // cache elements for the nth selectors
+
+ this.posNTH = {};
+ this.posNTHLast = {};
+ this.posNTHType = {};
+ this.posNTHTypeLast = {};
+
+ /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+ // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
+ this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
+
+ if (found == null) found = [];
+
+ // default engine
+
+ var j, m, n;
+ var combinator, tag, id, classList, classes, attributes, pseudos;
+ var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
+
+ search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
+
+ combinator = 'combinator:' + currentBit.combinator;
+ if (!this[combinator]) continue search;
+
+ tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
+ id = currentBit.id;
+ classList = currentBit.classList;
+ classes = currentBit.classes;
+ attributes = currentBit.attributes;
+ pseudos = currentBit.pseudos;
+ lastBit = (j === (currentExpression.length - 1));
+
+ this.bitUniques = {};
+
+ if (lastBit){
+ this.uniques = uniques;
+ this.found = found;
+ } else {
+ this.uniques = {};
+ this.found = [];
+ }
+
+ if (j === 0){
+ this[combinator](context, tag, id, classes, attributes, pseudos, classList);
+ if (first && lastBit && found.length) break search;
+ } else {
+ if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
+ this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ if (found.length) break search;
+ } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
+ }
+
+ currentItems = this.found;
+ }
+
+ // should sort if there are nodes in append and if you pass multiple expressions.
+ if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
+
+ return (first) ? (found[0] || null) : found;
+};
+
+// Utils
+
+local.uidx = 1;
+local.uidk = 'slick-uniqueid';
+
+local.getUIDXML = function(node){
+ var uid = node.getAttribute(this.uidk);
+ if (!uid){
+ uid = this.uidx++;
+ node.setAttribute(this.uidk, uid);
+ }
+ return uid;
+};
+
+local.getUIDHTML = function(node){
+ return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
+};
+
+// sort based on the setDocument documentSorter method.
+
+local.sort = function(results){
+ if (!this.documentSorter) return results;
+ results.sort(this.documentSorter);
+ return results;
+};
+
+/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
+
+local.cacheNTH = {};
+
+local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
+
+local.parseNTHArgument = function(argument){
+ var parsed = argument.match(this.matchNTH);
+ if (!parsed) return false;
+ var special = parsed[2] || false;
+ var a = parsed[1] || 1;
+ if (a == '-') a = -1;
+ var b = +parsed[3] || 0;
+ parsed =
+ (special == 'n') ? {a: a, b: b} :
+ (special == 'odd') ? {a: 2, b: 1} :
+ (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a};
+
+ return (this.cacheNTH[argument] = parsed);
+};
+
+local.createNTHPseudo = function(child, sibling, positions, ofType){
+ return function(node, argument){
+ var uid = this.getUID(node);
+ if (!this[positions][uid]){
+ var parent = node.parentNode;
+ if (!parent) return false;
+ var el = parent[child], count = 1;
+ if (ofType){
+ var nodeName = node.nodeName;
+ do {
+ if (el.nodeName != nodeName) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ } else {
+ do {
+ if (el.nodeType != 1) continue;
+ this[positions][this.getUID(el)] = count++;
+ } while ((el = el[sibling]));
+ }
+ }
+ argument = argument || 'n';
+ var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
+ if (!parsed) return false;
+ var a = parsed.a, b = parsed.b, pos = this[positions][uid];
+ if (a == 0) return b == pos;
+ if (a > 0){
+ if (pos < b) return false;
+ } else {
+ if (b < pos) return false;
+ }
+ return ((pos - b) % a) == 0;
+ };
+};
+
+/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
+
+local.pushArray = function(node, tag, id, classes, attributes, pseudos){
+ if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
+};
+
+local.pushUID = function(node, tag, id, classes, attributes, pseudos){
+ var uid = this.getUID(node);
+ if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
+ this.uniques[uid] = true;
+ this.found.push(node);
+ }
+};
+
+local.matchNode = function(node, selector){
+ if (this.isHTMLDocument && this.nativeMatchesSelector){
+ try {
+ return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
+ } catch(matchError) {}
+ }
+
+ var parsed = this.Slick.parse(selector);
+ if (!parsed) return true;
+
+ // simple (single) selectors
+ var expressions = parsed.expressions, simpleExpCounter = 0, i;
+ for (i = 0; (currentExpression = expressions[i]); i++){
+ if (currentExpression.length == 1){
+ var exp = currentExpression[0];
+ if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
+ simpleExpCounter++;
+ }
+ }
+
+ if (simpleExpCounter == parsed.length) return false;
+
+ var nodes = this.search(this.document, parsed), item;
+ for (i = 0; item = nodes[i++];){
+ if (item === node) return true;
+ }
+ return false;
+};
+
+local.matchPseudo = function(node, name, argument){
+ var pseudoName = 'pseudo:' + name;
+ if (this[pseudoName]) return this[pseudoName](node, argument);
+ var attribute = this.getAttribute(node, name);
+ return (argument) ? argument == attribute : !!attribute;
+};
+
+local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
+ if (tag){
+ var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
+ if (tag == '*'){
+ if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
+ } else {
+ if (nodeName != tag) return false;
+ }
+ }
+
+ if (id && node.getAttribute('id') != id) return false;
+
+ var i, part, cls;
+ if (classes) for (i = classes.length; i--;){
+ cls = this.getAttribute(node, 'class');
+ if (!(cls && classes[i].regexp.test(cls))) return false;
+ }
+ if (attributes) for (i = attributes.length; i--;){
+ part = attributes[i];
+ if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
+ }
+ if (pseudos) for (i = pseudos.length; i--;){
+ part = pseudos[i];
+ if (!this.matchPseudo(node, part.key, part.value)) return false;
+ }
+ return true;
+};
+
+var combinators = {
+
+ ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
+
+ var i, item, children;
+
+ if (this.isHTMLDocument){
+ getById: if (id){
+ item = this.document.getElementById(id);
+ if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
+ // all[id] returns all the elements with that name or id inside node
+ // if theres just one it will return the element, else it will be a collection
+ children = node.all[id];
+ if (!children) return;
+ if (!children[0]) children = [children];
+ for (i = 0; item = children[i++];){
+ var idNode = item.getAttributeNode('id');
+ if (idNode && idNode.nodeValue == id){
+ this.push(item, tag, null, classes, attributes, pseudos);
+ break;
+ }
+ }
+ return;
+ }
+ if (!item){
+ // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
+ if (this.contains(this.root, node)) return;
+ else break getById;
+ } else if (this.document !== node && !this.contains(node, item)) return;
+ this.push(item, tag, null, classes, attributes, pseudos);
+ return;
+ }
+ getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
+ children = node.getElementsByClassName(classList.join(' '));
+ if (!(children && children.length)) break getByClass;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
+ return;
+ }
+ }
+ getByTag: {
+ children = node.getElementsByTagName(tag);
+ if (!(children && children.length)) break getByTag;
+ if (!this.brokenStarGEBTN) tag = null;
+ for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
+ if ((node = node.firstChild)) do {
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ } while ((node = node.nextSibling));
+ },
+
+ '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
+ while ((node = node.nextSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '^': function(node, tag, id, classes, attributes, pseudos){ // first child
+ node = node.firstChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
+ while ((node = node.nextSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
+ this['combinator:+'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
+ this['combinator:~'](node, tag, id, classes, attributes, pseudos);
+ this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
+ while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
+ node = node.parentNode;
+ if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
+ },
+
+ '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
+ while ((node = node.previousSibling)) if (node.nodeType == 1){
+ this.push(node, tag, id, classes, attributes, pseudos);
+ break;
+ }
+ },
+
+ '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
+ node = node.lastChild;
+ if (node){
+ if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
+ else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
+ }
+ },
+
+ '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
+ while ((node = node.previousSibling)){
+ if (node.nodeType != 1) continue;
+ var uid = this.getUID(node);
+ if (this.bitUniques[uid]) break;
+ this.bitUniques[uid] = true;
+ this.push(node, tag, id, classes, attributes, pseudos);
+ }
+ }
+
+};
+
+for (var c in combinators) local['combinator:' + c] = combinators[c];
+
+var pseudos = {
+
+ /*<pseudo-selectors>*/
+
+ 'empty': function(node){
+ var child = node.firstChild;
+ return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
+ },
+
+ 'not': function(node, expression){
+ return !this.matchNode(node, expression);
+ },
+
+ 'contains': function(node, text){
+ return (node.innerText || node.textContent || '').indexOf(text) > -1;
+ },
+
+ 'first-child': function(node){
+ while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'last-child': function(node){
+ while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
+ return true;
+ },
+
+ 'only-child': function(node){
+ var prev = node;
+ while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
+ return true;
+ },
+
+ /*<nth-pseudo-selectors>*/
+
+ 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
+
+ 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
+
+ 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
+
+ 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
+
+ 'index': function(node, index){
+ return this['pseudo:nth-child'](node, '' + (index + 1));
+ },
+
+ 'even': function(node){
+ return this['pseudo:nth-child'](node, '2n');
+ },
+
+ 'odd': function(node){
+ return this['pseudo:nth-child'](node, '2n+1');
+ },
+
+ /*</nth-pseudo-selectors>*/
+
+ /*<of-type-pseudo-selectors>*/
+
+ 'first-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'last-of-type': function(node){
+ var nodeName = node.nodeName;
+ while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
+ return true;
+ },
+
+ 'only-of-type': function(node){
+ var prev = node, nodeName = node.nodeName;
+ while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
+ var next = node;
+ while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
+ return true;
+ },
+
+ /*</of-type-pseudo-selectors>*/
+
+ // custom pseudos
+
+ 'enabled': function(node){
+ return !node.disabled;
+ },
+
+ 'disabled': function(node){
+ return node.disabled;
+ },
+
+ 'checked': function(node){
+ return node.checked || node.selected;
+ },
+
+ 'focus': function(node){
+ return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
+ },
+
+ 'root': function(node){
+ return (node === this.root);
+ },
+
+ 'selected': function(node){
+ return node.selected;
+ }
+
+ /*</pseudo-selectors>*/
+};
+
+for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
+
+// attributes methods
+
+var attributeGetters = local.attributeGetters = {
+
+ 'for': function(){
+ return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
+ },
+
+ 'href': function(){
+ return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
+ },
+
+ 'style': function(){
+ return (this.style) ? this.style.cssText : this.getAttribute('style');
+ },
+
+ 'tabindex': function(){
+ var attributeNode = this.getAttributeNode('tabindex');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ },
+
+ 'type': function(){
+ return this.getAttribute('type');
+ },
+
+ 'maxlength': function(){
+ var attributeNode = this.getAttributeNode('maxLength');
+ return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
+ }
+
+};
+
+attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
+
+// Slick
+
+var Slick = local.Slick = (this.Slick || {});
+
+Slick.version = '1.1.7';
+
+// Slick finder
+
+Slick.search = function(context, expression, append){
+ return local.search(context, expression, append);
+};
+
+Slick.find = function(context, expression){
+ return local.search(context, expression, null, true);
+};
+
+// Slick containment checker
+
+Slick.contains = function(container, node){
+ local.setDocument(container);
+ return local.contains(container, node);
+};
+
+// Slick attribute getter
+
+Slick.getAttribute = function(node, name){
+ local.setDocument(node);
+ return local.getAttribute(node, name);
+};
+
+Slick.hasAttribute = function(node, name){
+ local.setDocument(node);
+ return local.hasAttribute(node, name);
+};
+
+// Slick matcher
+
+Slick.match = function(node, selector){
+ if (!(node && selector)) return false;
+ if (!selector || selector === node) return true;
+ local.setDocument(node);
+ return local.matchNode(node, selector);
+};
+
+// Slick attribute accessor
+
+Slick.defineAttributeGetter = function(name, fn){
+ local.attributeGetters[name] = fn;
+ return this;
+};
+
+Slick.lookupAttributeGetter = function(name){
+ return local.attributeGetters[name];
+};
+
+// Slick pseudo accessor
+
+Slick.definePseudo = function(name, fn){
+ local['pseudo:' + name] = function(node, argument){
+ return fn.call(node, argument);
+ };
+ return this;
+};
+
+Slick.lookupPseudo = function(name){
+ var pseudo = local['pseudo:' + name];
+ if (pseudo) return function(argument){
+ return pseudo.call(this, argument);
+ };
+ return null;
+};
+
+// Slick overrides accessor
+
+Slick.override = function(regexp, fn){
+ local.override(regexp, fn);
+ return this;
+};
+
+Slick.isXML = local.isXML;
+
+Slick.uidOf = function(node){
+ return local.getUIDHTML(node);
+};
+
+if (!this.Slick) this.Slick = Slick;
+
+}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);
+
+
+/*
+---
+
+name: Element
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder]
+
+provides: [Element, Elements, $, $$, IFrame, Selectors]
+
+...
+*/
+
+var Element = this.Element = function(tag, props){
+ var konstructor = Element.Constructors[tag];
+ if (konstructor) return konstructor(props);
+ if (typeof tag != 'string') return document.id(tag).set(props);
+
+ if (!props) props = {};
+
+ if (!(/^[\w-]+$/).test(tag)){
+ var parsed = Slick.parse(tag).expressions[0][0];
+ tag = (parsed.tag == '*') ? 'div' : parsed.tag;
+ if (parsed.id && props.id == null) props.id = parsed.id;
+
+ var attributes = parsed.attributes;
+ if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){
+ attr = attributes[i];
+ if (props[attr.key] != null) continue;
+
+ if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value;
+ else if (!attr.value && !attr.operator) props[attr.key] = true;
+ }
+
+ if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' ');
+ }
+
+ return document.newElement(tag, props);
+};
+
+
+if (Browser.Element){
+ Element.prototype = Browser.Element.prototype;
+ // IE8 and IE9 require the wrapping.
+ Element.prototype._fireEvent = (function(fireEvent){
+ return function(type, event){
+ return fireEvent.call(this, type, event);
+ };
+ })(Element.prototype.fireEvent);
+}
+
+new Type('Element', Element).mirror(function(name){
+ if (Array.prototype[name]) return;
+
+ var obj = {};
+ obj[name] = function(){
+ var results = [], args = arguments, elements = true;
+ for (var i = 0, l = this.length; i < l; i++){
+ var element = this[i], result = results[i] = element[name].apply(element, args);
+ elements = (elements && typeOf(result) == 'element');
+ }
+ return (elements) ? new Elements(results) : results;
+ };
+
+ Elements.implement(obj);
+});
+
+if (!Browser.Element){
+ Element.parent = Object;
+
+ Element.Prototype = {
+ '$constructor': Element,
+ '$family': Function.from('element').hide()
+ };
+
+ Element.mirror(function(name, method){
+ Element.Prototype[name] = method;
+ });
+}
+
+Element.Constructors = {};
+
+
+
+var IFrame = new Type('IFrame', function(){
+ var params = Array.link(arguments, {
+ properties: Type.isObject,
+ iframe: function(obj){
+ return (obj != null);
+ }
+ });
+
+ var props = params.properties || {}, iframe;
+ if (params.iframe) iframe = document.id(params.iframe);
+ var onload = props.onload || function(){};
+ delete props.onload;
+ props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick();
+ iframe = new Element(iframe || 'iframe', props);
+
+ var onLoad = function(){
+ onload.call(iframe.contentWindow);
+ };
+
+ if (window.frames[props.id]) onLoad();
+ else iframe.addListener('load', onLoad);
+ return iframe;
+});
+
+var Elements = this.Elements = function(nodes){
+ if (nodes && nodes.length){
+ var uniques = {}, node;
+ for (var i = 0; node = nodes[i++];){
+ var uid = Slick.uidOf(node);
+ if (!uniques[uid]){
+ uniques[uid] = true;
+ this.push(node);
+ }
+ }
+ }
+};
+
+Elements.prototype = {length: 0};
+Elements.parent = Array;
+
+new Type('Elements', Elements).implement({
+
+ filter: function(filter, bind){
+ if (!filter) return this;
+ return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){
+ return item.match(filter);
+ } : filter, bind));
+ }.protect(),
+
+ push: function(){
+ var length = this.length;
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) this[length++] = item;
+ }
+ return (this.length = length);
+ }.protect(),
+
+ unshift: function(){
+ var items = [];
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = document.id(arguments[i]);
+ if (item) items.push(item);
+ }
+ return Array.prototype.unshift.apply(this, items);
+ }.protect(),
+
+ concat: function(){
+ var newElements = new Elements(this);
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var item = arguments[i];
+ if (Type.isEnumerable(item)) newElements.append(item);
+ else newElements.push(item);
+ }
+ return newElements;
+ }.protect(),
+
+ append: function(collection){
+ for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]);
+ return this;
+ }.protect(),
+
+ empty: function(){
+ while (this.length) delete this[--this.length];
+ return this;
+ }.protect()
+
+});
+
+
+
+(function(){
+
+// FF, IE
+var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2};
+
+splice.call(object, 1, 1);
+if (object[1] == 1) Elements.implement('splice', function(){
+ var length = this.length;
+ var result = splice.apply(this, arguments);
+ while (length >= this.length) delete this[length--];
+ return result;
+}.protect());
+
+Array.forEachMethod(function(method, name){
+ Elements.implement(name, method);
+});
+
+Array.mirror(Elements);
+
+/*<ltIE8>*/
+var createElementAcceptsHTML;
+try {
+ createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x');
+} catch (e){}
+
+var escapeQuotes = function(html){
+ return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+};
+/*</ltIE8>*/
+
+Document.implement({
+
+ newElement: function(tag, props){
+ if (props && props.checked != null) props.defaultChecked = props.checked;
+ /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8
+ if (createElementAcceptsHTML && props){
+ tag = '<' + tag;
+ if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"';
+ if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"';
+ tag += '>';
+ delete props.name;
+ delete props.type;
+ }
+ /*</ltIE8>*/
+ return this.id(this.createElement(tag)).set(props);
+ }
+
+});
+
+})();
+
+(function(){
+
+Slick.uidOf(window);
+Slick.uidOf(document);
+
+Document.implement({
+
+ newTextNode: function(text){
+ return this.createTextNode(text);
+ },
+
+ getDocument: function(){
+ return this;
+ },
+
+ getWindow: function(){
+ return this.window;
+ },
+
+ id: (function(){
+
+ var types = {
+
+ string: function(id, nocash, doc){
+ id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1'));
+ return (id) ? types.element(id, nocash) : null;
+ },
+
+ element: function(el, nocash){
+ Slick.uidOf(el);
+ if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){
+ var fireEvent = el.fireEvent;
+ // wrapping needed in IE7, or else crash
+ el._fireEvent = function(type, event){
+ return fireEvent(type, event);
+ };
+ Object.append(el, Element.Prototype);
+ }
+ return el;
+ },
+
+ object: function(obj, nocash, doc){
+ if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+ return null;
+ }
+
+ };
+
+ types.textnode = types.whitespace = types.window = types.document = function(zero){
+ return zero;
+ };
+
+ return function(el, nocash, doc){
+ if (el && el.$family && el.uniqueNumber) return el;
+ var type = typeOf(el);
+ return (types[type]) ? types[type](el, nocash, doc || document) : null;
+ };
+
+ })()
+
+});
+
+if (window.$ == null) Window.implement('$', function(el, nc){
+ return document.id(el, nc, this.document);
+});
+
+Window.implement({
+
+ getDocument: function(){
+ return this.document;
+ },
+
+ getWindow: function(){
+ return this;
+ }
+
+});
+
+[Document, Element].invoke('implement', {
+
+ getElements: function(expression){
+ return Slick.search(this, expression, new Elements);
+ },
+
+ getElement: function(expression){
+ return document.id(Slick.find(this, expression));
+ }
+
+});
+
+var contains = {contains: function(element){
+ return Slick.contains(this, element);
+}};
+
+if (!document.contains) Document.implement(contains);
+if (!document.createElement('div').contains) Element.implement(contains);
+
+
+
+// tree walking
+
+var injectCombinator = function(expression, combinator){
+ if (!expression) return combinator;
+
+ expression = Object.clone(Slick.parse(expression));
+
+ var expressions = expression.expressions;
+ for (var i = expressions.length; i--;)
+ expressions[i][0].combinator = combinator;
+
+ return expression;
+};
+
+Object.forEach({
+ getNext: '~',
+ getPrevious: '!~',
+ getParent: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElement(injectCombinator(expression, combinator));
+ });
+});
+
+Object.forEach({
+ getAllNext: '~',
+ getAllPrevious: '!~',
+ getSiblings: '~~',
+ getChildren: '>',
+ getParents: '!'
+}, function(combinator, method){
+ Element.implement(method, function(expression){
+ return this.getElements(injectCombinator(expression, combinator));
+ });
+});
+
+Element.implement({
+
+ getFirst: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]);
+ },
+
+ getLast: function(expression){
+ return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast());
+ },
+
+ getWindow: function(){
+ return this.ownerDocument.window;
+ },
+
+ getDocument: function(){
+ return this.ownerDocument;
+ },
+
+ getElementById: function(id){
+ return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1')));
+ },
+
+ match: function(expression){
+ return !expression || Slick.match(this, expression);
+ }
+
+});
+
+
+
+if (window.$$ == null) Window.implement('$$', function(selector){
+ if (arguments.length == 1){
+ if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements);
+ else if (Type.isEnumerable(selector)) return new Elements(selector);
+ }
+ return new Elements(arguments);
+});
+
+// Inserters
+
+var inserters = {
+
+ before: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element);
+ },
+
+ after: function(context, element){
+ var parent = element.parentNode;
+ if (parent) parent.insertBefore(context, element.nextSibling);
+ },
+
+ bottom: function(context, element){
+ element.appendChild(context);
+ },
+
+ top: function(context, element){
+ element.insertBefore(context, element.firstChild);
+ }
+
+};
+
+inserters.inside = inserters.bottom;
+
+
+
+// getProperty / setProperty
+
+var propertyGetters = {}, propertySetters = {};
+
+// properties
+
+var properties = {};
+Array.forEach([
+ 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan',
+ 'frameBorder', 'rowSpan', 'tabIndex', 'useMap'
+], function(property){
+ properties[property.toLowerCase()] = property;
+});
+
+properties.html = 'innerHTML';
+properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent';
+
+Object.forEach(properties, function(real, key){
+ propertySetters[key] = function(node, value){
+ node[real] = value;
+ };
+ propertyGetters[key] = function(node){
+ return node[real];
+ };
+});
+
+// Booleans
+
+var bools = [
+ 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked',
+ 'disabled', 'readOnly', 'multiple', 'selected', 'noresize',
+ 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay',
+ 'loop'
+];
+
+var booleans = {};
+Array.forEach(bools, function(bool){
+ var lower = bool.toLowerCase();
+ booleans[lower] = bool;
+ propertySetters[lower] = function(node, value){
+ node[bool] = !!value;
+ };
+ propertyGetters[lower] = function(node){
+ return !!node[bool];
+ };
+});
+
+// Special cases
+
+Object.append(propertySetters, {
+
+ 'class': function(node, value){
+ ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value);
+ },
+
+ 'for': function(node, value){
+ ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value);
+ },
+
+ 'style': function(node, value){
+ (node.style) ? node.style.cssText = value : node.setAttribute('style', value);
+ },
+
+ 'value': function(node, value){
+ node.value = (value != null) ? value : '';
+ }
+
+});
+
+propertyGetters['class'] = function(node){
+ return ('className' in node) ? node.className || null : node.getAttribute('class');
+};
+
+/* <webkit> */
+var el = document.createElement('button');
+// IE sets type as readonly and throws
+try { el.type = 'button'; } catch(e){}
+if (el.type != 'button') propertySetters.type = function(node, value){
+ node.setAttribute('type', value);
+};
+el = null;
+/* </webkit> */
+
+/*<IE>*/
+var input = document.createElement('input');
+input.value = 't';
+input.type = 'submit';
+if (input.value != 't') propertySetters.type = function(node, type){
+ var value = node.value;
+ node.type = type;
+ node.value = value;
+};
+input = null;
+/*</IE>*/
+
+/* getProperty, setProperty */
+
+/* <ltIE9> */
+var pollutesGetAttribute = (function(div){
+ div.random = 'attribute';
+ return (div.getAttribute('random') == 'attribute');
+})(document.createElement('div'));
+
+var hasCloneBug = (function(test){
+ test.innerHTML = '<object><param name="should_fix" value="the unknown"></object>';
+ return test.cloneNode(true).firstChild.childNodes.length != 1;
+})(document.createElement('div'));
+/* </ltIE9> */
+
+var hasClassList = !!document.createElement('div').classList;
+
+var classes = function(className){
+ var classNames = (className || '').clean().split(" "), uniques = {};
+ return classNames.filter(function(className){
+ if (className !== "" && !uniques[className]) return uniques[className] = className;
+ });
+};
+
+var addToClassList = function(name){
+ this.classList.add(name);
+};
+
+var removeFromClassList = function(name){
+ this.classList.remove(name);
+};
+
+Element.implement({
+
+ setProperty: function(name, value){
+ var setter = propertySetters[name.toLowerCase()];
+ if (setter){
+ setter(this, value);
+ } else {
+ /* <ltIE9> */
+ var attributeWhiteList;
+ if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ /* </ltIE9> */
+
+ if (value == null){
+ this.removeAttribute(name);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) delete attributeWhiteList[name];
+ /* </ltIE9> */
+ } else {
+ this.setAttribute(name, '' + value);
+ /* <ltIE9> */
+ if (pollutesGetAttribute) attributeWhiteList[name] = true;
+ /* </ltIE9> */
+ }
+ }
+ return this;
+ },
+
+ setProperties: function(attributes){
+ for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+ return this;
+ },
+
+ getProperty: function(name){
+ var getter = propertyGetters[name.toLowerCase()];
+ if (getter) return getter(this);
+ /* <ltIE9> */
+ if (pollutesGetAttribute){
+ var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {});
+ if (!attr) return null;
+ if (attr.expando && !attributeWhiteList[name]){
+ var outer = this.outerHTML;
+ // segment by the opening tag and find mention of attribute name
+ if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null;
+ attributeWhiteList[name] = true;
+ }
+ }
+ /* </ltIE9> */
+ var result = Slick.getAttribute(this, name);
+ return (!result && !Slick.hasAttribute(this, name)) ? null : result;
+ },
+
+ getProperties: function(){
+ var args = Array.from(arguments);
+ return args.map(this.getProperty, this).associate(args);
+ },
+
+ removeProperty: function(name){
+ return this.setProperty(name, null);
+ },
+
+ removeProperties: function(){
+ Array.each(arguments, this.removeProperty, this);
+ return this;
+ },
+
+ set: function(prop, value){
+ var property = Element.Properties[prop];
+ (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value);
+ }.overloadSetter(),
+
+ get: function(prop){
+ var property = Element.Properties[prop];
+ return (property && property.get) ? property.get.apply(this) : this.getProperty(prop);
+ }.overloadGetter(),
+
+ erase: function(prop){
+ var property = Element.Properties[prop];
+ (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+ return this;
+ },
+
+ hasClass: hasClassList ? function(className){
+ return this.classList.contains(className);
+ } : function(className){
+ return this.className.clean().contains(className, ' ');
+ },
+
+ addClass: hasClassList ? function(className){
+ classes(className).forEach(addToClassList, this);
+ return this;
+ } : function(className){
+ this.className = classes(className + ' ' + this.className).join(' ');
+ return this;
+ },
+
+ removeClass: hasClassList ? function(className){
+ classes(className).forEach(removeFromClassList, this);
+ return this;
+ } : function(className){
+ var classNames = classes(this.className);
+ classes(className).forEach(classNames.erase, classNames);
+ this.className = classNames.join(' ');
+ return this;
+ },
+
+ toggleClass: function(className, force){
+ if (force == null) force = !this.hasClass(className);
+ return (force) ? this.addClass(className) : this.removeClass(className);
+ },
+
+ adopt: function(){
+ var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length;
+ if (length > 1) parent = fragment = document.createDocumentFragment();
+
+ for (var i = 0; i < length; i++){
+ var element = document.id(elements[i], true);
+ if (element) parent.appendChild(element);
+ }
+
+ if (fragment) this.appendChild(fragment);
+
+ return this;
+ },
+
+ appendText: function(text, where){
+ return this.grab(this.getDocument().newTextNode(text), where);
+ },
+
+ grab: function(el, where){
+ inserters[where || 'bottom'](document.id(el, true), this);
+ return this;
+ },
+
+ inject: function(el, where){
+ inserters[where || 'bottom'](this, document.id(el, true));
+ return this;
+ },
+
+ replaces: function(el){
+ el = document.id(el, true);
+ el.parentNode.replaceChild(this, el);
+ return this;
+ },
+
+ wraps: function(el, where){
+ el = document.id(el, true);
+ return this.replaces(el).grab(el, where);
+ },
+
+ getSelected: function(){
+ this.selectedIndex; // Safari 3.2.1
+ return new Elements(Array.from(this.options).filter(function(option){
+ return option.selected;
+ }));
+ },
+
+ toQueryString: function(){
+ var queryString = [];
+ this.getElements('input, select, textarea').each(function(el){
+ var type = el.type;
+ if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return;
+
+ var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){
+ // IE
+ return document.id(opt).get('value');
+ }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value');
+
+ Array.from(value).each(function(val){
+ if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val));
+ });
+ });
+ return queryString.join('&');
+ }
+
+});
+
+
+// appendHTML
+
+var appendInserters = {
+ before: 'beforeBegin',
+ after: 'afterEnd',
+ bottom: 'beforeEnd',
+ top: 'afterBegin',
+ inside: 'beforeEnd'
+};
+
+Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){
+ this.insertAdjacentHTML(appendInserters[where || 'bottom'], html);
+ return this;
+} : function(html, where){
+ var temp = new Element('div', {html: html}),
+ children = temp.childNodes,
+ fragment = temp.firstChild;
+
+ if (!fragment) return this;
+ if (children.length > 1){
+ fragment = document.createDocumentFragment();
+ for (var i = 0, l = children.length; i < l; i++){
+ fragment.appendChild(children[i]);
+ }
+ }
+
+ inserters[where || 'bottom'](fragment, this);
+ return this;
+});
+
+var collected = {}, storage = {};
+
+var get = function(uid){
+ return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item){
+ var uid = item.uniqueNumber;
+ if (item.removeEvents) item.removeEvents();
+ if (item.clearAttributes) item.clearAttributes();
+ if (uid != null){
+ delete collected[uid];
+ delete storage[uid];
+ }
+ return item;
+};
+
+var formProps = {input: 'checked', option: 'selected', textarea: 'value'};
+
+Element.implement({
+
+ destroy: function(){
+ var children = clean(this).getElementsByTagName('*');
+ Array.each(children, clean);
+ Element.dispose(this);
+ return null;
+ },
+
+ empty: function(){
+ Array.from(this.childNodes).each(Element.dispose);
+ return this;
+ },
+
+ dispose: function(){
+ return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+ },
+
+ clone: function(contents, keepid){
+ contents = contents !== false;
+ var clone = this.cloneNode(contents), ce = [clone], te = [this], i;
+
+ if (contents){
+ ce.append(Array.from(clone.getElementsByTagName('*')));
+ te.append(Array.from(this.getElementsByTagName('*')));
+ }
+
+ for (i = ce.length; i--;){
+ var node = ce[i], element = te[i];
+ if (!keepid) node.removeAttribute('id');
+ /*<ltIE9>*/
+ if (node.clearAttributes){
+ node.clearAttributes();
+ node.mergeAttributes(element);
+ node.removeAttribute('uniqueNumber');
+ if (node.options){
+ var no = node.options, eo = element.options;
+ for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+ }
+ }
+ /*</ltIE9>*/
+ var prop = formProps[element.tagName.toLowerCase()];
+ if (prop && element[prop]) node[prop] = element[prop];
+ }
+
+ /*<ltIE9>*/
+ if (hasCloneBug){
+ var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object');
+ for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML;
+ }
+ /*</ltIE9>*/
+ return document.id(clone);
+ }
+
+});
+
+[Element, Window, Document].invoke('implement', {
+
+ addListener: function(type, fn){
+ if (window.attachEvent && !window.addEventListener){
+ collected[Slick.uidOf(this)] = this;
+ }
+ if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]);
+ else this.attachEvent('on' + type, fn);
+ return this;
+ },
+
+ removeListener: function(type, fn){
+ if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]);
+ else this.detachEvent('on' + type, fn);
+ return this;
+ },
+
+ retrieve: function(property, dflt){
+ var storage = get(Slick.uidOf(this)), prop = storage[property];
+ if (dflt != null && prop == null) prop = storage[property] = dflt;
+ return prop != null ? prop : null;
+ },
+
+ store: function(property, value){
+ var storage = get(Slick.uidOf(this));
+ storage[property] = value;
+ return this;
+ },
+
+ eliminate: function(property){
+ var storage = get(Slick.uidOf(this));
+ delete storage[property];
+ return this;
+ }
+
+});
+
+/*<ltIE9>*/
+if (window.attachEvent && !window.addEventListener){
+ var gc = function(){
+ Object.each(collected, clean);
+ if (window.CollectGarbage) CollectGarbage();
+ window.removeListener('unload', gc);
+ }
+ window.addListener('unload', gc);
+}
+/*</ltIE9>*/
+
+Element.Properties = {};
+
+
+
+Element.Properties.style = {
+
+ set: function(style){
+ this.style.cssText = style;
+ },
+
+ get: function(){
+ return this.style.cssText;
+ },
+
+ erase: function(){
+ this.style.cssText = '';
+ }
+
+};
+
+Element.Properties.tag = {
+
+ get: function(){
+ return this.tagName.toLowerCase();
+ }
+
+};
+
+Element.Properties.html = {
+
+ set: function(html){
+ if (html == null) html = '';
+ else if (typeOf(html) == 'array') html = html.join('');
+ this.innerHTML = html;
+ },
+
+ erase: function(){
+ this.innerHTML = '';
+ }
+
+};
+
+var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true;
+
+/*<ltIE9>*/
+// technique by jdbarlett - http://jdbartlett.com/innershiv/
+var div = document.createElement('div');
+div.innerHTML = '<nav></nav>';
+supportsHTML5Elements = (div.childNodes.length == 1);
+if (!supportsHTML5Elements){
+ var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '),
+ fragment = document.createDocumentFragment(), l = tags.length;
+ while (l--) fragment.createElement(tags[l]);
+}
+div = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+supportsTableInnerHTML = Function.attempt(function(){
+ var table = document.createElement('table');
+ table.innerHTML = '<tr><td></td></tr>';
+ return true;
+});
+
+/*<ltFF4>*/
+var tr = document.createElement('tr'), html = '<td></td>';
+tr.innerHTML = html;
+supportsTRInnerHTML = (tr.innerHTML == html);
+tr = null;
+/*</ltFF4>*/
+
+if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){
+
+ Element.Properties.html.set = (function(set){
+
+ var translations = {
+ table: [1, '<table>', '</table>'],
+ select: [1, '<select>', '</select>'],
+ tbody: [2, '<table><tbody>', '</tbody></table>'],
+ tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+ };
+
+ translations.thead = translations.tfoot = translations.tbody;
+
+ return function(html){
+ var wrap = translations[this.get('tag')];
+ if (!wrap && !supportsHTML5Elements) wrap = [0, '', ''];
+ if (!wrap) return set.call(this, html);
+
+ var level = wrap[0], wrapper = document.createElement('div'), target = wrapper;
+ if (!supportsHTML5Elements) fragment.appendChild(wrapper);
+ wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join('');
+ while (level--) target = target.firstChild;
+ this.empty().adopt(target.childNodes);
+ if (!supportsHTML5Elements) fragment.removeChild(wrapper);
+ wrapper = null;
+ };
+
+ })(Element.Properties.html.set);
+}
+/*</IE>*/
+
+/*<ltIE9>*/
+var testForm = document.createElement('form');
+testForm.innerHTML = '<select><option>s</option></select>';
+
+if (testForm.firstChild.value != 's') Element.Properties.value = {
+
+ set: function(value){
+ var tag = this.get('tag');
+ if (tag != 'select') return this.setProperty('value', value);
+ var options = this.getElements('option');
+ value = String(value);
+ for (var i = 0; i < options.length; i++){
+ var option = options[i],
+ attr = option.getAttributeNode('value'),
+ optionValue = (attr && attr.specified) ? option.value : option.get('text');
+ if (optionValue === value) return option.selected = true;
+ }
+ },
+
+ get: function(){
+ var option = this, tag = option.get('tag');
+
+ if (tag != 'select' && tag != 'option') return this.getProperty('value');
+
+ if (tag == 'select' && !(option = option.getSelected()[0])) return '';
+
+ var attr = option.getAttributeNode('value');
+ return (attr && attr.specified) ? option.value : option.get('text');
+ }
+
+};
+testForm = null;
+/*</ltIE9>*/
+
+/*<IE>*/
+if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = {
+ set: function(id){
+ this.id = this.getAttributeNode('id').value = id;
+ },
+ get: function(){
+ return this.id || null;
+ },
+ erase: function(){
+ this.id = this.getAttributeNode('id').value = '';
+ }
+};
+/*</IE>*/
+
+})();
+
+
+/*
+---
+
+name: Element.Style
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires: Element
+
+provides: Element.Style
+
+...
+*/
+
+(function(){
+
+var html = document.html, el;
+
+//<ltIE9>
+// Check for oldIE, which does not remove styles when they're set to null
+el = document.createElement('div');
+el.style.color = 'red';
+el.style.color = null;
+var doesNotRemoveStyles = el.style.color == 'red';
+
+// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color)
+var border = '1px solid #123abc';
+el.style.border = border;
+var returnsBordersInWrongOrder = el.style.border != border;
+el = null;
+//</ltIE9>
+
+var hasGetComputedStyle = !!window.getComputedStyle;
+
+Element.Properties.styles = {set: function(styles){
+ this.setStyles(styles);
+}};
+
+var hasOpacity = (html.style.opacity != null),
+ hasFilter = (html.style.filter != null),
+ reAlpha = /alpha\(opacity=([\d.]+)\)/i;
+
+var setVisibility = function(element, opacity){
+ element.store('$opacity', opacity);
+ element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden';
+};
+
+//<ltIE9>
+var setFilter = function(element, regexp, value){
+ var style = element.style,
+ filter = style.filter || element.getComputedStyle('filter') || '';
+ style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim();
+ if (!style.filter) style.removeAttribute('filter');
+};
+//</ltIE9>
+
+var setOpacity = (hasOpacity ? function(element, opacity){
+ element.style.opacity = opacity;
+} : (hasFilter ? function(element, opacity){
+ if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1;
+ if (opacity == null || opacity == 1){
+ setFilter(element, reAlpha, '');
+ if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)');
+ } else {
+ setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')');
+ }
+} : setVisibility));
+
+var getOpacity = (hasOpacity ? function(element){
+ var opacity = element.style.opacity || element.getComputedStyle('opacity');
+ return (opacity == '') ? 1 : opacity.toFloat();
+} : (hasFilter ? function(element){
+ var filter = (element.style.filter || element.getComputedStyle('filter')),
+ opacity;
+ if (filter) opacity = filter.match(reAlpha);
+ return (opacity == null || filter == null) ? 1 : (opacity[1] / 100);
+} : function(element){
+ var opacity = element.retrieve('$opacity');
+ if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1);
+ return opacity;
+}));
+
+var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat',
+ namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'},
+ hasBackgroundPositionXY = (html.style.backgroundPositionX != null);
+
+//<ltIE9>
+var removeStyle = function(style, property){
+ if (property == 'backgroundPosition'){
+ style.removeAttribute(property + 'X');
+ property += 'Y';
+ }
+ style.removeAttribute(property);
+};
+//</ltIE9>
+
+Element.implement({
+
+ getComputedStyle: function(property){
+ if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()];
+ var defaultView = Element.getDocument(this).defaultView,
+ computed = defaultView ? defaultView.getComputedStyle(this, null) : null;
+ return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : '';
+ },
+
+ setStyle: function(property, value){
+ if (property == 'opacity'){
+ if (value != null) value = parseFloat(value);
+ setOpacity(this, value);
+ return this;
+ }
+ property = (property == 'float' ? floatName : property).camelCase();
+ if (typeOf(value) != 'string'){
+ var map = (Element.Styles[property] || '@').split(' ');
+ value = Array.from(value).map(function(val, i){
+ if (!map[i]) return '';
+ return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+ }).join(' ');
+ } else if (value == String(Number(value))){
+ value = Math.round(value);
+ }
+ this.style[property] = value;
+ //<ltIE9>
+ if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){
+ removeStyle(this.style, property);
+ }
+ //</ltIE9>
+ return this;
+ },
+
+ getStyle: function(property){
+ if (property == 'opacity') return getOpacity(this);
+ property = (property == 'float' ? floatName : property).camelCase();
+ var result = this.style[property];
+ if (!result || property == 'zIndex'){
+ if (Element.ShortStyles.hasOwnProperty(property)){
+ result = [];
+ for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s));
+ return result.join(' ');
+ }
+ result = this.getComputedStyle(property);
+ }
+ if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){
+ return result.replace(/(top|right|bottom|left)/g, function(position){
+ return namedPositions[position];
+ }) || '0px';
+ }
+ if (!result && property == 'backgroundPosition') return '0px 0px';
+ if (result){
+ result = String(result);
+ var color = result.match(/rgba?\([\d\s,]+\)/);
+ if (color) result = result.replace(color[0], color[0].rgbToHex());
+ }
+ if (!hasGetComputedStyle && !this.style[property]){
+ if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){
+ var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+ values.each(function(value){
+ size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+ }, this);
+ return this['offset' + property.capitalize()] - size + 'px';
+ }
+ if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){
+ return '0px';
+ }
+ }
+ //<ltIE9>
+ if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){
+ return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1');
+ }
+ //</ltIE9>
+ return result;
+ },
+
+ setStyles: function(styles){
+ for (var style in styles) this.setStyle(style, styles[style]);
+ return this;
+ },
+
+ getStyles: function(){
+ var result = {};
+ Array.flatten(arguments).each(function(key){
+ result[key] = this.getStyle(key);
+ }, this);
+ return result;
+ }
+
+});
+
+Element.Styles = {
+ left: '@px', top: '@px', bottom: '@px', right: '@px',
+ width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+ backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+ fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+ margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+ borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+ zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+};
+
+
+
+
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+ var Short = Element.ShortStyles;
+ var All = Element.Styles;
+ ['margin', 'padding'].each(function(style){
+ var sd = style + direction;
+ Short[style][sd] = All[sd] = '@px';
+ });
+ var bd = 'border' + direction;
+ Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+ var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+ Short[bd] = {};
+ Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+ Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+ Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+
+if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'};
+})();
+
+
+/*
+---
+
+name: Element.Event
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary.
+
+license: MIT-style license.
+
+requires: [Element, Event]
+
+provides: Element.Event
+
+...
+*/
+
+(function(){
+
+Element.Properties.events = {set: function(events){
+ this.addEvents(events);
+}};
+
+[Element, Window, Document].invoke('implement', {
+
+ addEvent: function(type, fn){
+ var events = this.retrieve('events', {});
+ if (!events[type]) events[type] = {keys: [], values: []};
+ if (events[type].keys.contains(fn)) return this;
+ events[type].keys.push(fn);
+ var realType = type,
+ custom = Element.Events[type],
+ condition = fn,
+ self = this;
+ if (custom){
+ if (custom.onAdd) custom.onAdd.call(this, fn, type);
+ if (custom.condition){
+ condition = function(event){
+ if (custom.condition.call(this, event, type)) return fn.call(this, event);
+ return true;
+ };
+ }
+ if (custom.base) realType = Function.from(custom.base).call(this, type);
+ }
+ var defn = function(){
+ return fn.call(self);
+ };
+ var nativeEvent = Element.NativeEvents[realType];
+ if (nativeEvent){
+ if (nativeEvent == 2){
+ defn = function(event){
+ event = new DOMEvent(event, self.getWindow());
+ if (condition.call(self, event) === false) event.stop();
+ };
+ }
+ this.addListener(realType, defn, arguments[2]);
+ }
+ events[type].values.push(defn);
+ return this;
+ },
+
+ removeEvent: function(type, fn){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ var list = events[type];
+ var index = list.keys.indexOf(fn);
+ if (index == -1) return this;
+ var value = list.values[index];
+ delete list.keys[index];
+ delete list.values[index];
+ var custom = Element.Events[type];
+ if (custom){
+ if (custom.onRemove) custom.onRemove.call(this, fn, type);
+ if (custom.base) type = Function.from(custom.base).call(this, type);
+ }
+ return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this;
+ },
+
+ addEvents: function(events){
+ for (var event in events) this.addEvent(event, events[event]);
+ return this;
+ },
+
+ removeEvents: function(events){
+ var type;
+ if (typeOf(events) == 'object'){
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ var attached = this.retrieve('events');
+ if (!attached) return this;
+ if (!events){
+ for (type in attached) this.removeEvents(type);
+ this.eliminate('events');
+ } else if (attached[events]){
+ attached[events].keys.each(function(fn){
+ this.removeEvent(events, fn);
+ }, this);
+ delete attached[events];
+ }
+ return this;
+ },
+
+ fireEvent: function(type, args, delay){
+ var events = this.retrieve('events');
+ if (!events || !events[type]) return this;
+ args = Array.from(args);
+
+ events[type].keys.each(function(fn){
+ if (delay) fn.delay(delay, this, args);
+ else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+
+ cloneEvents: function(from, type){
+ from = document.id(from);
+ var events = from.retrieve('events');
+ if (!events) return this;
+ if (!type){
+ for (var eventType in events) this.cloneEvents(from, eventType);
+ } else if (events[type]){
+ events[type].keys.each(function(fn){
+ this.addEvent(type, fn);
+ }, this);
+ }
+ return this;
+ }
+
+});
+
+Element.NativeEvents = {
+ click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+ mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+ mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+ keydown: 2, keypress: 2, keyup: 2, //keyboard
+ orientationchange: 2, // mobile
+ touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch
+ gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture
+ focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements
+ load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+ hashchange: 1, popstate: 2, // history
+ error: 1, abort: 1, scroll: 1 //misc
+};
+
+Element.Events = {
+ mousewheel: {
+ base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll'
+ }
+};
+
+var check = function(event){
+ var related = event.relatedTarget;
+ if (related == null) return true;
+ if (!related) return false;
+ return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related));
+};
+
+if ('onmouseenter' in document.documentElement){
+ Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2;
+ Element.MouseenterCheck = check;
+} else {
+ Element.Events.mouseenter = {
+ base: 'mouseover',
+ condition: check
+ };
+
+ Element.Events.mouseleave = {
+ base: 'mouseout',
+ condition: check
+ };
+}
+
+/*<ltIE9>*/
+if (!window.addEventListener){
+ Element.NativeEvents.propertychange = 2;
+ Element.Events.change = {
+ base: function(){
+ var type = this.type;
+ return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change';
+ },
+ condition: function(event){
+ return event.type != 'propertychange' || event.event.propertyName == 'checked';
+ }
+ };
+}
+/*</ltIE9>*/
+
+
+
+})();
+
+
+/*
+---
+
+name: Element.Delegation
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+license: MIT-style license.
+
+requires: [Element.Event]
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(){
+
+var eventListenerSupport = !!window.addEventListener;
+
+Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2;
+
+var bubbleUp = function(self, match, fn, event, target){
+ while (target && target != self){
+ if (match(target, event)) return fn.call(target, event, target);
+ target = document.id(target.parentNode);
+ }
+};
+
+var map = {
+ mouseenter: {
+ base: 'mouseover',
+ condition: Element.MouseenterCheck
+ },
+ mouseleave: {
+ base: 'mouseout',
+ condition: Element.MouseenterCheck
+ },
+ focus: {
+ base: 'focus' + (eventListenerSupport ? '' : 'in'),
+ capture: true
+ },
+ blur: {
+ base: eventListenerSupport ? 'blur' : 'focusout',
+ capture: true
+ }
+};
+
+/*<ltIE9>*/
+var _key = '$delegation:';
+var formObserver = function(type){
+
+ return {
+
+ base: 'focusin',
+
+ remove: function(self, uid){
+ var list = self.retrieve(_key + type + 'listeners', {})[uid];
+ if (list && list.forms) for (var i = list.forms.length; i--;){
+ list.forms[i].removeEvent(type, list.fns[i]);
+ }
+ },
+
+ listen: function(self, match, fn, event, target, uid){
+ var form = (target.get('tag') == 'form') ? target : event.target.getParent('form');
+ if (!form) return;
+
+ var listeners = self.retrieve(_key + type + 'listeners', {}),
+ listener = listeners[uid] || {forms: [], fns: []},
+ forms = listener.forms, fns = listener.fns;
+
+ if (forms.indexOf(form) != -1) return;
+ forms.push(form);
+
+ var _fn = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ form.addEvent(type, _fn);
+ fns.push(_fn);
+
+ listeners[uid] = listener;
+ self.store(_key + type + 'listeners', listeners);
+ }
+ };
+};
+
+var inputObserver = function(type){
+ return {
+ base: 'focusin',
+ listen: function(self, match, fn, event, target){
+ var events = {blur: function(){
+ this.removeEvents(events);
+ }};
+ events[type] = function(event){
+ bubbleUp(self, match, fn, event, target);
+ };
+ event.target.addEvents(events);
+ }
+ };
+};
+
+if (!eventListenerSupport) Object.append(map, {
+ submit: formObserver('submit'),
+ reset: formObserver('reset'),
+ change: inputObserver('change'),
+ select: inputObserver('select')
+});
+/*</ltIE9>*/
+
+var proto = Element.prototype,
+ addEvent = proto.addEvent,
+ removeEvent = proto.removeEvent;
+
+var relay = function(old, method){
+ return function(type, fn, useCapture){
+ if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture);
+ var parsed = Slick.parse(type).expressions[0][0];
+ if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture);
+ var newType = parsed.tag;
+ parsed.pseudos.slice(1).each(function(pseudo){
+ newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : '');
+ });
+ old.call(this, type, fn);
+ return method.call(this, newType, parsed.pseudos[0].value, fn);
+ };
+};
+
+var delegation = {
+
+ addEvent: function(type, match, fn){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (stored) for (var _uid in stored){
+ if (stored[_uid].fn == fn && stored[_uid].match == match) return this;
+ }
+
+ var _type = type, _match = match, _fn = fn, _map = map[type] || {};
+ type = _map.base || _type;
+
+ match = function(target){
+ return Slick.match(target, _match);
+ };
+
+ var elementEvent = Element.Events[_type];
+ if (_map.condition || elementEvent && elementEvent.condition){
+ var __match = match, condition = _map.condition || elementEvent.condition;
+ match = function(target, event){
+ return __match(target, event) && condition.call(target, event, type);
+ };
+ }
+
+ var self = this, uid = String.uniqueID();
+ var delegator = _map.listen ? function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) _map.listen(self, match, fn, event, target, uid);
+ } : function(event, target){
+ if (!target && event && event.target) target = event.target;
+ if (target) bubbleUp(self, match, fn, event, target);
+ };
+
+ if (!stored) stored = {};
+ stored[uid] = {
+ match: _match,
+ fn: _fn,
+ delegator: delegator
+ };
+ storage[_type] = stored;
+ return addEvent.call(this, type, delegator, _map.capture);
+ },
+
+ removeEvent: function(type, match, fn, _uid){
+ var storage = this.retrieve('$delegates', {}), stored = storage[type];
+ if (!stored) return this;
+
+ if (_uid){
+ var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {};
+ type = _map.base || _type;
+ if (_map.remove) _map.remove(this, _uid);
+ delete stored[_uid];
+ storage[_type] = stored;
+ return removeEvent.call(this, type, delegator, _map.capture);
+ }
+
+ var __uid, s;
+ if (fn) for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid);
+ } else for (__uid in stored){
+ s = stored[__uid];
+ if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid);
+ }
+ return this;
+ }
+
+};
+
+[Element, Window, Document].invoke('implement', {
+ addEvent: relay(addEvent, delegation.addEvent),
+ removeEvent: relay(removeEvent, delegation.removeEvent)
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Dimensions
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+ - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+ - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires: [Element, Element.Style]
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+var element = document.createElement('div'),
+ child = document.createElement('div');
+element.style.height = '0';
+element.appendChild(child);
+var brokenOffsetParent = (child.offsetParent === element);
+element = child = null;
+
+var isOffset = function(el){
+ return styleString(el, 'position') != 'static' || isBody(el);
+};
+
+var isOffsetStatic = function(el){
+ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName);
+};
+
+Element.implement({
+
+ scrollTo: function(x, y){
+ if (isBody(this)){
+ this.getWindow().scrollTo(x, y);
+ } else {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+ return this;
+ },
+
+ getSize: function(){
+ if (isBody(this)) return this.getWindow().getSize();
+ return {x: this.offsetWidth, y: this.offsetHeight};
+ },
+
+ getScrollSize: function(){
+ if (isBody(this)) return this.getWindow().getScrollSize();
+ return {x: this.scrollWidth, y: this.scrollHeight};
+ },
+
+ getScroll: function(){
+ if (isBody(this)) return this.getWindow().getScroll();
+ return {x: this.scrollLeft, y: this.scrollTop};
+ },
+
+ getScrolls: function(){
+ var element = this.parentNode, position = {x: 0, y: 0};
+ while (element && !isBody(element)){
+ position.x += element.scrollLeft;
+ position.y += element.scrollTop;
+ element = element.parentNode;
+ }
+ return position;
+ },
+
+ getOffsetParent: brokenOffsetParent ? function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset;
+ while ((element = element.parentNode)){
+ if (isOffsetCheck(element)) return element;
+ }
+ return null;
+ } : function(){
+ var element = this;
+ if (isBody(element) || styleString(element, 'position') == 'fixed') return null;
+
+ try {
+ return element.offsetParent;
+ } catch(e) {}
+ return null;
+ },
+
+ getOffsets: function(){
+ var hasGetBoundingClientRect = this.getBoundingClientRect;
+
+ if (hasGetBoundingClientRect){
+ var bound = this.getBoundingClientRect(),
+ html = document.id(this.getDocument().documentElement),
+ htmlScroll = html.getScroll(),
+ elemScrolls = this.getScrolls(),
+ isFixed = (styleString(this, 'position') == 'fixed');
+
+ return {
+ x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+ y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+ };
+ }
+
+ var element = this, position = {x: 0, y: 0};
+ if (isBody(this)) return position;
+
+ while (element && !isBody(element)){
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+
+ element = element.offsetParent;
+ }
+
+ return position;
+ },
+
+ getPosition: function(relative){
+ var offset = this.getOffsets(),
+ scroll = this.getScrolls();
+ var position = {
+ x: offset.x - scroll.x,
+ y: offset.y - scroll.y
+ };
+
+ if (relative && (relative = document.id(relative))){
+ var relativePosition = relative.getPosition();
+ return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)};
+ }
+ return position;
+ },
+
+ getCoordinates: function(element){
+ if (isBody(this)) return this.getWindow().getCoordinates();
+ var position = this.getPosition(element),
+ size = this.getSize();
+ var obj = {
+ left: position.x,
+ top: position.y,
+ width: size.x,
+ height: size.y
+ };
+ obj.right = obj.left + obj.width;
+ obj.bottom = obj.top + obj.height;
+ return obj;
+ },
+
+ computePosition: function(obj){
+ return {
+ left: obj.x - styleNumber(this, 'margin-left'),
+ top: obj.y - styleNumber(this, 'margin-top')
+ };
+ },
+
+ setPosition: function(obj){
+ return this.setStyles(this.computePosition(obj));
+ }
+
+});
+
+
+[Document, Window].invoke('implement', {
+
+ getSize: function(){
+ var doc = getCompatElement(this);
+ return {x: doc.clientWidth, y: doc.clientHeight};
+ },
+
+ getScroll: function(){
+ var win = this.getWindow(), doc = getCompatElement(this);
+ return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+ },
+
+ getScrollSize: function(){
+ var doc = getCompatElement(this),
+ min = this.getSize(),
+ body = this.getDocument().body;
+
+ return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)};
+ },
+
+ getPosition: function(){
+ return {x: 0, y: 0};
+ },
+
+ getCoordinates: function(){
+ var size = this.getSize();
+ return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+ }
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+ return styleString(element, style).toInt() || 0;
+}
+
+function borderBox(element){
+ return styleString(element, '-moz-box-sizing') == 'border-box';
+}
+
+function topBorder(element){
+ return styleNumber(element, 'border-top-width');
+}
+
+function leftBorder(element){
+ return styleNumber(element, 'border-left-width');
+}
+
+function isBody(element){
+ return (/^(?:body|html)$/i).test(element.tagName);
+}
+
+function getCompatElement(element){
+ var doc = element.getDocument();
+ return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+}
+
+})();
+
+//aliases
+Element.alias({position: 'setPosition'}); //compatability
+
+[Window, Document, Element].invoke('implement', {
+
+ getHeight: function(){
+ return this.getSize().y;
+ },
+
+ getWidth: function(){
+ return this.getSize().x;
+ },
+
+ getScrollTop: function(){
+ return this.getScroll().y;
+ },
+
+ getScrollLeft: function(){
+ return this.getScroll().x;
+ },
+
+ getScrollHeight: function(){
+ return this.getScrollSize().y;
+ },
+
+ getScrollWidth: function(){
+ return this.getScrollSize().x;
+ },
+
+ getTop: function(){
+ return this.getPosition().y;
+ },
+
+ getLeft: function(){
+ return this.getPosition().x;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires: [Chain, Events, Options]
+
+provides: Fx
+
+...
+*/
+
+(function(){
+
+var Fx = this.Fx = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {
+ /*
+ onStart: nil,
+ onCancel: nil,
+ onComplete: nil,
+ */
+ fps: 60,
+ unit: false,
+ duration: 500,
+ frames: null,
+ frameSkip: true,
+ link: 'ignore'
+ },
+
+ initialize: function(options){
+ this.subject = this.subject || this;
+ this.setOptions(options);
+ },
+
+ getTransition: function(){
+ return function(p){
+ return -(Math.cos(Math.PI * p) - 1) / 2;
+ };
+ },
+
+ step: function(now){
+ if (this.options.frameSkip){
+ var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
+ this.time = now;
+ this.frame += frames;
+ } else {
+ this.frame++;
+ }
+
+ if (this.frame < this.frames){
+ var delta = this.transition(this.frame / this.frames);
+ this.set(this.compute(this.from, this.to, delta));
+ } else {
+ this.frame = this.frames;
+ this.set(this.compute(this.from, this.to, 1));
+ this.stop();
+ }
+ },
+
+ set: function(now){
+ return now;
+ },
+
+ compute: function(from, to, delta){
+ return Fx.compute(from, to, delta);
+ },
+
+ check: function(){
+ if (!this.isRunning()) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ start: function(from, to){
+ if (!this.check(from, to)) return this;
+ this.from = from;
+ this.to = to;
+ this.frame = (this.options.frameSkip) ? 0 : -1;
+ this.time = null;
+ this.transition = this.getTransition();
+ var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration;
+ this.duration = Fx.Durations[duration] || duration.toInt();
+ this.frameInterval = 1000 / fps;
+ this.frames = frames || Math.round(this.duration / this.frameInterval);
+ this.fireEvent('start', this.subject);
+ pushInstance.call(this, fps);
+ return this;
+ },
+
+ stop: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ if (this.frames == this.frame){
+ this.fireEvent('complete', this.subject);
+ if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+ } else {
+ this.fireEvent('stop', this.subject);
+ }
+ }
+ return this;
+ },
+
+ cancel: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ this.frame = this.frames;
+ this.fireEvent('cancel', this.subject).clearChain();
+ }
+ return this;
+ },
+
+ pause: function(){
+ if (this.isRunning()){
+ this.time = null;
+ pullInstance.call(this, this.options.fps);
+ }
+ return this;
+ },
+
+ resume: function(){
+ if (this.isPaused()) pushInstance.call(this, this.options.fps);
+ return this;
+ },
+
+ isRunning: function(){
+ var list = instances[this.options.fps];
+ return list && list.contains(this);
+ },
+
+ isPaused: function(){
+ return (this.frame < this.frames) && !this.isRunning();
+ }
+
+});
+
+Fx.compute = function(from, to, delta){
+ return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+
+// global timers
+
+var instances = {}, timers = {};
+
+var loop = function(){
+ var now = Date.now();
+ for (var i = this.length; i--;){
+ var instance = this[i];
+ if (instance) instance.step(now);
+ }
+};
+
+var pushInstance = function(fps){
+ var list = instances[fps] || (instances[fps] = []);
+ list.push(this);
+ if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list);
+};
+
+var pullInstance = function(fps){
+ var list = instances[fps];
+ if (list){
+ list.erase(this);
+ if (!list.length && timers[fps]){
+ delete instances[fps];
+ timers[fps] = clearInterval(timers[fps]);
+ }
+ }
+};
+
+})();
+
+
+/*
+---
+
+name: Fx.CSS
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires: [Fx, Element.Style]
+
+provides: Fx.CSS
+
+...
+*/
+
+Fx.CSS = new Class({
+
+ Extends: Fx,
+
+ //prepares the base from/to object
+
+ prepare: function(element, property, values){
+ values = Array.from(values);
+ var from = values[0], to = values[1];
+ if (to == null){
+ to = from;
+ from = element.getStyle(property);
+ var unit = this.options.unit;
+ // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299
+ if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){
+ element.setStyle(property, to + unit);
+ var value = element.getComputedStyle(property);
+ // IE and Opera support pixelLeft or pixelWidth
+ if (!(/px$/.test(value))){
+ value = element.style[('pixel-' + property).camelCase()];
+ if (value == null){
+ // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+ var left = element.style.left;
+ element.style.left = to + unit;
+ value = element.style.pixelLeft;
+ element.style.left = left;
+ }
+ }
+ from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0);
+ element.setStyle(property, from + unit);
+ }
+ }
+ return {from: this.parse(from), to: this.parse(to)};
+ },
+
+ //parses a value into an array
+
+ parse: function(value){
+ value = Function.from(value)();
+ value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
+ return value.map(function(val){
+ val = String(val);
+ var found = false;
+ Object.each(Fx.CSS.Parsers, function(parser, key){
+ if (found) return;
+ var parsed = parser.parse(val);
+ if (parsed || parsed === 0) found = {value: parsed, parser: parser};
+ });
+ found = found || {value: val, parser: Fx.CSS.Parsers.String};
+ return found;
+ });
+ },
+
+ //computes by a from and to prepared objects, using their parsers.
+
+ compute: function(from, to, delta){
+ var computed = [];
+ (Math.min(from.length, to.length)).times(function(i){
+ computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+ });
+ computed.$family = Function.from('fx:css:value');
+ return computed;
+ },
+
+ //serves the value as settable
+
+ serve: function(value, unit){
+ if (typeOf(value) != 'fx:css:value') value = this.parse(value);
+ var returned = [];
+ value.each(function(bit){
+ returned = returned.concat(bit.parser.serve(bit.value, unit));
+ });
+ return returned;
+ },
+
+ //renders the change to an element
+
+ render: function(element, property, value, unit){
+ element.setStyle(property, this.serve(value, unit));
+ },
+
+ //searches inside the page css to find the values for a selector
+
+ search: function(selector){
+ if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+ var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
+
+ var searchStyles = function(rules){
+ Array.each(rules, function(rule, i){
+ if (rule.media){
+ searchStyles(rule.rules || rule.cssRules);
+ return;
+ }
+ if (!rule.style) return;
+ var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+ return m.toLowerCase();
+ }) : null;
+ if (!selectorText || !selectorTest.test(selectorText)) return;
+ Object.each(Element.Styles, function(value, style){
+ if (!rule.style[style] || Element.ShortStyles[style]) return;
+ value = String(rule.style[style]);
+ to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
+ });
+ });
+ };
+
+ Array.each(document.styleSheets, function(sheet, j){
+ var href = sheet.href;
+ if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return;
+ var rules = sheet.rules || sheet.cssRules;
+ searchStyles(rules);
+ });
+ return Fx.CSS.Cache[selector] = to;
+ }
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = {
+
+ Color: {
+ parse: function(value){
+ if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+ return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+ },
+ compute: function(from, to, delta){
+ return from.map(function(value, i){
+ return Math.round(Fx.compute(from[i], to[i], delta));
+ });
+ },
+ serve: function(value){
+ return value.map(Number);
+ }
+ },
+
+ Number: {
+ parse: parseFloat,
+ compute: Fx.compute,
+ serve: function(value, unit){
+ return (unit) ? value + unit : value;
+ }
+ },
+
+ String: {
+ parse: Function.from(false),
+ compute: function(zero, one){
+ return one;
+ },
+ serve: function(zero){
+ return zero;
+ }
+ }
+
+};
+
+
+
+
+/*
+---
+
+name: Fx.Tween
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(property, now){
+ if (arguments.length == 1){
+ now = property;
+ property = this.property || this.options.property;
+ }
+ this.render(this.element, property, now, this.options.unit);
+ return this;
+ },
+
+ start: function(property, from, to){
+ if (!this.check(property, from, to)) return this;
+ var args = Array.flatten(arguments);
+ this.property = this.options.property || args.shift();
+ var parsed = this.prepare(this.element, this.property, args);
+ return this.parent(parsed.from, parsed.to);
+ }
+
+});
+
+Element.Properties.tween = {
+
+ set: function(options){
+ this.get('tween').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var tween = this.retrieve('tween');
+ if (!tween){
+ tween = new Fx.Tween(this, {link: 'cancel'});
+ this.store('tween', tween);
+ }
+ return tween;
+ }
+
+};
+
+Element.implement({
+
+ tween: function(property, from, to){
+ this.get('tween').start(property, from, to);
+ return this;
+ },
+
+ fade: function(how){
+ var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle;
+ if (args[1] == null) args[1] = 'toggle';
+ switch (args[1]){
+ case 'in': method = 'start'; args[1] = 1; break;
+ case 'out': method = 'start'; args[1] = 0; break;
+ case 'show': method = 'set'; args[1] = 1; break;
+ case 'hide': method = 'set'; args[1] = 0; break;
+ case 'toggle':
+ var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
+ method = 'start';
+ args[1] = flag ? 0 : 1;
+ this.store('fade:flag', !flag);
+ toggle = true;
+ break;
+ default: method = 'start';
+ }
+ if (!toggle) this.eliminate('fade:flag');
+ fade[method].apply(fade, args);
+ var to = args[args.length - 1];
+ if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
+ else fade.chain(function(){
+ this.element.setStyle('visibility', 'hidden');
+ this.callChain();
+ });
+ return this;
+ },
+
+ highlight: function(start, end){
+ if (!end){
+ end = this.retrieve('highlight:original', this.getStyle('background-color'));
+ end = (end == 'transparent') ? '#fff' : end;
+ }
+ var tween = this.get('tween');
+ tween.start('background-color', start || '#ffff88', end).chain(function(){
+ this.setStyle('background-color', this.retrieve('highlight:original'));
+ tween.callChain();
+ }.bind(this));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Morph
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires: Fx.CSS
+
+provides: Fx.Morph
+
+...
+*/
+
+Fx.Morph = new Class({
+
+ Extends: Fx.CSS,
+
+ initialize: function(element, options){
+ this.element = this.subject = document.id(element);
+ this.parent(options);
+ },
+
+ set: function(now){
+ if (typeof now == 'string') now = this.search(now);
+ for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+ return this;
+ },
+
+ compute: function(from, to, delta){
+ var now = {};
+ for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+ return now;
+ },
+
+ start: function(properties){
+ if (!this.check(properties)) return this;
+ if (typeof properties == 'string') properties = this.search(properties);
+ var from = {}, to = {};
+ for (var p in properties){
+ var parsed = this.prepare(this.element, p, properties[p]);
+ from[p] = parsed.from;
+ to[p] = parsed.to;
+ }
+ return this.parent(from, to);
+ }
+
+});
+
+Element.Properties.morph = {
+
+ set: function(options){
+ this.get('morph').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var morph = this.retrieve('morph');
+ if (!morph){
+ morph = new Fx.Morph(this, {link: 'cancel'});
+ this.store('morph', morph);
+ }
+ return morph;
+ }
+
+};
+
+Element.implement({
+
+ morph: function(props){
+ this.get('morph').start(props);
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: Fx.Transitions
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+ - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires: Fx
+
+provides: Fx.Transitions
+
+...
+*/
+
+Fx.implement({
+
+ getTransition: function(){
+ var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+ if (typeof trans == 'string'){
+ var data = trans.split(':');
+ trans = Fx.Transitions;
+ trans = trans[data[0]] || trans[data[0].capitalize()];
+ if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+ }
+ return trans;
+ }
+
+});
+
+Fx.Transition = function(transition, params){
+ params = Array.from(params);
+ var easeIn = function(pos){
+ return transition(pos, params);
+ };
+ return Object.append(easeIn, {
+ easeIn: easeIn,
+ easeOut: function(pos){
+ return 1 - transition(1 - pos, params);
+ },
+ easeInOut: function(pos){
+ return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
+ }
+ });
+};
+
+Fx.Transitions = {
+
+ linear: function(zero){
+ return zero;
+ }
+
+};
+
+
+
+Fx.Transitions.extend = function(transitions){
+ for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+ Pow: function(p, x){
+ return Math.pow(p, x && x[0] || 6);
+ },
+
+ Expo: function(p){
+ return Math.pow(2, 8 * (p - 1));
+ },
+
+ Circ: function(p){
+ return 1 - Math.sin(Math.acos(p));
+ },
+
+ Sine: function(p){
+ return 1 - Math.cos(p * Math.PI / 2);
+ },
+
+ Back: function(p, x){
+ x = x && x[0] || 1.618;
+ return Math.pow(p, 2) * ((x + 1) * p - x);
+ },
+
+ Bounce: function(p){
+ var value;
+ for (var a = 0, b = 1; 1; a += b, b /= 2){
+ if (p >= (7 - 4 * a) / 11){
+ value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+ break;
+ }
+ }
+ return value;
+ },
+
+ Elastic: function(p, x){
+ return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
+ }
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+ Fx.Transitions[transition] = new Fx.Transition(function(p){
+ return Math.pow(p, i + 2);
+ });
+});
+
+
+/*
+---
+
+name: Request
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires: [Object, Element, Chain, Events, Options, Browser]
+
+provides: Request
+
+...
+*/
+
+(function(){
+
+var empty = function(){},
+ progressSupport = ('onprogress' in new Browser.Request);
+
+var Request = this.Request = new Class({
+
+ Implements: [Chain, Events, Options],
+
+ options: {/*
+ onRequest: function(){},
+ onLoadstart: function(event, xhr){},
+ onProgress: function(event, xhr){},
+ onComplete: function(){},
+ onCancel: function(){},
+ onSuccess: function(responseText, responseXML){},
+ onFailure: function(xhr){},
+ onException: function(headerName, value){},
+ onTimeout: function(){},
+ user: '',
+ password: '',*/
+ url: '',
+ data: '',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+ },
+ async: true,
+ format: false,
+ method: 'post',
+ link: 'ignore',
+ isSuccess: null,
+ emulation: true,
+ urlEncoded: true,
+ encoding: 'utf-8',
+ evalScripts: false,
+ evalResponse: false,
+ timeout: 0,
+ noCache: false
+ },
+
+ initialize: function(options){
+ this.xhr = new Browser.Request();
+ this.setOptions(options);
+ this.headers = this.options.headers;
+ },
+
+ onStateChange: function(){
+ var xhr = this.xhr;
+ if (xhr.readyState != 4 || !this.running) return;
+ this.running = false;
+ this.status = 0;
+ Function.attempt(function(){
+ var status = xhr.status;
+ this.status = (status == 1223) ? 204 : status;
+ }.bind(this));
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ clearTimeout(this.timer);
+
+ this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
+ if (this.options.isSuccess.call(this, this.status))
+ this.success(this.response.text, this.response.xml);
+ else
+ this.failure();
+ },
+
+ isSuccess: function(){
+ var status = this.status;
+ return (status >= 200 && status < 300);
+ },
+
+ isRunning: function(){
+ return !!this.running;
+ },
+
+ processScripts: function(text){
+ if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
+ return text.stripScripts(this.options.evalScripts);
+ },
+
+ success: function(text, xml){
+ this.onSuccess(this.processScripts(text), xml);
+ },
+
+ onSuccess: function(){
+ this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+ },
+
+ failure: function(){
+ this.onFailure();
+ },
+
+ onFailure: function(){
+ this.fireEvent('complete').fireEvent('failure', this.xhr);
+ },
+
+ loadstart: function(event){
+ this.fireEvent('loadstart', [event, this.xhr]);
+ },
+
+ progress: function(event){
+ this.fireEvent('progress', [event, this.xhr]);
+ },
+
+ timeout: function(){
+ this.fireEvent('timeout', this.xhr);
+ },
+
+ setHeader: function(name, value){
+ this.headers[name] = value;
+ return this;
+ },
+
+ getHeader: function(name){
+ return Function.attempt(function(){
+ return this.xhr.getResponseHeader(name);
+ }.bind(this));
+ },
+
+ check: function(){
+ if (!this.running) return true;
+ switch (this.options.link){
+ case 'cancel': this.cancel(); return true;
+ case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
+ }
+ return false;
+ },
+
+ send: function(options){
+ if (!this.check(options)) return this;
+
+ this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+ this.running = true;
+
+ var type = typeOf(options);
+ if (type == 'string' || type == 'element') options = {data: options};
+
+ var old = this.options;
+ options = Object.append({data: old.data, url: old.url, method: old.method}, options);
+ var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+ switch (typeOf(data)){
+ case 'element': data = document.id(data).toQueryString(); break;
+ case 'object': case 'hash': data = Object.toQueryString(data);
+ }
+
+ if (this.options.format){
+ var format = 'format=' + this.options.format;
+ data = (data) ? format + '&' + data : format;
+ }
+
+ if (this.options.emulation && !['get', 'post'].contains(method)){
+ var _method = '_method=' + method;
+ data = (data) ? _method + '&' + data : _method;
+ method = 'post';
+ }
+
+ if (this.options.urlEncoded && ['post', 'put'].contains(method)){
+ var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+ this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
+ }
+
+ if (!url) url = document.location.pathname;
+
+ var trimPosition = url.lastIndexOf('/');
+ if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+ if (this.options.noCache)
+ url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
+
+ if (data && (method == 'get' || method == 'delete')){
+ url += (url.indexOf('?') > -1 ? '&' : '?') + data;
+ data = null;
+ }
+
+ var xhr = this.xhr;
+ if (progressSupport){
+ xhr.onloadstart = this.loadstart.bind(this);
+ xhr.onprogress = this.progress.bind(this);
+ }
+
+ xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
+ if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
+
+ xhr.onreadystatechange = this.onStateChange.bind(this);
+
+ Object.each(this.headers, function(value, key){
+ try {
+ xhr.setRequestHeader(key, value);
+ } catch (e){
+ this.fireEvent('exception', [key, value]);
+ }
+ }, this);
+
+ this.fireEvent('request');
+ xhr.send(data);
+ if (!this.options.async) this.onStateChange();
+ else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
+ return this;
+ },
+
+ cancel: function(){
+ if (!this.running) return this;
+ this.running = false;
+ var xhr = this.xhr;
+ xhr.abort();
+ clearTimeout(this.timer);
+ xhr.onreadystatechange = empty;
+ if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
+ this.xhr = new Browser.Request();
+ this.fireEvent('cancel');
+ return this;
+ }
+
+});
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+ methods[method] = function(data){
+ var object = {
+ method: method
+ };
+ if (data != null) object.data = data;
+ return this.send(object);
+ };
+});
+
+Request.implement(methods);
+
+Element.Properties.send = {
+
+ set: function(options){
+ var send = this.get('send').cancel();
+ send.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var send = this.retrieve('send');
+ if (!send){
+ send = new Request({
+ data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+ });
+ this.store('send', send);
+ }
+ return send;
+ }
+
+};
+
+Element.implement({
+
+ send: function(url){
+ var sender = this.get('send');
+ sender.send({data: this, url: url || sender.options.url});
+ return this;
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+name: Request.HTML
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires: [Element, Request]
+
+provides: Request.HTML
+
+...
+*/
+
+Request.HTML = new Class({
+
+ Extends: Request,
+
+ options: {
+ update: false,
+ append: false,
+ evalScripts: true,
+ filter: false,
+ headers: {
+ Accept: 'text/html, application/xml, text/xml, */*'
+ }
+ },
+
+ success: function(text){
+ var options = this.options, response = this.response;
+
+ response.html = text.stripScripts(function(script){
+ response.javascript = script;
+ });
+
+ var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+ if (match) response.html = match[1];
+ var temp = new Element('div').set('html', response.html);
+
+ response.tree = temp.childNodes;
+ response.elements = temp.getElements(options.filter || '*');
+
+ if (options.filter) response.tree = response.elements;
+ if (options.update){
+ var update = document.id(options.update).empty();
+ if (options.filter) update.adopt(response.elements);
+ else update.set('html', response.html);
+ } else if (options.append){
+ var append = document.id(options.append);
+ if (options.filter) response.elements.reverse().inject(append);
+ else append.adopt(temp.getChildren());
+ }
+ if (options.evalScripts) Browser.exec(response.javascript);
+
+ this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+ }
+
+});
+
+Element.Properties.load = {
+
+ set: function(options){
+ var load = this.get('load').cancel();
+ load.setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var load = this.retrieve('load');
+ if (!load){
+ load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'});
+ this.store('load', load);
+ }
+ return load;
+ }
+
+};
+
+Element.implement({
+
+ load: function(){
+ this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString}));
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+name: JSON
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+
+if (typeof JSON == 'undefined') this.JSON = {};
+
+
+
+(function(){
+
+var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+var escape = function(chr){
+ return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4);
+};
+
+JSON.validate = function(string){
+ string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+
+ return (/^[\],:{}\s]*$/).test(string);
+};
+
+JSON.encode = JSON.stringify ? function(obj){
+ return JSON.stringify(obj);
+} : function(obj){
+ if (obj && obj.toJSON) obj = obj.toJSON();
+
+ switch (typeOf(obj)){
+ case 'string':
+ return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"';
+ case 'array':
+ return '[' + obj.map(JSON.encode).clean() + ']';
+ case 'object': case 'hash':
+ var string = [];
+ Object.each(obj, function(value, key){
+ var json = JSON.encode(value);
+ if (json) string.push(JSON.encode(key) + ':' + json);
+ });
+ return '{' + string + '}';
+ case 'number': case 'boolean': return '' + obj;
+ case 'null': return 'null';
+ }
+
+ return null;
+};
+
+JSON.secure = true;
+
+
+JSON.decode = function(string, secure){
+ if (!string || typeOf(string) != 'string') return null;
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
+ if (JSON.parse) return JSON.parse(string);
+ if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
+ }
+
+ return eval('(' + string + ')');
+};
+
+})();
+
+
+/*
+---
+
+name: Request.JSON
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires: [Request, JSON]
+
+provides: Request.JSON
+
+...
+*/
+
+Request.JSON = new Class({
+
+ Extends: Request,
+
+ options: {
+ /*onError: function(text, error){},*/
+ secure: true
+ },
+
+ initialize: function(options){
+ this.parent(options);
+ Object.append(this.headers, {
+ 'Accept': 'application/json',
+ 'X-Request': 'JSON'
+ });
+ },
+
+ success: function(text){
+ var json;
+ try {
+ json = this.response.json = JSON.decode(text, this.options.secure);
+ } catch (error){
+ this.fireEvent('error', [text, error]);
+ return;
+ }
+ if (json == null) this.onFailure();
+ else this.onSuccess(json, text);
+ }
+
+});
+
+
+/*
+---
+
+name: Cookie
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+ - Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires: [Options, Browser]
+
+provides: Cookie
+
+...
+*/
+
+var Cookie = new Class({
+
+ Implements: Options,
+
+ options: {
+ path: '/',
+ domain: false,
+ duration: false,
+ secure: false,
+ document: document,
+ encode: true
+ },
+
+ initialize: function(key, options){
+ this.key = key;
+ this.setOptions(options);
+ },
+
+ write: function(value){
+ if (this.options.encode) value = encodeURIComponent(value);
+ if (this.options.domain) value += '; domain=' + this.options.domain;
+ if (this.options.path) value += '; path=' + this.options.path;
+ if (this.options.duration){
+ var date = new Date();
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+ value += '; expires=' + date.toGMTString();
+ }
+ if (this.options.secure) value += '; secure';
+ this.options.document.cookie = this.key + '=' + value;
+ return this;
+ },
+
+ read: function(){
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+ return (value) ? decodeURIComponent(value[1]) : null;
+ },
+
+ dispose: function(){
+ new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write('');
+ return this;
+ }
+
+});
+
+Cookie.write = function(key, value, options){
+ return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+ return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+ return new Cookie(key, options).dispose();
+};
+
+
+/*
+---
+
+name: DOMReady
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires: [Browser, Element, Element.Event]
+
+provides: [DOMReady, DomReady]
+
+...
+*/
+
+(function(window, document){
+
+var ready,
+ loaded,
+ checks = [],
+ shouldPoll,
+ timer,
+ testElement = document.createElement('div');
+
+var domready = function(){
+ clearTimeout(timer);
+ if (ready) return;
+ Browser.loaded = ready = true;
+ document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check);
+
+ document.fireEvent('domready');
+ window.fireEvent('domready');
+};
+
+var check = function(){
+ for (var i = checks.length; i--;) if (checks[i]()){
+ domready();
+ return true;
+ }
+ return false;
+};
+
+var poll = function(){
+ clearTimeout(timer);
+ if (!check()) timer = setTimeout(poll, 10);
+};
+
+document.addListener('DOMContentLoaded', domready);
+
+/*<ltIE8>*/
+// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/
+// testElement.doScroll() throws when the DOM is not ready, only in the top window
+var doScrollWorks = function(){
+ try {
+ testElement.doScroll();
+ return true;
+ } catch (e){}
+ return false;
+};
+// If doScroll works already, it can't be used to determine domready
+// e.g. in an iframe
+if (testElement.doScroll && !doScrollWorks()){
+ checks.push(doScrollWorks);
+ shouldPoll = true;
+}
+/*</ltIE8>*/
+
+if (document.readyState) checks.push(function(){
+ var state = document.readyState;
+ return (state == 'loaded' || state == 'complete');
+});
+
+if ('onreadystatechange' in document) document.addListener('readystatechange', check);
+else shouldPoll = true;
+
+if (shouldPoll) poll();
+
+Element.Events.domready = {
+ onAdd: function(fn){
+ if (ready) fn.call(this);
+ }
+};
+
+// Make sure that domready fires before load
+Element.Events.load = {
+ base: 'load',
+ onAdd: function(fn){
+ if (loaded && this == window) fn.call(this);
+ },
+ condition: function(){
+ if (this == window){
+ domready();
+ delete Element.Events.load;
+ }
+ return true;
+ }
+};
+
+// This is based on the custom load event
+window.addEvent('load', function(){
+ loaded = true;
+});
+
+})(window, document);
+
diff --git a/pyload/webui/themes/flat/js/static/mootools-core.min.js b/pyload/webui/themes/flat/js/static/mootools-core.min.js
new file mode 100644
index 000000000..354f94196
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-core.min.js
@@ -0,0 +1,491 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/core/8423c12ffd6a6bfcde9ea22554aec795
+
+packager build:
+ - packager build Core/Core Core/Array Core/String Core/Number Core/Function Core/Object Core/Event Core/Browser Core/Class Core/Class.Extras Core/Slick.Parser Core/Slick.Finder Core/Element Core/Element.Style Core/Element.Event Core/Element.Delegation Core/Element.Dimensions Core/Fx Core/Fx.CSS Core/Fx.Tween Core/Fx.Morph Core/Fx.Transitions Core/Request Core/Request.HTML Core/Request.JSON Core/Cookie Core/JSON Core/DOMReady
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+(function(){this.MooTools={version:"1.5.0",build:"0f7b690afee9349b15909f33016a25d2e4d9f4e3"};var o=this.typeOf=function(i){if(i==null){return"null";}if(i.$family!=null){return i.$family();
+}if(i.nodeName){if(i.nodeType==1){return"element";}if(i.nodeType==3){return(/\S/).test(i.nodeValue)?"textnode":"whitespace";}}else{if(typeof i.length=="number"){if("callee" in i){return"arguments";
+}if("item" in i){return"collection";}}}return typeof i;};var j=this.instanceOf=function(t,i){if(t==null){return false;}var s=t.$constructor||t.constructor;
+while(s){if(s===i){return true;}s=s.parent;}if(!t.hasOwnProperty){return false;}return t instanceof i;};var f=this.Function;var p=true;for(var k in {toString:1}){p=null;
+}if(p){p=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"];}f.prototype.overloadSetter=function(s){var i=this;
+return function(u,t){if(u==null){return this;}if(s||typeof u!="string"){for(var v in u){i.call(this,v,u[v]);}if(p){for(var w=p.length;w--;){v=p[w];if(u.hasOwnProperty(v)){i.call(this,v,u[v]);
+}}}}else{i.call(this,u,t);}return this;};};f.prototype.overloadGetter=function(s){var i=this;return function(u){var v,t;if(typeof u!="string"){v=u;}else{if(arguments.length>1){v=arguments;
+}else{if(s){v=[u];}}}if(v){t={};for(var w=0;w<v.length;w++){t[v[w]]=i.call(this,v[w]);}}else{t=i.call(this,u);}return t;};};f.prototype.extend=function(i,s){this[i]=s;
+}.overloadSetter();f.prototype.implement=function(i,s){this.prototype[i]=s;}.overloadSetter();var n=Array.prototype.slice;f.from=function(i){return(o(i)=="function")?i:function(){return i;
+};};Array.from=function(i){if(i==null){return[];}return(a.isEnumerable(i)&&typeof i!="string")?(o(i)=="array")?i:n.call(i):[i];};Number.from=function(s){var i=parseFloat(s);
+return isFinite(i)?i:null;};String.from=function(i){return i+"";};f.implement({hide:function(){this.$hidden=true;return this;},protect:function(){this.$protected=true;
+return this;}});var a=this.Type=function(u,t){if(u){var s=u.toLowerCase();var i=function(v){return(o(v)==s);};a["is"+u]=i;if(t!=null){t.prototype.$family=(function(){return s;
+}).hide();}}if(t==null){return null;}t.extend(this);t.$constructor=a;t.prototype.$constructor=t;return t;};var e=Object.prototype.toString;a.isEnumerable=function(i){return(i!=null&&typeof i.length=="number"&&e.call(i)!="[object Function]");
+};var q={};var r=function(i){var s=o(i.prototype);return q[s]||(q[s]=[]);};var b=function(t,x){if(x&&x.$hidden){return;}var s=r(this);for(var u=0;u<s.length;
+u++){var w=s[u];if(o(w)=="type"){b.call(w,t,x);}else{w.call(this,t,x);}}var v=this.prototype[t];if(v==null||!v.$protected){this.prototype[t]=x;}if(this[t]==null&&o(x)=="function"){m.call(this,t,function(i){return x.apply(i,n.call(arguments,1));
+});}};var m=function(i,t){if(t&&t.$hidden){return;}var s=this[i];if(s==null||!s.$protected){this[i]=t;}};a.implement({implement:b.overloadSetter(),extend:m.overloadSetter(),alias:function(i,s){b.call(this,i,this.prototype[s]);
+}.overloadSetter(),mirror:function(i){r(this).push(i);return this;}});new a("Type",a);var d=function(s,x,v){var u=(x!=Object),B=x.prototype;if(u){x=new a(s,x);
+}for(var y=0,w=v.length;y<w;y++){var C=v[y],A=x[C],z=B[C];if(A){A.protect();}if(u&&z){x.implement(C,z.protect());}}if(u){var t=B.propertyIsEnumerable(v[0]);
+x.forEachMethod=function(G){if(!t){for(var F=0,D=v.length;F<D;F++){G.call(B,B[v[F]],v[F]);}}for(var E in B){G.call(B,B[E],E);}};}return d;};d("String",String,["charAt","charCodeAt","concat","contains","indexOf","lastIndexOf","match","quote","replace","search","slice","split","substr","substring","trim","toLowerCase","toUpperCase"])("Array",Array,["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice","indexOf","lastIndexOf","filter","forEach","every","map","some","reduce","reduceRight"])("Number",Number,["toExponential","toFixed","toLocaleString","toPrecision"])("Function",f,["apply","call","bind"])("RegExp",RegExp,["exec","test"])("Object",Object,["create","defineProperty","defineProperties","keys","getPrototypeOf","getOwnPropertyDescriptor","getOwnPropertyNames","preventExtensions","isExtensible","seal","isSealed","freeze","isFrozen"])("Date",Date,["now"]);
+Object.extend=m.overloadSetter();Date.extend("now",function(){return +(new Date);});new a("Boolean",Boolean);Number.prototype.$family=function(){return isFinite(this)?"number":"null";
+}.hide();Number.extend("random",function(s,i){return Math.floor(Math.random()*(i-s+1)+s);});var g=Object.prototype.hasOwnProperty;Object.extend("forEach",function(i,t,u){for(var s in i){if(g.call(i,s)){t.call(u,i[s],s,i);
+}}});Object.each=Object.forEach;Array.implement({forEach:function(u,v){for(var t=0,s=this.length;t<s;t++){if(t in this){u.call(v,this[t],t,this);}}},each:function(i,s){Array.forEach(this,i,s);
+return this;}});var l=function(i){switch(o(i)){case"array":return i.clone();case"object":return Object.clone(i);default:return i;}};Array.implement("clone",function(){var s=this.length,t=new Array(s);
+while(s--){t[s]=l(this[s]);}return t;});var h=function(s,i,t){switch(o(t)){case"object":if(o(s[i])=="object"){Object.merge(s[i],t);}else{s[i]=Object.clone(t);
+}break;case"array":s[i]=t.clone();break;default:s[i]=t;}return s;};Object.extend({merge:function(z,u,t){if(o(u)=="string"){return h(z,u,t);}for(var y=1,s=arguments.length;
+y<s;y++){var w=arguments[y];for(var x in w){h(z,x,w[x]);}}return z;},clone:function(i){var t={};for(var s in i){t[s]=l(i[s]);}return t;},append:function(w){for(var v=1,t=arguments.length;
+v<t;v++){var s=arguments[v]||{};for(var u in s){w[u]=s[u];}}return w;}});["Object","WhiteSpace","TextNode","Collection","Arguments"].each(function(i){new a(i);
+});var c=Date.now();String.extend("uniqueID",function(){return(c++).toString(36);});})();Array.implement({every:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&!c.call(d,this[b],b,this)){return false;}}return true;},filter:function(d,f){var c=[];for(var e,b=0,a=this.length>>>0;b<a;b++){if(b in this){e=this[b];
+if(d.call(f,e,b,this)){c.push(e);}}}return c;},indexOf:function(c,d){var b=this.length>>>0;for(var a=(d<0)?Math.max(0,b+d):d||0;a<b;a++){if(this[a]===c){return a;
+}}return -1;},map:function(c,e){var d=this.length>>>0,b=Array(d);for(var a=0;a<d;a++){if(a in this){b[a]=c.call(e,this[a],a,this);}}return b;},some:function(c,d){for(var b=0,a=this.length>>>0;
+b<a;b++){if((b in this)&&c.call(d,this[b],b,this)){return true;}}return false;},clean:function(){return this.filter(function(a){return a!=null;});},invoke:function(a){var b=Array.slice(arguments,1);
+return this.map(function(c){return c[a].apply(c,b);});},associate:function(c){var d={},b=Math.min(this.length,c.length);for(var a=0;a<b;a++){d[c[a]]=this[a];
+}return d;},link:function(c){var a={};for(var e=0,b=this.length;e<b;e++){for(var d in c){if(c[d](this[e])){a[d]=this[e];delete c[d];break;}}}return a;},contains:function(a,b){return this.indexOf(a,b)!=-1;
+},append:function(a){this.push.apply(this,a);return this;},getLast:function(){return(this.length)?this[this.length-1]:null;},getRandom:function(){return(this.length)?this[Number.random(0,this.length-1)]:null;
+},include:function(a){if(!this.contains(a)){this.push(a);}return this;},combine:function(c){for(var b=0,a=c.length;b<a;b++){this.include(c[b]);}return this;
+},erase:function(b){for(var a=this.length;a--;){if(this[a]===b){this.splice(a,1);}}return this;},empty:function(){this.length=0;return this;},flatten:function(){var d=[];
+for(var b=0,a=this.length;b<a;b++){var c=typeOf(this[b]);if(c=="null"){continue;}d=d.concat((c=="array"||c=="collection"||c=="arguments"||instanceOf(this[b],Array))?Array.flatten(this[b]):this[b]);
+}return d;},pick:function(){for(var b=0,a=this.length;b<a;b++){if(this[b]!=null){return this[b];}}return null;},hexToRgb:function(b){if(this.length!=3){return null;
+}var a=this.map(function(c){if(c.length==1){c+=c;}return parseInt(c,16);});return(b)?a:"rgb("+a+")";},rgbToHex:function(d){if(this.length<3){return null;
+}if(this.length==4&&this[3]==0&&!d){return"transparent";}var b=[];for(var a=0;a<3;a++){var c=(this[a]-0).toString(16);b.push((c.length==1)?"0"+c:c);}return(d)?b:"#"+b.join("");
+}});String.implement({contains:function(b,a){return(a?String(this).slice(a):String(this)).indexOf(b)>-1;},test:function(a,b){return((typeOf(a)=="regexp")?a:new RegExp(""+a,b)).test(this);
+},trim:function(){return String(this).replace(/^\s+|\s+$/g,"");},clean:function(){return String(this).replace(/\s+/g," ").trim();},camelCase:function(){return String(this).replace(/-\D/g,function(a){return a.charAt(1).toUpperCase();
+});},hyphenate:function(){return String(this).replace(/[A-Z]/g,function(a){return("-"+a.charAt(0).toLowerCase());});},capitalize:function(){return String(this).replace(/\b[a-z]/g,function(a){return a.toUpperCase();
+});},escapeRegExp:function(){return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1");},toInt:function(a){return parseInt(this,a||10);},toFloat:function(){return parseFloat(this);
+},hexToRgb:function(b){var a=String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(a)?a.slice(1).hexToRgb(b):null;},rgbToHex:function(b){var a=String(this).match(/\d{1,3}/g);
+return(a)?a.rgbToHex(b):null;},substitute:function(a,b){return String(this).replace(b||(/\\?\{([^{}]+)\}/g),function(d,c){if(d.charAt(0)=="\\"){return d.slice(1);
+}return(a[c]!=null)?a[c]:"";});}});Number.implement({limit:function(b,a){return Math.min(a,Math.max(b,this));},round:function(a){a=Math.pow(10,a||0).toFixed(a<0?-a:0);
+return Math.round(this*a)/a;},times:function(b,c){for(var a=0;a<this;a++){b.call(c,a,this);}},toFloat:function(){return parseFloat(this);},toInt:function(a){return parseInt(this,a||10);
+}});Number.alias("each","times");(function(b){var a={};b.each(function(c){if(!Number[c]){a[c]=function(){return Math[c].apply(null,[this].concat(Array.from(arguments)));
+};}});Number.implement(a);})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);Function.extend({attempt:function(){for(var b=0,a=arguments.length;
+b<a;b++){try{return arguments[b]();}catch(c){}}return null;}});Function.implement({attempt:function(a,c){try{return this.apply(c,Array.from(a));}catch(b){}return null;
+},bind:function(e){var a=this,b=arguments.length>1?Array.slice(arguments,1):null,d=function(){};var c=function(){var g=e,h=arguments.length;if(this instanceof c){d.prototype=a.prototype;
+g=new d;}var f=(!b&&!h)?a.call(g):a.apply(g,b&&h?b.concat(Array.slice(arguments)):b||arguments);return g==e?f:g;};return c;},pass:function(b,c){var a=this;
+if(b!=null){b=Array.from(b);}return function(){return a.apply(c,b||arguments);};},delay:function(b,c,a){return setTimeout(this.pass((a==null?[]:a),c),b);
+},periodical:function(c,b,a){return setInterval(this.pass((a==null?[]:a),b),c);}});(function(){var a=Object.prototype.hasOwnProperty;Object.extend({subset:function(d,g){var f={};
+for(var e=0,b=g.length;e<b;e++){var c=g[e];if(c in d){f[c]=d[c];}}return f;},map:function(b,e,f){var d={};for(var c in b){if(a.call(b,c)){d[c]=e.call(f,b[c],c,b);
+}}return d;},filter:function(b,e,g){var d={};for(var c in b){var f=b[c];if(a.call(b,c)&&e.call(g,f,c,b)){d[c]=f;}}return d;},every:function(b,d,e){for(var c in b){if(a.call(b,c)&&!d.call(e,b[c],c)){return false;
+}}return true;},some:function(b,d,e){for(var c in b){if(a.call(b,c)&&d.call(e,b[c],c)){return true;}}return false;},keys:function(b){var d=[];for(var c in b){if(a.call(b,c)){d.push(c);
+}}return d;},values:function(c){var b=[];for(var d in c){if(a.call(c,d)){b.push(c[d]);}}return b;},getLength:function(b){return Object.keys(b).length;},keyOf:function(b,d){for(var c in b){if(a.call(b,c)&&b[c]===d){return c;
+}}return null;},contains:function(b,c){return Object.keyOf(b,c)!=null;},toQueryString:function(b,c){var d=[];Object.each(b,function(h,g){if(c){g=c+"["+g+"]";
+}var f;switch(typeOf(h)){case"object":f=Object.toQueryString(h,g);break;case"array":var e={};h.each(function(k,j){e[j]=k;});f=Object.toQueryString(e,g);
+break;default:f=g+"="+encodeURIComponent(h);}if(h!=null){d.push(f);}});return d.join("&");}});})();(function(){var f=this.document;var d=f.window=this;
+var a=function(k,e){k=k.toLowerCase();e=(e?e.toLowerCase():"");var l=k.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/)||[null,"unknown",0];
+if(l[1]=="trident"){l[1]="ie";if(l[4]){l[2]=l[4];}}else{if(l[1]=="crios"){l[1]="chrome";}}var e=k.match(/ip(?:ad|od|hone)/)?"ios":(k.match(/(?:webos|android)/)||e.match(/mac|win|linux/)||["other"])[0];
+if(e=="win"){e="windows";}return{extend:Function.prototype.extend,name:(l[1]=="version")?l[3]:l[1],version:parseFloat((l[1]=="opera"&&l[4])?l[4]:l[2]),platform:e};
+};var j=this.Browser=a(navigator.userAgent,navigator.platform);if(j.ie){j.version=f.documentMode;}j.extend({Features:{xpath:!!(f.evaluate),air:!!(d.runtime),query:!!(f.querySelector),json:!!(d.JSON)},parseUA:a});
+j.Request=(function(){var l=function(){return new XMLHttpRequest();};var k=function(){return new ActiveXObject("MSXML2.XMLHTTP");};var e=function(){return new ActiveXObject("Microsoft.XMLHTTP");
+};return Function.attempt(function(){l();return l;},function(){k();return k;},function(){e();return e;});})();j.Features.xhr=!!(j.Request);j.exec=function(k){if(!k){return k;
+}if(d.execScript){d.execScript(k);}else{var e=f.createElement("script");e.setAttribute("type","text/javascript");e.text=k;f.head.appendChild(e);f.head.removeChild(e);
+}return k;};String.implement("stripScripts",function(k){var e="";var l=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(m,n){e+=n+"\n";return"";
+});if(k===true){j.exec(e);}else{if(typeOf(k)=="function"){k(e,l);}}return l;});j.extend({Document:this.Document,Window:this.Window,Element:this.Element,Event:this.Event});
+this.Window=this.$constructor=new Type("Window",function(){});this.$family=Function.from("window").hide();Window.mirror(function(e,k){d[e]=k;});this.Document=f.$constructor=new Type("Document",function(){});
+f.$family=Function.from("document").hide();Document.mirror(function(e,k){f[e]=k;});f.html=f.documentElement;if(!f.head){f.head=f.getElementsByTagName("head")[0];
+}if(f.execCommand){try{f.execCommand("BackgroundImageCache",false,true);}catch(c){}}if(this.attachEvent&&!this.addEventListener){var b=function(){this.detachEvent("onunload",b);
+f.head=f.html=f.window=null;};this.attachEvent("onunload",b);}var g=Array.from;try{g(f.html.childNodes);}catch(c){Array.from=function(k){if(typeof k!="string"&&Type.isEnumerable(k)&&typeOf(k)!="array"){var e=k.length,l=new Array(e);
+while(e--){l[e]=k[e];}return l;}return g(k);};var h=Array.prototype,i=h.slice;["pop","push","reverse","shift","sort","splice","unshift","concat","join","slice"].each(function(e){var k=h[e];
+Array[e]=function(l){return k.apply(Array.from(l),i.call(arguments,1));};});}})();(function(){var b={};var a=this.DOMEvent=new Type("DOMEvent",function(c,g){if(!g){g=window;
+}c=c||g.event;if(c.$extended){return c;}this.event=c;this.$extended=true;this.shift=c.shiftKey;this.control=c.ctrlKey;this.alt=c.altKey;this.meta=c.metaKey;
+var i=this.type=c.type;var h=c.target||c.srcElement;while(h&&h.nodeType==3){h=h.parentNode;}this.target=document.id(h);if(i.indexOf("key")==0){var d=this.code=(c.which||c.keyCode);
+this.key=b[d];if(i=="keydown"||i=="keyup"){if(d>111&&d<124){this.key="f"+(d-111);}else{if(d>95&&d<106){this.key=d-96;}}}if(this.key==null){this.key=String.fromCharCode(d).toLowerCase();
+}}else{if(i=="click"||i=="dblclick"||i=="contextmenu"||i=="DOMMouseScroll"||i.indexOf("mouse")==0){var j=g.document;j=(!j.compatMode||j.compatMode=="CSS1Compat")?j.html:j.body;
+this.page={x:(c.pageX!=null)?c.pageX:c.clientX+j.scrollLeft,y:(c.pageY!=null)?c.pageY:c.clientY+j.scrollTop};this.client={x:(c.pageX!=null)?c.pageX-g.pageXOffset:c.clientX,y:(c.pageY!=null)?c.pageY-g.pageYOffset:c.clientY};
+if(i=="DOMMouseScroll"||i=="mousewheel"){this.wheel=(c.wheelDelta)?c.wheelDelta/120:-(c.detail||0)/3;}this.rightClick=(c.which==3||c.button==2);if(i=="mouseover"||i=="mouseout"){var k=c.relatedTarget||c[(i=="mouseover"?"from":"to")+"Element"];
+while(k&&k.nodeType==3){k=k.parentNode;}this.relatedTarget=document.id(k);}}else{if(i.indexOf("touch")==0||i.indexOf("gesture")==0){this.rotation=c.rotation;
+this.scale=c.scale;this.targetTouches=c.targetTouches;this.changedTouches=c.changedTouches;var f=this.touches=c.touches;if(f&&f[0]){var e=f[0];this.page={x:e.pageX,y:e.pageY};
+this.client={x:e.clientX,y:e.clientY};}}}}if(!this.client){this.client={};}if(!this.page){this.page={};}});a.implement({stop:function(){return this.preventDefault().stopPropagation();
+},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation();}else{this.event.cancelBubble=true;}return this;},preventDefault:function(){if(this.event.preventDefault){this.event.preventDefault();
+}else{this.event.returnValue=false;}return this;}});a.defineKey=function(d,c){b[d]=c;return this;};a.defineKeys=a.defineKey.overloadSetter(true);a.defineKeys({"38":"up","40":"down","37":"left","39":"right","27":"esc","32":"space","8":"backspace","9":"tab","46":"delete","13":"enter"});
+})();(function(){var a=this.Class=new Type("Class",function(h){if(instanceOf(h,Function)){h={initialize:h};}var g=function(){e(this);if(g.$prototyping){return this;
+}this.$caller=null;var i=(this.initialize)?this.initialize.apply(this,arguments):this;this.$caller=this.caller=null;return i;}.extend(this).implement(h);
+g.$constructor=a;g.prototype.$constructor=g;g.prototype.parent=c;return g;});var c=function(){if(!this.$caller){throw new Error('The method "parent" cannot be called.');
+}var g=this.$caller.$name,h=this.$caller.$owner.parent,i=(h)?h.prototype[g]:null;if(!i){throw new Error('The method "'+g+'" has no parent.');}return i.apply(this,arguments);
+};var e=function(g){for(var h in g){var j=g[h];switch(typeOf(j)){case"object":var i=function(){};i.prototype=j;g[h]=e(new i);break;case"array":g[h]=j.clone();
+break;}}return g;};var b=function(g,h,j){if(j.$origin){j=j.$origin;}var i=function(){if(j.$protected&&this.$caller==null){throw new Error('The method "'+h+'" cannot be called.');
+}var l=this.caller,m=this.$caller;this.caller=m;this.$caller=i;var k=j.apply(this,arguments);this.$caller=m;this.caller=l;return k;}.extend({$owner:g,$origin:j,$name:h});
+return i;};var f=function(h,i,g){if(a.Mutators.hasOwnProperty(h)){i=a.Mutators[h].call(this,i);if(i==null){return this;}}if(typeOf(i)=="function"){if(i.$hidden){return this;
+}this.prototype[h]=(g)?i:b(this,h,i);}else{Object.merge(this.prototype,h,i);}return this;};var d=function(g){g.$prototyping=true;var h=new g;delete g.$prototyping;
+return h;};a.implement("implement",f.overloadSetter());a.Mutators={Extends:function(g){this.parent=g;this.prototype=d(g);},Implements:function(g){Array.from(g).each(function(j){var h=new j;
+for(var i in h){f.call(this,i,h[i],true);}},this);}};})();(function(){this.Chain=new Class({$chain:[],chain:function(){this.$chain.append(Array.flatten(arguments));
+return this;},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false;},clearChain:function(){this.$chain.empty();
+return this;}});var a=function(b){return b.replace(/^on([A-Z])/,function(c,d){return d.toLowerCase();});};this.Events=new Class({$events:{},addEvent:function(d,c,b){d=a(d);
+this.$events[d]=(this.$events[d]||[]).include(c);if(b){c.internal=true;}return this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},fireEvent:function(e,c,b){e=a(e);var d=this.$events[e];if(!d){return this;}c=Array.from(c);d.each(function(f){if(b){f.delay(b,this,c);}else{f.apply(this,c);
+}},this);return this;},removeEvent:function(e,d){e=a(e);var c=this.$events[e];if(c&&!d.internal){var b=c.indexOf(d);if(b!=-1){delete c[b];}}return this;
+},removeEvents:function(d){var e;if(typeOf(d)=="object"){for(e in d){this.removeEvent(e,d[e]);}return this;}if(d){d=a(d);}for(e in this.$events){if(d&&d!=e){continue;
+}var c=this.$events[e];for(var b=c.length;b--;){if(b in c){this.removeEvent(e,c[b]);}}}return this;}});this.Options=new Class({setOptions:function(){var b=this.options=Object.merge.apply(null,[{},this.options].append(arguments));
+if(this.addEvent){for(var c in b){if(typeOf(b[c])!="function"||!(/^on[A-Z]/).test(c)){continue;}this.addEvent(c,b[c]);delete b[c];}}return this;}});})();
+(function(){var k,n,l,g,a={},c={},m=/\\/g;var e=function(q,p){if(q==null){return null;}if(q.Slick===true){return q;}q=(""+q).replace(/^\s+|\s+$/g,"");g=!!p;
+var o=(g)?c:a;if(o[q]){return o[q];}k={Slick:true,expressions:[],raw:q,reverse:function(){return e(this.raw,true);}};n=-1;while(q!=(q=q.replace(j,b))){}k.length=k.expressions.length;
+return o[k.raw]=(g)?h(k):k;};var i=function(o){if(o==="!"){return" ";}else{if(o===" "){return"!";}else{if((/^!/).test(o)){return o.replace(/^!/,"");}else{return"!"+o;
+}}}};var h=function(u){var r=u.expressions;for(var p=0;p<r.length;p++){var t=r[p];var q={parts:[],tag:"*",combinator:i(t[0].combinator)};for(var o=0;o<t.length;
+o++){var s=t[o];if(!s.reverseCombinator){s.reverseCombinator=" ";}s.combinator=s.reverseCombinator;delete s.reverseCombinator;}t.reverse().push(q);}return u;
+};var f=function(o){return o.replace(/[-[\]{}()*+?.\\^$|,#\s]/g,function(p){return"\\"+p;});};var j=new RegExp("^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)".replace(/<combinator>/,"["+f(">+~`!@$%^&={}\\;</")+"]").replace(/<unicode>/g,"(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])").replace(/<unicode1>/g,"(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])"));
+function b(x,s,D,z,r,C,q,B,A,y,u,F,G,v,p,w){if(s||n===-1){k.expressions[++n]=[];l=-1;if(s){return"";}}if(D||z||l===-1){D=D||" ";var t=k.expressions[n];
+if(g&&t[l]){t[l].reverseCombinator=i(D);}t[++l]={combinator:D,tag:"*"};}var o=k.expressions[n][l];if(r){o.tag=r.replace(m,"");}else{if(C){o.id=C.replace(m,"");
+}else{if(q){q=q.replace(m,"");if(!o.classList){o.classList=[];}if(!o.classes){o.classes=[];}o.classList.push(q);o.classes.push({value:q,regexp:new RegExp("(^|\\s)"+f(q)+"(\\s|$)")});
+}else{if(G){w=w||p;w=w?w.replace(m,""):null;if(!o.pseudos){o.pseudos=[];}o.pseudos.push({key:G.replace(m,""),value:w,type:F.length==1?"class":"element"});
+}else{if(B){B=B.replace(m,"");u=(u||"").replace(m,"");var E,H;switch(A){case"^=":H=new RegExp("^"+f(u));break;case"$=":H=new RegExp(f(u)+"$");break;case"~=":H=new RegExp("(^|\\s)"+f(u)+"(\\s|$)");
+break;case"|=":H=new RegExp("^"+f(u)+"(-|$)");break;case"=":E=function(I){return u==I;};break;case"*=":E=function(I){return I&&I.indexOf(u)>-1;};break;
+case"!=":E=function(I){return u!=I;};break;default:E=function(I){return !!I;};}if(u==""&&(/^[*$^]=$/).test(A)){E=function(){return false;};}if(!E){E=function(I){return I&&H.test(I);
+};}if(!o.attributes){o.attributes=[];}o.attributes.push({key:B,operator:A,value:u,test:E});}}}}}return"";}var d=(this.Slick||{});d.parse=function(o){return e(o);
+};d.escapeRegExp=f;if(!this.Slick){this.Slick=d;}}).apply((typeof exports!="undefined")?exports:this);(function(){var k={},m={},d=Object.prototype.toString;
+k.isNativeCode=function(c){return(/\{\s*\[native code\]\s*\}/).test(""+c);};k.isXML=function(c){return(!!c.xmlVersion)||(!!c.xml)||(d.call(c)=="[object XMLDocument]")||(c.nodeType==9&&c.documentElement.nodeName!="HTML");
+};k.setDocument=function(w){var p=w.nodeType;if(p==9){}else{if(p){w=w.ownerDocument;}else{if(w.navigator){w=w.document;}else{return;}}}if(this.document===w){return;
+}this.document=w;var A=w.documentElement,o=this.getUIDXML(A),s=m[o],r;if(s){for(r in s){this[r]=s[r];}return;}s=m[o]={};s.root=A;s.isXMLDocument=this.isXML(w);
+s.brokenStarGEBTN=s.starSelectsClosedQSA=s.idGetsName=s.brokenMixedCaseQSA=s.brokenGEBCN=s.brokenCheckedQSA=s.brokenEmptyAttributeQSA=s.isHTMLDocument=s.nativeMatchesSelector=false;
+var q,u,y,z,t;var x,v="slick_uniqueid";var c=w.createElement("div");var n=w.body||w.getElementsByTagName("body")[0]||A;n.appendChild(c);try{c.innerHTML='<a id="'+v+'"></a>';
+s.isHTMLDocument=!!w.getElementById(v);}catch(C){}if(s.isHTMLDocument){c.style.display="none";c.appendChild(w.createComment(""));u=(c.getElementsByTagName("*").length>1);
+try{c.innerHTML="foo</foo>";x=c.getElementsByTagName("*");q=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");}catch(C){}s.brokenStarGEBTN=u||q;try{c.innerHTML='<a name="'+v+'"></a><b id="'+v+'"></b>';
+s.idGetsName=w.getElementById(v)===c.firstChild;}catch(C){}if(c.getElementsByClassName){try{c.innerHTML='<a class="f"></a><a class="b"></a>';c.getElementsByClassName("b").length;
+c.firstChild.className="b";z=(c.getElementsByClassName("b").length!=2);}catch(C){}try{c.innerHTML='<a class="a"></a><a class="f b a"></a>';y=(c.getElementsByClassName("a").length!=2);
+}catch(C){}s.brokenGEBCN=z||y;}if(c.querySelectorAll){try{c.innerHTML="foo</foo>";x=c.querySelectorAll("*");s.starSelectsClosedQSA=(x&&!!x.length&&x[0].nodeName.charAt(0)=="/");
+}catch(C){}try{c.innerHTML='<a class="MiX"></a>';s.brokenMixedCaseQSA=!c.querySelectorAll(".MiX").length;}catch(C){}try{c.innerHTML='<select><option selected="selected">a</option></select>';
+s.brokenCheckedQSA=(c.querySelectorAll(":checked").length==0);}catch(C){}try{c.innerHTML='<a class=""></a>';s.brokenEmptyAttributeQSA=(c.querySelectorAll('[class*=""]').length!=0);
+}catch(C){}}try{c.innerHTML='<form action="s"><input id="action"/></form>';t=(c.firstChild.getAttribute("action")!="s");}catch(C){}s.nativeMatchesSelector=A.matches||A.mozMatchesSelector||A.webkitMatchesSelector;
+if(s.nativeMatchesSelector){try{s.nativeMatchesSelector.call(A,":slick");s.nativeMatchesSelector=null;}catch(C){}}}try{A.slick_expando=1;delete A.slick_expando;
+s.getUID=this.getUIDHTML;}catch(C){s.getUID=this.getUIDXML;}n.removeChild(c);c=x=n=null;s.getAttribute=(s.isHTMLDocument&&t)?function(G,E){var H=this.attributeGetters[E];
+if(H){return H.call(G);}var F=G.getAttributeNode(E);return(F)?F.nodeValue:null;}:function(F,E){var G=this.attributeGetters[E];return(G)?G.call(F):F.getAttribute(E);
+};s.hasAttribute=(A&&this.isNativeCode(A.hasAttribute))?function(F,E){return F.hasAttribute(E);}:function(F,E){F=F.getAttributeNode(E);return !!(F&&(F.specified||F.nodeValue));
+};var D=A&&this.isNativeCode(A.contains),B=w&&this.isNativeCode(w.contains);s.contains=(D&&B)?function(E,F){return E.contains(F);}:(D&&!B)?function(E,F){return E===F||((E===w)?w.documentElement:E).contains(F);
+}:(A&&A.compareDocumentPosition)?function(E,F){return E===F||!!(E.compareDocumentPosition(F)&16);}:function(E,F){if(F){do{if(F===E){return true;}}while((F=F.parentNode));
+}return false;};s.documentSorter=(A.compareDocumentPosition)?function(F,E){if(!F.compareDocumentPosition||!E.compareDocumentPosition){return 0;}return F.compareDocumentPosition(E)&4?-1:F===E?0:1;
+}:("sourceIndex" in A)?function(F,E){if(!F.sourceIndex||!E.sourceIndex){return 0;}return F.sourceIndex-E.sourceIndex;}:(w.createRange)?function(H,F){if(!H.ownerDocument||!F.ownerDocument){return 0;
+}var G=H.ownerDocument.createRange(),E=F.ownerDocument.createRange();G.setStart(H,0);G.setEnd(H,0);E.setStart(F,0);E.setEnd(F,0);return G.compareBoundaryPoints(Range.START_TO_END,E);
+}:null;A=null;for(r in s){this[r]=s[r];}};var f=/^([#.]?)((?:[\w-]+|\*))$/,h=/\[.+[*$^]=(?:""|'')?\]/,g={};k.search=function(U,z,H,s){var p=this.found=(s)?null:(H||[]);
+if(!U){return p;}else{if(U.navigator){U=U.document;}else{if(!U.nodeType){return p;}}}var F,O,V=this.uniques={},I=!!(H&&H.length),y=(U.nodeType==9);if(this.document!==(y?U:U.ownerDocument)){this.setDocument(U);
+}if(I){for(O=p.length;O--;){V[this.getUID(p[O])]=true;}}if(typeof z=="string"){var r=z.match(f);simpleSelectors:if(r){var u=r[1],v=r[2],A,E;if(!u){if(v=="*"&&this.brokenStarGEBTN){break simpleSelectors;
+}E=U.getElementsByTagName(v);if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{if(u=="#"){if(!this.isHTMLDocument||!y){break simpleSelectors;
+}A=U.getElementById(v);if(!A){return p;}if(this.idGetsName&&A.getAttributeNode("id").nodeValue!=v){break simpleSelectors;}if(s){return A||null;}if(!(I&&V[this.getUID(A)])){p.push(A);
+}}else{if(u=="."){if(!this.isHTMLDocument||((!U.getElementsByClassName||this.brokenGEBCN)&&U.querySelectorAll)){break simpleSelectors;}if(U.getElementsByClassName&&!this.brokenGEBCN){E=U.getElementsByClassName(v);
+if(s){return E[0]||null;}for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}else{var T=new RegExp("(^|\\s)"+e.escapeRegExp(v)+"(\\s|$)");E=U.getElementsByTagName("*");
+for(O=0;A=E[O++];){className=A.className;if(!(className&&T.test(className))){continue;}if(s){return A;}if(!(I&&V[this.getUID(A)])){p.push(A);}}}}}}if(I){this.sort(p);
+}return(s)?null:p;}querySelector:if(U.querySelectorAll){if(!this.isHTMLDocument||g[z]||this.brokenMixedCaseQSA||(this.brokenCheckedQSA&&z.indexOf(":checked")>-1)||(this.brokenEmptyAttributeQSA&&h.test(z))||(!y&&z.indexOf(",")>-1)||e.disableQSA){break querySelector;
+}var S=z,x=U;if(!y){var C=x.getAttribute("id"),t="slickid__";x.setAttribute("id",t);S="#"+t+" "+S;U=x.parentNode;}try{if(s){return U.querySelector(S)||null;
+}else{E=U.querySelectorAll(S);}}catch(Q){g[z]=1;break querySelector;}finally{if(!y){if(C){x.setAttribute("id",C);}else{x.removeAttribute("id");}U=x;}}if(this.starSelectsClosedQSA){for(O=0;
+A=E[O++];){if(A.nodeName>"@"&&!(I&&V[this.getUID(A)])){p.push(A);}}}else{for(O=0;A=E[O++];){if(!(I&&V[this.getUID(A)])){p.push(A);}}}if(I){this.sort(p);
+}return p;}F=this.Slick.parse(z);if(!F.length){return p;}}else{if(z==null){return p;}else{if(z.Slick){F=z;}else{if(this.contains(U.documentElement||U,z)){(p)?p.push(z):p=z;
+return p;}else{return p;}}}}this.posNTH={};this.posNTHLast={};this.posNTHType={};this.posNTHTypeLast={};this.push=(!I&&(s||(F.length==1&&F.expressions[0].length==1)))?this.pushArray:this.pushUID;
+if(p==null){p=[];}var M,L,K;var B,J,D,c,q,G,W;var N,P,o,w,R=F.expressions;search:for(O=0;(P=R[O]);O++){for(M=0;(o=P[M]);M++){B="combinator:"+o.combinator;
+if(!this[B]){continue search;}J=(this.isXMLDocument)?o.tag:o.tag.toUpperCase();D=o.id;c=o.classList;q=o.classes;G=o.attributes;W=o.pseudos;w=(M===(P.length-1));
+this.bitUniques={};if(w){this.uniques=V;this.found=p;}else{this.uniques={};this.found=[];}if(M===0){this[B](U,J,D,q,G,W,c);if(s&&w&&p.length){break search;
+}}else{if(s&&w){for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);if(p.length){break search;}}}else{for(L=0,K=N.length;L<K;L++){this[B](N[L],J,D,q,G,W,c);
+}}}N=this.found;}}if(I||(F.expressions.length>1)){this.sort(p);}return(s)?(p[0]||null):p;};k.uidx=1;k.uidk="slick-uniqueid";k.getUIDXML=function(n){var c=n.getAttribute(this.uidk);
+if(!c){c=this.uidx++;n.setAttribute(this.uidk,c);}return c;};k.getUIDHTML=function(c){return c.uniqueNumber||(c.uniqueNumber=this.uidx++);};k.sort=function(c){if(!this.documentSorter){return c;
+}c.sort(this.documentSorter);return c;};k.cacheNTH={};k.matchNTH=/^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;k.parseNTHArgument=function(q){var o=q.match(this.matchNTH);
+if(!o){return false;}var p=o[2]||false;var n=o[1]||1;if(n=="-"){n=-1;}var c=+o[3]||0;o=(p=="n")?{a:n,b:c}:(p=="odd")?{a:2,b:1}:(p=="even")?{a:2,b:0}:{a:0,b:n};
+return(this.cacheNTH[q]=o);};k.createNTHPseudo=function(p,n,c,o){return function(s,q){var u=this.getUID(s);if(!this[c][u]){var A=s.parentNode;if(!A){return false;
+}var r=A[p],t=1;if(o){var z=s.nodeName;do{if(r.nodeName!=z){continue;}this[c][this.getUID(r)]=t++;}while((r=r[n]));}else{do{if(r.nodeType!=1){continue;
+}this[c][this.getUID(r)]=t++;}while((r=r[n]));}}q=q||"n";var v=this.cacheNTH[q]||this.parseNTHArgument(q);if(!v){return false;}var y=v.a,x=v.b,w=this[c][u];
+if(y==0){return x==w;}if(y>0){if(w<x){return false;}}else{if(x<w){return false;}}return((w-x)%y)==0;};};k.pushArray=function(p,c,r,o,n,q){if(this.matchSelector(p,c,r,o,n,q)){this.found.push(p);
+}};k.pushUID=function(q,c,s,p,n,r){var o=this.getUID(q);if(!this.uniques[o]&&this.matchSelector(q,c,s,p,n,r)){this.uniques[o]=true;this.found.push(q);}};
+k.matchNode=function(n,o){if(this.isHTMLDocument&&this.nativeMatchesSelector){try{return this.nativeMatchesSelector.call(n,o.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g,'[$1="$2"]'));
+}catch(u){}}var t=this.Slick.parse(o);if(!t){return true;}var r=t.expressions,s=0,q;for(q=0;(currentExpression=r[q]);q++){if(currentExpression.length==1){var p=currentExpression[0];
+if(this.matchSelector(n,(this.isXMLDocument)?p.tag:p.tag.toUpperCase(),p.id,p.classes,p.attributes,p.pseudos)){return true;}s++;}}if(s==t.length){return false;
+}var c=this.search(this.document,t),v;for(q=0;v=c[q++];){if(v===n){return true;}}return false;};k.matchPseudo=function(q,c,p){var n="pseudo:"+c;if(this[n]){return this[n](q,p);
+}var o=this.getAttribute(q,c);return(p)?p==o:!!o;};k.matchSelector=function(o,v,c,p,q,s){if(v){var t=(this.isXMLDocument)?o.nodeName:o.nodeName.toUpperCase();
+if(v=="*"){if(t<"@"){return false;}}else{if(t!=v){return false;}}}if(c&&o.getAttribute("id")!=c){return false;}var r,n,u;if(p){for(r=p.length;r--;){u=this.getAttribute(o,"class");
+if(!(u&&p[r].regexp.test(u))){return false;}}}if(q){for(r=q.length;r--;){n=q[r];if(n.operator?!n.test(this.getAttribute(o,n.key)):!this.hasAttribute(o,n.key)){return false;
+}}}if(s){for(r=s.length;r--;){n=s[r];if(!this.matchPseudo(o,n.key,n.value)){return false;}}}return true;};var j={" ":function(q,w,n,r,s,u,p){var t,v,o;
+if(this.isHTMLDocument){getById:if(n){v=this.document.getElementById(n);if((!v&&q.all)||(this.idGetsName&&v&&v.getAttributeNode("id").nodeValue!=n)){o=q.all[n];
+if(!o){return;}if(!o[0]){o=[o];}for(t=0;v=o[t++];){var c=v.getAttributeNode("id");if(c&&c.nodeValue==n){this.push(v,w,null,r,s,u);break;}}return;}if(!v){if(this.contains(this.root,q)){return;
+}else{break getById;}}else{if(this.document!==q&&!this.contains(q,v)){return;}}this.push(v,w,null,r,s,u);return;}getByClass:if(r&&q.getElementsByClassName&&!this.brokenGEBCN){o=q.getElementsByClassName(p.join(" "));
+if(!(o&&o.length)){break getByClass;}for(t=0;v=o[t++];){this.push(v,w,n,null,s,u);}return;}}getByTag:{o=q.getElementsByTagName(w);if(!(o&&o.length)){break getByTag;
+}if(!this.brokenStarGEBTN){w=null;}for(t=0;v=o[t++];){this.push(v,w,n,r,s,u);}}},">":function(p,c,r,o,n,q){if((p=p.firstChild)){do{if(p.nodeType==1){this.push(p,c,r,o,n,q);
+}}while((p=p.nextSibling));}},"+":function(p,c,r,o,n,q){while((p=p.nextSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);break;}}},"^":function(p,c,r,o,n,q){p=p.firstChild;
+if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:+"](p,c,r,o,n,q);}}},"~":function(q,c,s,p,n,r){while((q=q.nextSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}},"++":function(p,c,r,o,n,q){this["combinator:+"](p,c,r,o,n,q);
+this["combinator:!+"](p,c,r,o,n,q);},"~~":function(p,c,r,o,n,q){this["combinator:~"](p,c,r,o,n,q);this["combinator:!~"](p,c,r,o,n,q);},"!":function(p,c,r,o,n,q){while((p=p.parentNode)){if(p!==this.document){this.push(p,c,r,o,n,q);
+}}},"!>":function(p,c,r,o,n,q){p=p.parentNode;if(p!==this.document){this.push(p,c,r,o,n,q);}},"!+":function(p,c,r,o,n,q){while((p=p.previousSibling)){if(p.nodeType==1){this.push(p,c,r,o,n,q);
+break;}}},"!^":function(p,c,r,o,n,q){p=p.lastChild;if(p){if(p.nodeType==1){this.push(p,c,r,o,n,q);}else{this["combinator:!+"](p,c,r,o,n,q);}}},"!~":function(q,c,s,p,n,r){while((q=q.previousSibling)){if(q.nodeType!=1){continue;
+}var o=this.getUID(q);if(this.bitUniques[o]){break;}this.bitUniques[o]=true;this.push(q,c,s,p,n,r);}}};for(var i in j){k["combinator:"+i]=j[i];}var l={empty:function(c){var n=c.firstChild;
+return !(n&&n.nodeType==1)&&!(c.innerText||c.textContent||"").length;},not:function(c,n){return !this.matchNode(c,n);},contains:function(c,n){return(c.innerText||c.textContent||"").indexOf(n)>-1;
+},"first-child":function(c){while((c=c.previousSibling)){if(c.nodeType==1){return false;}}return true;},"last-child":function(c){while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"only-child":function(o){var n=o;while((n=n.previousSibling)){if(n.nodeType==1){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeType==1){return false;
+}}return true;},"nth-child":k.createNTHPseudo("firstChild","nextSibling","posNTH"),"nth-last-child":k.createNTHPseudo("lastChild","previousSibling","posNTHLast"),"nth-of-type":k.createNTHPseudo("firstChild","nextSibling","posNTHType",true),"nth-last-of-type":k.createNTHPseudo("lastChild","previousSibling","posNTHTypeLast",true),index:function(n,c){return this["pseudo:nth-child"](n,""+(c+1));
+},even:function(c){return this["pseudo:nth-child"](c,"2n");},odd:function(c){return this["pseudo:nth-child"](c,"2n+1");},"first-of-type":function(c){var n=c.nodeName;
+while((c=c.previousSibling)){if(c.nodeName==n){return false;}}return true;},"last-of-type":function(c){var n=c.nodeName;while((c=c.nextSibling)){if(c.nodeName==n){return false;
+}}return true;},"only-of-type":function(o){var n=o,p=o.nodeName;while((n=n.previousSibling)){if(n.nodeName==p){return false;}}var c=o;while((c=c.nextSibling)){if(c.nodeName==p){return false;
+}}return true;},enabled:function(c){return !c.disabled;},disabled:function(c){return c.disabled;},checked:function(c){return c.checked||c.selected;},focus:function(c){return this.isHTMLDocument&&this.document.activeElement===c&&(c.href||c.type||this.hasAttribute(c,"tabindex"));
+},root:function(c){return(c===this.root);},selected:function(c){return c.selected;}};for(var b in l){k["pseudo:"+b]=l[b];}var a=k.attributeGetters={"for":function(){return("htmlFor" in this)?this.htmlFor:this.getAttribute("for");
+},href:function(){return("href" in this)?this.getAttribute("href",2):this.getAttribute("href");},style:function(){return(this.style)?this.style.cssText:this.getAttribute("style");
+},tabindex:function(){var c=this.getAttributeNode("tabindex");return(c&&c.specified)?c.nodeValue:null;},type:function(){return this.getAttribute("type");
+},maxlength:function(){var c=this.getAttributeNode("maxLength");return(c&&c.specified)?c.nodeValue:null;}};a.MAXLENGTH=a.maxLength=a.maxlength;var e=k.Slick=(this.Slick||{});
+e.version="1.1.7";e.search=function(n,o,c){return k.search(n,o,c);};e.find=function(c,n){return k.search(c,n,null,true);};e.contains=function(c,n){k.setDocument(c);
+return k.contains(c,n);};e.getAttribute=function(n,c){k.setDocument(n);return k.getAttribute(n,c);};e.hasAttribute=function(n,c){k.setDocument(n);return k.hasAttribute(n,c);
+};e.match=function(n,c){if(!(n&&c)){return false;}if(!c||c===n){return true;}k.setDocument(n);return k.matchNode(n,c);};e.defineAttributeGetter=function(c,n){k.attributeGetters[c]=n;
+return this;};e.lookupAttributeGetter=function(c){return k.attributeGetters[c];};e.definePseudo=function(c,n){k["pseudo:"+c]=function(p,o){return n.call(p,o);
+};return this;};e.lookupPseudo=function(c){var n=k["pseudo:"+c];if(n){return function(o){return n.call(this,o);};}return null;};e.override=function(n,c){k.override(n,c);
+return this;};e.isXML=k.isXML;e.uidOf=function(c){return k.getUIDHTML(c);};if(!this.Slick){this.Slick=e;}}).apply((typeof exports!="undefined")?exports:this);
+var Element=this.Element=function(b,g){var h=Element.Constructors[b];if(h){return h(g);}if(typeof b!="string"){return document.id(b).set(g);}if(!g){g={};
+}if(!(/^[\w-]+$/).test(b)){var e=Slick.parse(b).expressions[0][0];b=(e.tag=="*")?"div":e.tag;if(e.id&&g.id==null){g.id=e.id;}var d=e.attributes;if(d){for(var a,f=0,c=d.length;
+f<c;f++){a=d[f];if(g[a.key]!=null){continue;}if(a.value!=null&&a.operator=="="){g[a.key]=a.value;}else{if(!a.value&&!a.operator){g[a.key]=true;}}}}if(e.classList&&g["class"]==null){g["class"]=e.classList.join(" ");
+}}return document.newElement(b,g);};if(Browser.Element){Element.prototype=Browser.Element.prototype;Element.prototype._fireEvent=(function(a){return function(b,c){return a.call(this,b,c);
+};})(Element.prototype.fireEvent);}new Type("Element",Element).mirror(function(a){if(Array.prototype[a]){return;}var b={};b[a]=function(){var h=[],e=arguments,j=true;
+for(var g=0,d=this.length;g<d;g++){var f=this[g],c=h[g]=f[a].apply(f,e);j=(j&&typeOf(c)=="element");}return(j)?new Elements(h):h;};Elements.implement(b);
+});if(!Browser.Element){Element.parent=Object;Element.Prototype={"$constructor":Element,"$family":Function.from("element").hide()};Element.mirror(function(a,b){Element.Prototype[a]=b;
+});}Element.Constructors={};var IFrame=new Type("IFrame",function(){var e=Array.link(arguments,{properties:Type.isObject,iframe:function(f){return(f!=null);
+}});var c=e.properties||{},b;if(e.iframe){b=document.id(e.iframe);}var d=c.onload||function(){};delete c.onload;c.id=c.name=[c.id,c.name,b?(b.id||b.name):"IFrame_"+String.uniqueID()].pick();
+b=new Element(b||"iframe",c);var a=function(){d.call(b.contentWindow);};if(window.frames[c.id]){a();}else{b.addListener("load",a);}return b;});var Elements=this.Elements=function(a){if(a&&a.length){var e={},d;
+for(var c=0;d=a[c++];){var b=Slick.uidOf(d);if(!e[b]){e[b]=true;this.push(d);}}}};Elements.prototype={length:0};Elements.parent=Array;new Type("Elements",Elements).implement({filter:function(a,b){if(!a){return this;
+}return new Elements(Array.filter(this,(typeOf(a)=="string")?function(c){return c.match(a);}:a,b));}.protect(),push:function(){var d=this.length;for(var b=0,a=arguments.length;
+b<a;b++){var c=document.id(arguments[b]);if(c){this[d++]=c;}}return(this.length=d);}.protect(),unshift:function(){var b=[];for(var c=0,a=arguments.length;
+c<a;c++){var d=document.id(arguments[c]);if(d){b.push(d);}}return Array.prototype.unshift.apply(this,b);}.protect(),concat:function(){var b=new Elements(this);
+for(var c=0,a=arguments.length;c<a;c++){var d=arguments[c];if(Type.isEnumerable(d)){b.append(d);}else{b.push(d);}}return b;}.protect(),append:function(c){for(var b=0,a=c.length;
+b<a;b++){this.push(c[b]);}return this;}.protect(),empty:function(){while(this.length){delete this[--this.length];}return this;}.protect()});(function(){var f=Array.prototype.splice,a={"0":0,"1":1,length:2};
+f.call(a,1,1);if(a[1]==1){Elements.implement("splice",function(){var g=this.length;var e=f.apply(this,arguments);while(g>=this.length){delete this[g--];
+}return e;}.protect());}Array.forEachMethod(function(g,e){Elements.implement(e,g);});Array.mirror(Elements);var d;try{d=(document.createElement("<input name=x>").name=="x");
+}catch(b){}var c=function(e){return(""+e).replace(/&/g,"&amp;").replace(/"/g,"&quot;");};Document.implement({newElement:function(e,g){if(g&&g.checked!=null){g.defaultChecked=g.checked;
+}if(d&&g){e="<"+e;if(g.name){e+=' name="'+c(g.name)+'"';}if(g.type){e+=' type="'+c(g.type)+'"';}e+=">";delete g.name;delete g.type;}return this.id(this.createElement(e)).set(g);
+}});})();(function(){Slick.uidOf(window);Slick.uidOf(document);Document.implement({newTextNode:function(e){return this.createTextNode(e);},getDocument:function(){return this;
+},getWindow:function(){return this.window;},id:(function(){var e={string:function(L,K,l){L=Slick.find(l,"#"+L.replace(/(\W)/g,"\\$1"));return(L)?e.element(L,K):null;
+},element:function(K,L){Slick.uidOf(K);if(!L&&!K.$family&&!(/^(?:object|embed)$/i).test(K.tagName)){var l=K.fireEvent;K._fireEvent=function(M,N){return l(M,N);
+};Object.append(K,Element.Prototype);}return K;},object:function(K,L,l){if(K.toElement){return e.element(K.toElement(l),L);}return null;}};e.textnode=e.whitespace=e.window=e.document=function(l){return l;
+};return function(K,M,L){if(K&&K.$family&&K.uniqueNumber){return K;}var l=typeOf(K);return(e[l])?e[l](K,M,L||document):null;};})()});if(window.$==null){Window.implement("$",function(e,l){return document.id(e,l,this.document);
+});}Window.implement({getDocument:function(){return this.document;},getWindow:function(){return this;}});[Document,Element].invoke("implement",{getElements:function(e){return Slick.search(this,e,new Elements);
+},getElement:function(e){return document.id(Slick.find(this,e));}});var p={contains:function(e){return Slick.contains(this,e);}};if(!document.contains){Document.implement(p);
+}if(!document.createElement("div").contains){Element.implement(p);}var v=function(L,K){if(!L){return K;}L=Object.clone(Slick.parse(L));var l=L.expressions;
+for(var e=l.length;e--;){l[e][0].combinator=K;}return L;};Object.forEach({getNext:"~",getPrevious:"!~",getParent:"!"},function(e,l){Element.implement(l,function(K){return this.getElement(v(K,e));
+});});Object.forEach({getAllNext:"~",getAllPrevious:"!~",getSiblings:"~~",getChildren:">",getParents:"!"},function(e,l){Element.implement(l,function(K){return this.getElements(v(K,e));
+});});Element.implement({getFirst:function(e){return document.id(Slick.search(this,v(e,">"))[0]);},getLast:function(e){return document.id(Slick.search(this,v(e,">")).getLast());
+},getWindow:function(){return this.ownerDocument.window;},getDocument:function(){return this.ownerDocument;},getElementById:function(e){return document.id(Slick.find(this,"#"+(""+e).replace(/(\W)/g,"\\$1")));
+},match:function(e){return !e||Slick.match(this,e);}});if(window.$$==null){Window.implement("$$",function(e){if(arguments.length==1){if(typeof e=="string"){return Slick.search(this.document,e,new Elements);
+}else{if(Type.isEnumerable(e)){return new Elements(e);}}}return new Elements(arguments);});}var A={before:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e);
+}},after:function(l,e){var K=e.parentNode;if(K){K.insertBefore(l,e.nextSibling);}},bottom:function(l,e){e.appendChild(l);},top:function(l,e){e.insertBefore(l,e.firstChild);
+}};A.inside=A.bottom;var n={},d={};var o={};Array.forEach(["type","value","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","rowSpan","tabIndex","useMap"],function(e){o[e.toLowerCase()]=e;
+});o.html="innerHTML";o.text=(document.createElement("div").textContent==null)?"innerText":"textContent";Object.forEach(o,function(l,e){d[e]=function(K,L){K[l]=L;
+};n[e]=function(K){return K[l];};});var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readOnly","multiple","selected","noresize","defer","defaultChecked","autofocus","controls","autoplay","loop"];
+var k={};Array.forEach(B,function(e){var l=e.toLowerCase();k[l]=e;d[l]=function(K,L){K[e]=!!L;};n[l]=function(K){return !!K[e];};});Object.append(d,{"class":function(e,l){("className" in e)?e.className=(l||""):e.setAttribute("class",l);
+},"for":function(e,l){("htmlFor" in e)?e.htmlFor=l:e.setAttribute("for",l);},style:function(e,l){(e.style)?e.style.cssText=l:e.setAttribute("style",l);
+},value:function(e,l){e.value=(l!=null)?l:"";}});n["class"]=function(e){return("className" in e)?e.className||null:e.getAttribute("class");};var f=document.createElement("button");
+try{f.type="button";}catch(E){}if(f.type!="button"){d.type=function(e,l){e.setAttribute("type",l);};}f=null;var s=document.createElement("input");s.value="t";
+s.type="submit";if(s.value!="t"){d.type=function(l,e){var K=l.value;l.type=e;l.value=K;};}s=null;var u=(function(e){e.random="attribute";return(e.getAttribute("random")=="attribute");
+})(document.createElement("div"));var i=(function(e){e.innerHTML='<object><param name="should_fix" value="the unknown"></object>';return e.cloneNode(true).firstChild.childNodes.length!=1;
+})(document.createElement("div"));var j=!!document.createElement("div").classList;var F=function(e){var l=(e||"").clean().split(" "),K={};return l.filter(function(L){if(L!==""&&!K[L]){return K[L]=L;
+}});};var t=function(e){this.classList.add(e);};var g=function(e){this.classList.remove(e);};Element.implement({setProperty:function(l,K){var L=d[l.toLowerCase()];
+if(L){L(this,K);}else{var e;if(u){e=this.retrieve("$attributeWhiteList",{});}if(K==null){this.removeAttribute(l);if(u){delete e[l];}}else{this.setAttribute(l,""+K);
+if(u){e[l]=true;}}}return this;},setProperties:function(e){for(var l in e){this.setProperty(l,e[l]);}return this;},getProperty:function(M){var K=n[M.toLowerCase()];
+if(K){return K(this);}if(u){var l=this.getAttributeNode(M),L=this.retrieve("$attributeWhiteList",{});if(!l){return null;}if(l.expando&&!L[M]){var N=this.outerHTML;
+if(N.substr(0,N.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(M)<0){return null;}L[M]=true;}}var e=Slick.getAttribute(this,M);return(!e&&!Slick.hasAttribute(this,M))?null:e;
+},getProperties:function(){var e=Array.from(arguments);return e.map(this.getProperty,this).associate(e);},removeProperty:function(e){return this.setProperty(e,null);
+},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this;},set:function(K,l){var e=Element.Properties[K];(e&&e.set)?e.set.call(this,l):this.setProperty(K,l);
+}.overloadSetter(),get:function(l){var e=Element.Properties[l];return(e&&e.get)?e.get.apply(this):this.getProperty(l);}.overloadGetter(),erase:function(l){var e=Element.Properties[l];
+(e&&e.erase)?e.erase.apply(this):this.removeProperty(l);return this;},hasClass:j?function(e){return this.classList.contains(e);}:function(e){return this.className.clean().contains(e," ");
+},addClass:j?function(e){F(e).forEach(t,this);return this;}:function(e){this.className=F(e+" "+this.className).join(" ");return this;},removeClass:j?function(e){F(e).forEach(g,this);
+return this;}:function(e){var l=F(this.className);F(e).forEach(l.erase,l);this.className=l.join(" ");return this;},toggleClass:function(e,l){if(l==null){l=!this.hasClass(e);
+}return(l)?this.addClass(e):this.removeClass(e);},adopt:function(){var L=this,e,N=Array.flatten(arguments),M=N.length;if(M>1){L=e=document.createDocumentFragment();
+}for(var K=0;K<M;K++){var l=document.id(N[K],true);if(l){L.appendChild(l);}}if(e){this.appendChild(e);}return this;},appendText:function(l,e){return this.grab(this.getDocument().newTextNode(l),e);
+},grab:function(l,e){A[e||"bottom"](document.id(l,true),this);return this;},inject:function(l,e){A[e||"bottom"](this,document.id(l,true));return this;},replaces:function(e){e=document.id(e,true);
+e.parentNode.replaceChild(this,e);return this;},wraps:function(l,e){l=document.id(l,true);return this.replaces(l).grab(l,e);},getSelected:function(){this.selectedIndex;
+return new Elements(Array.from(this.options).filter(function(e){return e.selected;}));},toQueryString:function(){var e=[];this.getElements("input, select, textarea").each(function(K){var l=K.type;
+if(!K.name||K.disabled||l=="submit"||l=="reset"||l=="file"||l=="image"){return;}var L=(K.get("tag")=="select")?K.getSelected().map(function(M){return document.id(M).get("value");
+}):((l=="radio"||l=="checkbox")&&!K.checked)?null:K.get("value");Array.from(L).each(function(M){if(typeof M!="undefined"){e.push(encodeURIComponent(K.name)+"="+encodeURIComponent(M));
+}});});return e.join("&");}});var I={before:"beforeBegin",after:"afterEnd",bottom:"beforeEnd",top:"afterBegin",inside:"beforeEnd"};Element.implement("appendHTML",("insertAdjacentHTML" in document.createElement("div"))?function(l,e){this.insertAdjacentHTML(I[e||"bottom"],l);
+return this;}:function(P,M){var K=new Element("div",{html:P}),O=K.childNodes,L=K.firstChild;if(!L){return this;}if(O.length>1){L=document.createDocumentFragment();
+for(var N=0,e=O.length;N<e;N++){L.appendChild(O[N]);}}A[M||"bottom"](L,this);return this;});var m={},D={};var G=function(e){return(D[e]||(D[e]={}));};var z=function(l){var e=l.uniqueNumber;
+if(l.removeEvents){l.removeEvents();}if(l.clearAttributes){l.clearAttributes();}if(e!=null){delete m[e];delete D[e];}return l;};var H={input:"checked",option:"selected",textarea:"value"};
+Element.implement({destroy:function(){var e=z(this).getElementsByTagName("*");Array.each(e,z);Element.dispose(this);return null;},empty:function(){Array.from(this.childNodes).each(Element.dispose);
+return this;},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this;},clone:function(N,L){N=N!==false;var S=this.cloneNode(N),K=[S],M=[this],Q;
+if(N){K.append(Array.from(S.getElementsByTagName("*")));M.append(Array.from(this.getElementsByTagName("*")));}for(Q=K.length;Q--;){var O=K[Q],R=M[Q];if(!L){O.removeAttribute("id");
+}if(O.clearAttributes){O.clearAttributes();O.mergeAttributes(R);O.removeAttribute("uniqueNumber");if(O.options){var V=O.options,e=R.options;for(var P=V.length;
+P--;){V[P].selected=e[P].selected;}}}var l=H[R.tagName.toLowerCase()];if(l&&R[l]){O[l]=R[l];}}if(i){var T=S.getElementsByTagName("object"),U=this.getElementsByTagName("object");
+for(Q=T.length;Q--;){T[Q].outerHTML=U[Q].outerHTML;}}return document.id(S);}});[Element,Window,Document].invoke("implement",{addListener:function(l,e){if(window.attachEvent&&!window.addEventListener){m[Slick.uidOf(this)]=this;
+}if(this.addEventListener){this.addEventListener(l,e,!!arguments[2]);}else{this.attachEvent("on"+l,e);}return this;},removeListener:function(l,e){if(this.removeEventListener){this.removeEventListener(l,e,!!arguments[2]);
+}else{this.detachEvent("on"+l,e);}return this;},retrieve:function(l,e){var L=G(Slick.uidOf(this)),K=L[l];if(e!=null&&K==null){K=L[l]=e;}return K!=null?K:null;
+},store:function(l,e){var K=G(Slick.uidOf(this));K[l]=e;return this;},eliminate:function(e){var l=G(Slick.uidOf(this));delete l[e];return this;}});if(window.attachEvent&&!window.addEventListener){var J=function(){Object.each(m,z);
+if(window.CollectGarbage){CollectGarbage();}window.removeListener("unload",J);};window.addListener("unload",J);}Element.Properties={};Element.Properties.style={set:function(e){this.style.cssText=e;
+},get:function(){return this.style.cssText;},erase:function(){this.style.cssText="";}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase();
+}};Element.Properties.html={set:function(e){if(e==null){e="";}else{if(typeOf(e)=="array"){e=e.join("");}}this.innerHTML=e;},erase:function(){this.innerHTML="";
+}};var a=true,h=true,C=true;var x=document.createElement("div");x.innerHTML="<nav></nav>";a=(x.childNodes.length==1);if(!a){var w="abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),b=document.createDocumentFragment(),y=w.length;
+while(y--){b.createElement(w[y]);}}x=null;h=Function.attempt(function(){var e=document.createElement("table");e.innerHTML="<tr><td></td></tr>";return true;
+});var c=document.createElement("tr"),r="<td></td>";c.innerHTML=r;C=(c.innerHTML==r);c=null;if(!h||!C||!a){Element.Properties.html.set=(function(l){var e={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};
+e.thead=e.tfoot=e.tbody;return function(K){var L=e[this.get("tag")];if(!L&&!a){L=[0,"",""];}if(!L){return l.call(this,K);}var O=L[0],N=document.createElement("div"),M=N;
+if(!a){b.appendChild(N);}N.innerHTML=[L[1],K,L[2]].flatten().join("");while(O--){M=M.firstChild;}this.empty().adopt(M.childNodes);if(!a){b.removeChild(N);
+}N=null;};})(Element.Properties.html.set);}var q=document.createElement("form");q.innerHTML="<select><option>s</option></select>";if(q.firstChild.value!="s"){Element.Properties.value={set:function(N){var l=this.get("tag");
+if(l!="select"){return this.setProperty("value",N);}var K=this.getElements("option");N=String(N);for(var L=0;L<K.length;L++){var M=K[L],e=M.getAttributeNode("value"),O=(e&&e.specified)?M.value:M.get("text");
+if(O===N){return M.selected=true;}}},get:function(){var K=this,l=K.get("tag");if(l!="select"&&l!="option"){return this.getProperty("value");}if(l=="select"&&!(K=K.getSelected()[0])){return"";
+}var e=K.getAttributeNode("value");return(e&&e.specified)?K.value:K.get("text");}};}q=null;if(document.createElement("div").getAttributeNode("id")){Element.Properties.id={set:function(e){this.id=this.getAttributeNode("id").value=e;
+},get:function(){return this.id||null;},erase:function(){this.id=this.getAttributeNode("id").value="";}};}})();(function(){var l=document.html,f;f=document.createElement("div");
+f.style.color="red";f.style.color=null;var e=f.style.color=="red";var k="1px solid #123abc";f.style.border=k;var o=f.style.border!=k;f=null;var n=!!window.getComputedStyle;
+Element.Properties.styles={set:function(r){this.setStyles(r);}};var j=(l.style.opacity!=null),g=(l.style.filter!=null),q=/alpha\(opacity=([\d.]+)\)/i;var b=function(s,r){s.store("$opacity",r);
+s.style.visibility=r>0||r==null?"visible":"hidden";};var p=function(r,v,u){var t=r.style,s=t.filter||r.getComputedStyle("filter")||"";t.filter=(v.test(s)?s.replace(v,u):s+" "+u).trim();
+if(!t.filter){t.removeAttribute("filter");}};var h=(j?function(s,r){s.style.opacity=r;}:(g?function(s,r){if(!s.currentStyle||!s.currentStyle.hasLayout){s.style.zoom=1;
+}if(r==null||r==1){p(s,q,"");if(r==1&&i(s)!=1){p(s,q,"alpha(opacity=100)");}}else{p(s,q,"alpha(opacity="+(r*100).limit(0,100).round()+")");}}:b));var i=(j?function(s){var r=s.style.opacity||s.getComputedStyle("opacity");
+return(r=="")?1:r.toFloat();}:(g?function(s){var t=(s.style.filter||s.getComputedStyle("filter")),r;if(t){r=t.match(q);}return(r==null||t==null)?1:(r[1]/100);
+}:function(s){var r=s.retrieve("$opacity");if(r==null){r=(s.style.visibility=="hidden"?0:1);}return r;}));var d=(l.style.cssFloat==null)?"styleFloat":"cssFloat",a={left:"0%",top:"0%",center:"50%",right:"100%",bottom:"100%"},c=(l.style.backgroundPositionX!=null);
+var m=function(r,s){if(s=="backgroundPosition"){r.removeAttribute(s+"X");s+="Y";}r.removeAttribute(s);};Element.implement({getComputedStyle:function(t){if(!n&&this.currentStyle){return this.currentStyle[t.camelCase()];
+}var s=Element.getDocument(this).defaultView,r=s?s.getComputedStyle(this,null):null;return(r)?r.getPropertyValue((t==d)?"float":t.hyphenate()):"";},setStyle:function(s,r){if(s=="opacity"){if(r!=null){r=parseFloat(r);
+}h(this,r);return this;}s=(s=="float"?d:s).camelCase();if(typeOf(r)!="string"){var t=(Element.Styles[s]||"@").split(" ");r=Array.from(r).map(function(v,u){if(!t[u]){return"";
+}return(typeOf(v)=="number")?t[u].replace("@",Math.round(v)):v;}).join(" ");}else{if(r==String(Number(r))){r=Math.round(r);}}this.style[s]=r;if((r==""||r==null)&&e&&this.style.removeAttribute){m(this.style,s);
+}return this;},getStyle:function(x){if(x=="opacity"){return i(this);}x=(x=="float"?d:x).camelCase();var r=this.style[x];if(!r||x=="zIndex"){if(Element.ShortStyles.hasOwnProperty(x)){r=[];
+for(var w in Element.ShortStyles[x]){r.push(this.getStyle(w));}return r.join(" ");}r=this.getComputedStyle(x);}if(c&&/^backgroundPosition[XY]?$/.test(x)){return r.replace(/(top|right|bottom|left)/g,function(s){return a[s];
+})||"0px";}if(!r&&x=="backgroundPosition"){return"0px 0px";}if(r){r=String(r);var u=r.match(/rgba?\([\d\s,]+\)/);if(u){r=r.replace(u[0],u[0].rgbToHex());
+}}if(!n&&!this.style[x]){if((/^(height|width)$/).test(x)&&!(/px$/.test(r))){var t=(x=="width")?["left","right"]:["top","bottom"],v=0;t.each(function(s){v+=this.getStyle("border-"+s+"-width").toInt()+this.getStyle("padding-"+s).toInt();
+},this);return this["offset"+x.capitalize()]-v+"px";}if((/^border(.+)Width|margin|padding/).test(x)&&isNaN(parseFloat(r))){return"0px";}}if(o&&/^border(Top|Right|Bottom|Left)?$/.test(x)&&/^#/.test(r)){return r.replace(/^(.+)\s(.+)\s(.+)$/,"$2 $3 $1");
+}return r;},setStyles:function(s){for(var r in s){this.setStyle(r,s[r]);}return this;},getStyles:function(){var r={};Array.flatten(arguments).each(function(s){r[s]=this.getStyle(s);
+},this);return r;}});Element.Styles={left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundSize:"@px",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"};
+Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(x){var w=Element.ShortStyles;
+var s=Element.Styles;["margin","padding"].each(function(y){var z=y+x;w[y][z]=s[z]="@px";});var v="border"+x;w.border[v]=s[v]="@px @ rgb(@, @, @)";var u=v+"Width",r=v+"Style",t=v+"Color";
+w[v]={};w.borderWidth[u]=w[v][u]=s[u]="@px";w.borderStyle[r]=w[v][r]=s[r]="@";w.borderColor[t]=w[v][t]=s[t]="rgb(@, @, @)";});if(c){Element.ShortStyles.backgroundPosition={backgroundPositionX:"@",backgroundPositionY:"@"};
+}})();(function(){Element.Properties.events={set:function(b){this.addEvents(b);}};[Element,Window,Document].invoke("implement",{addEvent:function(f,h){var i=this.retrieve("events",{});
+if(!i[f]){i[f]={keys:[],values:[]};}if(i[f].keys.contains(h)){return this;}i[f].keys.push(h);var g=f,b=Element.Events[f],d=h,j=this;if(b){if(b.onAdd){b.onAdd.call(this,h,f);
+}if(b.condition){d=function(k){if(b.condition.call(this,k,f)){return h.call(this,k);}return true;};}if(b.base){g=Function.from(b.base).call(this,f);}}var e=function(){return h.call(j);
+};var c=Element.NativeEvents[g];if(c){if(c==2){e=function(k){k=new DOMEvent(k,j.getWindow());if(d.call(j,k)===false){k.stop();}};}this.addListener(g,e,arguments[2]);
+}i[f].values.push(e);return this;},removeEvent:function(e,d){var c=this.retrieve("events");if(!c||!c[e]){return this;}var h=c[e];var b=h.keys.indexOf(d);
+if(b==-1){return this;}var g=h.values[b];delete h.keys[b];delete h.values[b];var f=Element.Events[e];if(f){if(f.onRemove){f.onRemove.call(this,d,e);}if(f.base){e=Function.from(f.base).call(this,e);
+}}return(Element.NativeEvents[e])?this.removeListener(e,g,arguments[2]):this;},addEvents:function(b){for(var c in b){this.addEvent(c,b[c]);}return this;
+},removeEvents:function(b){var d;if(typeOf(b)=="object"){for(d in b){this.removeEvent(d,b[d]);}return this;}var c=this.retrieve("events");if(!c){return this;
+}if(!b){for(d in c){this.removeEvents(d);}this.eliminate("events");}else{if(c[b]){c[b].keys.each(function(e){this.removeEvent(b,e);},this);delete c[b];
+}}return this;},fireEvent:function(e,c,b){var d=this.retrieve("events");if(!d||!d[e]){return this;}c=Array.from(c);d[e].keys.each(function(f){if(b){f.delay(b,this,c);
+}else{f.apply(this,c);}},this);return this;},cloneEvents:function(e,d){e=document.id(e);var c=e.retrieve("events");if(!c){return this;}if(!d){for(var b in c){this.cloneEvents(e,b);
+}}else{if(c[d]){c[d].keys.each(function(f){this.addEvent(d,f);},this);}}return this;}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,orientationchange:2,touchstart:2,touchmove:2,touchend:2,touchcancel:2,gesturestart:2,gesturechange:2,gestureend:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,paste:2,input:2,load:2,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,hashchange:1,popstate:2,error:1,abort:1,scroll:1};
+Element.Events={mousewheel:{base:"onwheel" in document?"wheel":"onmousewheel" in document?"mousewheel":"DOMMouseScroll"}};var a=function(b){var c=b.relatedTarget;
+if(c==null){return true;}if(!c){return false;}return(c!=this&&c.prefix!="xul"&&typeOf(this)!="document"&&!this.contains(c));};if("onmouseenter" in document.documentElement){Element.NativeEvents.mouseenter=Element.NativeEvents.mouseleave=2;
+Element.MouseenterCheck=a;}else{Element.Events.mouseenter={base:"mouseover",condition:a};Element.Events.mouseleave={base:"mouseout",condition:a};}if(!window.addEventListener){Element.NativeEvents.propertychange=2;
+Element.Events.change={base:function(){var b=this.type;return(this.get("tag")=="input"&&(b=="radio"||b=="checkbox"))?"propertychange":"change";},condition:function(b){return b.type!="propertychange"||b.event.propertyName=="checked";
+}};}})();(function(){var c=!!window.addEventListener;Element.NativeEvents.focusin=Element.NativeEvents.focusout=2;var k=function(l,m,n,o,p){while(p&&p!=l){if(m(p,o)){return n.call(p,o,p);
+}p=document.id(p.parentNode);}};var a={mouseenter:{base:"mouseover",condition:Element.MouseenterCheck},mouseleave:{base:"mouseout",condition:Element.MouseenterCheck},focus:{base:"focus"+(c?"":"in"),capture:true},blur:{base:c?"blur":"focusout",capture:true}};
+var b="$delegation:";var i=function(l){return{base:"focusin",remove:function(m,o){var p=m.retrieve(b+l+"listeners",{})[o];if(p&&p.forms){for(var n=p.forms.length;
+n--;){p.forms[n].removeEvent(l,p.fns[n]);}}},listen:function(x,r,v,n,t,s){var o=(t.get("tag")=="form")?t:n.target.getParent("form");if(!o){return;}var u=x.retrieve(b+l+"listeners",{}),p=u[s]||{forms:[],fns:[]},m=p.forms,w=p.fns;
+if(m.indexOf(o)!=-1){return;}m.push(o);var q=function(y){k(x,r,v,y,t);};o.addEvent(l,q);w.push(q);u[s]=p;x.store(b+l+"listeners",u);}};};var d=function(l){return{base:"focusin",listen:function(m,n,p,q,r){var o={blur:function(){this.removeEvents(o);
+}};o[l]=function(s){k(m,n,p,s,r);};q.target.addEvents(o);}};};if(!c){Object.append(a,{submit:i("submit"),reset:i("reset"),change:d("change"),select:d("select")});
+}var h=Element.prototype,f=h.addEvent,j=h.removeEvent;var e=function(l,m){return function(r,q,n){if(r.indexOf(":relay")==-1){return l.call(this,r,q,n);
+}var o=Slick.parse(r).expressions[0][0];if(o.pseudos[0].key!="relay"){return l.call(this,r,q,n);}var p=o.tag;o.pseudos.slice(1).each(function(s){p+=":"+s.key+(s.value?"("+s.value+")":"");
+});l.call(this,r,q);return m.call(this,p,o.pseudos[0].value,q);};};var g={addEvent:function(v,q,x){var t=this.retrieve("$delegates",{}),r=t[v];if(r){for(var y in r){if(r[y].fn==x&&r[y].match==q){return this;
+}}}var p=v,u=q,o=x,n=a[v]||{};v=n.base||p;q=function(B){return Slick.match(B,u);};var w=Element.Events[p];if(n.condition||w&&w.condition){var l=q,m=n.condition||w.condition;
+q=function(C,B){return l(C,B)&&m.call(C,B,v);};}var z=this,s=String.uniqueID();var A=n.listen?function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){n.listen(z,q,x,B,C,s);
+}}:function(B,C){if(!C&&B&&B.target){C=B.target;}if(C){k(z,q,x,B,C);}};if(!r){r={};}r[s]={match:u,fn:o,delegator:A};t[p]=r;return f.call(this,v,A,n.capture);
+},removeEvent:function(r,n,t,u){var q=this.retrieve("$delegates",{}),p=q[r];if(!p){return this;}if(u){var m=r,w=p[u].delegator,l=a[r]||{};r=l.base||m;if(l.remove){l.remove(this,u);
+}delete p[u];q[m]=p;return j.call(this,r,w,l.capture);}var o,v;if(t){for(o in p){v=p[o];if(v.match==n&&v.fn==t){return g.removeEvent.call(this,r,n,t,o);
+}}}else{for(o in p){v=p[o];if(v.match==n){g.removeEvent.call(this,r,n,v.fn,o);}}}return this;}};[Element,Window,Document].invoke("implement",{addEvent:e(f,g.addEvent),removeEvent:e(j,g.removeEvent)});
+})();(function(){var h=document.createElement("div"),e=document.createElement("div");h.style.height="0";h.appendChild(e);var d=(e.offsetParent===h);h=e=null;
+var l=function(m){return k(m,"position")!="static"||a(m);};var i=function(m){return l(m)||(/^(?:table|td|th)$/i).test(m.tagName);};Element.implement({scrollTo:function(m,n){if(a(this)){this.getWindow().scrollTo(m,n);
+}else{this.scrollLeft=m;this.scrollTop=n;}return this;},getSize:function(){if(a(this)){return this.getWindow().getSize();}return{x:this.offsetWidth,y:this.offsetHeight};
+},getScrollSize:function(){if(a(this)){return this.getWindow().getScrollSize();}return{x:this.scrollWidth,y:this.scrollHeight};},getScroll:function(){if(a(this)){return this.getWindow().getScroll();
+}return{x:this.scrollLeft,y:this.scrollTop};},getScrolls:function(){var n=this.parentNode,m={x:0,y:0};while(n&&!a(n)){m.x+=n.scrollLeft;m.y+=n.scrollTop;
+n=n.parentNode;}return m;},getOffsetParent:d?function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}var n=(k(m,"position")=="static")?i:l;
+while((m=m.parentNode)){if(n(m)){return m;}}return null;}:function(){var m=this;if(a(m)||k(m,"position")=="fixed"){return null;}try{return m.offsetParent;
+}catch(n){}return null;},getOffsets:function(){var n=this.getBoundingClientRect;if(n){var r=this.getBoundingClientRect(),p=document.id(this.getDocument().documentElement),q=p.getScroll(),t=this.getScrolls(),s=(k(this,"position")=="fixed");
+return{x:r.left.toInt()+t.x+((s)?0:q.x)-p.clientLeft,y:r.top.toInt()+t.y+((s)?0:q.y)-p.clientTop};}var o=this,m={x:0,y:0};if(a(this)){return m;}while(o&&!a(o)){m.x+=o.offsetLeft;
+m.y+=o.offsetTop;o=o.offsetParent;}return m;},getPosition:function(p){var q=this.getOffsets(),n=this.getScrolls();var m={x:q.x-n.x,y:q.y-n.y};if(p&&(p=document.id(p))){var o=p.getPosition();
+return{x:m.x-o.x-b(p),y:m.y-o.y-g(p)};}return m;},getCoordinates:function(o){if(a(this)){return this.getWindow().getCoordinates();}var m=this.getPosition(o),n=this.getSize();
+var p={left:m.x,top:m.y,width:n.x,height:n.y};p.right=p.left+p.width;p.bottom=p.top+p.height;return p;},computePosition:function(m){return{left:m.x-j(this,"margin-left"),top:m.y-j(this,"margin-top")};
+},setPosition:function(m){return this.setStyles(this.computePosition(m));}});[Document,Window].invoke("implement",{getSize:function(){var m=f(this);return{x:m.clientWidth,y:m.clientHeight};
+},getScroll:function(){var n=this.getWindow(),m=f(this);return{x:n.pageXOffset||m.scrollLeft,y:n.pageYOffset||m.scrollTop};},getScrollSize:function(){var o=f(this),n=this.getSize(),m=this.getDocument().body;
+return{x:Math.max(o.scrollWidth,m.scrollWidth,n.x),y:Math.max(o.scrollHeight,m.scrollHeight,n.y)};},getPosition:function(){return{x:0,y:0};},getCoordinates:function(){var m=this.getSize();
+return{top:0,left:0,bottom:m.y,right:m.x,height:m.y,width:m.x};}});var k=Element.getComputedStyle;function j(m,n){return k(m,n).toInt()||0;}function c(m){return k(m,"-moz-box-sizing")=="border-box";
+}function g(m){return j(m,"border-top-width");}function b(m){return j(m,"border-left-width");}function a(m){return(/^(?:body|html)$/i).test(m.tagName);
+}function f(m){var n=m.getDocument();return(!n.compatMode||n.compatMode=="CSS1Compat")?n.html:n.body;}})();Element.alias({position:"setPosition"});[Window,Document,Element].invoke("implement",{getHeight:function(){return this.getSize().y;
+},getWidth:function(){return this.getSize().x;},getScrollTop:function(){return this.getScroll().y;},getScrollLeft:function(){return this.getScroll().x;
+},getScrollHeight:function(){return this.getScrollSize().y;},getScrollWidth:function(){return this.getScrollSize().x;},getTop:function(){return this.getPosition().y;
+},getLeft:function(){return this.getPosition().x;}});(function(){var f=this.Fx=new Class({Implements:[Chain,Events,Options],options:{fps:60,unit:false,duration:500,frames:null,frameSkip:true,link:"ignore"},initialize:function(g){this.subject=this.subject||this;
+this.setOptions(g);},getTransition:function(){return function(g){return -(Math.cos(Math.PI*g)-1)/2;};},step:function(g){if(this.options.frameSkip){var h=(this.time!=null)?(g-this.time):0,i=h/this.frameInterval;
+this.time=g;this.frame+=i;}else{this.frame++;}if(this.frame<this.frames){var j=this.transition(this.frame/this.frames);this.set(this.compute(this.from,this.to,j));
+}else{this.frame=this.frames;this.set(this.compute(this.from,this.to,1));this.stop();}},set:function(g){return g;},compute:function(i,h,g){return f.compute(i,h,g);
+},check:function(){if(!this.isRunning()){return true;}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));
+return false;}return false;},start:function(k,j){if(!this.check(k,j)){return this;}this.from=k;this.to=j;this.frame=(this.options.frameSkip)?0:-1;this.time=null;
+this.transition=this.getTransition();var i=this.options.frames,h=this.options.fps,g=this.options.duration;this.duration=f.Durations[g]||g.toInt();this.frameInterval=1000/h;
+this.frames=i||Math.round(this.duration/this.frameInterval);this.fireEvent("start",this.subject);b.call(this,h);return this;},stop:function(){if(this.isRunning()){this.time=null;
+d.call(this,this.options.fps);if(this.frames==this.frame){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject);
+}}else{this.fireEvent("stop",this.subject);}}return this;},cancel:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);this.frame=this.frames;
+this.fireEvent("cancel",this.subject).clearChain();}return this;},pause:function(){if(this.isRunning()){this.time=null;d.call(this,this.options.fps);}return this;
+},resume:function(){if(this.isPaused()){b.call(this,this.options.fps);}return this;},isRunning:function(){var g=e[this.options.fps];return g&&g.contains(this);
+},isPaused:function(){return(this.frame<this.frames)&&!this.isRunning();}});f.compute=function(i,h,g){return(h-i)*g+i;};f.Durations={"short":250,normal:500,"long":1000};
+var e={},c={};var a=function(){var h=Date.now();for(var j=this.length;j--;){var g=this[j];if(g){g.step(h);}}};var b=function(h){var g=e[h]||(e[h]=[]);g.push(this);
+if(!c[h]){c[h]=a.periodical(Math.round(1000/h),g);}};var d=function(h){var g=e[h];if(g){g.erase(this);if(!g.length&&c[h]){delete e[h];c[h]=clearInterval(c[h]);
+}}};})();Fx.CSS=new Class({Extends:Fx,prepare:function(b,e,a){a=Array.from(a);var h=a[0],g=a[1];if(g==null){g=h;h=b.getStyle(e);var c=this.options.unit;
+if(c&&h&&typeof h=="string"&&h.slice(-c.length)!=c&&parseFloat(h)!=0){b.setStyle(e,g+c);var d=b.getComputedStyle(e);if(!(/px$/.test(d))){d=b.style[("pixel-"+e).camelCase()];
+if(d==null){var f=b.style.left;b.style.left=g+c;d=b.style.pixelLeft;b.style.left=f;}}h=(g||1)/(parseFloat(d)||1)*(parseFloat(h)||0);b.setStyle(e,h+c);}}return{from:this.parse(h),to:this.parse(g)};
+},parse:function(a){a=Function.from(a)();a=(typeof a=="string")?a.split(" "):Array.from(a);return a.map(function(c){c=String(c);var b=false;Object.each(Fx.CSS.Parsers,function(f,e){if(b){return;
+}var d=f.parse(c);if(d||d===0){b={value:d,parser:f};}});b=b||{value:c,parser:Fx.CSS.Parsers.String};return b;});},compute:function(d,c,b){var a=[];(Math.min(d.length,c.length)).times(function(e){a.push({value:d[e].parser.compute(d[e].value,c[e].value,b),parser:d[e].parser});
+});a.$family=Function.from("fx:css:value");return a;},serve:function(c,b){if(typeOf(c)!="fx:css:value"){c=this.parse(c);}var a=[];c.each(function(d){a=a.concat(d.parser.serve(d.value,b));
+});return a;},render:function(a,d,c,b){a.setStyle(d,this.serve(c,b));},search:function(a){if(Fx.CSS.Cache[a]){return Fx.CSS.Cache[a];}var d={},c=new RegExp("^"+a.escapeRegExp()+"$");
+var b=function(e){Array.each(e,function(h,f){if(h.media){b(h.rules||h.cssRules);return;}if(!h.style){return;}var g=(h.selectorText)?h.selectorText.replace(/^\w+/,function(i){return i.toLowerCase();
+}):null;if(!g||!c.test(g)){return;}Object.each(Element.Styles,function(j,i){if(!h.style[i]||Element.ShortStyles[i]){return;}j=String(h.style[i]);d[i]=((/^rgb/).test(j))?j.rgbToHex():j;
+});});};Array.each(document.styleSheets,function(g,f){var e=g.href;if(e&&e.indexOf("://")>-1&&e.indexOf(document.domain)==-1){return;}var h=g.rules||g.cssRules;
+b(h);});return Fx.CSS.Cache[a]=d;}});Fx.CSS.Cache={};Fx.CSS.Parsers={Color:{parse:function(a){if(a.match(/^#[0-9a-f]{3,6}$/i)){return a.hexToRgb(true);
+}return((a=a.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[a[1],a[2],a[3]]:false;},compute:function(c,b,a){return c.map(function(e,d){return Math.round(Fx.compute(c[d],b[d],a));
+});},serve:function(a){return a.map(Number);}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(b,a){return(a)?b+a:b;}},String:{parse:Function.from(false),compute:function(b,a){return a;
+},serve:function(a){return a;}}};Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);},set:function(b,a){if(arguments.length==1){a=b;
+b=this.property||this.options.property;}this.render(this.element,b,a,this.options.unit);return this;},start:function(c,e,d){if(!this.check(c,e,d)){return this;
+}var b=Array.flatten(arguments);this.property=this.options.property||b.shift();var a=this.prepare(this.element,this.property,b);return this.parent(a.from,a.to);
+}});Element.Properties.tween={set:function(a){this.get("tween").cancel().setOptions(a);return this;},get:function(){var a=this.retrieve("tween");if(!a){a=new Fx.Tween(this,{link:"cancel"});
+this.store("tween",a);}return a;}};Element.implement({tween:function(a,c,b){this.get("tween").start(a,c,b);return this;},fade:function(d){var e=this.get("tween"),g,c=["opacity"].append(arguments),a;
+if(c[1]==null){c[1]="toggle";}switch(c[1]){case"in":g="start";c[1]=1;break;case"out":g="start";c[1]=0;break;case"show":g="set";c[1]=1;break;case"hide":g="set";
+c[1]=0;break;case"toggle":var b=this.retrieve("fade:flag",this.getStyle("opacity")==1);g="start";c[1]=b?0:1;this.store("fade:flag",!b);a=true;break;default:g="start";
+}if(!a){this.eliminate("fade:flag");}e[g].apply(e,c);var f=c[c.length-1];if(g=="set"||f!=0){this.setStyle("visibility",f==0?"hidden":"visible");}else{e.chain(function(){this.element.setStyle("visibility","hidden");
+this.callChain();});}return this;},highlight:function(c,a){if(!a){a=this.retrieve("highlight:original",this.getStyle("background-color"));a=(a=="transparent")?"#fff":a;
+}var b=this.get("tween");b.start("background-color",c||"#ffff88",a).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));
+b.callChain();}.bind(this));return this;}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:function(b,a){this.element=this.subject=document.id(b);this.parent(a);
+},set:function(a){if(typeof a=="string"){a=this.search(a);}for(var b in a){this.render(this.element,b,a[b],this.options.unit);}return this;},compute:function(e,d,c){var a={};
+for(var b in e){a[b]=this.parent(e[b],d[b],c);}return a;},start:function(b){if(!this.check(b)){return this;}if(typeof b=="string"){b=this.search(b);}var e={},d={};
+for(var c in b){var a=this.prepare(this.element,c,b[c]);e[c]=a.from;d[c]=a.to;}return this.parent(e,d);}});Element.Properties.morph={set:function(a){this.get("morph").cancel().setOptions(a);
+return this;},get:function(){var a=this.retrieve("morph");if(!a){a=new Fx.Morph(this,{link:"cancel"});this.store("morph",a);}return a;}};Element.implement({morph:function(a){this.get("morph").start(a);
+return this;}});Fx.implement({getTransition:function(){var a=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof a=="string"){var b=a.split(":");
+a=Fx.Transitions;a=a[b[0]]||a[b[0].capitalize()];if(b[1]){a=a["ease"+b[1].capitalize()+(b[2]?b[2].capitalize():"")];}}return a;}});Fx.Transition=function(c,b){b=Array.from(b);
+var a=function(d){return c(d,b);};return Object.append(a,{easeIn:a,easeOut:function(d){return 1-c(1-d,b);},easeInOut:function(d){return(d<=0.5?c(2*d,b):(2-c(2*(1-d),b)))/2;
+}});};Fx.Transitions={linear:function(a){return a;}};Fx.Transitions.extend=function(a){for(var b in a){Fx.Transitions[b]=new Fx.Transition(a[b]);}};Fx.Transitions.extend({Pow:function(b,a){return Math.pow(b,a&&a[0]||6);
+},Expo:function(a){return Math.pow(2,8*(a-1));},Circ:function(a){return 1-Math.sin(Math.acos(a));},Sine:function(a){return 1-Math.cos(a*Math.PI/2);},Back:function(b,a){a=a&&a[0]||1.618;
+return Math.pow(b,2)*((a+1)*b-a);},Bounce:function(f){var e;for(var d=0,c=1;1;d+=c,c/=2){if(f>=(7-4*d)/11){e=c*c-Math.pow((11-6*d-11*f)/4,2);break;}}return e;
+},Elastic:function(b,a){return Math.pow(2,10*--b)*Math.cos(20*b*Math.PI*(a&&a[0]||1)/3);}});["Quad","Cubic","Quart","Quint"].each(function(b,a){Fx.Transitions[b]=new Fx.Transition(function(c){return Math.pow(c,a+2);
+});});(function(){var d=function(){},a=("onprogress" in new Browser.Request);var c=this.Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,timeout:0,noCache:false},initialize:function(e){this.xhr=new Browser.Request();
+this.setOptions(e);this.headers=this.options.headers;},onStateChange:function(){var e=this.xhr;if(e.readyState!=4||!this.running){return;}this.running=false;
+this.status=0;Function.attempt(function(){var f=e.status;this.status=(f==1223)?204:f;}.bind(this));e.onreadystatechange=d;if(a){e.onprogress=e.onloadstart=d;
+}clearTimeout(this.timer);this.response={text:this.xhr.responseText||"",xml:this.xhr.responseXML};if(this.options.isSuccess.call(this,this.status)){this.success(this.response.text,this.response.xml);
+}else{this.failure();}},isSuccess:function(){var e=this.status;return(e>=200&&e<300);},isRunning:function(){return !!this.running;},processScripts:function(e){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return Browser.exec(e);
+}return e.stripScripts(this.options.evalScripts);},success:function(f,e){this.onSuccess(this.processScripts(f),e);},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain();
+},failure:function(){this.onFailure();},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr);},loadstart:function(e){this.fireEvent("loadstart",[e,this.xhr]);
+},progress:function(e){this.fireEvent("progress",[e,this.xhr]);},timeout:function(){this.fireEvent("timeout",this.xhr);},setHeader:function(e,f){this.headers[e]=f;
+return this;},getHeader:function(e){return Function.attempt(function(){return this.xhr.getResponseHeader(e);}.bind(this));},check:function(){if(!this.running){return true;
+}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.pass(arguments,this));return false;}return false;},send:function(o){if(!this.check(o)){return this;
+}this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.running=true;var l=typeOf(o);if(l=="string"||l=="element"){o={data:o};}var h=this.options;
+o=Object.append({data:h.data,url:h.url,method:h.method},o);var j=o.data,f=String(o.url),e=o.method.toLowerCase();switch(typeOf(j)){case"element":j=document.id(j).toQueryString();
+break;case"object":case"hash":j=Object.toQueryString(j);}if(this.options.format){var m="format="+this.options.format;j=(j)?m+"&"+j:m;}if(this.options.emulation&&!["get","post"].contains(e)){var k="_method="+e;
+j=(j)?k+"&"+j:k;e="post";}if(this.options.urlEncoded&&["post","put"].contains(e)){var g=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers["Content-type"]="application/x-www-form-urlencoded"+g;
+}if(!f){f=document.location.pathname;}var i=f.lastIndexOf("/");if(i>-1&&(i=f.indexOf("#"))>-1){f=f.substr(0,i);}if(this.options.noCache){f+=(f.indexOf("?")>-1?"&":"?")+String.uniqueID();
+}if(j&&(e=="get"||e=="delete")){f+=(f.indexOf("?")>-1?"&":"?")+j;j=null;}var n=this.xhr;if(a){n.onloadstart=this.loadstart.bind(this);n.onprogress=this.progress.bind(this);
+}n.open(e.toUpperCase(),f,this.options.async,this.options.user,this.options.password);if(this.options.user&&"withCredentials" in n){n.withCredentials=true;
+}n.onreadystatechange=this.onStateChange.bind(this);Object.each(this.headers,function(q,p){try{n.setRequestHeader(p,q);}catch(r){this.fireEvent("exception",[p,q]);
+}},this);this.fireEvent("request");n.send(j);if(!this.options.async){this.onStateChange();}else{if(this.options.timeout){this.timer=this.timeout.delay(this.options.timeout,this);
+}}return this;},cancel:function(){if(!this.running){return this;}this.running=false;var e=this.xhr;e.abort();clearTimeout(this.timer);e.onreadystatechange=d;
+if(a){e.onprogress=e.onloadstart=d;}this.xhr=new Browser.Request();this.fireEvent("cancel");return this;}});var b={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(e){b[e]=function(g){var f={method:e};
+if(g!=null){f.data=g;}return this.send(f);};});c.implement(b);Element.Properties.send={set:function(e){var f=this.get("send").cancel();f.setOptions(e);
+return this;},get:function(){var e=this.retrieve("send");if(!e){e=new c({data:this,link:"cancel",method:this.get("method")||"post",url:this.get("action")});
+this.store("send",e);}return e;}};Element.implement({send:function(e){var f=this.get("send");f.send({data:this,url:e||f.options.url});return this;}});})();
+Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false,headers:{Accept:"text/html, application/xml, text/xml, */*"}},success:function(f){var e=this.options,c=this.response;
+c.html=f.stripScripts(function(h){c.javascript=h;});var d=c.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);if(d){c.html=d[1];}var b=new Element("div").set("html",c.html);
+c.tree=b.childNodes;c.elements=b.getElements(e.filter||"*");if(e.filter){c.tree=c.elements;}if(e.update){var g=document.id(e.update).empty();if(e.filter){g.adopt(c.elements);
+}else{g.set("html",c.html);}}else{if(e.append){var a=document.id(e.append);if(e.filter){c.elements.reverse().inject(a);}else{a.adopt(b.getChildren());}}}if(e.evalScripts){Browser.exec(c.javascript);
+}this.onSuccess(c.tree,c.elements,c.html,c.javascript);}});Element.Properties.load={set:function(a){var b=this.get("load").cancel();b.setOptions(a);return this;
+},get:function(){var a=this.retrieve("load");if(!a){a=new Request.HTML({data:this,link:"cancel",update:this,method:"get"});this.store("load",a);}return a;
+}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Type.isObject,url:Type.isString}));return this;}});if(typeof JSON=="undefined"){this.JSON={};
+}(function(){var special={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};var escape=function(chr){return special[chr]||"\\u"+("0000"+chr.charCodeAt(0).toString(16)).slice(-4);
+};JSON.validate=function(string){string=string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"");
+return(/^[\],:{}\s]*$/).test(string);};JSON.encode=JSON.stringify?function(obj){return JSON.stringify(obj);}:function(obj){if(obj&&obj.toJSON){obj=obj.toJSON();
+}switch(typeOf(obj)){case"string":return'"'+obj.replace(/[\x00-\x1f\\"]/g,escape)+'"';case"array":return"["+obj.map(JSON.encode).clean()+"]";case"object":case"hash":var string=[];
+Object.each(obj,function(value,key){var json=JSON.encode(value);if(json){string.push(JSON.encode(key)+":"+json);}});return"{"+string+"}";case"number":case"boolean":return""+obj;
+case"null":return"null";}return null;};JSON.secure=true;JSON.decode=function(string,secure){if(!string||typeOf(string)!="string"){return null;}if(secure==null){secure=JSON.secure;
+}if(secure){if(JSON.parse){return JSON.parse(string);}if(!JSON.validate(string)){throw new Error("JSON could not decode the input; security is enabled and the value is not secure.");
+}}return eval("("+string+")");};})();Request.JSON=new Class({Extends:Request,options:{secure:true},initialize:function(a){this.parent(a);Object.append(this.headers,{Accept:"application/json","X-Request":"JSON"});
+},success:function(c){var b;try{b=this.response.json=JSON.decode(c,this.options.secure);}catch(a){this.fireEvent("error",[c,a]);return;}if(b==null){this.onFailure();
+}else{this.onSuccess(b,c);}}});var Cookie=new Class({Implements:Options,options:{path:"/",domain:false,duration:false,secure:false,document:document,encode:true},initialize:function(b,a){this.key=b;
+this.setOptions(a);},write:function(b){if(this.options.encode){b=encodeURIComponent(b);}if(this.options.domain){b+="; domain="+this.options.domain;}if(this.options.path){b+="; path="+this.options.path;
+}if(this.options.duration){var a=new Date();a.setTime(a.getTime()+this.options.duration*24*60*60*1000);b+="; expires="+a.toGMTString();}if(this.options.secure){b+="; secure";
+}this.options.document.cookie=this.key+"="+b;return this;},read:function(){var a=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");
+return(a)?decodeURIComponent(a[1]):null;},dispose:function(){new Cookie(this.key,Object.merge({},this.options,{duration:-1})).write("");return this;}});
+Cookie.write=function(b,c,a){return new Cookie(b,a).write(c);};Cookie.read=function(a){return new Cookie(a).read();};Cookie.dispose=function(b,a){return new Cookie(b,a).dispose();
+};(function(i,k){var l,f,e=[],c,b,d=k.createElement("div");var g=function(){clearTimeout(b);if(l){return;}Browser.loaded=l=true;k.removeListener("DOMContentLoaded",g).removeListener("readystatechange",a);
+k.fireEvent("domready");i.fireEvent("domready");};var a=function(){for(var m=e.length;m--;){if(e[m]()){g();return true;}}return false;};var j=function(){clearTimeout(b);
+if(!a()){b=setTimeout(j,10);}};k.addListener("DOMContentLoaded",g);var h=function(){try{d.doScroll();return true;}catch(m){}return false;};if(d.doScroll&&!h()){e.push(h);
+c=true;}if(k.readyState){e.push(function(){var m=k.readyState;return(m=="loaded"||m=="complete");});}if("onreadystatechange" in k){k.addListener("readystatechange",a);
+}else{c=true;}if(c){j();}Element.Events.domready={onAdd:function(m){if(l){m.call(this);}}};Element.Events.load={base:"load",onAdd:function(m){if(f&&this==i){m.call(this);
+}},condition:function(){if(this==i){g();delete Element.Events.load;}return true;}};i.addEvent("load",function(){f=true;});})(window,document); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/mootools-more.js b/pyload/webui/themes/flat/js/static/mootools-more.js
new file mode 100644
index 000000000..c7f4a1a0e
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-more.js
@@ -0,0 +1,2856 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+...
+*/
+
+/*
+---
+
+script: More.js
+
+name: More
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+ - Arian Stolwijk
+ - Tim Wienk
+ - Christoph Pojer
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+ version: '1.5.0',
+ build: '73db5e24e6e9c5c87b3a27aebef2248053f7db37'
+};
+
+
+/*
+---
+
+script: Class.Binds.js
+
+name: Class.Binds
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+ if (!this.prototype.initialize) this.implement('initialize', function(){});
+ return Array.from(binds).concat(this.prototype.Binds || []);
+};
+
+Class.Mutators.initialize = function(initialize){
+ return function(){
+ Array.from(this.Binds).each(function(name){
+ var original = this[name];
+ if (original) this[name] = original.bind(this);
+ }, this);
+ return initialize.apply(this, arguments);
+ };
+};
+
+
+/*
+---
+
+script: Class.Occlude.js
+
+name: Class.Occlude
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - Core/Element
+ - MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+ occlude: function(property, element){
+ element = document.id(element || this.element);
+ var instance = element.retrieve(property || this.property);
+ if (instance && !this.occluded)
+ return (this.occluded = instance);
+
+ this.occluded = false;
+ element.store(property || this.property, this);
+ return this.occluded;
+ }
+
+});
+
+
+/*
+---
+
+script: Class.Refactor.js
+
+name: Class.Refactor
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Class
+ - MooTools.More
+
+# Some modules declare themselves dependent on Class.Refactor
+provides: [Class.refactor, Class.Refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+ Object.each(refactors, function(item, name){
+ var origin = original.prototype[name];
+ origin = (origin && origin.$origin) || origin || function(){};
+ original.implement(name, (typeof item == 'function') ? function(){
+ var old = this.previous;
+ this.previous = origin;
+ var value = item.apply(this, arguments);
+ this.previous = old;
+ return value;
+ } : item);
+ });
+
+ return original;
+
+};
+
+
+/*
+---
+
+script: Element.Measure.js
+
+name: Element.Measure
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+(function(){
+
+var getStylesList = function(styles, planes){
+ var list = [];
+ Object.each(planes, function(directions){
+ Object.each(directions, function(edge){
+ styles.each(function(style){
+ list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
+ });
+ });
+ });
+ return list;
+};
+
+var calculateEdgeSize = function(edge, styles){
+ var total = 0;
+ Object.each(styles, function(value, style){
+ if (style.test(edge)) total = total + value.toInt();
+ });
+ return total;
+};
+
+var isVisible = function(el){
+ return !!(!el || el.offsetHeight || el.offsetWidth);
+};
+
+
+Element.implement({
+
+ measure: function(fn){
+ if (isVisible(this)) return fn.call(this);
+ var parent = this.getParent(),
+ toMeasure = [];
+ while (!isVisible(parent) && parent != document.body){
+ toMeasure.push(parent.expose());
+ parent = parent.getParent();
+ }
+ var restore = this.expose(),
+ result = fn.call(this);
+ restore();
+ toMeasure.each(function(restore){
+ restore();
+ });
+ return result;
+ },
+
+ expose: function(){
+ if (this.getStyle('display') != 'none') return function(){};
+ var before = this.style.cssText;
+ this.setStyles({
+ display: 'block',
+ position: 'absolute',
+ visibility: 'hidden'
+ });
+ return function(){
+ this.style.cssText = before;
+ }.bind(this);
+ },
+
+ getDimensions: function(options){
+ options = Object.merge({computeSize: false}, options);
+ var dim = {x: 0, y: 0};
+
+ var getSize = function(el, options){
+ return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
+ };
+
+ var parent = this.getParent('body');
+
+ if (parent && this.getStyle('display') == 'none'){
+ dim = this.measure(function(){
+ return getSize(this, options);
+ });
+ } else if (parent){
+ try { //safari sometimes crashes here, so catch it
+ dim = getSize(this, options);
+ }catch(e){}
+ }
+
+ return Object.append(dim, (dim.x || dim.x === 0) ? {
+ width: dim.x,
+ height: dim.y
+ } : {
+ x: dim.width,
+ y: dim.height
+ }
+ );
+ },
+
+ getComputedSize: function(options){
+
+
+ options = Object.merge({
+ styles: ['padding','border'],
+ planes: {
+ height: ['top','bottom'],
+ width: ['left','right']
+ },
+ mode: 'both'
+ }, options);
+
+ var styles = {},
+ size = {width: 0, height: 0},
+ dimensions;
+
+ if (options.mode == 'vertical'){
+ delete size.width;
+ delete options.planes.width;
+ } else if (options.mode == 'horizontal'){
+ delete size.height;
+ delete options.planes.height;
+ }
+
+ getStylesList(options.styles, options.planes).each(function(style){
+ styles[style] = this.getStyle(style).toInt();
+ }, this);
+
+ Object.each(options.planes, function(edges, plane){
+
+ var capitalized = plane.capitalize(),
+ style = this.getStyle(plane);
+
+ if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
+
+ style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
+ size['total' + capitalized] = style;
+
+ edges.each(function(edge){
+ var edgesize = calculateEdgeSize(edge, styles);
+ size['computed' + edge.capitalize()] = edgesize;
+ size['total' + capitalized] += edgesize;
+ });
+
+ }, this);
+
+ return Object.append(size, styles);
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Element.Position.js
+
+name: Element.Position
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Jacob Thornton
+
+requires:
+ - Core/Options
+ - Core/Element.Dimensions
+ - Element.Measure
+
+provides: [Element.Position]
+
+...
+*/
+
+(function(original){
+
+var local = Element.Position = {
+
+ options: {/*
+ edge: false,
+ returnPos: false,
+ minimum: {x: 0, y: 0},
+ maximum: {x: 0, y: 0},
+ relFixedPosition: false,
+ ignoreMargins: false,
+ ignoreScroll: false,
+ allowNegative: false,*/
+ relativeTo: document.body,
+ position: {
+ x: 'center', //left, center, right
+ y: 'center' //top, center, bottom
+ },
+ offset: {x: 0, y: 0}
+ },
+
+ getOptions: function(element, options){
+ options = Object.merge({}, local.options, options);
+ local.setPositionOption(options);
+ local.setEdgeOption(options);
+ local.setOffsetOption(element, options);
+ local.setDimensionsOption(element, options);
+ return options;
+ },
+
+ setPositionOption: function(options){
+ options.position = local.getCoordinateFromValue(options.position);
+ },
+
+ setEdgeOption: function(options){
+ var edgeOption = local.getCoordinateFromValue(options.edge);
+ options.edge = edgeOption ? edgeOption :
+ (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
+ {x: 'left', y: 'top'};
+ },
+
+ setOffsetOption: function(element, options){
+ var parentOffset = {x: 0, y: 0};
+ var parentScroll = {x: 0, y: 0};
+ var offsetParent = element.measure(function(){
+ return document.id(this.getOffsetParent());
+ });
+
+ if (!offsetParent || offsetParent == element.getDocument().body) return;
+
+ parentScroll = offsetParent.getScroll();
+ parentOffset = offsetParent.measure(function(){
+ var position = this.getPosition();
+ if (this.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.x += scroll.x;
+ position.y += scroll.y;
+ }
+ return position;
+ });
+
+ options.offset = {
+ parentPositioned: offsetParent != document.id(options.relativeTo),
+ x: options.offset.x - parentOffset.x + parentScroll.x,
+ y: options.offset.y - parentOffset.y + parentScroll.y
+ };
+ },
+
+ setDimensionsOption: function(element, options){
+ options.dimensions = element.getDimensions({
+ computeSize: true,
+ styles: ['padding', 'border', 'margin']
+ });
+ },
+
+ getPosition: function(element, options){
+ var position = {};
+ options = local.getOptions(element, options);
+ var relativeTo = document.id(options.relativeTo) || document.body;
+
+ local.setPositionCoordinates(options, position, relativeTo);
+ if (options.edge) local.toEdge(position, options);
+
+ var offset = options.offset;
+ position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
+ position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
+
+ local.toMinMax(position, options);
+
+ if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
+ if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
+ if (options.ignoreMargins) local.toIgnoreMargins(position, options);
+
+ position.left = Math.ceil(position.left);
+ position.top = Math.ceil(position.top);
+ delete position.x;
+ delete position.y;
+
+ return position;
+ },
+
+ setPositionCoordinates: function(options, position, relativeTo){
+ var offsetY = options.offset.y,
+ offsetX = options.offset.x,
+ calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
+ top = calc.y,
+ left = calc.x,
+ winSize = window.getSize();
+
+ switch(options.position.x){
+ case 'left': position.x = left + offsetX; break;
+ case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
+ default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
+ }
+
+ switch(options.position.y){
+ case 'top': position.y = top + offsetY; break;
+ case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
+ default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
+ }
+ },
+
+ toMinMax: function(position, options){
+ var xy = {left: 'x', top: 'y'}, value;
+ ['minimum', 'maximum'].each(function(minmax){
+ ['left', 'top'].each(function(lr){
+ value = options[minmax] ? options[minmax][xy[lr]] : null;
+ if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
+ });
+ });
+ },
+
+ toRelFixedPosition: function(relativeTo, position){
+ var winScroll = window.getScroll();
+ position.top += winScroll.y;
+ position.left += winScroll.x;
+ },
+
+ toIgnoreScroll: function(relativeTo, position){
+ var relScroll = relativeTo.getScroll();
+ position.top -= relScroll.y;
+ position.left -= relScroll.x;
+ },
+
+ toIgnoreMargins: function(position, options){
+ position.left += options.edge.x == 'right'
+ ? options.dimensions['margin-right']
+ : (options.edge.x != 'center'
+ ? -options.dimensions['margin-left']
+ : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
+
+ position.top += options.edge.y == 'bottom'
+ ? options.dimensions['margin-bottom']
+ : (options.edge.y != 'center'
+ ? -options.dimensions['margin-top']
+ : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
+ },
+
+ toEdge: function(position, options){
+ var edgeOffset = {},
+ dimensions = options.dimensions,
+ edge = options.edge;
+
+ switch(edge.x){
+ case 'left': edgeOffset.x = 0; break;
+ case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
+ // center
+ default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
+ }
+
+ switch(edge.y){
+ case 'top': edgeOffset.y = 0; break;
+ case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
+ // center
+ default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
+ }
+
+ position.x += edgeOffset.x;
+ position.y += edgeOffset.y;
+ },
+
+ getCoordinateFromValue: function(option){
+ if (typeOf(option) != 'string') return option;
+ option = option.toLowerCase();
+
+ return {
+ x: option.test('left') ? 'left'
+ : (option.test('right') ? 'right' : 'center'),
+ y: option.test(/upper|top/) ? 'top'
+ : (option.test('bottom') ? 'bottom' : 'center')
+ };
+ }
+
+};
+
+Element.implement({
+
+ position: function(options){
+ if (options && (options.x != null || options.y != null)){
+ return (original ? original.apply(this, arguments) : this);
+ }
+ var position = this.setStyle('position', 'absolute').calculatePosition(options);
+ return (options && options.returnPos) ? position : this.setStyles(position);
+ },
+
+ calculatePosition: function(options){
+ return local.getPosition(this, options);
+ }
+
+});
+
+})(Element.prototype.position);
+
+
+/*
+---
+
+script: IframeShim.js
+
+name: IframeShim
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Options
+ - Core/Events
+ - Element.Position
+ - Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+(function(){
+
+var browsers = false;
+
+
+this.IframeShim = new Class({
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {
+ className: 'iframeShim',
+ src: 'javascript:false;document.write("");',
+ display: false,
+ zIndex: null,
+ margin: 0,
+ offset: {x: 0, y: 0},
+ browsers: browsers
+ },
+
+ property: 'IframeShim',
+
+ initialize: function(element, options){
+ this.element = document.id(element);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options);
+ this.makeShim();
+ return this;
+ },
+
+ makeShim: function(){
+ if (this.options.browsers){
+ var zIndex = this.element.getStyle('zIndex').toInt();
+
+ if (!zIndex){
+ zIndex = 1;
+ var pos = this.element.getStyle('position');
+ if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+ this.element.setStyle('zIndex', zIndex);
+ }
+ zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+ if (zIndex < 0) zIndex = 1;
+ this.shim = new Element('iframe', {
+ src: this.options.src,
+ scrolling: 'no',
+ frameborder: 0,
+ styles: {
+ zIndex: zIndex,
+ position: 'absolute',
+ border: 'none',
+ filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+ },
+ 'class': this.options.className
+ }).store('IframeShim', this);
+ var inject = (function(){
+ this.shim.inject(this.element, 'after');
+ this[this.options.display ? 'show' : 'hide']();
+ this.fireEvent('inject');
+ }).bind(this);
+ if (!IframeShim.ready) window.addEvent('load', inject);
+ else inject();
+ } else {
+ this.position = this.hide = this.show = this.dispose = Function.from(this);
+ }
+ },
+
+ position: function(){
+ if (!IframeShim.ready || !this.shim) return this;
+ var size = this.element.measure(function(){
+ return this.getSize();
+ });
+ if (this.options.margin != undefined){
+ size.x = size.x - (this.options.margin * 2);
+ size.y = size.y - (this.options.margin * 2);
+ this.options.offset.x += this.options.margin;
+ this.options.offset.y += this.options.margin;
+ }
+ this.shim.set({width: size.x, height: size.y}).position({
+ relativeTo: this.element,
+ offset: this.options.offset
+ });
+ return this;
+ },
+
+ hide: function(){
+ if (this.shim) this.shim.setStyle('display', 'none');
+ return this;
+ },
+
+ show: function(){
+ if (this.shim) this.shim.setStyle('display', 'block');
+ return this.position();
+ },
+
+ dispose: function(){
+ if (this.shim) this.shim.dispose();
+ return this;
+ },
+
+ destroy: function(){
+ if (this.shim) this.shim.destroy();
+ return this;
+ }
+
+});
+
+})();
+
+window.addEvent('load', function(){
+ IframeShim.ready = true;
+});
+
+
+/*
+---
+
+script: Mask.js
+
+name: Mask
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Options
+ - Core/Events
+ - Core/Element.Event
+ - Class.Binds
+ - Element.Position
+ - IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+ Implements: [Options, Events],
+
+ Binds: ['position'],
+
+ options: {/*
+ onShow: function(){},
+ onHide: function(){},
+ onDestroy: function(){},
+ onClick: function(event){},
+ inject: {
+ where: 'after',
+ target: null,
+ },
+ hideOnClick: false,
+ id: null,
+ destroyOnHide: false,*/
+ style: {},
+ 'class': 'mask',
+ maskMargins: false,
+ useIframeShim: true,
+ iframeShimOptions: {}
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('mask', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+ },
+
+ render: function(){
+ this.element = new Element('div', {
+ 'class': this.options['class'],
+ id: this.options.id || 'mask-' + String.uniqueID(),
+ styles: Object.merge({}, this.options.style, {
+ display: 'none'
+ }),
+ events: {
+ click: function(event){
+ this.fireEvent('click', event);
+ if (this.options.hideOnClick) this.hide();
+ }.bind(this)
+ }
+ });
+
+ this.hidden = true;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ inject: function(target, where){
+ where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after');
+ target = target || (this.options.inject && this.options.inject.target) || this.target;
+
+ this.element.inject(target, where);
+
+ if (this.options.useIframeShim){
+ this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+
+ this.addEvents({
+ show: this.shim.show.bind(this.shim),
+ hide: this.shim.hide.bind(this.shim),
+ destroy: this.shim.destroy.bind(this.shim)
+ });
+ }
+ },
+
+ position: function(){
+ this.resize(this.options.width, this.options.height);
+
+ this.element.position({
+ relativeTo: this.target,
+ position: 'topLeft',
+ ignoreMargins: !this.options.maskMargins,
+ ignoreScroll: this.target == document.body
+ });
+
+ return this;
+ },
+
+ resize: function(x, y){
+ var opt = {
+ styles: ['padding', 'border']
+ };
+ if (this.options.maskMargins) opt.styles.push('margin');
+
+ var dim = this.target.getComputedSize(opt);
+ if (this.target == document.body){
+ this.element.setStyles({width: 0, height: 0});
+ var win = window.getScrollSize();
+ if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+ if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+ }
+ this.element.setStyles({
+ width: Array.pick([x, dim.totalWidth, dim.x]),
+ height: Array.pick([y, dim.totalHeight, dim.y])
+ });
+
+ return this;
+ },
+
+ show: function(){
+ if (!this.hidden) return this;
+
+ window.addEvent('resize', this.position);
+ this.position();
+ this.showMask.apply(this, arguments);
+
+ return this;
+ },
+
+ showMask: function(){
+ this.element.setStyle('display', 'block');
+ this.hidden = false;
+ this.fireEvent('show');
+ },
+
+ hide: function(){
+ if (this.hidden) return this;
+
+ window.removeEvent('resize', this.position);
+ this.hideMask.apply(this, arguments);
+ if (this.options.destroyOnHide) return this.destroy();
+
+ return this;
+ },
+
+ hideMask: function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ },
+
+ toggle: function(){
+ this[this.hidden ? 'show' : 'hide']();
+ },
+
+ destroy: function(){
+ this.hide();
+ this.element.destroy();
+ this.fireEvent('destroy');
+ this.target.eliminate('mask');
+ }
+
+});
+
+Element.Properties.mask = {
+
+ set: function(options){
+ var mask = this.retrieve('mask');
+ if (mask) mask.destroy();
+ return this.eliminate('mask').store('mask:options', options);
+ },
+
+ get: function(){
+ var mask = this.retrieve('mask');
+ if (!mask){
+ mask = new Mask(this, this.retrieve('mask:options'));
+ this.store('mask', mask);
+ }
+ return mask;
+ }
+
+};
+
+Element.implement({
+
+ mask: function(options){
+ if (options) this.set('mask', options);
+ this.get('mask').show();
+ return this;
+ },
+
+ unmask: function(){
+ this.get('mask').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: Spinner.js
+
+name: Spinner
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Tween
+ - Core/Request
+ - Class.refactor
+ - Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+ Extends: Mask,
+
+ Implements: Chain,
+
+ options: {/*
+ message: false,*/
+ 'class': 'spinner',
+ containerPosition: {},
+ content: {
+ 'class': 'spinner-content'
+ },
+ messageContainer: {
+ 'class': 'spinner-msg'
+ },
+ img: {
+ 'class': 'spinner-img'
+ },
+ fxOptions: {
+ link: 'chain'
+ }
+ },
+
+ initialize: function(target, options){
+ this.target = document.id(target) || document.id(document.body);
+ this.target.store('spinner', this);
+ this.setOptions(options);
+ this.render();
+ this.inject();
+
+ // Add this to events for when noFx is true; parent methods handle hide/show.
+ var deactivate = function(){ this.active = false; }.bind(this);
+ this.addEvents({
+ hide: deactivate,
+ show: deactivate
+ });
+ },
+
+ render: function(){
+ this.parent();
+
+ this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
+
+ this.content = document.id(this.options.content) || new Element('div', this.options.content);
+ this.content.inject(this.element);
+
+ if (this.options.message){
+ this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+ this.msg.inject(this.content);
+ }
+
+ if (this.options.img){
+ this.img = document.id(this.options.img) || new Element('div', this.options.img);
+ this.img.inject(this.content);
+ }
+
+ this.element.set('tween', this.options.fxOptions);
+ },
+
+ show: function(noFx){
+ if (this.active) return this.chain(this.show.bind(this));
+ if (!this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'true');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ showMask: function(noFx){
+ var pos = function(){
+ this.content.position(Object.merge({
+ relativeTo: this.element
+ }, this.options.containerPosition));
+ }.bind(this);
+
+ if (noFx){
+ this.parent();
+ pos();
+ } else {
+ if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
+ this.element.setStyles({
+ display: 'block',
+ opacity: 0
+ }).tween('opacity', this.options.style.opacity);
+ pos();
+ this.hidden = false;
+ this.fireEvent('show');
+ this.callChain();
+ }
+ },
+
+ hide: function(noFx){
+ if (this.active) return this.chain(this.hide.bind(this));
+ if (this.hidden){
+ this.callChain.delay(20, this);
+ return this;
+ }
+
+ this.target.set('aria-busy', 'false');
+ this.active = true;
+
+ return this.parent(noFx);
+ },
+
+ hideMask: function(noFx){
+ if (noFx) return this.parent();
+ this.element.tween('opacity', 0).get('tween').chain(function(){
+ this.element.setStyle('display', 'none');
+ this.hidden = true;
+ this.fireEvent('hide');
+ this.callChain();
+ }.bind(this));
+ },
+
+ destroy: function(){
+ this.content.destroy();
+ this.parent();
+ this.target.eliminate('spinner');
+ }
+
+});
+
+Request = Class.refactor(Request, {
+
+ options: {
+ useSpinner: false,
+ spinnerOptions: {},
+ spinnerTarget: false
+ },
+
+ initialize: function(options){
+ this._send = this.send;
+ this.send = function(options){
+ var spinner = this.getSpinner();
+ if (spinner) spinner.chain(this._send.pass(options, this)).show();
+ else this._send(options);
+ return this;
+ };
+ this.previous(options);
+ },
+
+ getSpinner: function(){
+ if (!this.spinner){
+ var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+ if (this.options.useSpinner && update){
+ update.set('spinner', this.options.spinnerOptions);
+ var spinner = this.spinner = update.get('spinner');
+ ['complete', 'exception', 'cancel'].each(function(event){
+ this.addEvent(event, spinner.hide.bind(spinner));
+ }, this);
+ }
+ }
+ return this.spinner;
+ }
+
+});
+
+Element.Properties.spinner = {
+
+ set: function(options){
+ var spinner = this.retrieve('spinner');
+ if (spinner) spinner.destroy();
+ return this.eliminate('spinner').store('spinner:options', options);
+ },
+
+ get: function(){
+ var spinner = this.retrieve('spinner');
+ if (!spinner){
+ spinner = new Spinner(this, this.retrieve('spinner:options'));
+ this.store('spinner', spinner);
+ }
+ return spinner;
+ }
+
+};
+
+Element.implement({
+
+ spin: function(options){
+ if (options) this.set('spinner', options);
+ this.get('spinner').show();
+ return this;
+ },
+
+ unspin: function(){
+ this.get('spinner').hide();
+ return this;
+ }
+
+});
+
+
+/*
+---
+
+script: String.QueryString.js
+
+name: String.QueryString
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+ - Lennart Pilon
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+ parseQueryString: function(decodeKeys, decodeValues){
+ if (decodeKeys == null) decodeKeys = true;
+ if (decodeValues == null) decodeValues = true;
+
+ var vars = this.split(/[&;]/),
+ object = {};
+ if (!vars.length) return object;
+
+ vars.each(function(val){
+ var index = val.indexOf('=') + 1,
+ value = index ? val.substr(index) : '',
+ keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
+ obj = object;
+ if (!keys) return;
+ if (decodeValues) value = decodeURIComponent(value);
+ keys.each(function(key, i){
+ if (decodeKeys) key = decodeURIComponent(key);
+ var current = obj[key];
+
+ if (i < keys.length - 1) obj = obj[key] = current || {};
+ else if (typeOf(current) == 'array') current.push(value);
+ else obj[key] = current != null ? [current, value] : value;
+ });
+ });
+
+ return object;
+ },
+
+ cleanQueryString: function(method){
+ return this.split('&').filter(function(val){
+ var index = val.indexOf('='),
+ key = index < 0 ? '' : val.substr(0, index),
+ value = val.substr(index + 1);
+
+ return method ? method.call(null, key, value) : (value || value === 0);
+ }).join('&');
+ }
+
+});
+
+
+/*
+---
+
+name: Events.Pseudos
+
+description: Adds the functionality to add pseudo events
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More]
+
+provides: [Events.Pseudos]
+
+...
+*/
+
+(function(){
+
+Events.Pseudos = function(pseudos, addEvent, removeEvent){
+
+ var storeKey = '_monitorEvents:';
+
+ var storageOf = function(object){
+ return {
+ store: object.store ? function(key, value){
+ object.store(storeKey + key, value);
+ } : function(key, value){
+ (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
+ },
+ retrieve: object.retrieve ? function(key, dflt){
+ return object.retrieve(storeKey + key, dflt);
+ } : function(key, dflt){
+ if (!object._monitorEvents) return dflt;
+ return object._monitorEvents[key] || dflt;
+ }
+ };
+ };
+
+ var splitType = function(type){
+ if (type.indexOf(':') == -1 || !pseudos) return null;
+
+ var parsed = Slick.parse(type).expressions[0][0],
+ parsedPseudos = parsed.pseudos,
+ l = parsedPseudos.length,
+ splits = [];
+
+ while (l--){
+ var pseudo = parsedPseudos[l].key,
+ listener = pseudos[pseudo];
+ if (listener != null) splits.push({
+ event: parsed.tag,
+ value: parsedPseudos[l].value,
+ pseudo: pseudo,
+ original: type,
+ listener: listener
+ });
+ }
+ return splits.length ? splits : null;
+ };
+
+ return {
+
+ addEvent: function(type, fn, internal){
+ var split = splitType(type);
+ if (!split) return addEvent.call(this, type, fn, internal);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type, []),
+ eventType = split[0].event,
+ args = Array.slice(arguments, 2),
+ stack = fn,
+ self = this;
+
+ split.each(function(item){
+ var listener = item.listener,
+ stackFn = stack;
+ if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
+ else stack = function(){
+ listener.call(self, item, stackFn, arguments, stack);
+ };
+ });
+
+ events.include({type: eventType, event: fn, monitor: stack});
+ storage.store(type, events);
+
+ if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
+ return addEvent.apply(this, [eventType, stack].concat(args));
+ },
+
+ removeEvent: function(type, fn){
+ var split = splitType(type);
+ if (!split) return removeEvent.call(this, type, fn);
+
+ var storage = storageOf(this),
+ events = storage.retrieve(type);
+ if (!events) return this;
+
+ var args = Array.slice(arguments, 2);
+
+ removeEvent.apply(this, [type, fn].concat(args));
+ events.each(function(monitor, i){
+ if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
+ delete events[i];
+ }, this);
+
+ storage.store(type, events);
+ return this;
+ }
+
+ };
+
+};
+
+var pseudos = {
+
+ once: function(split, fn, args, monitor){
+ fn.apply(this, args);
+ this.removeEvent(split.event, monitor)
+ .removeEvent(split.original, fn);
+ },
+
+ throttle: function(split, fn, args){
+ if (!fn._throttled){
+ fn.apply(this, args);
+ fn._throttled = setTimeout(function(){
+ fn._throttled = false;
+ }, split.value || 250);
+ }
+ },
+
+ pause: function(split, fn, args){
+ clearTimeout(fn._pause);
+ fn._pause = fn.delay(split.value || 250, this, args);
+ }
+
+};
+
+Events.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+Events.lookupPseudo = function(key){
+ return pseudos[key];
+};
+
+var proto = Events.prototype;
+Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+['Request', 'Fx'].each(function(klass){
+ if (this[klass]) this[klass].implement(Events.prototype);
+});
+
+})();
+
+
+/*
+---
+
+name: Element.Event.Pseudos
+
+description: Adds the functionality to add pseudo events for Elements
+
+license: MIT-style license
+
+authors:
+ - Arian Stolwijk
+
+requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
+
+provides: [Element.Event.Pseudos, Element.Delegation.Pseudo]
+
+...
+*/
+
+(function(){
+
+var pseudos = {relay: false},
+ copyFromEvents = ['once', 'throttle', 'pause'],
+ count = copyFromEvents.length;
+
+while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
+
+DOMEvent.definePseudo = function(key, listener){
+ pseudos[key] = listener;
+ return this;
+};
+
+var proto = Element.prototype;
+[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
+
+})();
+
+
+/*
+---
+
+script: Form.Request.js
+
+name: Form.Request
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Request.HTML
+ - Class.Binds
+ - Class.Occlude
+ - Spinner
+ - String.QueryString
+ - Element.Delegation.Pseudo
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+ Form.Request = new Class({
+
+ Binds: ['onSubmit', 'onFormValidate'],
+
+ Implements: [Options, Events, Class.Occlude],
+
+ options: {/*
+ onFailure: function(){},
+ onSuccess: function(){}, // aliased to onComplete,
+ onSend: function(){}*/
+ requestOptions: {
+ evalScripts: true,
+ useSpinner: true,
+ emulation: false,
+ link: 'ignore'
+ },
+ sendButtonClicked: true,
+ extraData: {},
+ resetForm: true
+ },
+
+ property: 'form.request',
+
+ initialize: function(form, target, options){
+ this.element = document.id(form);
+ if (this.occlude()) return this.occluded;
+ this.setOptions(options)
+ .setTarget(target)
+ .attach();
+ },
+
+ setTarget: function(target){
+ this.target = document.id(target);
+ if (!this.request){
+ this.makeRequest();
+ } else {
+ this.request.setOptions({
+ update: this.target
+ });
+ }
+ return this;
+ },
+
+ toElement: function(){
+ return this.element;
+ },
+
+ makeRequest: function(){
+ var self = this;
+ this.request = new Request.HTML(Object.merge({
+ update: this.target,
+ emulation: false,
+ spinnerTarget: this.element,
+ method: this.element.get('method') || 'post'
+ }, this.options.requestOptions)).addEvents({
+ success: function(tree, elements, html, javascript){
+ ['complete', 'success'].each(function(evt){
+ self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
+ });
+ },
+ failure: function(){
+ self.fireEvent('complete', arguments).fireEvent('failure', arguments);
+ },
+ exception: function(){
+ self.fireEvent('failure', arguments);
+ }
+ });
+ return this.attachReset();
+ },
+
+ attachReset: function(){
+ if (!this.options.resetForm) return this;
+ this.request.addEvent('success', function(){
+ Function.attempt(function(){
+ this.element.reset();
+ }.bind(this));
+ if (window.OverText) OverText.update();
+ }.bind(this));
+ return this;
+ },
+
+ attach: function(attach){
+ var method = (attach != false) ? 'addEvent' : 'removeEvent';
+ this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
+
+ var fv = this.element.retrieve('validator');
+ if (fv) fv[method]('onFormValidate', this.onFormValidate);
+ else this.element[method]('submit', this.onSubmit);
+
+ return this;
+ },
+
+ detach: function(){
+ return this.attach(false);
+ },
+
+ //public method
+ enable: function(){
+ return this.attach();
+ },
+
+ //public method
+ disable: function(){
+ return this.detach();
+ },
+
+ onFormValidate: function(valid, form, event){
+ //if there's no event, then this wasn't a submit event
+ if (!event) return;
+ var fv = this.element.retrieve('validator');
+ if (valid || (fv && !fv.options.stopOnFailure)){
+ event.stop();
+ this.send();
+ }
+ },
+
+ onSubmit: function(event){
+ var fv = this.element.retrieve('validator');
+ if (fv){
+ //form validator was created after Form.Request
+ this.element.removeEvent('submit', this.onSubmit);
+ fv.addEvent('onFormValidate', this.onFormValidate);
+ fv.validate(event);
+ return;
+ }
+ if (event) event.stop();
+ this.send();
+ },
+
+ saveClickedButton: function(event, target){
+ var targetName = target.get('name');
+ if (!targetName || !this.options.sendButtonClicked) return;
+ this.options.extraData[targetName] = target.get('value') || true;
+ this.clickedCleaner = function(){
+ delete this.options.extraData[targetName];
+ this.clickedCleaner = function(){};
+ }.bind(this);
+ },
+
+ clickedCleaner: function(){},
+
+ send: function(){
+ var str = this.element.toQueryString().trim(),
+ data = Object.toQueryString(this.options.extraData);
+
+ if (str) str += "&" + data;
+ else str = data;
+
+ this.fireEvent('send', [this.element, str.parseQueryString()]);
+ this.request.send({
+ data: str,
+ url: this.options.requestOptions.url || this.element.get('action')
+ });
+ this.clickedCleaner();
+ return this;
+ }
+
+ });
+
+ Element.implement('formUpdate', function(update, options){
+ var fq = this.retrieve('form.request');
+ if (!fq){
+ fq = new Form.Request(this, update, options);
+ } else {
+ if (update) fq.setTarget(update);
+ if (options) fq.setOptions(options).makeRequest();
+ }
+ fq.send();
+ return this;
+ });
+
+})();
+
+
+/*
+---
+
+script: Element.Shortcuts.js
+
+name: Element.Shortcuts
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Element.Style
+ - MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+ isDisplayed: function(){
+ return this.getStyle('display') != 'none';
+ },
+
+ isVisible: function(){
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
+ },
+
+ toggle: function(){
+ return this[this.isDisplayed() ? 'hide' : 'show']();
+ },
+
+ hide: function(){
+ var d;
+ try {
+ //IE fails here if the element is not in the dom
+ d = this.getStyle('display');
+ } catch(e){}
+ if (d == 'none') return this;
+ return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
+ },
+
+ show: function(display){
+ if (!display && this.isDisplayed()) return this;
+ display = display || this.retrieve('element:_originalDisplay') || 'block';
+ return this.setStyle('display', (display == 'none') ? 'block' : display);
+ },
+
+ swapClass: function(remove, add){
+ return this.removeClass(remove).addClass(add);
+ }
+
+});
+
+Document.implement({
+
+ clearSelection: function(){
+ if (window.getSelection){
+ var selection = window.getSelection();
+ if (selection && selection.removeAllRanges) selection.removeAllRanges();
+ } else if (document.selection && document.selection.empty){
+ try {
+ //IE fails here if selected element is not in dom
+ document.selection.empty();
+ } catch(e){}
+ }
+ }
+
+});
+
+
+/*
+---
+
+script: Fx.Reveal.js
+
+name: Fx.Reveal
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - Core/Fx.Morph
+ - Element.Shortcuts
+ - Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+(function(){
+
+
+var hideTheseOf = function(object){
+ var hideThese = object.options.hideInputs;
+ if (window.OverText){
+ var otClasses = [null];
+ OverText.each(function(ot){
+ otClasses.include('.' + ot.options.labelClass);
+ });
+ if (otClasses) hideThese += otClasses.join(', ');
+ }
+ return (hideThese) ? object.element.getElements(hideThese) : null;
+};
+
+
+Fx.Reveal = new Class({
+
+ Extends: Fx.Morph,
+
+ options: {/*
+ onShow: function(thisElement){},
+ onHide: function(thisElement){},
+ onComplete: function(thisElement){},
+ heightOverride: null,
+ widthOverride: null,*/
+ link: 'cancel',
+ styles: ['padding', 'border', 'margin'],
+ transitionOpacity: 'opacity' in document.documentElement,
+ mode: 'vertical',
+ display: function(){
+ return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
+ },
+ opacity: 1,
+ hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null
+ },
+
+ dissolve: function(){
+ if (!this.hiding && !this.showing){
+ if (this.element.getStyle('display') != 'none'){
+ this.hiding = true;
+ this.showing = false;
+ this.hidden = true;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
+
+ var zero = {};
+ Object.each(startStyles, function(style, name){
+ zero[name] = [style, 0];
+ });
+
+ this.element.setStyles({
+ display: Function.from(this.options.display).call(this),
+ overflow: 'hidden'
+ });
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ if (this.hidden){
+ this.hiding = false;
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', 'none');
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ }
+ this.fireEvent('hide', this.element);
+ this.callChain();
+ }.bind(this));
+
+ this.start(zero);
+ } else {
+ this.callChain.delay(10, this);
+ this.fireEvent('complete', this.element);
+ this.fireEvent('hide', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.dissolve.bind(this));
+ } else if (this.options.link == 'cancel' && !this.hiding){
+ this.cancel();
+ this.dissolve();
+ }
+ return this;
+ },
+
+ reveal: function(){
+ if (!this.showing && !this.hiding){
+ if (this.element.getStyle('display') == 'none'){
+ this.hiding = false;
+ this.showing = true;
+ this.hidden = false;
+ this.cssText = this.element.style.cssText;
+
+ var startStyles;
+ this.element.measure(function(){
+ startStyles = this.element.getComputedSize({
+ styles: this.options.styles,
+ mode: this.options.mode
+ });
+ }.bind(this));
+ if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
+ if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
+ if (this.options.transitionOpacity){
+ this.element.setStyle('opacity', 0);
+ startStyles.opacity = this.options.opacity;
+ }
+
+ var zero = {
+ height: 0,
+ display: Function.from(this.options.display).call(this)
+ };
+ Object.each(startStyles, function(style, name){
+ zero[name] = 0;
+ });
+ zero.overflow = 'hidden';
+
+ this.element.setStyles(zero);
+
+ var hideThese = hideTheseOf(this);
+ if (hideThese) hideThese.setStyle('visibility', 'hidden');
+
+ this.$chain.unshift(function(){
+ this.element.style.cssText = this.cssText;
+ this.element.setStyle('display', Function.from(this.options.display).call(this));
+ if (!this.hidden) this.showing = false;
+ if (hideThese) hideThese.setStyle('visibility', 'visible');
+ this.callChain();
+ this.fireEvent('show', this.element);
+ }.bind(this));
+
+ this.start(startStyles);
+ } else {
+ this.callChain();
+ this.fireEvent('complete', this.element);
+ this.fireEvent('show', this.element);
+ }
+ } else if (this.options.link == 'chain'){
+ this.chain(this.reveal.bind(this));
+ } else if (this.options.link == 'cancel' && !this.showing){
+ this.cancel();
+ this.reveal();
+ }
+ return this;
+ },
+
+ toggle: function(){
+ if (this.element.getStyle('display') == 'none'){
+ this.reveal();
+ } else {
+ this.dissolve();
+ }
+ return this;
+ },
+
+ cancel: function(){
+ this.parent.apply(this, arguments);
+ if (this.cssText != null) this.element.style.cssText = this.cssText;
+ this.hiding = false;
+ this.showing = false;
+ return this;
+ }
+
+});
+
+Element.Properties.reveal = {
+
+ set: function(options){
+ this.get('reveal').cancel().setOptions(options);
+ return this;
+ },
+
+ get: function(){
+ var reveal = this.retrieve('reveal');
+ if (!reveal){
+ reveal = new Fx.Reveal(this);
+ this.store('reveal', reveal);
+ }
+ return reveal;
+ }
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+ reveal: function(options){
+ this.get('reveal').setOptions(options).reveal();
+ return this;
+ },
+
+ dissolve: function(options){
+ this.get('reveal').setOptions(options).dissolve();
+ return this;
+ },
+
+ nix: function(options){
+ var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
+ this.get('reveal').setOptions(options).dissolve().chain(function(){
+ this[params.destroy ? 'destroy' : 'dispose']();
+ }.bind(this));
+ return this;
+ },
+
+ wink: function(){
+ var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
+ var reveal = this.get('reveal').setOptions(params.options);
+ reveal.reveal().chain(function(){
+ (function(){
+ reveal.dissolve();
+ }).delay(params.duration || 2000);
+ });
+ }
+
+});
+
+})();
+
+
+/*
+---
+
+script: Drag.js
+
+name: Drag
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - Core/Events
+ - Core/Options
+ - Core/Element.Event
+ - Core/Element.Style
+ - Core/Element.Dimensions
+ - MooTools.More
+
+provides: [Drag]
+...
+
+*/
+
+var Drag = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onBeforeStart: function(thisElement){},
+ onStart: function(thisElement, event){},
+ onSnap: function(thisElement){},
+ onDrag: function(thisElement, event){},
+ onCancel: function(thisElement){},
+ onComplete: function(thisElement, event){},*/
+ snap: 6,
+ unit: 'px',
+ grid: false,
+ style: true,
+ limit: false,
+ handle: false,
+ invert: false,
+ preventDefault: false,
+ stopPropagation: false,
+ modifiers: {x: 'left', y: 'top'}
+ },
+
+ initialize: function(){
+ var params = Array.link(arguments, {
+ 'options': Type.isObject,
+ 'element': function(obj){
+ return obj != null;
+ }
+ });
+
+ this.element = document.id(params.element);
+ this.document = this.element.getDocument();
+ this.setOptions(params.options || {});
+ var htype = typeOf(this.options.handle);
+ this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+ this.mouse = {'now': {}, 'pos': {}};
+ this.value = {'start': {}, 'now': {}};
+
+ this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown';
+
+
+ if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){
+ document.ondragstart = Function.from(false);
+ Drag.ondragstartFixed = true;
+ }
+
+ this.bound = {
+ start: this.start.bind(this),
+ check: this.check.bind(this),
+ drag: this.drag.bind(this),
+ stop: this.stop.bind(this),
+ cancel: this.cancel.bind(this),
+ eventStop: Function.from(false)
+ };
+ this.attach();
+ },
+
+ attach: function(){
+ this.handles.addEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ detach: function(){
+ this.handles.removeEvent('mousedown', this.bound.start);
+ return this;
+ },
+
+ start: function(event){
+ var options = this.options;
+
+ if (event.rightClick) return;
+
+ if (options.preventDefault) event.preventDefault();
+ if (options.stopPropagation) event.stopPropagation();
+ this.mouse.start = event.page;
+
+ this.fireEvent('beforeStart', this.element);
+
+ var limit = options.limit;
+ this.limit = {x: [], y: []};
+
+ var z, coordinates;
+ for (z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+
+ var style = this.element.getStyle(options.modifiers[z]);
+
+ // Some browsers (IE and Opera) don't always return pixels.
+ if (style && !style.match(/px$/)){
+ if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
+ style = coordinates[options.modifiers[z]];
+ }
+
+ if (options.style) this.value.now[z] = (style || 0).toInt();
+ else this.value.now[z] = this.element[options.modifiers[z]];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ this.mouse.pos[z] = event.page[z] - this.value.now[z];
+
+ if (limit && limit[z]){
+ var i = 2;
+ while (i--){
+ var limitZI = limit[z][i];
+ if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
+ }
+ }
+ }
+
+ if (typeOf(this.options.grid) == 'number') this.options.grid = {
+ x: this.options.grid,
+ y: this.options.grid
+ };
+
+ var events = {
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.addEvents(events);
+ },
+
+ check: function(event){
+ if (this.options.preventDefault) event.preventDefault();
+ var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+ if (distance > this.options.snap){
+ this.cancel();
+ this.document.addEvents({
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ });
+ this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+ }
+ },
+
+ drag: function(event){
+ var options = this.options;
+
+ if (options.preventDefault) event.preventDefault();
+ this.mouse.now = event.page;
+
+ for (var z in options.modifiers){
+ if (!options.modifiers[z]) continue;
+ this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+
+ if (options.invert) this.value.now[z] *= -1;
+
+ if (options.limit && this.limit[z]){
+ if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
+ this.value.now[z] = this.limit[z][1];
+ } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
+ this.value.now[z] = this.limit[z][0];
+ }
+ }
+
+ if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
+
+ if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
+ else this.element[options.modifiers[z]] = this.value.now[z];
+ }
+
+ this.fireEvent('drag', [this.element, event]);
+ },
+
+ cancel: function(event){
+ this.document.removeEvents({
+ mousemove: this.bound.check,
+ mouseup: this.bound.cancel
+ });
+ if (event){
+ this.document.removeEvent(this.selection, this.bound.eventStop);
+ this.fireEvent('cancel', this.element);
+ }
+ },
+
+ stop: function(event){
+ var events = {
+ mousemove: this.bound.drag,
+ mouseup: this.bound.stop
+ };
+ events[this.selection] = this.bound.eventStop;
+ this.document.removeEvents(events);
+ if (event) this.fireEvent('complete', [this.element, event]);
+ }
+
+});
+
+Element.implement({
+
+ makeResizable: function(options){
+ var drag = new Drag(this, Object.merge({
+ modifiers: {
+ x: 'width',
+ y: 'height'
+ }
+ }, options));
+
+ this.store('resizer', drag);
+ return drag.addEvent('drag', function(){
+ this.fireEvent('resize', drag);
+ }.bind(this));
+ }
+
+});
+
+
+/*
+---
+
+script: Drag.Move.js
+
+name: Drag.Move
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - Core/Element.Dimensions
+ - Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+ Extends: Drag,
+
+ options: {/*
+ onEnter: function(thisElement, overed){},
+ onLeave: function(thisElement, overed){},
+ onDrop: function(thisElement, overed, event){},*/
+ droppables: [],
+ container: false,
+ precalculate: false,
+ includeMargins: true,
+ checkDroppables: true
+ },
+
+ initialize: function(element, options){
+ this.parent(element, options);
+ element = this.element;
+
+ this.droppables = $$(this.options.droppables);
+ this.setContainer(this.options.container);
+
+ if (this.options.style){
+ if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
+ var parent = element.getOffsetParent(),
+ styles = element.getStyles('left', 'top');
+ if (parent && (styles.left == 'auto' || styles.top == 'auto')){
+ element.setPosition(element.getPosition(parent));
+ }
+ }
+
+ if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
+ }
+
+ this.addEvent('start', this.checkDroppables, true);
+ this.overed = null;
+ },
+
+ setContainer: function(container) {
+ this.container = document.id(container);
+ if (this.container && typeOf(this.container) != 'element'){
+ this.container = document.id(this.container.getDocument().body);
+ }
+ },
+
+ start: function(event){
+ if (this.container) this.options.limit = this.calculateLimit();
+
+ if (this.options.precalculate){
+ this.positions = this.droppables.map(function(el){
+ return el.getCoordinates();
+ });
+ }
+
+ this.parent(event);
+ },
+
+ calculateLimit: function(){
+ var element = this.element,
+ container = this.container,
+
+ offsetParent = document.id(element.getOffsetParent()) || document.body,
+ containerCoordinates = container.getCoordinates(offsetParent),
+ elementMargin = {},
+ elementBorder = {},
+ containerMargin = {},
+ containerBorder = {},
+ offsetParentPadding = {};
+
+ ['top', 'right', 'bottom', 'left'].each(function(pad){
+ elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
+ elementBorder[pad] = element.getStyle('border-' + pad).toInt();
+ containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
+ containerBorder[pad] = container.getStyle('border-' + pad).toInt();
+ offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+ }, this);
+
+ var width = element.offsetWidth + elementMargin.left + elementMargin.right,
+ height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
+ left = 0,
+ top = 0,
+ right = containerCoordinates.right - containerBorder.right - width,
+ bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+ if (this.options.includeMargins){
+ left += elementMargin.left;
+ top += elementMargin.top;
+ } else {
+ right += elementMargin.right;
+ bottom += elementMargin.bottom;
+ }
+
+ if (element.getStyle('position') == 'relative'){
+ var coords = element.getCoordinates(offsetParent);
+ coords.left -= element.getStyle('left').toInt();
+ coords.top -= element.getStyle('top').toInt();
+
+ left -= coords.left;
+ top -= coords.top;
+ if (container.getStyle('position') != 'relative'){
+ left += containerBorder.left;
+ top += containerBorder.top;
+ }
+ right += elementMargin.left - coords.left;
+ bottom += elementMargin.top - coords.top;
+
+ if (container != offsetParent){
+ left += containerMargin.left + offsetParentPadding.left;
+ if (!offsetParentPadding.left && left < 0) left = 0;
+ top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top;
+ if (!offsetParentPadding.top && top < 0) top = 0;
+ }
+ } else {
+ left -= elementMargin.left;
+ top -= elementMargin.top;
+ if (container != offsetParent){
+ left += containerCoordinates.left + containerBorder.left;
+ top += containerCoordinates.top + containerBorder.top;
+ }
+ }
+
+ return {
+ x: [left, right],
+ y: [top, bottom]
+ };
+ },
+
+ getDroppableCoordinates: function(element){
+ var position = element.getCoordinates();
+ if (element.getStyle('position') == 'fixed'){
+ var scroll = window.getScroll();
+ position.left += scroll.x;
+ position.right += scroll.x;
+ position.top += scroll.y;
+ position.bottom += scroll.y;
+ }
+ return position;
+ },
+
+ checkDroppables: function(){
+ var overed = this.droppables.filter(function(el, i){
+ el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
+ var now = this.mouse.now;
+ return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+ }, this).getLast();
+
+ if (this.overed != overed){
+ if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+ if (overed) this.fireEvent('enter', [this.element, overed]);
+ this.overed = overed;
+ }
+ },
+
+ drag: function(event){
+ this.parent(event);
+ if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+ },
+
+ stop: function(event){
+ this.checkDroppables();
+ this.fireEvent('drop', [this.element, this.overed, event]);
+ this.overed = null;
+ return this.parent(event);
+ }
+
+});
+
+Element.implement({
+
+ makeDraggable: function(options){
+ var drag = new Drag.Move(this, options);
+ this.store('dragger', drag);
+ return drag;
+ }
+
+});
+
+
+/*
+---
+
+script: Sortables.js
+
+name: Sortables
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - Core/Fx.Morph
+ - Drag.Move
+
+provides: [Sortables]
+
+...
+*/
+
+var Sortables = new Class({
+
+ Implements: [Events, Options],
+
+ options: {/*
+ onSort: function(element, clone){},
+ onStart: function(element, clone){},
+ onComplete: function(element){},*/
+ opacity: 1,
+ clone: false,
+ revert: false,
+ handle: false,
+ dragOptions: {},
+ unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
+ },
+
+ initialize: function(lists, options){
+ this.setOptions(options);
+
+ this.elements = [];
+ this.lists = [];
+ this.idle = true;
+
+ this.addLists($$(document.id(lists) || lists));
+
+ if (!this.options.clone) this.options.revert = false;
+ if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
+ duration: 250,
+ link: 'cancel'
+ }, this.options.revert));
+ },
+
+ attach: function(){
+ this.addLists(this.lists);
+ return this;
+ },
+
+ detach: function(){
+ this.lists = this.removeLists(this.lists);
+ return this;
+ },
+
+ addItems: function(){
+ Array.flatten(arguments).each(function(element){
+ this.elements.push(element);
+ var start = element.retrieve('sortables:start', function(event){
+ this.start.call(this, event, element);
+ }.bind(this));
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+ }, this);
+ return this;
+ },
+
+ addLists: function(){
+ Array.flatten(arguments).each(function(list){
+ this.lists.include(list);
+ this.addItems(list.getChildren());
+ }, this);
+ return this;
+ },
+
+ removeItems: function(){
+ return $$(Array.flatten(arguments).map(function(element){
+ this.elements.erase(element);
+ var start = element.retrieve('sortables:start');
+ (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+
+ return element;
+ }, this));
+ },
+
+ removeLists: function(){
+ return $$(Array.flatten(arguments).map(function(list){
+ this.lists.erase(list);
+ this.removeItems(list.getChildren());
+
+ return list;
+ }, this));
+ },
+
+ getDroppableCoordinates: function (element){
+ var offsetParent = element.getOffsetParent();
+ var position = element.getPosition(offsetParent);
+ var scroll = {
+ w: window.getScroll(),
+ offsetParent: offsetParent.getScroll()
+ };
+ position.x += scroll.offsetParent.x;
+ position.y += scroll.offsetParent.y;
+
+ if (offsetParent.getStyle('position') == 'fixed'){
+ position.x -= scroll.w.x;
+ position.y -= scroll.w.y;
+ }
+
+ return position;
+ },
+
+ getClone: function(event, element){
+ if (!this.options.clone) return new Element(element.tagName).inject(document.body);
+ if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+ var clone = element.clone(true).setStyles({
+ margin: 0,
+ position: 'absolute',
+ visibility: 'hidden',
+ width: element.getStyle('width')
+ }).addEvent('mousedown', function(event){
+ element.fireEvent('mousedown', event);
+ });
+ //prevent the duplicated radio inputs from unchecking the real one
+ if (clone.get('html').test('radio')){
+ clone.getElements('input[type=radio]').each(function(input, i){
+ input.set('name', 'clone_' + i);
+ if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
+ });
+ }
+
+ return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
+ },
+
+ getDroppables: function(){
+ var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
+ if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
+ return droppables;
+ },
+
+ insert: function(dragging, element){
+ var where = 'inside';
+ if (this.lists.contains(element)){
+ this.list = element;
+ this.drag.droppables = this.getDroppables();
+ } else {
+ where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+ }
+ this.element.inject(element, where);
+ this.fireEvent('sort', [this.element, this.clone]);
+ },
+
+ start: function(event, element){
+ if (
+ !this.idle ||
+ event.rightClick ||
+ (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
+ ) return;
+
+ this.idle = false;
+ this.element = element;
+ this.opacity = element.getStyle('opacity');
+ this.list = element.getParent();
+ this.clone = this.getClone(event, element);
+
+ this.drag = new Drag.Move(this.clone, Object.merge({
+
+ droppables: this.getDroppables()
+ }, this.options.dragOptions)).addEvents({
+ onSnap: function(){
+ event.stop();
+ this.clone.setStyle('visibility', 'visible');
+ this.element.setStyle('opacity', this.options.opacity || 0);
+ this.fireEvent('start', [this.element, this.clone]);
+ }.bind(this),
+ onEnter: this.insert.bind(this),
+ onCancel: this.end.bind(this),
+ onComplete: this.end.bind(this)
+ });
+
+ this.clone.inject(this.element, 'before');
+ this.drag.start(event);
+ },
+
+ end: function(){
+ this.drag.detach();
+ this.element.setStyle('opacity', this.opacity);
+ var self = this;
+ if (this.effect){
+ var dim = this.element.getStyles('width', 'height'),
+ clone = this.clone,
+ pos = clone.computePosition(this.getDroppableCoordinates(clone));
+
+ var destroy = function(){
+ this.removeEvent('cancel', destroy);
+ clone.destroy();
+ self.reset();
+ };
+
+ this.effect.element = clone;
+ this.effect.start({
+ top: pos.top,
+ left: pos.left,
+ width: dim.width,
+ height: dim.height,
+ opacity: 0.25
+ }).addEvent('cancel', destroy).chain(destroy);
+ } else {
+ this.clone.destroy();
+ self.reset();
+ }
+
+ },
+
+ reset: function(){
+ this.idle = true;
+ this.fireEvent('complete', this.element);
+ },
+
+ serialize: function(){
+ var params = Array.link(arguments, {
+ modifier: Type.isFunction,
+ index: function(obj){
+ return obj != null;
+ }
+ });
+ var serial = this.lists.map(function(list){
+ return list.getChildren().map(params.modifier || function(element){
+ return element.get('id');
+ }, this);
+ }, this);
+
+ var index = params.index;
+ if (this.lists.length == 1) index = 0;
+ return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+ }
+
+});
+
+
+/*
+---
+
+script: Request.Periodical.js
+
+name: Request.Periodical
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - Core/Request
+ - MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+ options: {
+ initialDelay: 5000,
+ delay: 5000,
+ limit: 60000
+ },
+
+ startTimer: function(data){
+ var fn = function(){
+ if (!this.running) this.send({data: data});
+ };
+ this.lastDelay = this.options.initialDelay;
+ this.timer = fn.delay(this.lastDelay, this);
+ this.completeCheck = function(response){
+ clearTimeout(this.timer);
+ this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+ this.timer = fn.delay(this.lastDelay, this);
+ };
+ return this.addEvent('complete', this.completeCheck);
+ },
+
+ stopTimer: function(){
+ clearTimeout(this.timer);
+ return this.removeEvent('complete', this.completeCheck);
+ }
+
+});
+
+
+/*
+---
+
+script: Color.js
+
+name: Color
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - Core/Array
+ - Core/String
+ - Core/Number
+ - Core/Hash
+ - Core/Function
+ - MooTools.More
+
+provides: [Color]
+
+...
+*/
+
+(function(){
+
+var Color = this.Color = new Type('Color', function(color, type){
+ if (arguments.length >= 3){
+ type = 'rgb'; color = Array.slice(arguments, 0, 3);
+ } else if (typeof color == 'string'){
+ if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+ else if (color.match(/hsb/)) color = color.hsbToRgb();
+ else color = color.hexToRgb(true);
+ }
+ type = type || 'rgb';
+ switch (type){
+ case 'hsb':
+ var old = color;
+ color = color.hsbToRgb();
+ color.hsb = old;
+ break;
+ case 'hex': color = color.hexToRgb(true); break;
+ }
+ color.rgb = color.slice(0, 3);
+ color.hsb = color.hsb || color.rgbToHsb();
+ color.hex = color.rgbToHex();
+ return Object.append(color, this);
+});
+
+Color.implement({
+
+ mix: function(){
+ var colors = Array.slice(arguments);
+ var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
+ var rgb = this.slice();
+ colors.each(function(color){
+ color = new Color(color);
+ for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+ });
+ return new Color(rgb, 'rgb');
+ },
+
+ invert: function(){
+ return new Color(this.map(function(value){
+ return 255 - value;
+ }));
+ },
+
+ setHue: function(value){
+ return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+ },
+
+ setSaturation: function(percent){
+ return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+ },
+
+ setBrightness: function(percent){
+ return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+ }
+
+});
+
+this.$RGB = function(r, g, b){
+ return new Color([r, g, b], 'rgb');
+};
+
+this.$HSB = function(h, s, b){
+ return new Color([h, s, b], 'hsb');
+};
+
+this.$HEX = function(hex){
+ return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+ rgbToHsb: function(){
+ var red = this[0],
+ green = this[1],
+ blue = this[2],
+ hue = 0;
+ var max = Math.max(red, green, blue),
+ min = Math.min(red, green, blue);
+ var delta = max - min;
+ var brightness = max / 255,
+ saturation = (max != 0) ? delta / max : 0;
+ if (saturation != 0){
+ var rr = (max - red) / delta;
+ var gr = (max - green) / delta;
+ var br = (max - blue) / delta;
+ if (red == max) hue = br - gr;
+ else if (green == max) hue = 2 + rr - br;
+ else hue = 4 + gr - rr;
+ hue /= 6;
+ if (hue < 0) hue++;
+ }
+ return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+ },
+
+ hsbToRgb: function(){
+ var br = Math.round(this[2] / 100 * 255);
+ if (this[1] == 0){
+ return [br, br, br];
+ } else {
+ var hue = this[0] % 360;
+ var f = hue % 60;
+ var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+ var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+ var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+ switch (Math.floor(hue / 60)){
+ case 0: return [br, t, p];
+ case 1: return [q, br, p];
+ case 2: return [p, br, t];
+ case 3: return [p, q, br];
+ case 4: return [t, p, br];
+ case 5: return [br, p, q];
+ }
+ }
+ return false;
+ }
+
+});
+
+String.implement({
+
+ rgbToHsb: function(){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHsb() : null;
+ },
+
+ hsbToRgb: function(){
+ var hsb = this.match(/\d{1,3}/g);
+ return (hsb) ? hsb.hsbToRgb() : null;
+ }
+
+});
+
+})();
+
+
diff --git a/pyload/webui/themes/flat/js/static/mootools-more.min.js b/pyload/webui/themes/flat/js/static/mootools-more.min.js
new file mode 100644
index 000000000..ce03a60fd
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/mootools-more.min.js
@@ -0,0 +1,226 @@
+/*
+---
+MooTools: the javascript framework
+
+web build:
+ - http://mootools.net/more/c1cc18c2fff04bcc58921b4dff80a6f1
+
+packager build:
+ - packager build More/Form.Request More/Fx.Reveal More/Sortables More/Request.Periodical More/Color
+
+copyrights:
+ - [MooTools](http://mootools.net)
+
+licenses:
+ - [MIT License](http://mootools.net/license.txt)
+...
+*/
+
+MooTools.More={version:"1.5.0",build:"73db5e24e6e9c5c87b3a27aebef2248053f7db37"};Class.Mutators.Binds=function(a){if(!this.prototype.initialize){this.implement("initialize",function(){});
+}return Array.from(a).concat(this.prototype.Binds||[]);};Class.Mutators.initialize=function(a){return function(){Array.from(this.Binds).each(function(b){var c=this[b];
+if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);};};Class.Occlude=new Class({occlude:function(c,b){b=document.id(b||this.element);var a=b.retrieve(c||this.property);
+if(a&&!this.occluded){return(this.occluded=a);}this.occluded=false;b.store(c||this.property,this);return this.occluded;}});Class.refactor=function(b,a){Object.each(a,function(e,d){var c=b.prototype[d];
+c=(c&&c.$origin)||c||function(){};b.implement(d,(typeof e=="function")?function(){var f=this.previous;this.previous=c;var g=e.apply(this,arguments);this.previous=f;
+return g;}:e);});return b;};(function(){var b=function(e,d){var f=[];Object.each(d,function(g){Object.each(g,function(h){e.each(function(i){f.push(i+"-"+h+(i=="border"?"-width":""));
+});});});return f;};var c=function(f,e){var d=0;Object.each(e,function(h,g){if(g.test(f)){d=d+h.toInt();}});return d;};var a=function(d){return !!(!d||d.offsetHeight||d.offsetWidth);
+};Element.implement({measure:function(h){if(a(this)){return h.call(this);}var g=this.getParent(),e=[];while(!a(g)&&g!=document.body){e.push(g.expose());
+g=g.getParent();}var f=this.expose(),d=h.call(this);f();e.each(function(i){i();});return d;},expose:function(){if(this.getStyle("display")!="none"){return function(){};
+}var d=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=d;}.bind(this);
+},getDimensions:function(d){d=Object.merge({computeSize:false},d);var i={x:0,y:0};var h=function(j,e){return(e.computeSize)?j.getComputedSize(e):j.getSize();
+};var f=this.getParent("body");if(f&&this.getStyle("display")=="none"){i=this.measure(function(){return h(this,d);});}else{if(f){try{i=h(this,d);}catch(g){}}}return Object.append(i,(i.x||i.x===0)?{width:i.x,height:i.y}:{x:i.width,y:i.height});
+},getComputedSize:function(d){d=Object.merge({styles:["padding","border"],planes:{height:["top","bottom"],width:["left","right"]},mode:"both"},d);var g={},e={width:0,height:0},f;
+if(d.mode=="vertical"){delete e.width;delete d.planes.width;}else{if(d.mode=="horizontal"){delete e.height;delete d.planes.height;}}b(d.styles,d.planes).each(function(h){g[h]=this.getStyle(h).toInt();
+},this);Object.each(d.planes,function(i,h){var k=h.capitalize(),j=this.getStyle(h);if(j=="auto"&&!f){f=this.getDimensions();}j=g[h]=(j=="auto")?f[h]:j.toInt();
+e["total"+k]=j;i.each(function(m){var l=c(m,g);e["computed"+m.capitalize()]=l;e["total"+k]+=l;});},this);return Object.append(e,g);}});})();(function(b){var a=Element.Position={options:{relativeTo:document.body,position:{x:"center",y:"center"},offset:{x:0,y:0}},getOptions:function(d,c){c=Object.merge({},a.options,c);
+a.setPositionOption(c);a.setEdgeOption(c);a.setOffsetOption(d,c);a.setDimensionsOption(d,c);return c;},setPositionOption:function(c){c.position=a.getCoordinateFromValue(c.position);
+},setEdgeOption:function(d){var c=a.getCoordinateFromValue(d.edge);d.edge=c?c:(d.position.x=="center"&&d.position.y=="center")?{x:"center",y:"center"}:{x:"left",y:"top"};
+},setOffsetOption:function(f,d){var c={x:0,y:0};var e={x:0,y:0};var g=f.measure(function(){return document.id(this.getOffsetParent());});if(!g||g==f.getDocument().body){return;
+}e=g.getScroll();c=g.measure(function(){var i=this.getPosition();if(this.getStyle("position")=="fixed"){var h=window.getScroll();i.x+=h.x;i.y+=h.y;}return i;
+});d.offset={parentPositioned:g!=document.id(d.relativeTo),x:d.offset.x-c.x+e.x,y:d.offset.y-c.y+e.y};},setDimensionsOption:function(d,c){c.dimensions=d.getDimensions({computeSize:true,styles:["padding","border","margin"]});
+},getPosition:function(e,d){var c={};d=a.getOptions(e,d);var f=document.id(d.relativeTo)||document.body;a.setPositionCoordinates(d,c,f);if(d.edge){a.toEdge(c,d);
+}var g=d.offset;c.left=((c.x>=0||g.parentPositioned||d.allowNegative)?c.x:0).toInt();c.top=((c.y>=0||g.parentPositioned||d.allowNegative)?c.y:0).toInt();
+a.toMinMax(c,d);if(d.relFixedPosition||f.getStyle("position")=="fixed"){a.toRelFixedPosition(f,c);}if(d.ignoreScroll){a.toIgnoreScroll(f,c);}if(d.ignoreMargins){a.toIgnoreMargins(c,d);
+}c.left=Math.ceil(c.left);c.top=Math.ceil(c.top);delete c.x;delete c.y;return c;},setPositionCoordinates:function(k,g,d){var f=k.offset.y,h=k.offset.x,e=(d==document.body)?window.getScroll():d.getPosition(),j=e.y,c=e.x,i=window.getSize();
+switch(k.position.x){case"left":g.x=c+h;break;case"right":g.x=c+h+d.offsetWidth;break;default:g.x=c+((d==document.body?i.x:d.offsetWidth)/2)+h;break;}switch(k.position.y){case"top":g.y=j+f;
+break;case"bottom":g.y=j+f+d.offsetHeight;break;default:g.y=j+((d==document.body?i.y:d.offsetHeight)/2)+f;break;}},toMinMax:function(c,d){var f={left:"x",top:"y"},e;
+["minimum","maximum"].each(function(g){["left","top"].each(function(h){e=d[g]?d[g][f[h]]:null;if(e!=null&&((g=="minimum")?c[h]<e:c[h]>e)){c[h]=e;}});});
+},toRelFixedPosition:function(e,c){var d=window.getScroll();c.top+=d.y;c.left+=d.x;},toIgnoreScroll:function(e,d){var c=e.getScroll();d.top-=c.y;d.left-=c.x;
+},toIgnoreMargins:function(c,d){c.left+=d.edge.x=="right"?d.dimensions["margin-right"]:(d.edge.x!="center"?-d.dimensions["margin-left"]:-d.dimensions["margin-left"]+((d.dimensions["margin-right"]+d.dimensions["margin-left"])/2));
+c.top+=d.edge.y=="bottom"?d.dimensions["margin-bottom"]:(d.edge.y!="center"?-d.dimensions["margin-top"]:-d.dimensions["margin-top"]+((d.dimensions["margin-bottom"]+d.dimensions["margin-top"])/2));
+},toEdge:function(c,d){var e={},g=d.dimensions,f=d.edge;switch(f.x){case"left":e.x=0;break;case"right":e.x=-g.x-g.computedRight-g.computedLeft;break;default:e.x=-(Math.round(g.totalWidth/2));
+break;}switch(f.y){case"top":e.y=0;break;case"bottom":e.y=-g.y-g.computedTop-g.computedBottom;break;default:e.y=-(Math.round(g.totalHeight/2));break;}c.x+=e.x;
+c.y+=e.y;},getCoordinateFromValue:function(c){if(typeOf(c)!="string"){return c;}c=c.toLowerCase();return{x:c.test("left")?"left":(c.test("right")?"right":"center"),y:c.test(/upper|top/)?"top":(c.test("bottom")?"bottom":"center")};
+}};Element.implement({position:function(d){if(d&&(d.x!=null||d.y!=null)){return(b?b.apply(this,arguments):this);}var c=this.setStyle("position","absolute").calculatePosition(d);
+return(d&&d.returnPos)?c:this.setStyles(c);},calculatePosition:function(c){return a.getPosition(this,c);}});})(Element.prototype.position);(function(){var a=false;
+this.IframeShim=new Class({Implements:[Options,Events,Class.Occlude],options:{className:"iframeShim",src:'javascript:false;document.write("");',display:false,zIndex:null,margin:0,offset:{x:0,y:0},browsers:a},property:"IframeShim",initialize:function(c,b){this.element=document.id(c);
+if(this.occlude()){return this.occluded;}this.setOptions(b);this.makeShim();return this;},makeShim:function(){if(this.options.browsers){var d=this.element.getStyle("zIndex").toInt();
+if(!d){d=1;var c=this.element.getStyle("position");if(c=="static"||!c){this.element.setStyle("position","relative");}this.element.setStyle("zIndex",d);
+}d=((this.options.zIndex!=null||this.options.zIndex===0)&&d>this.options.zIndex)?this.options.zIndex:d-1;if(d<0){d=1;}this.shim=new Element("iframe",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:d,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);
+var b=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject");}).bind(this);if(!IframeShim.ready){window.addEvent("load",b);
+}else{b();}}else{this.position=this.hide=this.show=this.dispose=Function.from(this);}},position:function(){if(!IframeShim.ready||!this.shim){return this;
+}var b=this.element.measure(function(){return this.getSize();});if(this.options.margin!=undefined){b.x=b.x-(this.options.margin*2);b.y=b.y-(this.options.margin*2);
+this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin;}this.shim.set({width:b.x,height:b.y}).position({relativeTo:this.element,offset:this.options.offset});
+return this;},hide:function(){if(this.shim){this.shim.setStyle("display","none");}return this;},show:function(){if(this.shim){this.shim.setStyle("display","block");
+}return this.position();},dispose:function(){if(this.shim){this.shim.dispose();}return this;},destroy:function(){if(this.shim){this.shim.destroy();}return this;
+}});})();window.addEvent("load",function(){IframeShim.ready=true;});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(b,a){this.target=document.id(b)||document.id(document.body);
+this.target.store("mask",this);this.setOptions(a);this.render();this.inject();},render:function(){this.element=new Element("div",{"class":this.options["class"],id:this.options.id||"mask-"+String.uniqueID(),styles:Object.merge({},this.options.style,{display:"none"}),events:{click:function(a){this.fireEvent("click",a);
+if(this.options.hideOnClick){this.hide();}}.bind(this)}});this.hidden=true;},toElement:function(){return this.element;},inject:function(b,a){a=a||(this.options.inject?this.options.inject.where:"")||(this.target==document.body?"inside":"after");
+b=b||(this.options.inject&&this.options.inject.target)||this.target;this.element.inject(b,a);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);
+this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)});}},position:function(){this.resize(this.options.width,this.options.height);
+this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this;
+},resize:function(a,e){var b={styles:["padding","border"]};if(this.options.maskMargins){b.styles.push("margin");}var d=this.target.getComputedSize(b);if(this.target==document.body){this.element.setStyles({width:0,height:0});
+var c=window.getScrollSize();if(d.totalHeight<c.y){d.totalHeight=c.y;}if(d.totalWidth<c.x){d.totalWidth=c.x;}}this.element.setStyles({width:Array.pick([a,d.totalWidth,d.x]),height:Array.pick([e,d.totalHeight,d.y])});
+return this;},show:function(){if(!this.hidden){return this;}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);
+return this;},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show");},hide:function(){if(this.hidden){return this;
+}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy();}return this;},hideMask:function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");},toggle:function(){this[this.hidden?"show":"hide"]();},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");
+this.target.eliminate("mask");}});Element.Properties.mask={set:function(b){var a=this.retrieve("mask");if(a){a.destroy();}return this.eliminate("mask").store("mask:options",b);
+},get:function(){var a=this.retrieve("mask");if(!a){a=new Mask(this,this.retrieve("mask:options"));this.store("mask",a);}return a;}};Element.implement({mask:function(a){if(a){this.set("mask",a);
+}this.get("mask").show();return this;},unmask:function(){this.get("mask").hide();return this;}});var Spinner=new Class({Extends:Mask,Implements:Chain,options:{"class":"spinner",containerPosition:{},content:{"class":"spinner-content"},messageContainer:{"class":"spinner-msg"},img:{"class":"spinner-img"},fxOptions:{link:"chain"}},initialize:function(c,a){this.target=document.id(c)||document.id(document.body);
+this.target.store("spinner",this);this.setOptions(a);this.render();this.inject();var b=function(){this.active=false;}.bind(this);this.addEvents({hide:b,show:b});
+},render:function(){this.parent();this.element.set("id",this.options.id||"spinner-"+String.uniqueID());this.content=document.id(this.options.content)||new Element("div",this.options.content);
+this.content.inject(this.element);if(this.options.message){this.msg=document.id(this.options.message)||new Element("p",this.options.messageContainer).appendText(this.options.message);
+this.msg.inject(this.content);}if(this.options.img){this.img=document.id(this.options.img)||new Element("div",this.options.img);this.img.inject(this.content);
+}this.element.set("tween",this.options.fxOptions);},show:function(a){if(this.active){return this.chain(this.show.bind(this));}if(!this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","true");this.active=true;return this.parent(a);},showMask:function(a){var b=function(){this.content.position(Object.merge({relativeTo:this.element},this.options.containerPosition));
+}.bind(this);if(a){this.parent();b();}else{if(!this.options.style.opacity){this.options.style.opacity=this.element.getStyle("opacity").toFloat();}this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity);
+b();this.hidden=false;this.fireEvent("show");this.callChain();}},hide:function(a){if(this.active){return this.chain(this.hide.bind(this));}if(this.hidden){this.callChain.delay(20,this);
+return this;}this.target.set("aria-busy","false");this.active=true;return this.parent(a);},hideMask:function(a){if(a){return this.parent();}this.element.tween("opacity",0).get("tween").chain(function(){this.element.setStyle("display","none");
+this.hidden=true;this.fireEvent("hide");this.callChain();}.bind(this));},destroy:function(){this.content.destroy();this.parent();this.target.eliminate("spinner");
+}});Request=Class.refactor(Request,{options:{useSpinner:false,spinnerOptions:{},spinnerTarget:false},initialize:function(a){this._send=this.send;this.send=function(b){var c=this.getSpinner();
+if(c){c.chain(this._send.pass(b,this)).show();}else{this._send(b);}return this;};this.previous(a);},getSpinner:function(){if(!this.spinner){var b=document.id(this.options.spinnerTarget)||document.id(this.options.update);
+if(this.options.useSpinner&&b){b.set("spinner",this.options.spinnerOptions);var a=this.spinner=b.get("spinner");["complete","exception","cancel"].each(function(c){this.addEvent(c,a.hide.bind(a));
+},this);}}return this.spinner;}});Element.Properties.spinner={set:function(a){var b=this.retrieve("spinner");if(b){b.destroy();}return this.eliminate("spinner").store("spinner:options",a);
+},get:function(){var a=this.retrieve("spinner");if(!a){a=new Spinner(this,this.retrieve("spinner:options"));this.store("spinner",a);}return a;}};Element.implement({spin:function(a){if(a){this.set("spinner",a);
+}this.get("spinner").show();return this;},unspin:function(){this.get("spinner").hide();return this;}});String.implement({parseQueryString:function(d,a){if(d==null){d=true;
+}if(a==null){a=true;}var c=this.split(/[&;]/),b={};if(!c.length){return b;}c.each(function(i){var e=i.indexOf("=")+1,g=e?i.substr(e):"",f=e?i.substr(0,e-1).match(/([^\]\[]+|(\B)(?=\]))/g):[i],h=b;
+if(!f){return;}if(a){g=decodeURIComponent(g);}f.each(function(k,j){if(d){k=decodeURIComponent(k);}var l=h[k];if(j<f.length-1){h=h[k]=l||{};}else{if(typeOf(l)=="array"){l.push(g);
+}else{h[k]=l!=null?[l,g]:g;}}});});return b;},cleanQueryString:function(a){return this.split("&").filter(function(e){var b=e.indexOf("="),c=b<0?"":e.substr(0,b),d=e.substr(b+1);
+return a?a.call(null,c,d):(d||d===0);}).join("&");}});(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
+}:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
+}return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
+while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
+if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
+if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
+}return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
+}var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
+},this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
+e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
+}};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
+["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
+while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
+})();if(!window.Form){window.Form={};}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},sendButtonClicked:true,extraData:{},resetForm:true},property:"form.request",initialize:function(b,c,a){this.element=document.id(b);
+if(this.occlude()){return this.occluded;}this.setOptions(a).setTarget(c).attach();},setTarget:function(a){this.target=document.id(a);if(!this.request){this.makeRequest();
+}else{this.request.setOptions({update:this.target});}return this;},toElement:function(){return this.element;},makeRequest:function(){var a=this;this.request=new Request.HTML(Object.merge({update:this.target,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(c,e,d,b){["complete","success"].each(function(f){a.fireEvent(f,[a.target,c,e,d,b]);
+});},failure:function(){a.fireEvent("complete",arguments).fireEvent("failure",arguments);},exception:function(){a.fireEvent("failure",arguments);}});return this.attachReset();
+},attachReset:function(){if(!this.options.resetForm){return this;}this.request.addEvent("success",function(){Function.attempt(function(){this.element.reset();
+}.bind(this));if(window.OverText){OverText.update();}}.bind(this));return this;},attach:function(a){var c=(a!=false)?"addEvent":"removeEvent";this.element[c]("click:relay(button, input[type=submit])",this.saveClickedButton.bind(this));
+var b=this.element.retrieve("validator");if(b){b[c]("onFormValidate",this.onFormValidate);}else{this.element[c]("submit",this.onSubmit);}return this;},detach:function(){return this.attach(false);
+},enable:function(){return this.attach();},disable:function(){return this.detach();},onFormValidate:function(c,b,a){if(!a){return;}var d=this.element.retrieve("validator");
+if(c||(d&&!d.options.stopOnFailure)){a.stop();this.send();}},onSubmit:function(a){var b=this.element.retrieve("validator");if(b){this.element.removeEvent("submit",this.onSubmit);
+b.addEvent("onFormValidate",this.onFormValidate);b.validate(a);return;}if(a){a.stop();}this.send();},saveClickedButton:function(b,c){var a=c.get("name");
+if(!a||!this.options.sendButtonClicked){return;}this.options.extraData[a]=c.get("value")||true;this.clickedCleaner=function(){delete this.options.extraData[a];
+this.clickedCleaner=function(){};}.bind(this);},clickedCleaner:function(){},send:function(){var b=this.element.toQueryString().trim(),a=Object.toQueryString(this.options.extraData);
+if(b){b+="&"+a;}else{b=a;}this.fireEvent("send",[this.element,b.parseQueryString()]);this.request.send({data:b,url:this.options.requestOptions.url||this.element.get("action")});
+this.clickedCleaner();return this;}});Element.implement("formUpdate",function(c,b){var a=this.retrieve("form.request");if(!a){a=new Form.Request(this,c,b);
+}else{if(c){a.setTarget(c);}if(b){a.setOptions(b).makeRequest();}}a.send();return this;});})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none";
+},isVisible:function(){var a=this.offsetWidth,b=this.offsetHeight;return(a==0&&b==0)?false:(a>0&&b>0)?true:this.style.display!="none";},toggle:function(){return this[this.isDisplayed()?"hide":"show"]();
+},hide:function(){var b;try{b=this.getStyle("display");}catch(a){}if(b=="none"){return this;}return this.store("element:_originalDisplay",b||"").setStyle("display","none");
+},show:function(a){if(!a&&this.isDisplayed()){return this;}a=a||this.retrieve("element:_originalDisplay")||"block";return this.setStyle("display",(a=="none")?"block":a);
+},swapClass:function(a,b){return this.removeClass(a).addClass(b);}});Document.implement({clearSelection:function(){if(window.getSelection){var a=window.getSelection();
+if(a&&a.removeAllRanges){a.removeAllRanges();}}else{if(document.selection&&document.selection.empty){try{document.selection.empty();}catch(b){}}}}});(function(){var a=function(d){var b=d.options.hideInputs;
+if(window.OverText){var c=[null];OverText.each(function(e){c.include("."+e.options.labelClass);});if(c){b+=c.join(", ");}}return(b)?d.element.getElements(b):null;
+};Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:"opacity" in document.documentElement,mode:"vertical",display:function(){return this.element.get("tag")!="tr"?"block":"table-row";
+},opacity:1,hideInputs:!("opacity" in document.documentElement)?"select, input, textarea, object, embed":null},dissolve:function(){if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;
+this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+if(this.options.transitionOpacity){d.opacity=this.options.opacity;}var c={};Object.each(d,function(f,e){c[e]=[f,0];});this.element.setStyles({display:Function.from(this.options.display).call(this),overflow:"hidden"});
+var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){if(this.hidden){this.hiding=false;this.element.style.cssText=this.cssText;
+this.element.setStyle("display","none");if(b){b.setStyle("visibility","visible");}}this.fireEvent("hide",this.element);this.callChain();}.bind(this));this.start(c);
+}else{this.callChain.delay(10,this);this.fireEvent("complete",this.element);this.fireEvent("hide",this.element);}}else{if(this.options.link=="chain"){this.chain(this.dissolve.bind(this));
+}else{if(this.options.link=="cancel"&&!this.hiding){this.cancel();this.dissolve();}}}return this;},reveal:function(){if(!this.showing&&!this.hiding){if(this.element.getStyle("display")=="none"){this.hiding=false;
+this.showing=true;this.hidden=false;this.cssText=this.element.style.cssText;var d;this.element.measure(function(){d=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode});
+}.bind(this));if(this.options.heightOverride!=null){d.height=this.options.heightOverride.toInt();}if(this.options.widthOverride!=null){d.width=this.options.widthOverride.toInt();
+}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);d.opacity=this.options.opacity;}var c={height:0,display:Function.from(this.options.display).call(this)};
+Object.each(d,function(f,e){c[e]=0;});c.overflow="hidden";this.element.setStyles(c);var b=a(this);if(b){b.setStyle("visibility","hidden");}this.$chain.unshift(function(){this.element.style.cssText=this.cssText;
+this.element.setStyle("display",Function.from(this.options.display).call(this));if(!this.hidden){this.showing=false;}if(b){b.setStyle("visibility","visible");
+}this.callChain();this.fireEvent("show",this.element);}.bind(this));this.start(d);}else{this.callChain();this.fireEvent("complete",this.element);this.fireEvent("show",this.element);
+}}else{if(this.options.link=="chain"){this.chain(this.reveal.bind(this));}else{if(this.options.link=="cancel"&&!this.showing){this.cancel();this.reveal();
+}}}return this;},toggle:function(){if(this.element.getStyle("display")=="none"){this.reveal();}else{this.dissolve();}return this;},cancel:function(){this.parent.apply(this,arguments);
+if(this.cssText!=null){this.element.style.cssText=this.cssText;}this.hiding=false;this.showing=false;return this;}});Element.Properties.reveal={set:function(b){this.get("reveal").cancel().setOptions(b);
+return this;},get:function(){var b=this.retrieve("reveal");if(!b){b=new Fx.Reveal(this);this.store("reveal",b);}return b;}};Element.Properties.dissolve=Element.Properties.reveal;
+Element.implement({reveal:function(b){this.get("reveal").setOptions(b).reveal();return this;},dissolve:function(b){this.get("reveal").setOptions(b).dissolve();
+return this;},nix:function(b){var c=Array.link(arguments,{destroy:Type.isBoolean,options:Type.isObject});this.get("reveal").setOptions(b).dissolve().chain(function(){this[c.destroy?"destroy":"dispose"]();
+}.bind(this));return this;},wink:function(){var c=Array.link(arguments,{duration:Type.isNumber,options:Type.isObject});var b=this.get("reveal").setOptions(c.options);
+b.reveal().chain(function(){(function(){b.dissolve();}).delay(c.duration||2000);});}});})();var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var b=Array.link(arguments,{options:Type.isObject,element:function(c){return c!=null;
+}});this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=typeOf(this.options.handle);this.handles=((a=="array"||a=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;
+this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection="selectstart" in document?"selectstart":"mousedown";if("ondragstart" in document&&!("FileReader" in window)&&!Drag.ondragstartFixed){document.ondragstart=Function.from(false);
+Drag.ondragstartFixed=true;}this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:Function.from(false)};
+this.attach();},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this;},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);
+return this;},start:function(a){var j=this.options;if(a.rightClick){return;}if(j.preventDefault){a.preventDefault();}if(j.stopPropagation){a.stopPropagation();
+}this.mouse.start=a.page;this.fireEvent("beforeStart",this.element);var c=j.limit;this.limit={x:[],y:[]};var e,g;for(e in j.modifiers){if(!j.modifiers[e]){continue;
+}var b=this.element.getStyle(j.modifiers[e]);if(b&&!b.match(/px$/)){if(!g){g=this.element.getCoordinates(this.element.getOffsetParent());}b=g[j.modifiers[e]];
+}if(j.style){this.value.now[e]=(b||0).toInt();}else{this.value.now[e]=this.element[j.modifiers[e]];}if(j.invert){this.value.now[e]*=-1;}this.mouse.pos[e]=a.page[e]-this.value.now[e];
+if(c&&c[e]){var d=2;while(d--){var f=c[e][d];if(f||f===0){this.limit[e][d]=(typeof f=="function")?f():f;}}}}if(typeOf(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid};
+}var h={mousemove:this.bound.check,mouseup:this.bound.cancel};h[this.selection]=this.bound.eventStop;this.document.addEvents(h);},check:function(a){if(this.options.preventDefault){a.preventDefault();
+}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});
+this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);}},drag:function(b){var a=this.options;if(a.preventDefault){b.preventDefault();
+}this.mouse.now=b.page;for(var c in a.modifiers){if(!a.modifiers[c]){continue;}this.value.now[c]=this.mouse.now[c]-this.mouse.pos[c];if(a.invert){this.value.now[c]*=-1;
+}if(a.limit&&this.limit[c]){if((this.limit[c][1]||this.limit[c][1]===0)&&(this.value.now[c]>this.limit[c][1])){this.value.now[c]=this.limit[c][1];}else{if((this.limit[c][0]||this.limit[c][0]===0)&&(this.value.now[c]<this.limit[c][0])){this.value.now[c]=this.limit[c][0];
+}}}if(a.grid[c]){this.value.now[c]-=((this.value.now[c]-(this.limit[c][0]||0))%a.grid[c]);}if(a.style){this.element.setStyle(a.modifiers[c],this.value.now[c]+a.unit);
+}else{this.element[a.modifiers[c]]=this.value.now[c];}}this.fireEvent("drag",[this.element,b]);},cancel:function(a){this.document.removeEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});
+if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(b){var a={mousemove:this.bound.drag,mouseup:this.bound.stop};
+a[this.selection]=this.bound.eventStop;this.document.removeEvents(a);if(b){this.fireEvent("complete",[this.element,b]);}}});Element.implement({makeResizable:function(a){var b=new Drag(this,Object.merge({modifiers:{x:"width",y:"height"}},a));
+this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
+b=this.element;this.droppables=$$(this.options.droppables);this.setContainer(this.options.container);if(this.options.style){if(this.options.modifiers.x=="left"&&this.options.modifiers.y=="top"){var c=b.getOffsetParent(),d=b.getStyles("left","top");
+if(c&&(d.left=="auto"||d.top=="auto")){b.setPosition(b.getPosition(c));}}if(b.getStyle("position")=="static"){b.setStyle("position","absolute");}}this.addEvent("start",this.checkDroppables,true);
+this.overed=null;},setContainer:function(a){this.container=document.id(a);if(this.container&&typeOf(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
+}},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
+});}this.parent(a);},calculateLimit:function(){var j=this.element,e=this.container,d=document.id(j.getOffsetParent())||document.body,h=e.getCoordinates(d),c={},b={},k={},g={},m={};
+["top","right","bottom","left"].each(function(q){c[q]=j.getStyle("margin-"+q).toInt();b[q]=j.getStyle("border-"+q).toInt();k[q]=e.getStyle("margin-"+q).toInt();
+g[q]=e.getStyle("border-"+q).toInt();m[q]=d.getStyle("padding-"+q).toInt();},this);var f=j.offsetWidth+c.left+c.right,p=j.offsetHeight+c.top+c.bottom,i=0,l=0,o=h.right-g.right-f,a=h.bottom-g.bottom-p;
+if(this.options.includeMargins){i+=c.left;l+=c.top;}else{o+=c.right;a+=c.bottom;}if(j.getStyle("position")=="relative"){var n=j.getCoordinates(d);n.left-=j.getStyle("left").toInt();
+n.top-=j.getStyle("top").toInt();i-=n.left;l-=n.top;if(e.getStyle("position")!="relative"){i+=g.left;l+=g.top;}o+=c.left-n.left;a+=c.top-n.top;if(e!=d){i+=k.left+m.left;
+if(!m.left&&i<0){i=0;}l+=d==document.body?0:k.top+m.top;if(!m.top&&l<0){l=0;}}}else{i-=c.left;l-=c.top;if(e!=d){i+=h.left+g.left;l+=h.top+g.top;}}return{x:[i,o],y:[l,a]};
+},getDroppableCoordinates:function(c){var b=c.getCoordinates();if(c.getStyle("position")=="fixed"){var a=window.getScroll();b.left+=a.x;b.right+=a.x;b.top+=a.y;
+b.bottom+=a.y;}return b;},checkDroppables:function(){var a=this.droppables.filter(function(d,c){d=this.positions?this.positions[c]:this.getDroppableCoordinates(d);
+var b=this.mouse.now;return(b.x>d.left&&b.x<d.right&&b.y<d.bottom&&b.y>d.top);},this).getLast();if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);
+}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();
+}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);
+this.store("dragger",b);return b;}});var Sortables=new Class({Implements:[Events,Options],options:{opacity:1,clone:false,revert:false,handle:false,dragOptions:{},unDraggableTags:["button","input","a","textarea","select","option"]},initialize:function(a,b){this.setOptions(b);
+this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,Object.merge({duration:250,link:"cancel"},this.options.revert));
+}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
+var b=a.retrieve("sortables:start",function(c){this.start.call(this,c,a);}.bind(this));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
+},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.include(a);this.addItems(a.getChildren());},this);return this;
+},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
+return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
+},this));},getDroppableCoordinates:function(c){var d=c.getOffsetParent();var b=c.getPosition(d);var a={w:window.getScroll(),offsetParent:d.getScroll()};
+b.x+=a.offsetParent.x;b.y+=a.offsetParent.y;if(d.getStyle("position")=="fixed"){b.x-=a.w.x;b.y-=a.w.y;}return b;},getClone:function(b,a){if(!this.options.clone){return new Element(a.tagName).inject(document.body);
+}if(typeOf(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);}var c=a.clone(true).setStyles({margin:0,position:"absolute",visibility:"hidden",width:a.getStyle("width")}).addEvent("mousedown",function(d){a.fireEvent("mousedown",d);
+});if(c.get("html").test("radio")){c.getElements("input[type=radio]").each(function(d,e){d.set("name","clone_"+e);if(d.get("checked")){a.getElements("input[type=radio]")[e].set("checked",true);
+}});}return c.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));},getDroppables:function(){var a=this.list.getChildren().erase(this.clone).erase(this.element);
+if(!this.options.constrain){a.append(this.lists).erase(this.list);}return a;},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;
+this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);
+},start:function(b,a){if(!this.idle||b.rightClick||(!this.options.handle&&this.options.unDraggableTags.contains(b.target.get("tag")))){return;}this.idle=false;
+this.element=a;this.opacity=a.getStyle("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,Object.merge({droppables:this.getDroppables()},this.options.dragOptions)).addEvents({onSnap:function(){b.stop();
+this.clone.setStyle("visibility","visible");this.element.setStyle("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);
+}.bind(this),onEnter:this.insert.bind(this),onCancel:this.end.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(b);
+},end:function(){this.drag.detach();this.element.setStyle("opacity",this.opacity);var a=this;if(this.effect){var c=this.element.getStyles("width","height"),e=this.clone,d=e.computePosition(this.getDroppableCoordinates(e));
+var b=function(){this.removeEvent("cancel",b);e.destroy();a.reset();};this.effect.element=e;this.effect.start({top:d.top,left:d.left,width:c.width,height:c.height,opacity:0.25}).addEvent("cancel",b).chain(b);
+}else{this.clone.destroy();a.reset();}},reset:function(){this.idle=true;this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Type.isFunction,index:function(d){return d!=null;
+}});var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
+}return(a||a===0)&&a>=0&&a<this.lists.length?b[a]:b;}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(b){var a=function(){if(!this.running){this.send({data:b});
+}};this.lastDelay=this.options.initialDelay;this.timer=a.delay(this.lastDelay,this);this.completeCheck=function(c){clearTimeout(this.timer);this.lastDelay=(c)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);
+this.timer=a.delay(this.lastDelay,this);};return this.addEvent("complete",this.completeCheck);},stopTimer:function(){clearTimeout(this.timer);return this.removeEvent("complete",this.completeCheck);
+}});(function(){var a=this.Color=new Type("Color",function(c,d){if(arguments.length>=3){d="rgb";c=Array.slice(arguments,0,3);}else{if(typeof c=="string"){if(c.match(/rgb/)){c=c.rgbToHex().hexToRgb(true);
+}else{if(c.match(/hsb/)){c=c.hsbToRgb();}else{c=c.hexToRgb(true);}}}}d=d||"rgb";switch(d){case"hsb":var b=c;c=c.hsbToRgb();c.hsb=b;break;case"hex":c=c.hexToRgb(true);
+break;}c.rgb=c.slice(0,3);c.hsb=c.hsb||c.rgbToHsb();c.hex=c.rgbToHex();return Object.append(c,this);});a.implement({mix:function(){var b=Array.slice(arguments);
+var d=(typeOf(b.getLast())=="number")?b.pop():50;var c=this.slice();b.each(function(e){e=new a(e);for(var f=0;f<3;f++){c[f]=Math.round((c[f]/100*(100-d))+(e[f]/100*d));
+}});return new a(c,"rgb");},invert:function(){return new a(this.map(function(b){return 255-b;}));},setHue:function(b){return new a([b,this.hsb[1],this.hsb[2]],"hsb");
+},setSaturation:function(b){return new a([this.hsb[0],b,this.hsb[2]],"hsb");},setBrightness:function(b){return new a([this.hsb[0],this.hsb[1],b],"hsb");
+}});this.$RGB=function(e,d,c){return new a([e,d,c],"rgb");};this.$HSB=function(e,d,c){return new a([e,d,c],"hsb");};this.$HEX=function(b){return new a(b,"hex");
+};Array.implement({rgbToHsb:function(){var c=this[0],d=this[1],k=this[2],h=0;var j=Math.max(c,d,k),f=Math.min(c,d,k);var l=j-f;var i=j/255,g=(j!=0)?l/j:0;
+if(g!=0){var e=(j-c)/l;var b=(j-d)/l;var m=(j-k)/l;if(c==j){h=m-b;}else{if(d==j){h=2+e-m;}else{h=4+b-e;}}h/=6;if(h<0){h++;}}return[Math.round(h*360),Math.round(g*100),Math.round(i*100)];
+},hsbToRgb:function(){var d=Math.round(this[2]/100*255);if(this[1]==0){return[d,d,d];}else{var b=this[0]%360;var g=b%60;var h=Math.round((this[2]*(100-this[1]))/10000*255);
+var e=Math.round((this[2]*(6000-this[1]*g))/600000*255);var c=Math.round((this[2]*(6000-this[1]*(60-g)))/600000*255);switch(Math.floor(b/60)){case 0:return[d,c,h];
+case 1:return[e,d,h];case 2:return[h,d,c];case 3:return[h,e,d];case 4:return[c,h,d];case 5:return[d,h,e];}}return false;}});String.implement({rgbToHsb:function(){var b=this.match(/\d{1,3}/g);
+return(b)?b.rgbToHsb():null;},hsbToRgb:function(){var b=this.match(/\d{1,3}/g);return(b)?b.hsbToRgb():null;}});})(); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/purr.js b/pyload/webui/themes/flat/js/static/purr.js
new file mode 100644
index 000000000..9cbc503d9
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/purr.js
@@ -0,0 +1,309 @@
+/*
+---
+script: purr.js
+
+description: Class to create growl-style popup notifications.
+
+license: MIT-style
+
+authors: [atom smith]
+
+requires:
+- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph]
+
+provides: [Purr, Element.alert]
+...
+*/
+
+
+var Purr = new Class({
+
+ 'options': {
+ 'mode': 'top',
+ 'position': 'left',
+ 'elementAlertClass': 'purr-element-alert',
+ 'elements': {
+ 'wrapper': 'div',
+ 'alert': 'div',
+ 'buttonWrapper': 'div',
+ 'button': 'button'
+ },
+ 'elementOptions': {
+ 'wrapper': {
+ 'styles': {
+ 'position': 'fixed',
+ 'z-index': '9999'
+ },
+ 'class': 'purr-wrapper'
+ },
+ 'alert': {
+ 'class': 'purr-alert',
+ 'styles': {
+ 'opacity': '.85'
+ }
+ },
+ 'buttonWrapper': {
+ 'class': 'purr-button-wrapper'
+ },
+ 'button': {
+ 'class': 'purr-button'
+ }
+ },
+ 'alert': {
+ 'buttons': [],
+ 'clickDismiss': true,
+ 'hoverWait': true,
+ 'hideAfter': 5000,
+ 'fx': {
+ 'duration': 500
+ },
+ 'highlight': false,
+ 'highlightRepeat': false,
+ 'highlight': {
+ 'start': '#FF0',
+ 'end': false
+ }
+ }
+ },
+
+ 'Implements': [Options, Events, Chain],
+
+ 'initialize': function(options){
+ this.setOptions(options);
+ this.createWrapper();
+ return this;
+ },
+
+ 'bindAlert': function(){
+ return this.alert.bind(this);
+ },
+
+ 'createWrapper': function(){
+ this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper);
+ if(this.options.mode == 'top')
+ {
+ this.wrapper.setStyle('top', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('bottom', 0);
+ }
+ document.id(document.body).grab(this.wrapper);
+ this.positionWrapper(this.options.position);
+ },
+
+ 'positionWrapper': function(position){
+ if(typeOf(position) == 'object')
+ {
+
+ var wrapperCoords = this.getWrapperCoords();
+
+ this.wrapper.setStyles({
+ 'bottom': '',
+ 'left': position.x,
+ 'top': position.y - wrapperCoords.height,
+ 'position': 'absolute'
+ });
+ }
+ else if(position == 'left')
+ {
+ this.wrapper.setStyle('left', 0);
+ }
+ else if(position == 'right')
+ {
+ this.wrapper.setStyle('right', 0);
+ }
+ else
+ {
+ this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2));
+ }
+ return this;
+ },
+
+ 'getWrapperCoords': function(){
+ this.wrapper.setStyle('visibility', 'hidden');
+ var measurer = this.alert('need something in here to measure');
+ var coords = this.wrapper.getCoordinates();
+ measurer.destroy();
+ this.wrapper.setStyle('visibility','');
+ return coords;
+ },
+
+ 'alert': function(msg, options){
+
+ options = Object.merge({}, this.options.alert, options || {});
+
+ var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert);
+
+ if(typeOf(msg) == 'string')
+ {
+ alert.set('html', msg);
+ }
+ else if(typeOf(msg) == 'element')
+ {
+ alert.grab(msg);
+ }
+ else if(typeOf(msg) == 'array')
+ {
+ var alerts = [];
+ msg.each(function(m){
+ alerts.push(this.alert(m, options));
+ }, this);
+ return alerts;
+ }
+
+ alert.store('options', options);
+
+ if(options.buttons.length > 0)
+ {
+ options.clickDismiss = false;
+ options.hideAfter = false;
+ options.hoverWait = false;
+ var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper);
+ alert.grab(buttonWrapper);
+ options.buttons.each(function(button){
+ if(button.text != undefined)
+ {
+ var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button);
+ callbackButton.set('html', button.text);
+ if(button.callback != undefined)
+ {
+ callbackButton.addEvent('click', button.callback.pass(alert));
+ }
+ if(button.dismiss != undefined && button.dismiss)
+ {
+ callbackButton.addEvent('click', this.dismiss.pass(alert, this));
+ }
+ buttonWrapper.grab(callbackButton);
+ }
+ }, this);
+ }
+ if(options.className != undefined)
+ {
+ alert.addClass(options.className);
+ }
+
+ this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top');
+
+ var fx = Object.merge(this.options.alert.fx, options.fx);
+ var alertFx = new Fx.Morph(alert, fx);
+ alert.store('fx', alertFx);
+ this.fadeIn(alert);
+
+ if(options.highlight)
+ {
+ alertFx.addEvent('complete', function(){
+ alert.highlight(options.highlight.start, options.highlight.end);
+ if(options.highlightRepeat)
+ {
+ alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]);
+ }
+ });
+ }
+ if(options.hideAfter)
+ {
+ this.dismiss(alert);
+ }
+
+ if(options.clickDismiss)
+ {
+ alert.addEvent('click', function(){
+ this.holdUp = false;
+ this.dismiss(alert, true);
+ }.bind(this));
+ }
+
+ if(options.hoverWait)
+ {
+ alert.addEvents({
+ 'mouseenter': function(){
+ this.holdUp = true;
+ }.bind(this),
+ 'mouseleave': function(){
+ this.holdUp = false;
+ }.bind(this)
+ });
+ }
+
+ return alert;
+ },
+
+ 'fadeIn': function(alert){
+ var alertFx = alert.retrieve('fx');
+ alertFx.set({
+ 'opacity': 0
+ });
+ alertFx.start({
+ 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(),
+ });
+ },
+
+ 'dismiss': function(alert, now){
+ now = now || false;
+ var options = alert.retrieve('options');
+ if(now)
+ {
+ this.fadeOut(alert);
+ }
+ else
+ {
+ this.fadeOut.delay(options.hideAfter, this, alert);
+ }
+ },
+
+ 'fadeOut': function(alert){
+ if(this.holdUp)
+ {
+ this.dismiss.delay(100, this, [alert, true])
+ return null;
+ }
+ var alertFx = alert.retrieve('fx');
+ if(!alertFx)
+ {
+ return null;
+ }
+ var to = {
+ 'opacity': 0
+ }
+ if(this.options.mode == 'top')
+ {
+ to['margin-top'] = '-'+alert.offsetHeight+'px';
+ }
+ else
+ {
+ to['margin-bottom'] = '-'+alert.offsetHeight+'px';
+ }
+ alertFx.start(to);
+ alertFx.addEvent('complete', function(){
+ alert.destroy();
+ });
+ }
+});
+
+Element.implement({
+
+ 'alert': function(msg, options){
+ var alert = this.retrieve('alert');
+ if(!alert)
+ {
+ options = options || {
+ 'mode':'top'
+ };
+ alert = new Purr(options)
+ this.store('alert', alert);
+ }
+
+ var coords = this.getCoordinates();
+
+ alert.alert(msg, options);
+
+ alert.wrapper.setStyles({
+ 'bottom': '',
+ 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2),
+ 'top': coords.top - (alert.wrapper.getHeight()),
+ 'position': 'absolute'
+ });
+
+ }
+
+}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/purr.min.js b/pyload/webui/themes/flat/js/static/purr.min.js
new file mode 100644
index 000000000..bf70e357d
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/purr.min.js
@@ -0,0 +1 @@
+var Purr=new Class({options:{mode:"top",position:"left",elementAlertClass:"purr-element-alert",elements:{wrapper:"div",alert:"div",buttonWrapper:"div",button:"button"},elementOptions:{wrapper:{styles:{position:"fixed","z-index":"9999"},"class":"purr-wrapper"},alert:{"class":"purr-alert",styles:{opacity:".85"}},buttonWrapper:{"class":"purr-button-wrapper"},button:{"class":"purr-button"}},alert:{buttons:[],clickDismiss:!0,hoverWait:!0,hideAfter:5e3,fx:{duration:500},highlight:!1,highlightRepeat:!1,highlight:{start:"#FF0",end:!1}}},Implements:[Options,Events,Chain],initialize:function(t){return this.setOptions(t),this.createWrapper(),this},bindAlert:function(){return this.alert.bind(this)},createWrapper:function(){this.wrapper=new Element(this.options.elements.wrapper,this.options.elementOptions.wrapper),"top"==this.options.mode?this.wrapper.setStyle("top",0):this.wrapper.setStyle("bottom",0),document.id(document.body).grab(this.wrapper),this.positionWrapper(this.options.position)},positionWrapper:function(t){if("object"==typeOf(t)){var e=this.getWrapperCoords();this.wrapper.setStyles({bottom:"",left:t.x,top:t.y-e.height,position:"absolute"})}else"left"==t?this.wrapper.setStyle("left",0):"right"==t?this.wrapper.setStyle("right",0):this.wrapper.setStyle("left",window.innerWidth/2-this.getWrapperCoords().width/2);return this},getWrapperCoords:function(){this.wrapper.setStyle("visibility","hidden");var t=this.alert("need something in here to measure"),e=this.wrapper.getCoordinates();return t.destroy(),this.wrapper.setStyle("visibility",""),e},alert:function(t,e){e=Object.merge({},this.options.alert,e||{});var i=new Element(this.options.elements.alert,this.options.elementOptions.alert);if("string"==typeOf(t))i.set("html",t);else if("element"==typeOf(t))i.grab(t);else if("array"==typeOf(t)){var s=[];return t.each(function(t){s.push(this.alert(t,e))},this),s}if(i.store("options",e),e.buttons.length>0){e.clickDismiss=!1,e.hideAfter=!1,e.hoverWait=!1;var r=new Element(this.options.elements.buttonWrapper,this.options.elementOptions.buttonWrapper);i.grab(r),e.buttons.each(function(t){if(void 0!=t.text){var e=new Element(this.options.elements.button,this.options.elementOptions.button);e.set("html",t.text),void 0!=t.callback&&e.addEvent("click",t.callback.pass(i)),void 0!=t.dismiss&&t.dismiss&&e.addEvent("click",this.dismiss.pass(i,this)),r.grab(e)}},this)}void 0!=e.className&&i.addClass(e.className),this.wrapper.grab(i,"top"==this.options.mode?"bottom":"top");var o=Object.merge(this.options.alert.fx,e.fx),n=new Fx.Morph(i,o);return i.store("fx",n),this.fadeIn(i),e.highlight&&n.addEvent("complete",function(){i.highlight(e.highlight.start,e.highlight.end),e.highlightRepeat&&i.highlight.periodical(e.highlightRepeat,i,[e.highlight.start,e.highlight.end])}),e.hideAfter&&this.dismiss(i),e.clickDismiss&&i.addEvent("click",function(){this.holdUp=!1,this.dismiss(i,!0)}.bind(this)),e.hoverWait&&i.addEvents({mouseenter:function(){this.holdUp=!0}.bind(this),mouseleave:function(){this.holdUp=!1}.bind(this)}),i},fadeIn:function(t){var e=t.retrieve("fx");e.set({opacity:0}),e.start({opacity:[this.options.elementOptions.alert.styles.opacity,.9].pick()})},dismiss:function(t,e){e=e||!1;var i=t.retrieve("options");e?this.fadeOut(t):this.fadeOut.delay(i.hideAfter,this,t)},fadeOut:function(t){if(this.holdUp)return this.dismiss.delay(100,this,[t,!0]),null;var e=t.retrieve("fx");if(!e)return null;var i={opacity:0};"top"==this.options.mode?i["margin-top"]="-"+t.offsetHeight+"px":i["margin-bottom"]="-"+t.offsetHeight+"px",e.start(i),e.addEvent("complete",function(){t.destroy()})}});Element.implement({alert:function(t,e){var i=this.retrieve("alert");i||(e=e||{mode:"top"},i=new Purr(e),this.store("alert",i));var s=this.getCoordinates();i.alert(t,e),i.wrapper.setStyles({bottom:"",left:s.left-i.wrapper.getWidth()/2+this.getWidth()/2,top:s.top-i.wrapper.getHeight(),position:"absolute"})}}); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/tinytab.js b/pyload/webui/themes/flat/js/static/tinytab.js
new file mode 100644
index 000000000..de50279fc
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/tinytab.js
@@ -0,0 +1,43 @@
+/*
+---
+description: TinyTab - Tiny and simple tab handler for Mootools.
+
+license: MIT-style
+
+authors:
+- Danillo César de O. Melo
+
+requires:
+- core/1.2.4: '*'
+
+provides: TinyTab
+
+...
+*/
+(function($) {
+ this.TinyTab = new Class({
+ Implements: Events,
+ initialize: function(tabs, contents, opt) {
+ this.tabs = tabs;
+ this.contents = contents;
+ if(!opt) opt = {};
+ this.css = opt.selectedClass || 'selected';
+ this.select(this.tabs[0]);
+ tabs.each(function(el){
+ el.addEvent('click',function(e){
+ this.select(el);
+ e.stop();
+ }.bind(this));
+ }.bind(this));
+ },
+
+ select: function(el) {
+ this.tabs.removeClass(this.css);
+ el.addClass(this.css);
+ this.contents.setStyle('display','none');
+ var content = this.contents[this.tabs.indexOf(el)];
+ content.setStyle('display','block');
+ this.fireEvent('change',[content,el]);
+ }
+ });
+})(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/js/static/tinytab.min.js b/pyload/webui/themes/flat/js/static/tinytab.min.js
new file mode 100644
index 000000000..2f4fa0436
--- /dev/null
+++ b/pyload/webui/themes/flat/js/static/tinytab.min.js
@@ -0,0 +1 @@
+!function(){this.TinyTab=new Class({Implements:Events,initialize:function(s,t,e){this.tabs=s,this.contents=t,e||(e={}),this.css=e.selectedClass||"selected",this.select(this.tabs[0]),s.each(function(s){s.addEvent("click",function(t){this.select(s),t.stop()}.bind(this))}.bind(this))},select:function(s){this.tabs.removeClass(this.css),s.addClass(this.css),this.contents.setStyle("display","none");var t=this.contents[this.tabs.indexOf(s)];t.setStyle("display","block"),this.fireEvent("change",[t,s])}})}(document.id); \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/admin.html b/pyload/webui/themes/flat/tml/admin.html
new file mode 100644
index 000000000..c7bdd7894
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/admin.html
@@ -0,0 +1,98 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript" src="/flat/js/render/admin.min.js"></script>
+{% endblock %}
+
+
+{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Administrate") }}{% endblock %}
+
+{% block content %}
+
+ <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> |
+ <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a>
+ <br>
+ <br>
+
+ {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br>
+ {{ _("Important: Admin user have always all permissions!") }}
+
+ <form action="" method="POST">
+ <table class="settable wide">
+ <thead style="font-size: 11px">
+ <th>
+ {{ _("Name") }}
+ </th>
+ <th>
+ {{ _("Change Password") }}
+ </th>
+ <th>
+ {{ _("Admin") }}
+ </th>
+ <th>
+ {{ _("Permissions") }}
+ </th>
+ </thead>
+
+ {% for name, data in users.iteritems() %}
+ <tr>
+ <td>{{ name }}</td>
+ <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td>
+ <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %}
+ checked="True" {% endif %}"></td>
+ <td>
+ <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms">
+ {% for perm in permlist %}
+ {% if data.perms|getitem(perm) %}
+ <option selected="selected">{{ perm }}</option>
+ {% else %}
+ <option>{{ perm }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </td>
+ </tr>
+ {% endfor %}
+
+
+ </table>
+
+ <button class="styled_button" type="submit">{{ _("Submit") }}</button>
+ </form>
+{% endblock %}
+{% block hidden %}
+ <div id="password_box" class="window_box" style="z-index: 2">
+ <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data">
+ <h1>{{ _("Change Password") }}</h1>
+
+ <p>{{ _("Enter your current and desired Password.") }}</p>
+ <label for="user_login">{{ _("User") }}
+ <span class="small">{{ _("Your username.") }}</span>
+ </label>
+ <input id="user_login" name="user_login" type="text" size="20"/>
+
+ <label for="login_current_password">{{ _("Current password") }}
+ <span class="small">{{ _("The password for this account.") }}</span>
+ </label>
+ <input id="login_current_password" name="login_current_password" type="password" size="20"/>
+
+ <label for="login_new_password">{{ _("New password") }}
+ <span class="small">{{ _("The new password.") }}</span>
+ </label>
+ <input id="login_new_password" name="login_new_password" type="password" size="20"/>
+
+ <label for="login_new_password2">{{ _("New password (repeat)") }}
+ <span class="small">{{ _("Please repeat the new password.") }}</span>
+ </label>
+ <input id="login_new_password2" name="login_new_password2" type="password" size="20"/>
+
+
+ <button id="login_password_button" type="submit">{{ _("Submit") }}</button>
+ <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+ </div>
+{% endblock %}
diff --git a/pyload/webui/themes/flat/tml/base.html b/pyload/webui/themes/flat/tml/base.html
new file mode 100644
index 000000000..17349c5f1
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/base.html
@@ -0,0 +1,177 @@
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<link rel="stylesheet" type="text/css" href="/flat/css/flat.min.css"/>
+<link rel="stylesheet" type="text/css" href="/flat/css/window.min.css"/>
+<link rel="stylesheet" type="text/css" href="/flat/css/MooDialog.min.css"/>
+
+<script type="text/javascript" src="/flat/js/static/mootools-core.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/mootools-more.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/MooDialog.min.js"></script>
+<script type="text/javascript" src="/flat/js/static/purr.min.js"></script>
+
+
+<script type="text/javascript" src="/flat/js/render/base.min.js"></script>
+
+<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title>
+
+{% block head %}
+{% endblock %}
+</head>
+<body>
+<a class="anchor" name="top" id="top"></a>
+
+<div id="head-panel">
+
+
+ <div id="head-search-and-login">
+ {% block headpanel %}
+
+ {% if user.is_authenticated %}
+
+
+{% if update %}
+<span>
+<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span>
+</span>
+{% endif %}
+
+
+{% if plugins %}
+<span>
+<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span>
+</span>
+{% endif %}
+
+<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}">
+<img src="/flat/img/default/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" />
+<span style="font-weight: 300; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span>
+</span>
+
+ <img src="/flat/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span>
+ <ul id="user-actions">
+ <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li>
+ {% if user.is_admin %}
+ <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li>
+ {% endif %}
+ <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li>
+
+ </ul>
+{% else %}
+ <span style="padding-right: 2px;">{{_("Please Login!")}}</span>
+{% endif %}
+
+ {% endblock %}
+ </div>
+
+ <a href="/"><img id="head-logo" src="/flat/img/default/pyload-logo.png" alt="pyLoad" /></a>
+
+ <div id="head-menu">
+ <ul>
+
+ {% macro selected(name, right=False) -%}
+ {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %}
+ {% if not name in url and right -%}class="right"{%- endif %}
+ {%- endmacro %}
+
+
+ {% block menu %}
+ <li>
+ <a href="/" title=""><img src="/flat/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+ </li>
+ <li {{ selected('queue') }}>
+ <a href="/queue/" title=""><img src="/flat/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+ </li>
+ <li {{ selected('collector') }}>
+ <a href="/collector/" title=""><img src="/flat/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+ </li>
+ <li {{ selected('downloads') }}>
+ <a href="/downloads/" title=""><img src="/flat/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+ </li>
+ <li {{ selected('logs', True) }}>
+ <a href="/logs/" title=""><img src="/flat/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+ </li>
+ <li {{ selected('settings', True) }}>
+ <a href="/settings/" title=""><img src="/flat/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+ </li>
+ {% endblock %}
+
+ </ul>
+ </div>
+
+ <div style="clear:both;"></div>
+</div>
+
+{% if perms.STATUS %}
+<ul id="page-actions2">
+ <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li>
+ <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li>
+ <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li>
+ <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li>
+</ul>
+{% endif %}
+
+{% if perms.LIST %}
+<ul id="page-actions">
+ <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li>
+ <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li>
+ <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li>
+ <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li>
+</ul>
+{% endif %}
+
+{% block pageactions %}
+{% endblock %}
+<br/>
+
+<div id="body-wrapper" class="dokuwiki">
+
+<div id="content" lang="en" dir="ltr">
+
+<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1>
+
+{% block statusbar %}
+{% endblock %}
+
+
+<br/>
+
+<div class="level1" style="clear:both">
+</div>
+<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript>
+
+{% for message in messages %}
+ <b><p>{{message}}</p></b>
+{% endfor %}
+
+<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;">
+ <img src="/flat/img/default/ajax-loader.gif" alt="" style="padding-right: 5px"/>
+ {{_("loading")}}
+</div>
+
+{% block content %}
+{% endblock content %}
+
+ <hr style="clear: both;" />
+
+<div id="foot">&copy; 2008-2014 pyLoad Team
+<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br />
+<!--<div class="breadcrumbs"></div>-->
+
+</div>
+</div>
+</div>
+
+<div style="display: none;">
+ {% include '/flat/tml/window.html' %}
+ {% include '/flat/tml/captcha.html' %}
+ {% block hidden %}
+ {% endblock %}
+</div>
+</body>
+</html>
diff --git a/pyload/webui/themes/flat/tml/captcha.html b/pyload/webui/themes/flat/tml/captcha.html
new file mode 100644
index 000000000..ae1afe444
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/captcha.html
@@ -0,0 +1,42 @@
+<!-- Captcha box -->
+<div id="cap_box" class="window_box">
+
+ <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;">
+
+ <h1>{{_("Captcha reading")}}</h1>
+ <p id="cap_title">{{_("Please read the text on the captcha.")}}</p>
+
+ <div id="cap_textual">
+
+ <input id="cap_id" name="cap_id" type="hidden" value="" />
+
+ <label>{{_("Captcha")}}
+ <span class="small">{{_("The captcha.")}}</span>
+ </label>
+ <span class="cont">
+ <img id="cap_textual_img" src="">
+ </span>
+
+ <label>{{_("Text")}}
+ <span class="small">{{_("Input the text on the captcha.")}}</span>
+ </label>
+ <input id="cap_result" name="cap_result" type="text" size="20" />
+
+ </div>
+
+ <div id="cap_positional" style="text-align: center">
+ <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer">
+ </div>
+
+ <div id="button_bar" style="text-align: center">
+ <span>
+ <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button>
+ <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button>
+ </span>
+ </div>
+
+ <div class="spacer"></div>
+
+ </form>
+
+</div> \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/downloads.html b/pyload/webui/themes/flat/tml/downloads.html
new file mode 100644
index 000000000..445f8da37
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/downloads.html
@@ -0,0 +1,29 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}Downloads - {{super()}} {% endblock %}
+
+{% block subtitle %}
+{{_("Downloads")}}
+{% endblock %}
+
+{% block content %}
+
+<ul>
+ {% for folder in files.folder %}
+ <li>
+ {{ folder.name }}
+ <ul>
+ {% for file in folder.files %}
+ <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li>
+ {% endfor %}
+ </ul>
+ </li>
+ {% endfor %}
+
+ {% for file in files.files %}
+ <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li>
+ {% endfor %}
+
+</ul>
+
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/folder.html b/pyload/webui/themes/flat/tml/folder.html
new file mode 100644
index 000000000..89b84e7bb
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/folder.html
@@ -0,0 +1,15 @@
+<li class="folder">
+ <input type="hidden" name="path" class="path" value="{{ path }}" />
+ <input type="hidden" name="name" class="name" value="{{ name }}" />
+ <span>
+ <b>{{ name }}</b>
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/flat/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/flat/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/flat/img/default/add_folder.png" />
+ </span>
+ </span>
+ <div style="display:none">{{ _("Folder is empty") }}</div>
+</li> \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/home.html b/pyload/webui/themes/flat/tml/home.html
new file mode 100644
index 000000000..838a351ad
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/home.html
@@ -0,0 +1,263 @@
+{% extends '/flat/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript">
+
+var em;
+var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0);
+
+document.addEvent("domready", function(){
+ em = new EntryManager();
+});
+
+var EntryManager = new Class({
+ initialize: function(){
+ this.json = new Request.JSON({
+ url: "json/links",
+ secure: false,
+ async: true,
+ onSuccess: this.update.bind(this),
+ initialDelay: 0,
+ delay: 2500,
+ limit: 30000
+ });
+
+ this.ids = [{% for link in content %}
+ {% if forloop.last %}
+ {{ link.id }}
+ {% else %}
+ {{ link.id }},
+ {% endif %}
+ {% endfor %}];
+
+ this.entries = [];
+ this.container = $('LinksAktiv');
+
+ this.parseFromContent();
+
+ this.json.startTimer();
+ },
+ parseFromContent: function(){
+ this.ids.each(function(id,index){
+ var entry = new LinkEntry(id);
+ entry.parse();
+ this.entries.push(entry)
+ }, this);
+ },
+ update: function(data){
+
+ try{
+ this.ids = this.entries.map(function(item){
+ return item.fid
+ });
+
+ this.ids.filter(function(id){
+ return !this.ids.contains(id)
+ },data).each(function(id){
+ var index = this.ids.indexOf(id);
+ this.entries[index].remove();
+ this.entries = this.entries.filter(function(item){return item.fid != this},id);
+ this.ids = this.ids.erase(id)
+ }, this);
+
+ data.links.each(function(link, i){
+ if (this.ids.contains(link.fid)){
+
+ var index = this.ids.indexOf(link.fid);
+ this.entries[index].update(link)
+
+ }else{
+ var entry = new LinkEntry(link.fid);
+ entry.insert(link);
+ this.entries.push(entry);
+ this.ids.push(link.fid);
+ this.container.adopt(entry.elements.tr,entry.elements.pgbTr);
+ entry.fade.start('opacity', 1);
+ entry.fadeBar.start('opacity', 1);
+
+ }
+ }, this)
+ }catch(e){
+ //alert(e)
+ }
+ }
+});
+
+
+var LinkEntry = new Class({
+ initialize: function(id){
+ this.fid = id;
+ this.id = id;
+ },
+ parse: function(){
+ this.elements = {
+ tr: $("link_{id}".substitute({id: this.id})),
+ name: $("link_{id}_name".substitute({id: this.id})),
+ status: $("link_{id}_status".substitute({id: this.id})),
+ info: $("link_{id}_info".substitute({id: this.id})),
+ bleft: $("link_{id}_bleft".substitute({id: this.id})),
+ percent: $("link_{id}_percent".substitute({id: this.id})),
+ remove: $("link_{id}_remove".substitute({id: this.id})),
+ pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})),
+ pgb: $("link_{id}_pgb".substitute({id: this.id}))
+ };
+ this.initEffects();
+ },
+ insert: function(item){
+ try{
+
+ this.elements = {
+ tr: new Element('tr', {
+ 'html': '',
+ 'styles':{
+ 'opacity': 0
+ }
+ }),
+ name: new Element('td', {
+ 'html': item.name
+ }),
+ status: new Element('td', {
+ 'html': item.statusmsg
+ }),
+ info: new Element('td', {
+ 'html': item.info
+ }),
+ bleft: new Element('td', {
+ 'html': humanFileSize(item.size)
+ }),
+ percent: new Element('span', {
+ 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft)
+ }),
+ remove: new Element('img',{
+ 'src': '/flat/img/control_cancel.png',
+ 'styles':{
+ 'vertical-align': 'middle',
+ 'margin-right': '-20px',
+ 'margin-left': '5px',
+ 'margin-top': '-2px',
+ 'cursor': 'pointer'
+ }
+ }),
+ pgbTr: new Element('tr', {
+ 'html':''
+ }),
+ pgb: new Element('div', {
+ 'html': '&nbsp;',
+ 'styles':{
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': '#ddd'
+ }
+ })
+ };
+
+ this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove));
+ this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb));
+ this.initEffects();
+ }catch(e){
+ alert(e)
+ }
+ },
+ initEffects: function(){
+ if(!operafix)
+ this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30});
+ this.fade = new Fx.Tween(this.elements.tr);
+ this.fadeBar = new Fx.Tween(this.elements.pgbTr);
+
+ this.elements.remove.addEvent('click', function(){
+ new Request({method: 'get', url: '/json/abort_link/'+this.id}).send();
+ }.bind(this));
+
+ },
+ update: function(item){
+ this.elements.name.set('text', item.name);
+ this.elements.status.set('text', item.statusmsg);
+ this.elements.info.set('text', item.info);
+ this.elements.bleft.set('text', item.format_size);
+ this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft));
+ if(!operafix)
+ {
+ this.bar.start({
+ 'width': item.percent,
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex()
+ });
+ }
+ else
+ {
+ this.elements.pgb.set(
+ 'styles', {
+ 'height': '4px',
+ 'width': item.percent+'%',
+ 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(),
+ });
+ }
+ },
+ remove: function(){
+ this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this));
+ this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this));
+
+ }
+ });
+</script>
+
+{% endblock %}
+
+{% block subtitle %}
+{{_("Active Downloads")}}
+{% endblock %}
+
+{% block menu %}
+<li class="selected">
+ <a href="/" title=""><img src="/flat/img/head-menu-home.png" alt="" /> {{_("Home")}}</a>
+</li>
+<li>
+ <a href="/queue/" title=""><img src="/flat/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a>
+</li>
+<li>
+ <a href="/collector/" title=""><img src="/flat/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a>
+</li>
+<li>
+ <a href="/downloads/" title=""><img src="/flat/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a>
+</li>
+<li class="right">
+ <a href="/logs/" title=""><img src="/flat/img/head-menu-index.png" alt="" />{{_("Logs")}}</a>
+</li>
+<li class="right">
+ <a href="/settings/" title=""><img src="/flat/img/head-menu-config.png" alt="" />{{_("Config")}}</a>
+</li>
+{% endblock %}
+
+{% block content %}
+<table width="100%" class="queue">
+ <thead>
+ <tr class="header">
+ <th>{{_("Name")}}</th>
+ <th>{{_("Status")}}</th>
+ <th>{{_("Information")}}</th>
+ <th>{{_("Size")}}</th>
+ <th>{{_("Progress")}}</th>
+ </tr>
+ </thead>
+ <tbody id="LinksAktiv">
+
+ {% for link in content %}
+ <tr id="link_{{ link.id }}">
+ <td id="link_{{ link.id }}_name">{{ link.name }}</td>
+ <td id="link_{{ link.id }}_status">{{ link.status }}</td>
+ <td id="link_{{ link.id }}_info">{{ link.info }}</td>
+ <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td>
+ <td>
+ <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span>
+ <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/flat/img/control_cancel.png"/>
+ </td>
+ </tr>
+ <tr id="link_{{ link.id }}_pgb_tr">
+ <td colspan="5">
+ <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;">&nbsp;</div>
+ </td>
+ </tr>
+ {% endfor %}
+
+ </tbody>
+</table>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/info.html b/pyload/webui/themes/flat/tml/info.html
new file mode 100644
index 000000000..26b46736d
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/info.html
@@ -0,0 +1,81 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block head %}
+ <script type="text/javascript">
+ window.addEvent("domready", function() {
+ var ul = new Element('ul#twitter_update_list');
+ var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]');
+ var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]');
+ $("twitter").adopt(ul, script1, script2);
+ });
+ </script>
+{% endblock %}
+
+{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Information") }}{% endblock %}
+
+{% block content %}
+ <h3>{{ _("News") }}</h3>
+ <div id="twitter"></div>
+
+ <h3>{{ _("Support") }}</h3>
+
+ <ul>
+ <li style="font-weight:bold;">
+ <a href="http://pyload.org/wiki" target="_blank">Wiki</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://forum.pyload.org/" target="_blank">Forum</a>
+ &nbsp;&nbsp;|&nbsp;&nbsp;
+ <a href="http://pyload.org/irc/" target="_blank">Chat</a>
+ </li>
+ <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li>
+ <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li>
+
+ </ul>
+
+ <h3>{{ _("System") }}</h3>
+ <table class="system">
+ <tr>
+ <td>{{ _("Python:") }}</td>
+ <td>{{ python }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("OS:") }}</td>
+ <td>{{ os }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("pyLoad version:") }}</td>
+ <td>{{ version }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Installation Folder:") }}</td>
+ <td>{{ folder }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Config Folder:") }}</td>
+ <td>{{ config }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Download Folder:") }}</td>
+ <td>{{ download }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Free Space:") }}</td>
+ <td>{{ freespace }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Language:") }}</td>
+ <td>{{ language }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Webinterface Port:") }}</td>
+ <td>{{ webif }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("Remote Interface Port:") }}</td>
+ <td>{{ remote }}</td>
+ </tr>
+ </table>
+
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/login.html b/pyload/webui/themes/flat/tml/login.html
new file mode 100644
index 000000000..76365a79c
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/login.html
@@ -0,0 +1,36 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}{{_("Login")}} - {{super()}} {% endblock %}
+
+{% block content %}
+
+<div class="centeralign">
+<form action="" method="post" accept-charset="utf-8" id="login">
+ <div class="no">
+ <input type="hidden" name="do" value="login" />
+ <fieldset>
+ <legend>Login</legend>
+ <label>
+ <span>{{_("Username")}}</span>
+ <input type="text" size="20" name="username" />
+ </label>
+ <br />
+ <label>
+ <span>{{_("Password")}}</span>
+ <input type="password" size="20" name="password" />
+ </label>
+ <br />
+ <input type="submit" value="Login" class="button" />
+ </fieldset>
+ </div>
+</form>
+
+{% if errors %}
+<p>{{_("Your username and password didn't match. Please try again.")}}</p>
+ {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b>
+{% endif %}
+
+</div>
+<br>
+
+{% endblock %}
diff --git a/pyload/webui/themes/flat/tml/logout.html b/pyload/webui/themes/flat/tml/logout.html
new file mode 100644
index 000000000..370031b25
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/logout.html
@@ -0,0 +1,9 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block head %}
+<meta http-equiv="refresh" content="3; url=/">
+{% endblock %}
+
+{% block content %}
+<p><b>{{_("You were successfully logged out.")}}</b></p>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/logs.html b/pyload/webui/themes/flat/tml/logs.html
new file mode 100644
index 000000000..87e5a0f30
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/logs.html
@@ -0,0 +1,41 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}{{_("Logs")}} - {{super()}} {% endblock %}
+{% block subtitle %}{{_("Logs")}}{% endblock %}
+{% block head %}
+<link rel="stylesheet" type="text/css" href="/flat/css/log.min.css"/>
+{% endblock %}
+
+{% block content %}
+<div style="clear: both;"></div>
+
+<div class="logpaginator"><a href="{{ "/logs/1" }}">&lt;&lt; {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">&lt; {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} &gt;</a> <a href="/logs/">{{_("End")}} &gt;&gt;</a></div>
+<div class="logperpage">
+ <form id="logform1" action="" method="POST">
+ <label for="reversed">Reversed:</label>
+ <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
+ <label for="perpage">Lines per page:</label>
+ <select name="perpage" onchange="this.form.submit();">
+ {% for value in perpage_p %}
+ <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option>
+ {% endfor %}
+ </select>
+ </form>
+</div>
+<div class="logwarn">{{warning}}</div>
+<div style="clear: both;"></div>
+<div class="logdiv">
+ <table class="logtable" cellpadding="0" cellspacing="0">
+ {% for line in log %}
+ <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
+ {% endfor %}
+ </table>
+</div>
+<div class="logform">
+<form id="logform2" action="" method="POST">
+ <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/>
+ <input type="submit" value="ok" />
+</form>
+</div>
+<div style="clear: both; height: 10px;">&nbsp; </div>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/pathchooser.html b/pyload/webui/themes/flat/tml/pathchooser.html
new file mode 100644
index 000000000..95a1a3be5
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/pathchooser.html
@@ -0,0 +1,76 @@
+<html>
+<head>
+ <script class="javascript">
+ function chosen()
+ {
+ opener.ifield.value = document.forms[0].p.value;
+ close();
+ }
+ function exit()
+ {
+ close();
+ }
+ function setInvalid() {
+ document.forms[0].send.disabled = 'disabled';
+ document.forms[0].p.style.color = '#FF0000';
+ }
+ function setValid() {
+ document.forms[0].send.disabled = '';
+ document.forms[0].p.style.color = '#000000';
+ }
+ function setFile(file)
+ {
+ document.forms[0].p.value = file;
+ setValid();
+
+ }
+ </script>
+ <link rel="stylesheet" type="text/css" href="/flat/css/pathchooser.min.css"/>
+</head>
+<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}>
+<center>
+ <div id="paths">
+ <form method="get" action="?" onSubmit="chosen();" onReset="exit();">
+ <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();">
+ <input type="submit" value="Ok" name="send">
+ </form>
+
+ {% if type == 'folder' %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% else %}
+ <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span>
+ {% endif %}
+ </div>
+ <table border="0" cellspacing="0" cellpadding="3">
+ <tr>
+ <th>{{_("name")}}</th>
+ <th>{{_("size")}}</th>
+ <th>{{_("type")}}</th>
+ <th>{{_("last modified")}}</th>
+ </tr>
+ {% if parentdir %}
+ <tr>
+ <td colspan="4">
+ <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a>
+ </td>
+ </tr>
+ {% endif %}
+{% for file in files %}
+ <tr>
+ {% if type == 'folder' %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td>
+ {% else %}
+ <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td>
+ {% endif %}
+ <td class="size">{{ file.size|float|filesizeformat }}</td>
+ <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td>
+ <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td>
+ <tr>
+<!-- <tr>
+ <td colspan="4">{{_("no content")}}</td>
+ </tr> -->
+{% endfor %}
+ </table>
+ </center>
+</body>
+</html> \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/queue.html b/pyload/webui/themes/flat/tml/queue.html
new file mode 100644
index 000000000..0d3022ddd
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/queue.html
@@ -0,0 +1,104 @@
+{% extends '/flat/tml/base.html' %}
+{% block head %}
+
+<script type="text/javascript" src="/flat/js/render/package.min.js"></script>
+
+<script type="text/javascript">
+
+document.addEvent("domready", function(){
+ var pUI = new PackageUI("url", {{ target }});
+});
+</script>
+{% endblock %}
+
+{% if target %}
+ {% set name = _("Queue") %}
+{% else %}
+ {% set name = _("Collector") %}
+{% endif %}
+
+{% block title %}{{name}} - {{super()}} {% endblock %}
+{% block subtitle %}{{name}}{% endblock %}
+
+{% block pageactions %}
+<ul id="page-actions-more">
+ <li id="del_finished"><a style="padding: 0; font-weight: 300;" href="#">{{_("Delete Finished")}}</a></li>
+ <li id="restart_failed"><a style="padding: 0; font-weight: 300;" href="#">{{_("Restart Failed")}}</a></li>
+</ul>
+{% endblock %}
+
+{% block content %}
+{% autoescape true %}
+
+<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;">
+{% for package in content %}
+ <li>
+<div id="package_{{package.pid}}" class="package">
+ <div class="order" style="display: none;">{{ package.order }}</div>
+
+ <div class="packagename" style="cursor: pointer">
+ <img class="package_drag" src="/flat/img/folder.png" style="cursor: move; margin-bottom: -2px">
+ <span class="name">{{package.name}}</span>
+ &nbsp;&nbsp;
+ <span class="buttons" style="opacity:0">
+ <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/flat/img/delete.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/img/arrow_refresh.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/img/pencil.png" />
+ &nbsp;&nbsp;
+ <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/flat/img/package_go.png" />
+ </span>
+ </div>
+ {% set progress = (package.linksdone * 100) / package.linkstotal %}
+
+ <div id="progress" style="border-radius: 0px; border: 1px solid #AAAAAA; width: 50%; height: 1em">
+ <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div>
+ <label style="font-size: 0.8em; font-weight: 300; padding-left: 5px; position: relative; top: -17px">
+ {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label>
+ <label style="font-size: 0.8em; font-weight: 300; padding-right: 5px ;float: right; position: relative; top: -17px">
+ {{ package.linksdone }} / {{ package.linkstotal }}</label>
+ </div>
+ <div style="clear: both; margin-bottom: -10px"></div>
+
+ <div id="children_{{package.pid}}" style="display: none;" class="children">
+ <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span>
+ <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0">
+ </ul>
+ </div>
+</div>
+ </li>
+{% endfor %}
+</ul>
+{% endautoescape %}
+{% endblock %}
+
+{% block hidden %}
+<div id="pack_box" class="window_box" style="z-index: 2">
+ <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data">
+ <h1>{{_("Edit Package")}}</h1>
+ <p>{{_("Edit the package detais below.")}}</p>
+ <input name="pack_id" id="pack_id" type="hidden" value=""/>
+ <label for="pack_name">{{_("Name")}}
+ <span class="small">{{_("The name of the package.")}}</span>
+ </label>
+ <input id="pack_name" name="pack_name" type="text" size="20" />
+
+ <label for="pack_folder">{{_("Folder")}}
+ <span class="small">{{_("Name of subfolder for these downloads.")}}</span>
+ </label>
+ <input id="pack_folder" name="pack_folder" type="text" size="20" />
+
+ <label for="pack_pws">{{_("Password")}}
+ <span class="small">{{_("List of passwords used for unrar.")}}</span>
+ </label>
+ <textarea rows="3" name="pack_pws" id="pack_pws"></textarea>
+
+ <button type="submit">{{_("Submit")}}</button>
+ <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button>
+ <div class="spacer"></div>
+
+ </form>
+
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/pyload/webui/themes/flat/tml/settings.html b/pyload/webui/themes/flat/tml/settings.html
new file mode 100644
index 000000000..1bd0d1e17
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/settings.html
@@ -0,0 +1,204 @@
+{% extends '/flat/tml/base.html' %}
+
+{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %}
+{% block subtitle %}{{ _("Config") }}{% endblock %}
+
+{% block head %}
+ <script type="text/javascript" src="/flat/js/static/tinytab.min.js"></script>
+ <script type="text/javascript" src="/flat/js/static/MooDropMenu.min.js"></script>
+ <script type="text/javascript" src="/flat/js/render/settings.min.js"></script>
+
+{% endblock %}
+
+{% block content %}
+
+ <ul id="toptabs" class="tabs">
+ <li><a class="selected" href="#">{{ _("General") }}</a></li>
+ <li><a href="#">{{ _("Plugins") }}</a></li>
+ <li><a href="#">{{ _("Accounts") }}</a></li>
+ </ul>
+
+ <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;">
+
+ </div>
+
+ <span id="tabs-body">
+ <!-- General -->
+ <span id="general" class="active tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="general-menu">
+ {% for entry,name in conf.general %}
+ <nobr>
+ <li id="general|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+ <form id="general_form" action="" method="POST" autocomplete="off">
+ <span id="general_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+
+ <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+ </span>
+
+ <!-- Plugins -->
+ <span id="plugins" class="tabContent">
+ <ul class="nav tabs">
+ <li class>
+ <a>Menu</a>
+ <ul id="plugin-menu">
+ {% for entry,name in conf.plugin %}
+ <nobr>
+ <li id="plugin|{{ entry }}">{{ name }}</li>
+ </nobr>
+ <br>
+ {% endfor %}
+ </ul>
+ </li>
+ </ul>
+
+
+ <form id="plugin_form" action="" method="POST" autocomplete="off">
+
+ <span id="plugin_form_content">
+ <br>
+ <h3>&nbsp;&nbsp; {{ _("Choose a section from the menu") }}</h3>
+ <br>
+ </span>
+ <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/>
+ </form>
+
+ </span>
+
+ <!-- Accounts -->
+ <span id="accounts" class="tabContent">
+ <form id="account_form" action="/json/update_accounts" method="POST">
+
+ <table class="settable wide">
+
+ <thead>
+ <tr>
+ <th>{{ _("Plugin") }}</th>
+ <th>{{ _("Name") }}</th>
+ <th>{{ _("Password") }}</th>
+ <th>{{ _("Status") }}</th>
+ <th>{{ _("Premium") }}</th>
+ <th>{{ _("Valid until") }}</th>
+ <th>{{ _("Traffic left") }}</th>
+ <th>{{ _("Time") }}</th>
+ <th>{{ _("Max Parallel") }}</th>
+ <th>{{ _("Delete?") }}</th>
+ </tr>
+ </thead>
+
+
+ {% for account in conf.accs %}
+ {% set plugin = account.type %}
+ <tr>
+ <td>
+ <span style="padding:5px">{{ plugin }}</span>
+ </td>
+
+ <td><label for="{{plugin}}|password;{{account.login}}"
+ style="color:#424242;">{{ account.login }}</label></td>
+ <td>
+ <input id="{{plugin}}|password;{{account.login}}"
+ name="{{plugin}}|password;{{account.login}}"
+ type="password" value="{{account.password}}" size="12"/>
+ </td>
+ <td>
+ {% if account.valid %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("valid") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("not valid") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ {% if account.premium %}
+ <span style="font-weight: bold; color: #006400;">
+ {{ _("yes") }}
+ {% else %}
+ <span style="font-weight: bold; color: #8b0000;">
+ {{ _("no") }}
+ {% endif %}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.validuntil }}
+ </span>
+ </td>
+ <td>
+ <span style="font-weight: bold;">
+ {{ account.trafficleft }}
+ </span>
+ </td>
+ <td>
+ <input id="{{plugin}}|time;{{account.login}}"
+ name="{{plugin}}|time;{{account.login}}" type="text"
+ size="7" value="{{account.time}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|limitdl;{{account.login}}"
+ name="{{plugin}}|limitdl;{{account.login}}" type="text"
+ size="2" value="{{account.limitdl}}"/>
+ </td>
+ <td>
+ <input id="{{plugin}}|delete;{{account.login}}"
+ name="{{plugin}}|delete;{{account.login}}" type="checkbox"
+ value="True"/>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button>
+ <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button>
+ </form>
+ </span>
+ </span>
+{% endblock %}
+{% block hidden %}
+<div id="account_box" class="window_box" style="z-index: 2">
+<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Account")}}</h1>
+<p>{{_("Enter your account data to use premium features.")}}</p>
+<label for="account_login">{{_("Login")}}
+<span class="small">{{_("Your username.")}}</span>
+</label>
+<input id="account_login" name="account_login" type="text" size="20" />
+
+<label for="account_password">{{_("Password")}}
+<span class="small">{{_("The password for this account.")}}</span>
+</label>
+<input id="account_password" name="account_password" type="password" size="20" />
+
+<label for="account_type">{{_("Type")}}
+<span class="small">{{_("Choose the hoster for your account.")}}</span>
+</label>
+ <select name=account_type id="account_type">
+ {% for type in types|sort %}
+ <option value="{{ type }}">{{ type }}</option>
+ {% endfor %}
+ </select>
+
+<button id="account_add_button" type="submit">{{_("Add")}}</button>
+<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div>
+{% endblock %} \ No newline at end of file
diff --git a/module/web/templates/default/settings_item.html b/pyload/webui/themes/flat/tml/settings_item.html
index 813383343..813383343 100644
--- a/module/web/templates/default/settings_item.html
+++ b/pyload/webui/themes/flat/tml/settings_item.html
diff --git a/pyload/webui/themes/flat/tml/window.html b/pyload/webui/themes/flat/tml/window.html
new file mode 100644
index 000000000..8f57843c6
--- /dev/null
+++ b/pyload/webui/themes/flat/tml/window.html
@@ -0,0 +1,46 @@
+<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe>
+
+<div id="add_box" class="window_box">
+<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data">
+<h1>{{_("Add Package")}}</h1>
+<p>{{_("Paste your links or upload a container.")}}</p>
+<label for="add_name">{{_("Name")}}
+<span class="small">{{_("The name of the new package.")}}</span>
+</label>
+<input id="add_name" name="add_name" type="text" size="20" />
+
+<label for="add_links">{{_("Links")}}
+<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span>
+<span class="small"> {{ _("Filter urls") }}
+<img alt="URIParsing" Title="Parse Uri" src="/flat/img/default/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/>
+</span>
+
+</label>
+<textarea rows="5" name="add_links" id="add_links"></textarea>
+
+<label for="add_password">{{_("Password")}}
+ <span class="small">{{_("Password for RAR-Archive")}}</span>
+</label>
+<input id="add_password" name="add_password" type="text" size="20">
+
+<label>{{_("File")}}
+<span class="small">{{_("Upload a container.")}}</span>
+</label>
+<input type="file" name="add_file" id="add_file"/>
+
+<label for="add_dest">{{_("Destination")}}
+</label>
+<span class="cont">
+ {{_("Queue")}}
+ <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/>
+ {{_("Collector")}}
+ <input type="radio" name="add_dest" id="add_dest2" value="0"/>
+</span>
+
+<button type="submit">{{_("Add Package")}}</button>
+<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button>
+<div class="spacer"></div>
+
+</form>
+
+</div> \ No newline at end of file
diff --git a/scripts/Readme.txt b/scripts/Readme.txt
deleted file mode 100644
index 1c326801d..000000000
--- a/scripts/Readme.txt
+++ /dev/null
@@ -1,25 +0,0 @@
- #############################
- ### pyLoad Script Support ###
- #############################
-
-pyLoad is able to start any kind of scripts at given events.
-Simply put your script in a suitable folder and pyLoad will execute it at the given events and pass some arguments to them.
-
---> Note: Scripts, which starts with # will be ignored!
-For Example: #converter.sh will not be executed.
-
---> Note: You have to restart pyload when you change script names or locations.
-
-Below you see the list of arguments, which are passed to the scripts.
-
-## Argument list ##
-
-download_preparing: pluginname url
-
-download_finished: pluginname url filename filelocation
-
-package_finshed: packagename packagelocation
-
-before_reconnect: oldip
-
-after_reconnect: newip \ No newline at end of file
diff --git a/systemCheck.py b/systemCheck.py
deleted file mode 100644
index 60fe0313b..000000000
--- a/systemCheck.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import os
-import subprocess
-import sys
-
-#from module import InitHomeDir
-
-#very ugly prints, but at least it works with python 3
-
-def main():
- print("##### System Information #####\n")
- print("Platform:", sys.platform)
- print("Operating System:", os.name)
- print("Python:", sys.version.replace("\n", "")+ "\n")
-
- try:
- import pycurl
- print("pycurl:", pycurl.version)
- except:
- print("pycurl:", "missing")
-
- try:
- import Crypto
- print("py-crypto:", Crypto.__version__)
- except:
- print("py-crypto:", "missing")
-
-
- try:
- import OpenSSL
- print("OpenSSL:", OpenSSL.version.__version__)
- except:
- print("OpenSSL:", "missing")
-
- try:
- import Image
- print("image libary:", Image.VERSION)
- except:
- print("image libary:", "missing")
-
- try:
- import PyQt4.QtCore
- print("pyqt:", PyQt4.QtCore.PYQT_VERSION_STR)
- except:
- print("pyqt:", "missing")
-
- print("\n\n##### System Status #####")
- print("\n## pyLoadCore ##")
-
- core_err = []
- core_info = []
-
- if sys.version_info > (2, 8):
- core_err.append("Your python version is to new, Please use Python 2.6/2.7")
-
- if sys.version_info < (2, 5):
- core_err.append("Your python version is to old, Please use at least Python 2.5")
-
- try:
- import pycurl
- except:
- core_err.append("Please install py-curl to use pyLoad.")
-
-
- try:
- from pycurl import AUTOREFERER
- except:
- core_err.append("Your py-curl version is to old, please upgrade!")
-
- try:
- import Image
- except:
- core_err.append("Please install py-imaging/pil to use Hoster, which uses captchas.")
-
- pipe = subprocess.PIPE
- try:
- p = subprocess.call(["tesseract"], stdout=pipe, stderr=pipe)
- except:
- core_err.append("Please install tesseract to use Hoster, which uses captchas.")
-
- try:
- import OpenSSL
- except:
- core_info.append("Install OpenSSL if you want to create a secure connection to the core.")
-
- if core_err:
- print("The system check has detected some errors:\n")
- for err in core_err:
- print(err)
- else:
- print("No Problems detected, pyLoadCore should work fine.")
-
- if core_info:
- print("\nPossible improvements for pyload:\n")
- for line in core_info:
- print(line)
-
-
- print("\n## pyLoadGui ##")
-
- gui_err = []
-
- try:
- import PyQt4
- except:
- gui_err.append("GUI won't work without pyqt4 !!")
-
- if gui_err:
- print("The system check has detected some errors:\n")
- for err in gui_err:
- print(err)
- else:
- print("No Problems detected, pyLoadGui should work fine.")
-
-
- print("\n## Webinterface ##")
-
- web_err = []
- web_info = []
-
- try:
- import flup
- except:
- web_info.append("Install Flup to use FastCGI or optional webservers.")
-
-
- if web_err:
- print("The system check has detected some errors:\n")
- for err in web_err:
- print(err)
- else:
- print("No Problems detected, Webinterface should work fine.")
-
- if web_info:
- print("\nPossible improvements for webinterface:\n")
- for line in web_info:
- print(line)
-
-
-if __name__ == "__main__":
- main()
-
- raw_input("Press Enter to Exit.")
diff --git a/testlinks.txt b/testlinks.txt
deleted file mode 100644
index 428cf63ea..000000000
--- a/testlinks.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-sha1:
- 961486646bf3c1d5d7a45ec32bb62e1bc4f2d894 random.bin
-md5:
- d76505d0869f9f928a17d42d66326307 random.bin
-
-please save bandwith on our server,
-use this link only for remote uploads to new hoster
-http://get.pyload.org/static/random.bin
----------------------------------------
-http://netload.in/datei9XirAJZs79/random.bin.htm
-http://ul.to/file/o41isx
-http://rapidshare.com/files/445996776/random.bin
-http://dl.free.fr/d4aL5dyXY
-http://www.duckload.com/dl/QggW2
-http://files.mail.ru/32EW66
-http://www.fileserve.com/file/MxjZXjX
-http://www.4shared.com/file/-O5CBhQV/random.html
-http://hotfile.com/dl/101569859/2e01f04/random.bin.html
-http://www.megaupload.com/?d=1JZLOP3B
-http://www.share.cx/files/235687689252/random.bin.html
-http://www.share-online.biz/download.php?id=PTCOX1GL6XAH
-http://www.shragle.com/files/f899389b/random.bin
-http://www10.zippyshare.com/v/76557688/file.html
-http://yourfiles.to/?d=312EC6E911
-http://depositfiles.com/files/k8la98953
-http://uploading.com/files/3896f5a1/random.bin/
diff --git a/tests/APIExerciser.py b/tests/APIExerciser.py
new file mode 100644
index 000000000..886c72a4a
--- /dev/null
+++ b/tests/APIExerciser.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+import string
+from threading import Thread
+from random import choice, random, sample, randint
+from time import time, sleep
+from math import floor
+import gc
+
+from traceback import print_exc, format_exc
+
+from pyload.remote.thriftbackend.ThriftClient import ThriftClient, Destination
+
+def createURLs():
+ """ create some urls, some may fail """
+ urls = []
+ for x in range(0, randint(20, 100)):
+ name = "DEBUG_API"
+ if randint(0, 5) == 5:
+ name = "" #this link will fail
+
+ urls.append(name + "".join(sample(string.ascii_letters, randint(10, 20))))
+
+ return urls
+
+AVOID = (0, 3, 8)
+
+idPool = 0
+sumCalled = 0
+
+
+def startApiExerciser(core, n):
+ for i in range(n):
+ APIExerciser(core).start()
+
+class APIExerciser(Thread):
+
+
+ def __init__(self, core, thrift=False, user=None, pw=None):
+ global idPool
+
+ Thread.__init__(self)
+ self.setDaemon(True)
+ self.core = core
+ self.count = 0 #number of methods
+ self.time = time()
+
+ if thrift:
+ self.api = ThriftClient(user=user, password=pw)
+ else:
+ self.api = core.api
+
+
+ self.id = idPool
+
+ idPool += 1
+
+ #self.start()
+
+ def run(self):
+
+ self.core.log.info("API Excerciser started %d" % self.id)
+
+ out = open("error.log", "ab")
+ #core errors are not logged of course
+ out.write("\n" + "Starting\n")
+ out.flush()
+
+ while True:
+ try:
+ self.testAPI()
+ except Exception:
+ self.core.log.error("Excerciser %d throw an execption" % self.id)
+ print_exc()
+ out.write(format_exc() + 2 * "\n")
+ out.flush()
+
+ if not self.count % 100:
+ self.core.log.info("Exerciser %d tested %d api calls" % (self.id, self.count))
+ if not self.count % 1000:
+ out.flush()
+
+ if not sumCalled % 1000: #not thread safe
+ self.core.log.info("Exercisers tested %d api calls" % sumCalled)
+ persec = sumCalled / (time() - self.time)
+ self.core.log.info("Approx. %.2f calls per second." % persec)
+ self.core.log.info("Approx. %.2f ms per call." % (1000 / persec))
+ self.core.log.info("Collected garbage: %d" % gc.collect())
+
+
+ #sleep(random() / 500)
+
+ def testAPI(self):
+ global sumCalled
+
+ m = ["statusDownloads", "statusServer", "addPackage", "getPackageData", "getFileData", "deleteFiles",
+ "deletePackages", "getQueue", "getCollector", "getQueueData", "getCollectorData", "isCaptchaWaiting",
+ "getCaptchaTask", "stopAllDownloads", "getAllInfo", "getServices" , "getAccounts", "getAllUserData"]
+
+ method = choice(m)
+ #print "Testing:", method
+
+ if hasattr(self, method):
+ res = getattr(self, method)()
+ else:
+ res = getattr(self.api, method)()
+
+ self.count += 1
+ sumCalled += 1
+
+ #print res
+
+ def addPackage(self):
+ name = "".join(sample(string.ascii_letters, 10))
+ urls = createURLs()
+
+ self.api.addPackage(name, urls, choice([Destination.Queue, Destination.Collector]))
+
+
+ def deleteFiles(self):
+ info = self.api.getQueueData()
+ if not info: return
+
+ pack = choice(info)
+ fids = pack.links
+
+ if len(fids):
+ fids = [f.fid for f in sample(fids, randint(1, max(len(fids) / 2, 1)))]
+ self.api.deleteFiles(fids)
+
+
+ def deletePackages(self):
+ info = choice([self.api.getQueue(), self.api.getCollector()])
+ if not info: return
+
+ pids = [p.pid for p in info]
+ if len(pids):
+ pids = sample(pids, randint(1, max(floor(len(pids) / 2.5), 1)))
+ self.api.deletePackages(pids)
+
+ def getFileData(self):
+ info = self.api.getQueueData()
+ if info:
+ p = choice(info)
+ if p.links:
+ self.api.getFileData(choice(p.links).fid)
+
+ def getPackageData(self):
+ info = self.api.getQueue()
+ if info:
+ self.api.getPackageData(choice(info).pid)
+
+ def getAccounts(self):
+ self.api.getAccounts(False)
+
+ def getCaptchaTask(self):
+ self.api.getCaptchaTask(False)
diff --git a/tests/clonedigger.sh b/tests/clonedigger.sh
new file mode 100644
index 000000000..358c1b68b
--- /dev/null
+++ b/tests/clonedigger.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+PYLOAD="../pyload" # Check pyload directory
+clonedigger -o cpd.xml --cpd-output --fast --ignore-dir="lib" --ignore-dir="remote" $PYLOAD
diff --git a/tests/code_analysis.sh b/tests/code_analysis.sh
new file mode 100644
index 000000000..c853652bf
--- /dev/null
+++ b/tests/code_analysis.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+PYLOAD="../pyload" # Check pyload directory
+
+echo "Running sloccount ..."
+REPORT="sloccount.sc"
+sloccount --duplicates --wide --details $PYLOAD > $REPORT && echo "Done. Report saved to $REPORT"
+
+echo "Running pep8 ..."
+REPORT="pep8.txt"
+pep8 $PYLOAD > $REPORT && echo "Done. Report saved to $REPORT"
+
+echo "Running pylint ..."
+REPORT="pylint.txt"
+pylint --reports=no $PYLOAD > $REPORT && echo "Done. Report saved to $REPORT"
+
+#echo "Running pyflakes ..."
+#REPORT="pyflakes.txt"
+#{
+ # pyflakes to pylint syntak
+# find $PYLOAD -type f -name "*.py" | egrep -v '^\./lib' | xargs pyflakes > pyflakes.log || :
+ # Filter warnings and strip ./ from path
+# cat pyflakes.log | awk -F\: '{printf "%s:%s: [E]%s\n", $1, $2, $3}' | grep -i -E -v "'_'|pypath|webinterface|pyreq|addonmanager" > $REPORT
+# sed -i 's/^.\///g' $REPORT
+#} && echo "Done. Report saved to $REPORT"
diff --git a/tests/test_api.py b/tests/test_api.py
index f8901f731..a4229cfe6 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
-from module.common import APIExerciser
+import APIExerciser
+
from nose.tools import nottest
diff --git a/tests/test_json.py b/tests/test_json.py
index ff56e8f5a..320a42d4f 100644
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -45,4 +45,4 @@ class TestJson:
except HTTPError, e:
assert e.code == 404
else:
- assert False \ No newline at end of file
+ assert False